Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add challenge-response support for Nitrokey 3 #1

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions src/keys/drivers/YubiKeyInterfacePCSC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "core/Tools.h"
#include "crypto/Random.h"
#include <algorithm>

// MSYS2 does not define these macros
// So set them to the value used by pcsc-lite
Expand Down Expand Up @@ -242,23 +243,68 @@ namespace
if (rv == SCARD_S_SUCCESS) {
// Write to and read from the card
// pioRecvPci is nullptr because we do not expect any PCI response header
const SCUINT dwRecvBufferSize = dwRecvLength;
rv = SCardTransmit(handle, pioSendPci, pbSendBuffer, dwSendLength, nullptr, pbRecvBuffer, &dwRecvLength);

if (dwRecvLength < 2) {
// Any valid response should be at least 2 bytes (response status)
// However the protocol itself could fail
return SCARD_E_UNEXPECTED;
}

uint8_t SW1 = pbRecvBuffer[dwRecvLength - 2];
// Check for the MoreDataAvailable SW1 code. If present, send GetResponse command repeatedly, until success
// SW, or filling the receiving buffer.
if (SW1 == SW_MORE_DATA_HIGH) {
while (true) {
if (dwRecvBufferSize < dwRecvLength) {
// No free buffer space remaining
return SCARD_E_UNEXPECTED;
}
// Overwrite Status Word in the receiving buffer
dwRecvLength -= 2;
SCUINT dwRecvLength_sr = dwRecvBufferSize - dwRecvLength; // at least 2 bytes for SW are available
const uint8_t bRecvDataSize =
std::clamp(dwRecvLength_sr - 2, static_cast<SCUINT>(0), static_cast<SCUINT>(255));
uint8_t pbSendBuffer_sr[] = {CLA_ISO, INS_GET_RESPONSE, 0, 0, bRecvDataSize};
rv = SCardTransmit(handle,
pioSendPci,
pbSendBuffer_sr,
sizeof pbSendBuffer_sr,
nullptr,
pbRecvBuffer + dwRecvLength,
&dwRecvLength_sr);

// Check if any new data are received. Break if the smart card's status is other than success,
// or no new bytes were received.
if (!(rv == SCARD_S_SUCCESS && dwRecvLength_sr >= 2)) {
break;
}

dwRecvLength += dwRecvLength_sr;
SW1 = pbRecvBuffer[dwRecvLength - 2];
// Break the loop if there is no continuation status
if (SW1 != SW_MORE_DATA_HIGH) {
break;
}
}
}

if (rv == SCARD_S_SUCCESS) {
if (dwRecvLength < 2) {
// Any valid response should be at least 2 bytes (response status)
// However the protocol itself could fail
rv = SCARD_E_UNEXPECTED;
} else {
if (pbRecvBuffer[dwRecvLength - 2] == SW_OK_HIGH && pbRecvBuffer[dwRecvLength - 1] == SW_OK_LOW) {
const uint8_t SW_HIGH = pbRecvBuffer[dwRecvLength - 2];
const uint8_t SW_LOW = pbRecvBuffer[dwRecvLength - 1];
if (SW_HIGH == SW_OK_HIGH && SW_LOW == SW_OK_LOW) {
rv = SCARD_S_SUCCESS;
} else if (pbRecvBuffer[dwRecvLength - 2] == SW_PRECOND_HIGH
&& pbRecvBuffer[dwRecvLength - 1] == SW_PRECOND_LOW) {
} else if (SW_HIGH == SW_PRECOND_HIGH && SW_LOW == SW_PRECOND_LOW) {
// This happens if the key requires eg. a button press or if the applet times out
// Solution: Re-present the card to the reader
rv = SCARD_W_CARD_NOT_AUTHENTICATED;
} else if ((pbRecvBuffer[dwRecvLength - 2] == SW_NOTFOUND_HIGH
&& pbRecvBuffer[dwRecvLength - 1] == SW_NOTFOUND_LOW)
|| pbRecvBuffer[dwRecvLength - 2] == SW_UNSUP_HIGH) {
} else if ((SW_HIGH == SW_NOTFOUND_HIGH && SW_LOW == SW_NOTFOUND_LOW) || SW_HIGH == SW_UNSUP_HIGH) {
// This happens eg. during a select command when the AID is not found
rv = SCARD_E_FILE_NOT_FOUND;
} else {
Expand All @@ -285,9 +331,10 @@ namespace
auto pbSendBuffer = new uint8_t[5 + handle.second.size()];
memcpy(pbSendBuffer, pbSendBuffer_head, 5);
memcpy(pbSendBuffer + 5, handle.second.constData(), handle.second.size());
uint8_t pbRecvBuffer[12] = {
// Give it more space in case custom implementations have longer answer to select
uint8_t pbRecvBuffer[64] = {
0}; // 3 bytes version, 1 byte program counter, other stuff for various implementations, 2 bytes status
SCUINT dwRecvLength = 12;
SCUINT dwRecvLength = sizeof pbRecvBuffer;

auto rv = transmit(handle.first, pbSendBuffer, 5 + handle.second.size(), pbRecvBuffer, dwRecvLength);

Expand Down
8 changes: 7 additions & 1 deletion src/keys/drivers/YubiKeyInterfacePCSC.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#define CLA_ISO 0x00
#define INS_SELECT 0xA4
#define INS_GET_RESPONSE 0xC0
#define SEL_APP_AID 0x04
#define INS_API_REQ 0x01
#define INS_STATUS 0x03
Expand All @@ -37,6 +38,7 @@
#define SW_NOTFOUND_HIGH 0x6A
#define SW_NOTFOUND_LOW 0x82
#define SW_UNSUP_HIGH 0x6D
#define SW_MORE_DATA_HIGH 0x61

typedef QPair<SCARDHANDLE, QByteArray> SCardAID;

Expand Down Expand Up @@ -75,6 +77,7 @@ class YubiKeyInterfacePCSC : public YubiKeyInterface
// and also for compatible third-party ones. They will be tried one by one.
const QList<QByteArray> m_aid_codes = {
QByteArrayLiteral("\xA0\x00\x00\x05\x27\x20\x01"), // Yubico Yubikey
QByteArrayLiteral("\xA0\x00\x00\x05\x27\x21\x01"), // Yubico Yubikey OATH AID / Nitrokey 3 Secrets App
QByteArrayLiteral("\xA0\x00\x00\x06\x17\x00\x07\x53\x4E\xAF\x01") // Fidesmo development
};

Expand Down Expand Up @@ -109,7 +112,10 @@ class YubiKeyInterfacePCSC : public YubiKeyInterface
{QByteArrayLiteral("\x3B\x80\x80\x01\x01"), "Fidesmo Card 2.0"},
{QByteArrayLiteral("\x3B\x8A\x80\x01\x00\x31\xC1\x73\xC8\x40\x00\x00\x90\x00\x90"), "VivoKey Apex"},
{QByteArrayLiteral("\x3B\x8D\x80\x01\x00\x31\xC1\x73\xC8\x40\x00\x52\xA5\x10\x00\x90\x00\x70"),
"Dangerous Things FlexSecure"}};
"Dangerous Things FlexSecure"},
{QByteArrayLiteral("\x3b\x8f\x01\x80\x5d\x4e\x69\x74\x72\x6f\x6b\x65\x79\x00\x00\x00\x00\x00\x6a"),
"Nitrokey 3"},
};
};

#endif // KEEPASSX_YUBIKEY_INTERFACE_PCSC_H