diff --git a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp index 4d8dd5e..f5b27f9 100644 --- a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp +++ b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp @@ -30,6 +30,9 @@ #include // The rule of five (C++ Core guidelines C.21). +#define PCSC_CPP_DEFAULT_MOVE(Class) \ + Class(Class&&) = default; \ + Class& operator=(Class&&) = default #define PCSC_CPP_DISABLE_COPY(Class) \ Class(const Class&) = delete; \ Class& operator=(const Class&) = delete @@ -162,7 +165,14 @@ struct CommandApdu d.push_back(le); } - constexpr operator const byte_vector&() const { return d; } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu(const CommandApdu& other, byte_type le) : d(other.d) + { + size_t pos = d.size() <= 5 ? 4 : 5 + d[4]; // Case 1/2 or 3/4 + d.resize(pos + 1); + d[pos] = le; + } + + virtual ~CommandApdu() noexcept = default; /** * A helper function to create a SELECT FILE command APDU. @@ -213,6 +223,52 @@ struct CommandApdu return {0x00, 0xb0, byte_type(pos >> 8), byte_type(pos), le}; } + /** + * A helper function to create a VERIFY command APDU. + * The ISO 7816-4 Section 6.12 VERIFY command has the form: + * CLA = 0x00 + * INS = 0x20 + * P1 = Only P1=’00’ is valid (other values are RFU) + * P2 = Qualifier of the reference data + * Lc and Data field = Empty or verification data + * Le = Empty + */ + static PCSC_CPP_CONSTEXPR_VECTOR CommandApdu verify(byte_type p2, byte_vector&& pin, + size_t paddingLength, + pcsc_cpp::byte_type paddingChar) + { + if (!pin.empty() && pin.capacity() < paddingLength + 5) { + throw std::invalid_argument( + "PIN buffer does not have enough capacity to pad without reallocation"); + } + if (pin.size() < paddingLength) { + pin.insert(pin.end(), paddingLength - pin.size(), paddingChar); + } + struct VerifyApdu final : public CommandApdu + { + using CommandApdu::CommandApdu; + PCSC_CPP_DISABLE_COPY(VerifyApdu); + PCSC_CPP_DEFAULT_MOVE(VerifyApdu); + constexpr ~VerifyApdu() noexcept final { std::fill(d.begin(), d.end(), byte_type(0)); } + }; + return VerifyApdu {0x00, 0x20, 0x00, p2, std::move(pin)}; + } + + /** + * A helper function to create a GET RESPONSE command APDU. + * + * The ISO 7816-4 Section 7.1 GET RESPONSE command has the form: + * CLA = 0x00 + * INS = 0xC0 + * P1, P2 = ‘0000’ (other values are RFU) + * Lc and Data field = Empty + * Le = Maximum length of data expected in response + */ + static PCSC_CPP_CONSTEXPR_VECTOR CommandApdu getResponse(byte_type le = 0x00) + { + return {0x00, 0xc0, 0x00, 0x00, le}; + } + byte_vector d; }; diff --git a/lib/libpcsc-cpp/src/SmartCard.cpp b/lib/libpcsc-cpp/src/SmartCard.cpp index 2790a70..8400a0a 100644 --- a/lib/libpcsc-cpp/src/SmartCard.cpp +++ b/lib/libpcsc-cpp/src/SmartCard.cpp @@ -31,8 +31,8 @@ #include #endif +#include #include -#include #include // TODO: Someday, maybe SCARD_SHARE_SHARED vs SCARD_SHARE_EXCLUSIVE and SCARD_RESET_CARD on @@ -41,20 +41,6 @@ namespace { -template -constexpr std::map parseTLV(const std::array& data, DWORD size, Func transform) -{ - std::map result; - for (auto p = data.cbegin(); DWORD(std::distance(data.cbegin(), p)) < size;) { - auto tag = K(*p++); - V value {}; - for (unsigned int i = 0, len = *p++; i < len; ++i) - value |= V(*p++) << 8 * i; - result[tag] = transform(value); - } - return result; -} - constexpr uint32_t VENDOR_HID_GLOBAL = 0x076B; constexpr uint32_t OMNIKEY_3x21 = 0x3031; constexpr uint32_t OMNIKEY_6121 = 0x6632; @@ -89,27 +75,33 @@ class CardImpl try { DWORD size = 0; - std::array list {}; SCard(Control, cardHandle, DWORD(CM_IOCTL_GET_FEATURE_REQUEST), nullptr, 0U, - list.data(), DWORD(list.size() * sizeof(PCSC_TLV_STRUCTURE)), &size); + features.data(), DWORD(features.size() * sizeof(PCSC_TLV_STRUCTURE)), &size); if (size == 0 || size % sizeof(PCSC_TLV_STRUCTURE)) { return; // No features available or malformed response. } - for (const auto& f : list) { - features[DRIVER_FEATURES(f.tag)] = ntohl(f.value); + for (auto& f : features) { + f.value = ntohl(f.value); } - if (auto ioctl = features.find(FEATURE_GET_TLV_PROPERTIES); ioctl != features.cend()) { + if (auto ioctl = feature(FEATURE_GET_TLV_PROPERTIES); ioctl != features.cend()) { std::array buf {}; - SCard(Control, cardHandle, ioctl->second, nullptr, 0U, buf.data(), - DWORD(buf.size()), &size); - auto properties = parseTLV(buf, size, [](uint32_t t) { return t; }); - if (auto vendor = properties.find(TLV_PROPERTY_wIdVendor); - vendor != properties.cend()) - id_vendor = vendor->second; - if (auto product = properties.find(TLV_PROPERTY_wIdProduct); - product != properties.cend()) - id_product = product->second; + SCard(Control, cardHandle, ioctl->value, nullptr, 0U, buf.data(), DWORD(buf.size()), + &size); + for (auto p = buf.cbegin(); DWORD(std::distance(buf.cbegin(), p)) < size;) { + auto tag = TLV_PROPERTIES(*p++); + BYTE len = *p++; + if (DWORD(std::distance(buf.cbegin(), p)) + len > size) { + break; // Malformed TLV data. + } + uint32_t value {}; + for (BYTE i = 0; i < len; ++i) + value |= uint32_t(*p++) << 8 * i; + if (tag == TLV_PROPERTY_wIdVendor) + id_vendor = value; + if (tag == TLV_PROPERTY_wIdProduct) + id_product = value; + } } } catch (const ScardError&) { // Ignore driver errors during card feature requests. @@ -138,33 +130,20 @@ class CardImpl return false; if (getenv("SMARTCARDPP_NOPINPAD")) return false; - return features.contains(FEATURE_VERIFY_PIN_START) - || features.contains(FEATURE_VERIFY_PIN_DIRECT); + return feature(FEATURE_VERIFY_PIN_START) != features.cend() + || feature(FEATURE_VERIFY_PIN_DIRECT) != features.cend(); } - ResponseApdu transmitBytes(const byte_vector& commandBytes) const + ResponseApdu transmitBytes(const CommandApdu& commandApdu) const { byte_vector responseBytes(ResponseApdu::MAX_SIZE, 0); auto responseLength = DWORD(responseBytes.size()); - - // TODO: debug("Sending: " + bytes2hexstr(commandBytes)) - - SCard(Transmit, cardHandle, &_protocol, commandBytes.data(), DWORD(commandBytes.size()), + SCard(Transmit, cardHandle, &_protocol, commandApdu.d.data(), DWORD(commandApdu.d.size()), nullptr, responseBytes.data(), &responseLength); - - auto response = toResponse(std::move(responseBytes), responseLength); - - if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) { - getResponseWithLE(response, commandBytes); - } - if (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) { - getMoreResponseData(response); - } - - return response; + return toResponse(std::move(responseBytes), responseLength); } - ResponseApdu transmitBytesCTL(const byte_vector& commandBytes, uint16_t lang, + ResponseApdu transmitBytesCTL(const CommandApdu& commandApdu, uint16_t lang, uint8_t minlen) const { uint8_t PINFrameOffset = 0; @@ -182,20 +161,20 @@ class CardImpl data->bNumberMessage = CCIDDefaultInvitationMessage; data->wLangId = lang; data->bMsgIndex = NoInvitationMessage; - data->ulDataLength = uint32_t(commandBytes.size()); - cmd.insert(cmd.cend(), commandBytes.cbegin(), commandBytes.cend()); + data->ulDataLength = uint32_t(commandApdu.d.size()); + cmd.insert(cmd.cend(), commandApdu.d.cbegin(), commandApdu.d.cend()); - DWORD ioctl = - features.at(features.contains(FEATURE_VERIFY_PIN_START) ? FEATURE_VERIFY_PIN_START - : FEATURE_VERIFY_PIN_DIRECT); + auto ioctl = feature(FEATURE_VERIFY_PIN_START); + if (feature(FEATURE_VERIFY_PIN_START) == features.cend()) + ioctl = feature(FEATURE_VERIFY_PIN_DIRECT); byte_vector responseBytes(ResponseApdu::MAX_SIZE, 0); auto responseLength = DWORD(responseBytes.size()); - SCard(Control, cardHandle, ioctl, cmd.data(), DWORD(cmd.size()), + SCard(Control, cardHandle, ioctl->value, cmd.data(), DWORD(cmd.size()), LPVOID(responseBytes.data()), DWORD(responseBytes.size()), &responseLength); - if (auto finish = features.find(FEATURE_VERIFY_PIN_FINISH); finish != features.cend()) { + if (auto finish = feature(FEATURE_VERIFY_PIN_FINISH); finish != features.cend()) { responseLength = DWORD(responseBytes.size()); - SCard(Control, cardHandle, finish->second, nullptr, 0U, LPVOID(responseBytes.data()), + SCard(Control, cardHandle, finish->value, nullptr, 0U, LPVOID(responseBytes.data()), DWORD(responseBytes.size()), &responseLength); } @@ -224,10 +203,16 @@ class CardImpl private: SCARDHANDLE cardHandle {}; SCARD_IO_REQUEST _protocol {SCARD_PROTOCOL_UNDEFINED, sizeof(SCARD_IO_REQUEST)}; - std::map features; + std::array features {}; uint32_t id_vendor {}; uint32_t id_product {}; + constexpr decltype(features)::const_iterator feature(DRIVER_FEATURES tag) const + { + return std::find_if(features.cbegin(), features.cend(), + [tag](PCSC_TLV_STRUCTURE tlv) { return tlv.tag == tag; }); + } + ResponseApdu toResponse(byte_vector&& responseBytes, size_t responseLength) const { if (responseLength > responseBytes.size()) { @@ -266,31 +251,6 @@ class CardImpl + std::to_string(_protocol.dwProtocol)); } } - - void getMoreResponseData(ResponseApdu& response) const - { - byte_vector getResponseCommand {0x00, 0xc0, 0x00, 0x00, 0x00}; - - ResponseApdu newResponse {response.sw1, response.sw2}; - - while (newResponse.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) { - getResponseCommand[4] = newResponse.sw2; - newResponse = transmitBytes(getResponseCommand); - response.data.insert(response.data.end(), newResponse.data.cbegin(), - newResponse.data.cend()); - } - - response.sw1 = ResponseApdu::OK; - response.sw2 = 0; - } - - void getResponseWithLE(ResponseApdu& response, byte_vector command) const - { - size_t pos = command.size() <= 5 ? 4 : 5 + command[4]; // Case 1/2 or 3/4 - command.resize(pos + 1); - command[pos] = response.sw2; - response = transmitBytes(command); - } }; SmartCard::Session::Session(const CardImpl& card) : card(card) @@ -309,7 +269,22 @@ SmartCard::Session::~Session() noexcept ResponseApdu SmartCard::Session::transmit(const CommandApdu& command) const { - return card.transmitBytes(command); + auto response = card.transmitBytes(command); + if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) { + response = card.transmitBytes(CommandApdu(command, response.sw2)); + } + if (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) { + auto getResponseCommand = CommandApdu::getResponse(); + while (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) { + getResponseCommand.d[4] = response.sw2; + auto newResponse = card.transmitBytes(getResponseCommand); + response.sw1 = newResponse.sw1; + response.sw2 = newResponse.sw2; + response.data.insert(response.data.end(), newResponse.data.cbegin(), + newResponse.data.cend()); + } + } + return response; } ResponseApdu SmartCard::Session::transmitCTL(const CommandApdu& command, uint16_t lang, diff --git a/src/electronic-ids/pcsc/pcsc-common.hpp b/src/electronic-ids/pcsc/pcsc-common.hpp index 2524db1..3dcae6b 100644 --- a/src/electronic-ids/pcsc/pcsc-common.hpp +++ b/src/electronic-ids/pcsc/pcsc-common.hpp @@ -87,19 +87,6 @@ inline pcsc_cpp::byte_vector readFile(const pcsc_cpp::SmartCard::Session& sessio return readBinary(session, pcsc_cpp::toSW(*size.begin, *(size.begin + 1)), blockLength); } -PCSC_CPP_CONSTEXPR_VECTOR inline pcsc_cpp::byte_vector -addPaddingToPin(pcsc_cpp::byte_vector&& pin, size_t paddingLength, pcsc_cpp::byte_type paddingChar) -{ - if (pin.capacity() < paddingLength) { - THROW(ProgrammingError, - "PIN buffer does not have enough capacity to pad without reallocation"); - } - if (pin.size() < paddingLength) { - pin.insert(pin.end(), paddingLength - pin.size(), paddingChar); - } - return std::move(pin); -} - inline void verifyPin(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byte_type p2, pcsc_cpp::byte_vector&& pin, ElectronicID::PinMinMaxLength pinMinMax, pcsc_cpp::byte_type paddingChar) @@ -107,13 +94,12 @@ inline void verifyPin(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byt pcsc_cpp::ResponseApdu response; if (session.readerHasPinPad()) { - const pcsc_cpp::CommandApdu verifyPin { - 0x00, 0x20, 0x00, p2, pcsc_cpp::byte_vector(pinMinMax.second, paddingChar)}; - response = session.transmitCTL(verifyPin, 0, pinMinMax.first); + response = session.transmitCTL( + pcsc_cpp::CommandApdu::verify(p2, std::move(pin), pinMinMax.second, paddingChar), 0, + pinMinMax.first); } else { - const pcsc_cpp::CommandApdu verifyPin { - 0x00, 0x20, 0x00, p2, addPaddingToPin(std::move(pin), pinMinMax.second, paddingChar)}; - response = session.transmit(verifyPin); + response = session.transmit( + pcsc_cpp::CommandApdu::verify(p2, std::move(pin), pinMinMax.second, paddingChar)); } // NOTE: in case card-specific error handling logic is needed, diff --git a/tests/mock/test-get-certificate.cpp b/tests/mock/test-get-certificate.cpp index 4467dec..1359f10 100644 --- a/tests/mock/test-get-certificate.cpp +++ b/tests/mock/test-get-certificate.cpp @@ -55,7 +55,7 @@ TEST(electronic_id_test, selectCertificateEstIDEMIA) const HashAlgorithm hashAlgo = authAlgo.hashAlgorithm(); pcsc_cpp::byte_vector authPin {'1', '2', '3', '4'}; - authPin.reserve(12); + authPin.reserve(17); const auto hash = calculateDigest(hashAlgo, dataToSign); const auto authSignature = cardInfo->signWithAuthKey(std::move(authPin), hash); @@ -72,7 +72,7 @@ TEST(electronic_id_test, selectCertificateEstIDEMIA) EXPECT_EQ(signingRetriesLeft.maxRetry, 3); pcsc_cpp::byte_vector signPin {'1', '2', '3', '4', '5'}; - signPin.reserve(12); + signPin.reserve(17); EXPECT_EQ(cardInfo->isSupportedSigningHashAlgorithm(hashAlgo), true); const auto signSignature = cardInfo->signWithSigningKey(std::move(signPin), hash, hashAlgo); @@ -105,7 +105,7 @@ TEST(electronic_id_test, selectCertificateFinV3) const HashAlgorithm hashAlgo = authAlgo.hashAlgorithm(); pcsc_cpp::byte_vector authPin {'1', '2', '3', '4'}; - authPin.reserve(12); + authPin.reserve(17); const auto hash = calculateDigest(hashAlgo, dataToSign); const auto authSignature = cardInfo->signWithAuthKey(std::move(authPin), hash); @@ -122,7 +122,7 @@ TEST(electronic_id_test, selectCertificateFinV3) EXPECT_EQ(signingRetriesLeft.maxRetry, 5); pcsc_cpp::byte_vector signPin {'1', '2', '3', '4', '5', '6'}; - signPin.reserve(12); + signPin.reserve(17); EXPECT_EQ(cardInfo->isSupportedSigningHashAlgorithm(hashAlgo), true); const auto signSignature = cardInfo->signWithSigningKey(std::move(signPin), hash, hashAlgo); @@ -155,7 +155,7 @@ TEST(electronic_id_test, selectCertificateFinV4) const HashAlgorithm hashAlgo = authAlgo.hashAlgorithm(); pcsc_cpp::byte_vector authPin {'1', '2', '3', '4'}; - authPin.reserve(12); + authPin.reserve(17); const auto hash = calculateDigest(hashAlgo, dataToSign); const auto authSignature = cardInfo->signWithAuthKey(std::move(authPin), hash); @@ -172,7 +172,7 @@ TEST(electronic_id_test, selectCertificateFinV4) EXPECT_EQ(signingRetriesLeft.maxRetry, 5); pcsc_cpp::byte_vector signPin {'1', '2', '3', '4', '5', '6'}; - signPin.reserve(12); + signPin.reserve(17); EXPECT_EQ(cardInfo->isSupportedSigningHashAlgorithm(hashAlgo), true); const auto signSignature = cardInfo->signWithSigningKey(std::move(signPin), hash, hashAlgo); @@ -205,7 +205,7 @@ TEST(electronic_id_test, selectCertificateLatV2) const HashAlgorithm hashAlgo = authAlgo.hashAlgorithm(); pcsc_cpp::byte_vector authPin {'1', '2', '3', '4'}; - authPin.reserve(12); + authPin.reserve(17); const auto hash = calculateDigest(hashAlgo, dataToSign); const auto authSignature = cardInfo->signWithAuthKey(std::move(authPin), hash); @@ -222,7 +222,7 @@ TEST(electronic_id_test, selectCertificateLatV2) EXPECT_EQ(signingRetriesLeft.maxRetry, 3); pcsc_cpp::byte_vector signPin {'1', '2', '3', '4', '5', '6'}; - signPin.reserve(12); + signPin.reserve(17); EXPECT_EQ(cardInfo->isSupportedSigningHashAlgorithm(hashAlgo), true); const auto signSignature = cardInfo->signWithSigningKey(std::move(signPin), hash, hashAlgo);