diff --git a/CMakeLists.txt b/CMakeLists.txt index a707500..b616409 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,10 @@ add_library(${PROJECT_NAME} src/electronic-ids/x509.hpp src/electronic-ids/pcsc/EIDIDEMIA.cpp src/electronic-ids/pcsc/EIDIDEMIA.hpp + src/electronic-ids/pcsc/EIDThales.cpp + src/electronic-ids/pcsc/EIDThales.hpp src/electronic-ids/pcsc/EstEIDIDEMIA.hpp - src/electronic-ids/pcsc/FinEID.cpp + src/electronic-ids/pcsc/EstEIDThales.hpp src/electronic-ids/pcsc/FinEID.hpp src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp diff --git a/lib/libpcsc-cpp/CMakeLists.txt b/lib/libpcsc-cpp/CMakeLists.txt index 2ca5dbb..ac7ff8d 100644 --- a/lib/libpcsc-cpp/CMakeLists.txt +++ b/lib/libpcsc-cpp/CMakeLists.txt @@ -14,7 +14,6 @@ add_library(${PROJECT_NAME} src/SCardCall.hpp src/SmartCard.cpp src/listReaders.cpp - src/utils.cpp ) target_include_directories(${PROJECT_NAME} diff --git a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp-utils.hpp b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp-utils.hpp index 3cf375e..2b9d12d 100644 --- a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp-utils.hpp +++ b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp-utils.hpp @@ -29,6 +29,15 @@ namespace pcsc_cpp { +/** Convert bytes to hex string. */ +inline std::ostream& operator<<(std::ostream& os, const byte_vector& data) +{ + os << std::setfill('0') << std::hex; + for (const auto byte : data) + os << std::setw(2) << short(byte); + return os << std::setfill(' ') << std::dec; +} + /** Convert the given integer to a hex string. */ template inline std::string int2hexstr(const T value) diff --git a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp index 852447e..4d8dd5e 100644 --- a/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp +++ b/lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp @@ -85,8 +85,6 @@ constexpr uint16_t toSW(byte_type sw1, byte_type sw2) noexcept } /** Convert bytes to hex string. */ -std::ostream& operator<<(std::ostream& os, const pcsc_cpp::byte_vector& data); - std::string operator+(std::string lhs, const byte_vector& rhs); /** Struct that wraps response APDUs. */ @@ -111,23 +109,6 @@ struct ResponseApdu static constexpr size_t MAX_DATA_SIZE = 256; static constexpr size_t MAX_SIZE = MAX_DATA_SIZE + 2; // + sw1 and sw2 - PCSC_CPP_CONSTEXPR_VECTOR static ResponseApdu fromBytes(byte_vector data) - { - if (data.size() < 2) { - throw std::invalid_argument("Need at least 2 bytes for creating ResponseApdu"); - } - - PCSC_CPP_WARNING_PUSH - PCSC_CPP_WARNING_DISABLE_GCC("-Warray-bounds") // avoid GCC 13 false positive warning - byte_type sw1 = data[data.size() - 2]; - byte_type sw2 = data[data.size() - 1]; - data.resize(data.size() - 2); - PCSC_CPP_WARNING_POP - - // SW1 and SW2 are in the end - return {sw1, sw2, std::move(data)}; - } - constexpr uint16_t toSW() const noexcept { return pcsc_cpp::toSW(sw1, sw2); } constexpr bool isOK() const noexcept { return sw1 == OK && sw2 == 0x00; } @@ -300,16 +281,6 @@ class SmartCard */ std::vector listReaders(); -// Utility functions. - -/** Transmit APDU command and verify that expected response is received. */ -void transmitApduWithExpectedResponse(const SmartCard::Session& session, - const CommandApdu& command); - -/** Read lenght bytes from currently selected binary file in blockLength-sized chunks. */ -byte_vector readBinary(const SmartCard::Session& session, const uint16_t length, - byte_type blockLength = 0x00); - // Errors. /** Base class for all pcsc-cpp errors. */ diff --git a/lib/libpcsc-cpp/src/SmartCard.cpp b/lib/libpcsc-cpp/src/SmartCard.cpp index 3e2f0b9..2790a70 100644 --- a/lib/libpcsc-cpp/src/SmartCard.cpp +++ b/lib/libpcsc-cpp/src/SmartCard.cpp @@ -64,6 +64,14 @@ constexpr uint32_t OMNIKEY_6121 = 0x6632; namespace pcsc_cpp { +std::string operator+(std::string lhs, const byte_vector& rhs) +{ + lhs.reserve(lhs.size() + rhs.size() * 2); + std::ostringstream hexStringBuilder(std::move(lhs), std::ios::ate); + hexStringBuilder << rhs; + return hexStringBuilder.str(); +} + SmartCard Reader::connectToCard() const { return {*this}; @@ -146,6 +154,9 @@ class CardImpl 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); } @@ -222,34 +233,38 @@ class CardImpl if (responseLength > responseBytes.size()) { THROW(Error, "SCardTransmit: received more bytes than buffer size"); } + if (responseLength < 2) { + THROW(Error, "SCardTransmit: Need at least 2 bytes for creating ResponseApdu"); + } responseBytes.resize(responseLength); - // TODO: debug("Received: " + bytes2hexstr(responseBytes)) + PCSC_CPP_WARNING_PUSH + PCSC_CPP_WARNING_DISABLE_GCC("-Warray-bounds") // avoid GCC 13 false positive warning + // SW1 and SW2 are in the end + byte_type sw1 = responseBytes[responseLength - 2]; + byte_type sw2 = responseBytes[responseLength - 1]; + responseBytes.resize(responseLength - 2); + PCSC_CPP_WARNING_POP - auto response = ResponseApdu::fromBytes(std::move(responseBytes)); + ResponseApdu response {sw1, sw2, std::move(responseBytes)}; // Let expected errors through for handling in upper layers or in if blocks below. switch (response.sw1) { - case ResponseApdu::OK: - case ResponseApdu::MORE_DATA_AVAILABLE: // See the if block after next. - case ResponseApdu::VERIFICATION_FAILED: - case ResponseApdu::VERIFICATION_CANCELLED: - case ResponseApdu::WRONG_LENGTH: - case ResponseApdu::COMMAND_NOT_ALLOWED: - case ResponseApdu::WRONG_PARAMETERS: - case ResponseApdu::WRONG_LE_LENGTH: // See next if block. - break; + using enum ResponseApdu::Status; + case OK: + case MORE_DATA_AVAILABLE: + case WRONG_LE_LENGTH: + case VERIFICATION_FAILED: + case VERIFICATION_CANCELLED: + case WRONG_LENGTH: + case COMMAND_NOT_ALLOWED: + case WRONG_PARAMETERS: + return response; default: THROW(Error, "Error response: '" + response + "', protocol " + std::to_string(_protocol.dwProtocol)); } - - if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) { - THROW(Error, "Wrong LE length (SW1=0x6C) in response, please set LE"); - } - - return response; } void getMoreResponseData(ResponseApdu& response) const @@ -268,6 +283,14 @@ class CardImpl 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) diff --git a/lib/libpcsc-cpp/src/utils.cpp b/lib/libpcsc-cpp/src/utils.cpp deleted file mode 100644 index 005bd36..0000000 --- a/lib/libpcsc-cpp/src/utils.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2020-2024 Estonian Information System Authority - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "pcsc-cpp/pcsc-cpp.hpp" -#include "pcsc-cpp/pcsc-cpp-utils.hpp" - -#include -#include -#include - -using namespace pcsc_cpp; -using namespace std::string_literals; - -namespace -{ - -class UnexpectedResponseError : public Error -{ -public: - explicit UnexpectedResponseError(const CommandApdu& command, const ResponseApdu& response, - const char* file, const int line, - const char* callerFunctionName) : - Error("transmitApduWithExpectedResponse(): Unexpected response to command '"s + command - + "' - expected '9000', got '"s + response + "' in " + removeAbsolutePathPrefix(file) - + ':' + std::to_string(line) + ':' + callerFunctionName) - { - } -}; - -} // namespace - -namespace pcsc_cpp -{ - -std::ostream& operator<<(std::ostream& os, const pcsc_cpp::byte_vector& data) -{ - os << std::setfill('0') << std::hex; - for (const auto byte : data) - os << std::setw(2) << short(byte); - return os << std::setfill(' ') << std::dec; -} - -std::string operator+(std::string lhs, const byte_vector& rhs) -{ - lhs.reserve(lhs.size() + rhs.size() * 2); - std::ostringstream hexStringBuilder(std::move(lhs), std::ios::ate); - hexStringBuilder << rhs; - return hexStringBuilder.str(); -} - -void transmitApduWithExpectedResponse(const SmartCard::Session& session, const CommandApdu& command) -{ - const auto response = session.transmit(command); - if (!response.isOK()) { - throw UnexpectedResponseError(command, response, __FILE__, __LINE__, __func__); - } -} - -byte_vector readBinary(const SmartCard::Session& session, const uint16_t length, - byte_type blockLength) -{ - byte_vector resultBytes; - resultBytes.reserve(length); - while (resultBytes.size() < length) { - byte_type chunk = byte_type(std::min(length - resultBytes.size(), blockLength)); - auto response = - session.transmit(CommandApdu::readBinary(uint16_t(resultBytes.size()), chunk)); - if (chunk > 0 && response.data.size() != chunk) { - THROW(Error, - "Length mismatch, expected "s + std::to_string(chunk) + ", received " - + std::to_string(response.data.size()) + " bytes"); - } - resultBytes.insert(resultBytes.end(), response.data.cbegin(), response.data.cend()); - } - if (resultBytes.size() != length) { - THROW(Error, - "Length mismatch, expected "s + std::to_string(length) + ", received " - + std::to_string(resultBytes.size()) + " bytes"); - } - return resultBytes; -} - -} // namespace pcsc_cpp diff --git a/src/electronic-id.cpp b/src/electronic-id.cpp index 8066dc2..3c7d026 100644 --- a/src/electronic-id.cpp +++ b/src/electronic-id.cpp @@ -21,6 +21,7 @@ */ #include "electronic-ids/pcsc/EstEIDIDEMIA.hpp" +#include "electronic-ids/pcsc/EstEIDThales.hpp" #include "electronic-ids/pcsc/FinEID.hpp" #include "electronic-ids/pcsc/LatEIDIDEMIAv2.hpp" @@ -60,6 +61,10 @@ const std::map SUPPORTED {{0x3b, 0xdc, 0x96, 0x00, 0x80, 0xb1, 0xfe, 0x45, 0x1f, 0x83, 0x00, 0x12, 0x23, 0x3f, 0x54, 0x65, 0x49, 0x44, 0x32, 0x0f, 0x90, 0x00, 0xc3}, constructor}, + // EstEID Thales v1.0 + {{0x3b, 0xff, 0x96, 0x00, 0x00, 0x80, 0x31, 0xfe, 0x43, 0x80, 0x31, 0xb8, 0x53, + 0x65, 0x49, 0x44, 0x64, 0xb0, 0x85, 0x05, 0x10, 0x12, 0x23, 0x3f, 0x1d}, + constructor}, // FinEID v3.0 {{0x3B, 0x7F, 0x96, 0x00, 0x00, 0x80, 0x31, 0xB8, 0x65, 0xB0, 0x85, 0x03, 0x00, 0xEF, 0x12, 0x00, 0xF6, 0x82, 0x90, 0x00}, diff --git a/src/electronic-ids/TLV.hpp b/src/electronic-ids/TLV.hpp index ee63092..d3cc124 100644 --- a/src/electronic-ids/TLV.hpp +++ b/src/electronic-ids/TLV.hpp @@ -58,9 +58,9 @@ struct TLV tag = *begin++; if ((tag & 0x1F) == 0x1F) { // Multi-byte tag constexpr uint8_t MAX_TAG_BYTES = sizeof(tag); - uint8_t tagBytes = 1; + uint8_t tag_bytes = 1; do { - if (tagBytes >= MAX_TAG_BYTES) { + if (tag_bytes >= MAX_TAG_BYTES) { THROW(std::invalid_argument, "Invalid TLV: Tag too long or too large for uint32_t"); } @@ -68,7 +68,7 @@ struct TLV THROW(std::invalid_argument, "Invalid TLV: Unexpected end of tag"); } tag = (tag << 8) | (*begin++); - ++tagBytes; + ++tag_bytes; } while ((tag & 0x80) != 0x00); } @@ -78,13 +78,15 @@ struct TLV length = *begin++; if (length & 0x80) { // Extended length encoding - auto num_bytes = uint8_t(length & 0x7F); - if (num_bytes == 0 || num_bytes > 4 || std::distance(begin, end) < num_bytes) { + constexpr uint8_t MAX_LEN_BYTES = sizeof(length); + auto len_bytes = uint8_t(length & 0x7F); + if (len_bytes == 0 || len_bytes > MAX_LEN_BYTES + || std::distance(begin, end) < len_bytes) { THROW(std::invalid_argument, "Invalid TLV: Incorrect extended length encoding"); } length = 0; - for (uint8_t i = 0; i < num_bytes; ++i) { + for (uint8_t i = 0; i < len_bytes; ++i) { length = (length << 8) | (*begin++); } } @@ -94,28 +96,18 @@ struct TLV } } - PCSC_CPP_CONSTEXPR_VECTOR TLV child() const { return {begin, begin + length}; } - PCSC_CPP_CONSTEXPR_VECTOR TLV operator[](uint32_t find) const { - TLV tlv = child(); - for (; tlv && tlv.tag != find; ++tlv) {} - return tlv; + return TLV(begin, begin + length).find(find); } PCSC_CPP_CONSTEXPR_VECTOR TLV& operator++() { return *this = {begin + length, end}; } - template - static PCSC_CPP_CONSTEXPR_VECTOR TLV path(TLV tlv, uint32_t tag, Tags... tags) + PCSC_CPP_CONSTEXPR_VECTOR TLV find(uint32_t find) const { - for (; tlv; ++tlv) { - if (tlv.tag == tag) { - if constexpr (sizeof...(tags) > 0) { - return path(tlv.child(), uint32_t(tags)...); - } - return tlv; - } - } - return TLV({}); + TLV tlv = *this; + for (; tlv && tlv.tag != find; ++tlv) {} + // Return the found TLV or an empty one if not found + return tlv; } constexpr operator bool() const noexcept { return begin < end; } diff --git a/src/electronic-ids/pcsc/EIDIDEMIA.cpp b/src/electronic-ids/pcsc/EIDIDEMIA.cpp index 07a8c3e..bf08886 100644 --- a/src/electronic-ids/pcsc/EIDIDEMIA.cpp +++ b/src/electronic-ids/pcsc/EIDIDEMIA.cpp @@ -53,20 +53,20 @@ const auto SIGN_CERT = CommandApdu::selectEF(0x09, {0xAD, 0xF2, 0x34, 0x1F}); void EIDIDEMIA::selectMain(const SmartCard::Session& session) { - transmitApduWithExpectedResponse(session, MAIN_AID); + selectFile(session, MAIN_AID); } -void EIDIDEMIA::selectADF1(const pcsc_cpp::SmartCard::Session& session) +void EIDIDEMIA::selectADF1(const SmartCard::Session& session) { - transmitApduWithExpectedResponse(session, ADF1_AID); + selectFile(session, ADF1_AID); } -void EIDIDEMIA::selectADF2(const pcsc_cpp::SmartCard::Session& session) +void EIDIDEMIA::selectADF2(const SmartCard::Session& session) { - transmitApduWithExpectedResponse(session, ADF2_AID); + selectFile(session, ADF2_AID); } -byte_vector EIDIDEMIA::getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, +byte_vector EIDIDEMIA::getCertificateImpl(const SmartCard::Session& session, const CertificateType type) const { selectMain(session); @@ -74,51 +74,64 @@ byte_vector EIDIDEMIA::getCertificateImpl(const pcsc_cpp::SmartCard::Session& se return readFile(session, type.isAuthentication() ? AUTH_CERT : SIGN_CERT, 0xC0); } -EIDIDEMIA::KeyInfo EIDIDEMIA::authKeyRef(const pcsc_cpp::SmartCard::Session& /*session*/) const +EIDIDEMIA::KeyInfo EIDIDEMIA::authKeyRef(const SmartCard::Session& /*session*/) const { return {DEFAULT_AUTH_KEY_ID, true}; } -byte_vector EIDIDEMIA::signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, - byte_vector&& pin, const byte_vector& hash) const +byte_vector EIDIDEMIA::signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash) const { selectADF1(session); auto [keyId, isECC] = authKeyRef(session); selectSecurityEnv(session, 0xA4, isECC ? 0x04 : 0x02, keyId, name()); - verifyPin(session, AUTH_PIN_REFERENCE, std::move(pin), authPinMinMaxLength().first, - authPinMinMaxLength().second, PIN_PADDING_CHAR); - - return internalAuthenticate(session, - authSignatureAlgorithm().isRSAWithPKCS1Padding() - ? addRSAOID(authSignatureAlgorithm().hashAlgorithm(), hash) - : hash, - name()); + verifyPin(session, AUTH_PIN_REFERENCE, std::move(pin), authPinMinMaxLength(), PIN_PADDING_CHAR); + + CommandApdu internalAuth {0x00, + 0x88, + 0x00, + 0x00, + authSignatureAlgorithm().isRSAWithPKCS1Padding() + ? addRSAOID(authSignatureAlgorithm().hashAlgorithm(), hash) + : hash, + 0}; + auto response = session.transmit(internalAuth); + if (response.sw1 == ResponseApdu::WRONG_LENGTH) { + THROW(SmartCardError, + "Wrong data length in command INTERNAL AUTHENTICATE argument: " + response); + } + if (!response.isOK()) { + THROW(SmartCardError, "Command INTERNAL AUTHENTICATE failed with error " + response); + } + return std::move(response.data); } ElectronicID::PinRetriesRemainingAndMax -EIDIDEMIA::authPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const +EIDIDEMIA::authPinRetriesLeftImpl(const SmartCard::Session& session) const { selectMain(session); return pinRetriesLeft(session, AUTH_PIN_REFERENCE); } -EIDIDEMIA::KeyInfo EIDIDEMIA::signKeyRef(const pcsc_cpp::SmartCard::Session& /*session*/) const +EIDIDEMIA::KeyInfo EIDIDEMIA::signKeyRef(const SmartCard::Session& /*session*/) const { return {DEFAULT_SIGN_KEY_ID, true}; } -ElectronicID::Signature -EIDIDEMIA::signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash, const HashAlgorithm hashAlgo) const +ElectronicID::Signature EIDIDEMIA::signWithSigningKeyImpl(const SmartCard::Session& session, + byte_vector&& pin, + const byte_vector& hash, + const HashAlgorithm hashAlgo) const { selectADF2(session); auto [keyRef, isECC] = signKeyRef(session); selectSecurityEnv(session, 0xB6, isECC ? 0x54 : 0x42, keyRef, name()); - verifyPin(session, SIGN_PIN_REFERENCE, std::move(pin), signingPinMinMaxLength().first, - signingPinMinMaxLength().second, PIN_PADDING_CHAR); + verifyPin(session, SIGN_PIN_REFERENCE, std::move(pin), signingPinMinMaxLength(), + PIN_PADDING_CHAR); auto tmp = hash; if (isECC) { + // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf 6.4 constexpr size_t ECDSA384_INPUT_LENGTH = 384 / 8; if (tmp.size() < ECDSA384_INPUT_LENGTH) { // Zero-pad hashes that are shorter than SHA-384. @@ -128,12 +141,21 @@ EIDIDEMIA::signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, b tmp.resize(ECDSA384_INPUT_LENGTH); } } - return {computeSignature(session, tmp, name()), + CommandApdu computeSignature {0x00, 0x2A, 0x9E, 0x9A, std::move(tmp), 0}; + auto response = session.transmit(computeSignature); + if (response.sw1 == ResponseApdu::WRONG_LENGTH) { + THROW(SmartCardError, + "Wrong data length in command COMPUTE SIGNATURE argument: " + response); + } + if (!response.isOK()) { + THROW(SmartCardError, "Command COMPUTE SIGNATURE failed with error " + response); + } + return {std::move(response.data), {isECC ? SignatureAlgorithm::ES : SignatureAlgorithm::RS, hashAlgo}}; } ElectronicID::PinRetriesRemainingAndMax -EIDIDEMIA::signingPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const +EIDIDEMIA::signingPinRetriesLeftImpl(const SmartCard::Session& session) const { selectADF2(session); return pinRetriesLeft(session, SIGN_PIN_REFERENCE); @@ -143,13 +165,13 @@ ElectronicID::PinRetriesRemainingAndMax EIDIDEMIA::pinRetriesLeft(const SmartCar byte_type pinReference) { auto ref = byte_type(pinReference & 0x0F); - const pcsc_cpp::CommandApdu GET_DATA_ODD { + const CommandApdu GET_DATA_ODD { 0x00, 0xCB, 0x3F, 0xFF, {0x4D, 0x08, 0x70, 0x06, 0xBF, 0x81, ref, 0x02, 0xA0, 0x80}, 0x00}; const auto response = session.transmit(GET_DATA_ODD); if (!response.isOK()) { THROW(SmartCardError, "Command GET DATA ODD failed with error " + response); } - TLV info = TLV::path(TLV(response.data), 0x70, 0xBF8100 | ref, 0xA0); + TLV info = TLV(response.data).find(0x70)[0xBF8100 | ref][0xA0]; TLV max = info[0x9A]; TLV tries = info[0x9B]; if (max && tries) { diff --git a/src/electronic-ids/pcsc/EIDIDEMIA.hpp b/src/electronic-ids/pcsc/EIDIDEMIA.hpp index 29f3d77..9db4324 100644 --- a/src/electronic-ids/pcsc/EIDIDEMIA.hpp +++ b/src/electronic-ids/pcsc/EIDIDEMIA.hpp @@ -39,36 +39,28 @@ class EIDIDEMIA : public PcscElectronicID using PcscElectronicID::PcscElectronicID; protected: - byte_vector getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, + byte_vector getCertificateImpl(const SmartCard::Session& session, const CertificateType type) const override; PinRetriesRemainingAndMax - authPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const override; - JsonWebSignatureAlgorithm authSignatureAlgorithm() const override - { - return JsonWebSignatureAlgorithm::ES384; - } - virtual KeyInfo authKeyRef(const pcsc_cpp::SmartCard::Session& session) const; - byte_vector signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, + authPinRetriesLeftImpl(const SmartCard::Session& session) const override; + virtual KeyInfo authKeyRef(const SmartCard::Session& session) const; + byte_vector signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, const byte_vector& hash) const override; PinRetriesRemainingAndMax - signingPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const override; - const std::set& supportedSigningAlgorithms() const override - { - return ELLIPTIC_CURVE_SIGNATURE_ALGOS(); - } - virtual KeyInfo signKeyRef(const pcsc_cpp::SmartCard::Session& session) const; - Signature signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, + signingPinRetriesLeftImpl(const SmartCard::Session& session) const override; + virtual KeyInfo signKeyRef(const SmartCard::Session& session) const; + Signature signWithSigningKeyImpl(const SmartCard::Session& session, byte_vector&& pin, const byte_vector& hash, const HashAlgorithm hashAlgo) const override; - static PinRetriesRemainingAndMax pinRetriesLeft(const pcsc_cpp::SmartCard::Session& session, + static PinRetriesRemainingAndMax pinRetriesLeft(const SmartCard::Session& session, byte_type pinReference); - static void selectMain(const pcsc_cpp::SmartCard::Session& session); - static void selectADF1(const pcsc_cpp::SmartCard::Session& session); - static void selectADF2(const pcsc_cpp::SmartCard::Session& session); + static void selectMain(const SmartCard::Session& session); + static void selectADF1(const SmartCard::Session& session); + static void selectADF2(const SmartCard::Session& session); }; } // namespace electronic_id diff --git a/src/electronic-ids/pcsc/FinEID.cpp b/src/electronic-ids/pcsc/EIDThales.cpp similarity index 54% rename from src/electronic-ids/pcsc/FinEID.cpp rename to src/electronic-ids/pcsc/EIDThales.cpp index dcc6edf..a3985d0 100644 --- a/src/electronic-ids/pcsc/FinEID.cpp +++ b/src/electronic-ids/pcsc/EIDThales.cpp @@ -20,10 +20,12 @@ * SOFTWARE. */ -#include "FinEID.hpp" +#include "EIDThales.hpp" #include "pcsc-common.hpp" +#include "../TLV.hpp" + // FINEID specification: // App 3.0: // https://dvv.fi/documents/16079645/17324923/S1v30.pdf/0bad6ff1-1617-1b1f-ab49-56a2f36ecd38/S1v30.pdf @@ -35,77 +37,58 @@ // https://dvv.fi/documents/16079645/17324992/S1v40+(1).pdf/56a167fe-9f26-1fda-7d76-cfbbb29d184e/S1v40+(1).pdf // Imp 4.0: // https://dvv.fi/documents/16079645/17324992/S4-1v40.pdf/55bddc08-6893-b4b4-73fa-24dced600198/S4-1v40.pdf +// EstEID specification: +// https://www.id.ee/wp-content/uploads/2025/03/tdc_est_eid_developer_guide.pdf using namespace pcsc_cpp; +using namespace electronic_id; namespace { -const auto SELECT_MAIN_AID = CommandApdu::select( - 0x04, {0xa0, 0x00, 0x00, 0x00, 0x63, 0x50, 0x4b, 0x43, 0x53, 0x2d, 0x31, 0x35}); -const auto SELECT_AUTH_CERT_FILE = CommandApdu::selectEF(0x08, {0x43, 0x31}); -const auto SELECT_SIGN_CERT_FILE_V3 = CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x35}); -const auto SELECT_SIGN_CERT_FILE_V4 = CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x32}); - constexpr byte_type PIN_PADDING_CHAR = 0x00; -constexpr byte_type AUTH_PIN_REFERENCE = 0x11; -constexpr byte_type SIGNING_PIN_REFERENCE = 0x82; -constexpr byte_type AUTH_KEY_REFERENCE = 0x01; -constexpr byte_type SIGNING_KEY_REFERENCE_V3 = 0x03; -constexpr byte_type SIGNING_KEY_REFERENCE_V4 = 0x02; constexpr byte_type ECDSA_ALGO = 0x04; -constexpr byte_type RSA_PSS_ALGO = 0x05; - -} // namespace - -namespace electronic_id -{ +constexpr byte_type SIGNING_PIN_REFERENCE = 0x82; -byte_vector FinEIDv3::getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, - const CertificateType type) const -{ - transmitApduWithExpectedResponse(session, SELECT_MAIN_AID); - return readFile(session, - type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V3); -} +const auto SELECT_MAIN_AID = CommandApdu::select( + 0x04, {0xa0, 0x00, 0x00, 0x00, 0x63, 0x50, 0x4b, 0x43, 0x53, 0x2d, 0x31, 0x35}); -byte_vector FinEIDv3::signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, - byte_vector&& pin, const byte_vector& hash) const -{ - return sign(session, authSignatureAlgorithm().hashAlgorithm(), hash, std::move(pin), - AUTH_PIN_REFERENCE, authPinMinMaxLength(), AUTH_KEY_REFERENCE, RSA_PSS_ALGO, 0x00); -} +} // namespace ElectronicID::PinRetriesRemainingAndMax -FinEIDv3::authPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const +EIDThales::authPinRetriesLeftImpl(const SmartCard::Session& session) const { - return pinRetriesLeft(session, AUTH_PIN_REFERENCE); + return pinRetriesLeft(session, authPinReference()); } -const std::set& FinEIDv3::supportedSigningAlgorithms() const +byte_vector EIDThales::getCertificateImpl(const SmartCard::Session& session, + const CertificateType type) const { - return ELLIPTIC_CURVE_SIGNATURE_ALGOS(); + selectFile(session, SELECT_MAIN_AID); + return readFile(session, type.isAuthentication() ? authCertFile() : signCertFile()); } -ElectronicID::Signature -FinEIDv3::signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash, const HashAlgorithm hashAlgo) const -{ - return {sign(session, hashAlgo, hash, std::move(pin), SIGNING_PIN_REFERENCE, - signingPinMinMaxLength(), SIGNING_KEY_REFERENCE_V3, ECDSA_ALGO, 0x40), - {SignatureAlgorithm::ES, hashAlgo}}; -} - -ElectronicID::PinRetriesRemainingAndMax -FinEIDv3::signingPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const +ElectronicID::PinRetriesRemainingAndMax EIDThales::pinRetriesLeft(const SmartCard::Session& session, + byte_type pinReference) const { - return pinRetriesLeft(session, SIGNING_PIN_REFERENCE); + const auto GET_DATA = smartcard().protocol() == SmartCard::Protocol::T1 + ? CommandApdu {0x00, 0xCB, 0x00, 0xFF, {0xA0, 0x03, 0x83, 0x01, pinReference}, 0x00} + : CommandApdu {0x00, 0xCB, 0x00, 0xFF, {0xA0, 0x03, 0x83, 0x01, pinReference}}; + const auto response = session.transmit(GET_DATA); + if (!response.isOK()) { + THROW(SmartCardError, "Command GET DATA failed with error " + response); + } + if (TLV info = TLV(response.data).find(0xA0)[0xdf21]) { + return {*info.begin, maximumPinRetries()}; + } + THROW(SmartCardError, + "Command GET DATA failed: received data does not contain the PIN remaining retries info"); } -byte_vector FinEIDv3::sign(const pcsc_cpp::SmartCard::Session& session, - const HashAlgorithm hashAlgo, const byte_vector& hash, byte_vector&& pin, - byte_type pinReference, PinMinMaxLength pinMinMaxLength, - byte_type keyReference, byte_type signatureAlgo, byte_type LE) const +byte_vector EIDThales::sign(const SmartCard::Session& session, const HashAlgorithm hashAlgo, + const byte_vector& hash, byte_vector&& pin, byte_type pinReference, + PinMinMaxLength pinMinMaxLength, byte_type keyReference, + byte_type signatureAlgo) const { if (signatureAlgo != ECDSA_ALGO && hashAlgo.isSHA3()) { THROW(ArgumentFatalError, "No OID for algorithm " + std::string(hashAlgo)); @@ -133,8 +116,7 @@ byte_vector FinEIDv3::sign(const pcsc_cpp::SmartCard::Session& session, THROW(ArgumentFatalError, "No OID for algorithm " + std::string(hashAlgo)); } - verifyPin(session, pinReference, std::move(pin), pinMinMaxLength.first, pinMinMaxLength.second, - PIN_PADDING_CHAR); + verifyPin(session, pinReference, std::move(pin), pinMinMaxLength, PIN_PADDING_CHAR); // Select security environment for COMPUTE SIGNATURE. selectSecurityEnv(session, 0xB6, signatureAlgo, keyReference, name()); @@ -152,8 +134,8 @@ byte_vector FinEIDv3::sign(const pcsc_cpp::SmartCard::Session& session, THROW(SmartCardError, "Command COMPUTE SIGNATURE failed with error " + response); } - const CommandApdu getSignature {0x00, 0x2A, 0x9E, 0x9A, LE}; - const auto signature = session.transmit(getSignature); + const CommandApdu getSignature {0x00, 0x2A, 0x9E, 0x9A, 0x00}; + auto signature = session.transmit(getSignature); if (signature.sw1 == ResponseApdu::WRONG_LENGTH) { THROW(SmartCardError, "Wrong data length in command GET SIGNATURE argument: " + response); @@ -162,49 +144,28 @@ byte_vector FinEIDv3::sign(const pcsc_cpp::SmartCard::Session& session, THROW(SmartCardError, "Command GET SIGNATURE failed with error " + signature); } - return signature.data; + return std::move(signature.data); } ElectronicID::PinRetriesRemainingAndMax -FinEIDv3::pinRetriesLeft(const pcsc_cpp::SmartCard::Session& session, byte_type pinReference) const +EIDThales::signingPinRetriesLeftImpl(const SmartCard::Session& session) const { - const auto GET_DATA = smartcard().protocol() == SmartCard::Protocol::T1 - ? CommandApdu {0x00, 0xCB, 0x00, 0xFF, {0xA0, 0x03, 0x83, 0x01, pinReference}, 0x00} - : CommandApdu {0x00, 0xCB, 0x00, 0xFF, {0xA0, 0x03, 0x83, 0x01, pinReference}}; - const auto response = session.transmit(GET_DATA); - if (!response.isOK()) { - THROW(SmartCardError, "Command GET DATA failed with error " + response); - } - if (response.data.size() < 21) { - THROW(SmartCardError, - "Command GET DATA failed: received data size " + std::to_string(response.data.size()) - + " is less than the expected size of the PIN remaining retries offset 21"); - } - return {uint8_t(response.data[20]), int8_t(5)}; -} - -byte_vector FinEIDv4::getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, - const CertificateType type) const -{ - transmitApduWithExpectedResponse(session, SELECT_MAIN_AID); - return readFile(session, - type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V4); + return pinRetriesLeft(session, SIGNING_PIN_REFERENCE); } -byte_vector FinEIDv4::signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, - byte_vector&& pin, const byte_vector& hash) const +byte_vector EIDThales::signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash) const { return sign(session, authSignatureAlgorithm().hashAlgorithm(), hash, std::move(pin), - AUTH_PIN_REFERENCE, authPinMinMaxLength(), AUTH_KEY_REFERENCE, ECDSA_ALGO, 0x60); + authPinReference(), authPinMinMaxLength(), AUTH_KEY_REFERENCE, ECDSA_ALGO); } -ElectronicID::Signature -FinEIDv4::signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash, const HashAlgorithm hashAlgo) const +ElectronicID::Signature EIDThales::signWithSigningKeyImpl(const SmartCard::Session& session, + byte_vector&& pin, + const byte_vector& hash, + const HashAlgorithm hashAlgo) const { return {sign(session, hashAlgo, hash, std::move(pin), SIGNING_PIN_REFERENCE, - signingPinMinMaxLength(), SIGNING_KEY_REFERENCE_V4, ECDSA_ALGO, 0x60), + signingPinMinMaxLength(), signingKeyReference(), ECDSA_ALGO), {SignatureAlgorithm::ES, hashAlgo}}; } - -} // namespace electronic_id diff --git a/src/electronic-ids/pcsc/EIDThales.hpp b/src/electronic-ids/pcsc/EIDThales.hpp new file mode 100644 index 0000000..e1c955f --- /dev/null +++ b/src/electronic-ids/pcsc/EIDThales.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020-2024 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "PcscElectronicID.hpp" + +namespace electronic_id +{ + +class EIDThales : public PcscElectronicID +{ +public: + using PcscElectronicID::PcscElectronicID; + +protected: + using CommandApdu = pcsc_cpp::CommandApdu; + + virtual PCSC_CPP_CONSTEXPR_VECTOR CommandApdu authCertFile() const = 0; + virtual constexpr byte_type authPinReference() const = 0; + virtual constexpr int8_t maximumPinRetries() const = 0; + virtual PCSC_CPP_CONSTEXPR_VECTOR CommandApdu signCertFile() const = 0; + virtual constexpr byte_type signingKeyReference() const = 0; + + byte_vector getCertificateImpl(const SmartCard::Session& session, + const CertificateType type) const override; + PinRetriesRemainingAndMax + authPinRetriesLeftImpl(const SmartCard::Session& session) const override; + PinRetriesRemainingAndMax + signingPinRetriesLeftImpl(const SmartCard::Session& session) const override; + byte_vector signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash) const override; + Signature signWithSigningKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash, + const HashAlgorithm hashAlgo) const override; + + PinRetriesRemainingAndMax pinRetriesLeft(const SmartCard::Session& session, + byte_type pinReference) const; + byte_vector sign(const SmartCard::Session& session, const HashAlgorithm hashAlgo, + const byte_vector& hash, byte_vector&& pin, byte_type pinReference, + PinMinMaxLength pinMinMaxLength, byte_type keyReference, + byte_type signatureAlgo) const; + + static constexpr byte_type AUTH_KEY_REFERENCE = 0x01; +}; + +} // namespace electronic_id \ No newline at end of file diff --git a/src/electronic-ids/pcsc/EstEIDIDEMIA.hpp b/src/electronic-ids/pcsc/EstEIDIDEMIA.hpp index e4cdaf1..bed9c2e 100644 --- a/src/electronic-ids/pcsc/EstEIDIDEMIA.hpp +++ b/src/electronic-ids/pcsc/EstEIDIDEMIA.hpp @@ -36,8 +36,7 @@ class EstEIDIDEMIAV1 : public EIDIDEMIA using EIDIDEMIA::EIDIDEMIA; private: - PinMinMaxLength authPinMinMaxLength() const override { return {4, 12}; } - PinMinMaxLength signingPinMinMaxLength() const override { return {5, 12}; } + constexpr PinMinMaxLength signingPinMinMaxLength() const override { return {5, 12}; } std::string name() const override { return "EstEID IDEMIA v1"; } Type type() const override { return EstEID; } }; diff --git a/src/electronic-ids/pcsc/EstEIDThales.hpp b/src/electronic-ids/pcsc/EstEIDThales.hpp new file mode 100644 index 0000000..93352fc --- /dev/null +++ b/src/electronic-ids/pcsc/EstEIDThales.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020-2024 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "EIDThales.hpp" + +namespace electronic_id +{ + +class EstEIDThales : public EIDThales +{ +public: + using EIDThales::EIDThales; + +protected: + std::string name() const override { return "EstEIDThales"; } + Type type() const override { return EstEID; } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu authCertFile() const override + { + return CommandApdu::selectEF(0x08, {0xAD, 0xF1, 0x34, 0x11}); + } + constexpr byte_type authPinReference() const override { return 0x81; } + constexpr int8_t maximumPinRetries() const override { return 3; } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu signCertFile() const override + { + return CommandApdu::selectEF(0x08, {0xAD, 0xF2, 0x34, 0x21}); + } + constexpr byte_type signingKeyReference() const override { return 0x05; } + constexpr PinMinMaxLength signingPinMinMaxLength() const override { return {5, 12}; } +}; + +} // namespace electronic_id \ No newline at end of file diff --git a/src/electronic-ids/pcsc/FinEID.hpp b/src/electronic-ids/pcsc/FinEID.hpp index 583a85a..562bb03 100644 --- a/src/electronic-ids/pcsc/FinEID.hpp +++ b/src/electronic-ids/pcsc/FinEID.hpp @@ -22,74 +22,57 @@ #pragma once -#include "PcscElectronicID.hpp" +#include "EIDThales.hpp" namespace electronic_id { -class FinEIDv3 : public PcscElectronicID +class FinEIDv4 : public EIDThales { public: - using PcscElectronicID::PcscElectronicID; + using EIDThales::EIDThales; protected: - byte_vector getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, - const CertificateType type) const override; - - JsonWebSignatureAlgorithm authSignatureAlgorithm() const override + std::string name() const override { return "FinEID v4"; } + Type type() const override { return FinEID; } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu authCertFile() const override { - return JsonWebSignatureAlgorithm::PS256; + return CommandApdu::selectEF(0x08, {0x43, 0x31}); } - PinMinMaxLength authPinMinMaxLength() const override { return {4, 12}; } - PinRetriesRemainingAndMax - authPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const override; - - const std::set& supportedSigningAlgorithms() const override; - PinMinMaxLength signingPinMinMaxLength() const override { return {6, 12}; } - PinRetriesRemainingAndMax - signingPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const override; - - std::string name() const override { return "FinEID v3"; } - Type type() const override { return FinEID; } - - byte_vector signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash) const override; - - Signature signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash, - const HashAlgorithm hashAlgo) const override; - - byte_vector sign(const pcsc_cpp::SmartCard::Session& session, const HashAlgorithm hashAlgo, - const byte_vector& hash, byte_vector&& pin, byte_type pinReference, - PinMinMaxLength pinMinMaxLength, byte_type keyReference, - byte_type signatureAlgo, byte_type LE) const; - - PinRetriesRemainingAndMax pinRetriesLeft(const pcsc_cpp::SmartCard::Session& session, - byte_type pinReference) const; + constexpr byte_type authPinReference() const override { return 0x11; } + constexpr int8_t maximumPinRetries() const override { return 5; } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu signCertFile() const override + { + return CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x32}); + } + constexpr byte_type signingKeyReference() const override { return 0x02; } + constexpr PinMinMaxLength signingPinMinMaxLength() const override { return {6, 12}; } }; -class FinEIDv4 : public FinEIDv3 +class FinEIDv3 : public FinEIDv4 { public: - using FinEIDv3::FinEIDv3; + using FinEIDv4::FinEIDv4; -private: - JsonWebSignatureAlgorithm authSignatureAlgorithm() const override +protected: + std::string name() const override { return "FinEID v3"; } + constexpr JsonWebSignatureAlgorithm authSignatureAlgorithm() const override { - return JsonWebSignatureAlgorithm::ES384; + return JsonWebSignatureAlgorithm::PS256; + } + PCSC_CPP_CONSTEXPR_VECTOR CommandApdu signCertFile() const override + { + return CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x35}); + } + constexpr byte_type signingKeyReference() const override { return 0x03; } + byte_vector signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash) const override + { + return sign(session, authSignatureAlgorithm().hashAlgorithm(), hash, std::move(pin), + authPinReference(), authPinMinMaxLength(), AUTH_KEY_REFERENCE, RSA_PSS_ALGO); } - byte_vector getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, - const CertificateType type) const override; - - std::string name() const override { return "FinEID v4"; } - - byte_vector signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash) const override; - - Signature signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, byte_vector&& pin, - const byte_vector& hash, - const HashAlgorithm hashAlgo) const override; + static constexpr byte_type RSA_PSS_ALGO = 0x05; }; } // namespace electronic_id diff --git a/src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp b/src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp index 9d60c99..faa6a35 100644 --- a/src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp +++ b/src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp @@ -26,7 +26,6 @@ #include "pcsc-common.hpp" -#include #include using namespace pcsc_cpp; @@ -43,6 +42,7 @@ struct LatEIDIDEMIAV2::Private namespace { +// https://www.foo.be/docs/opensst/ref/pkcs/pkcs-15/pkcs-15v11d1.pdf const byte_vector EF_OD {0x50, 0x31}; constexpr byte_type PRIV_FILE_REF = 0xA0; constexpr byte_type CERT_FILE_REF = 0xA4; @@ -56,14 +56,14 @@ LatEIDIDEMIAV2::LatEIDIDEMIAV2(SmartCard&& _card) : LatEIDIDEMIAV2::~LatEIDIDEMIAV2() = default; -byte_vector LatEIDIDEMIAV2::getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, +byte_vector LatEIDIDEMIAV2::getCertificateImpl(const SmartCard::Session& session, const CertificateType type) const { selectMain(session); type.isAuthentication() ? selectADF1(session) : selectADF2(session); auto info = readDCODInfo(session, CERT_FILE_REF, type.isAuthentication() ? data->authCache : data->signCache); - if (TLV id = TLV::path(info, 0x30, 0xA1, 0x30, 0x30, 0x04)) { + if (TLV id = info.find(0x30)[0xA1][0x30][0x30][0x04]) { return readFile(session, CommandApdu::selectEF(0x02, {id.begin, id.end})); } THROW(SmartCardError, "EF.CD reference not found"); @@ -93,7 +93,7 @@ const std::set& LatEIDIDEMIAV2::supportedSigningAlgorithms() return data->signKeyInfo->isECC ? ELLIPTIC_CURVE_SIGNATURE_ALGOS() : RS256_SIGNATURE_ALGO; } -EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::authKeyRef(const pcsc_cpp::SmartCard::Session& session) const +EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::authKeyRef(const SmartCard::Session& session) const { if (!data->authKeyInfo.has_value()) { data->authKeyInfo = @@ -102,7 +102,7 @@ EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::authKeyRef(const pcsc_cpp::SmartCard::Session return data->authKeyInfo.value(); } -EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::signKeyRef(const pcsc_cpp::SmartCard::Session& session) const +EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::signKeyRef(const SmartCard::Session& session) const { if (!data->signKeyInfo.has_value()) { data->signKeyInfo = @@ -111,8 +111,8 @@ EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::signKeyRef(const pcsc_cpp::SmartCard::Session return data->signKeyInfo.value(); } -template -TLV LatEIDIDEMIAV2::readEF_File(const SmartCard::Session& session, byte_vector file, C& cache) const +TLV LatEIDIDEMIAV2::readEF_File(const SmartCard::Session& session, byte_vector file, + auto& cache) const { if (auto it = cache.find(file); it != cache.end()) { return TLV(it->second); @@ -120,31 +120,24 @@ TLV LatEIDIDEMIAV2::readEF_File(const SmartCard::Session& session, byte_vector f return TLV(cache[std::move(file)] = readFile(session, CommandApdu::selectEF(0x02, file))); } -template -TLV LatEIDIDEMIAV2::readDCODInfo(const pcsc_cpp::SmartCard::Session& session, byte_type type, - C& cache) const +TLV LatEIDIDEMIAV2::readDCODInfo(const SmartCard::Session& session, byte_type type, + auto& cache) const { const auto info = readEF_File(session, EF_OD, cache); - for (TLV ref(info); ref; ++ref) { - if (ref.tag != type) { - continue; - } - if (auto file = ref[0x30][0x04]; file && file.length == 2) { - return readEF_File(session, {file.begin, file.end}, cache); - } + if (auto file = info.find(type)[0x30][0x04]; file && file.length == 2) { + return readEF_File(session, {file.begin, file.end}, cache); } THROW(SmartCardError, "EF.DCOD reference not found"); } -template -EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::readPrKDInfo(const pcsc_cpp::SmartCard::Session& session, - byte_type keyID, C& cache) const +EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::readPrKDInfo(const SmartCard::Session& session, byte_type keyID, + auto& cache) const { TLV prKD = readDCODInfo(session, PRIV_FILE_REF, cache); if (!prKD) { THROW(SmartCardError, "EF.PrKD reference not found"); } - TLV key = prKD[0x30]; - key = TLV::path(++key, 0x30, 0x02); + TLV key = prKD[0x30]; // CommonObjectAttributes + key = (++key).find(0x30)[0x02]; // ClassAttributes.ID return {key.length == 2 ? *std::next(key.begin) : keyID, prKD.tag == 0xA0}; } diff --git a/src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp b/src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp index d2b5de2..112d833 100644 --- a/src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp +++ b/src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp @@ -34,33 +34,28 @@ struct TLV; class LatEIDIDEMIAV2 : public EIDIDEMIA { public: - explicit LatEIDIDEMIAV2(pcsc_cpp::SmartCard&& _card); + explicit LatEIDIDEMIAV2(SmartCard&& _card); ~LatEIDIDEMIAV2() override; PCSC_CPP_DISABLE_COPY_MOVE(LatEIDIDEMIAV2); private: - byte_vector getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, + byte_vector getCertificateImpl(const SmartCard::Session& session, const CertificateType type) const override; JsonWebSignatureAlgorithm authSignatureAlgorithm() const override; - PinMinMaxLength authPinMinMaxLength() const override { return {4, 12}; } const std::set& supportedSigningAlgorithms() const override; - PinMinMaxLength signingPinMinMaxLength() const override { return {6, 12}; } + constexpr PinMinMaxLength signingPinMinMaxLength() const override { return {6, 12}; } std::string name() const override { return "LatEID IDEMIA v2"; } Type type() const override { return LatEID; } - KeyInfo authKeyRef(const pcsc_cpp::SmartCard::Session& session) const override; - KeyInfo signKeyRef(const pcsc_cpp::SmartCard::Session& session) const override; + KeyInfo authKeyRef(const SmartCard::Session& session) const override; + KeyInfo signKeyRef(const SmartCard::Session& session) const override; - template - TLV readEF_File(const pcsc_cpp::SmartCard::Session& session, byte_vector file, C& cache) const; - template - TLV readDCODInfo(const pcsc_cpp::SmartCard::Session& session, byte_type type, C& cache) const; - template - KeyInfo readPrKDInfo(const pcsc_cpp::SmartCard::Session& session, byte_type keyID, - C& cache) const; + TLV readEF_File(const SmartCard::Session& session, byte_vector file, auto& cache) const; + TLV readDCODInfo(const SmartCard::Session& session, byte_type type, auto& cache) const; + KeyInfo readPrKDInfo(const SmartCard::Session& session, byte_type keyID, auto& cache) const; struct Private; std::unique_ptr data; diff --git a/src/electronic-ids/pcsc/PcscElectronicID.hpp b/src/electronic-ids/pcsc/PcscElectronicID.hpp index 05868a9..002085f 100644 --- a/src/electronic-ids/pcsc/PcscElectronicID.hpp +++ b/src/electronic-ids/pcsc/PcscElectronicID.hpp @@ -32,7 +32,19 @@ namespace electronic_id class PcscElectronicID : public ElectronicID { public: - explicit PcscElectronicID(pcsc_cpp::SmartCard&& _card) : ElectronicID(std::move(_card)) {} + using SmartCard = pcsc_cpp::SmartCard; + + explicit PcscElectronicID(SmartCard&& _card) : ElectronicID(std::move(_card)) {} + + constexpr PinMinMaxLength authPinMinMaxLength() const override { return {4, 12}; } + constexpr JsonWebSignatureAlgorithm authSignatureAlgorithm() const override + { + return JsonWebSignatureAlgorithm::ES384; + } + const std::set& supportedSigningAlgorithms() const override + { + return ELLIPTIC_CURVE_SIGNATURE_ALGOS(); + } protected: byte_vector getCertificate(const CertificateType type) const override @@ -68,21 +80,21 @@ class PcscElectronicID : public ElectronicID // they have to be implemented when adding a new electronic ID. // This design follows the non-virtual interface pattern. - virtual byte_vector getCertificateImpl(const pcsc_cpp::SmartCard::Session& session, + virtual byte_vector getCertificateImpl(const SmartCard::Session& session, const CertificateType type) const = 0; - virtual byte_vector signWithAuthKeyImpl(const pcsc_cpp::SmartCard::Session& session, - byte_vector&& pin, const byte_vector& hash) const = 0; + virtual byte_vector signWithAuthKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash) const = 0; virtual PinRetriesRemainingAndMax - authPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const = 0; + authPinRetriesLeftImpl(const SmartCard::Session& session) const = 0; - virtual Signature signWithSigningKeyImpl(const pcsc_cpp::SmartCard::Session& session, - byte_vector&& pin, const byte_vector& hash, + virtual Signature signWithSigningKeyImpl(const SmartCard::Session& session, byte_vector&& pin, + const byte_vector& hash, const HashAlgorithm hashAlgo) const = 0; virtual PinRetriesRemainingAndMax - signingPinRetriesLeftImpl(const pcsc_cpp::SmartCard::Session& session) const = 0; + signingPinRetriesLeftImpl(const SmartCard::Session& session) const = 0; }; } // namespace electronic_id diff --git a/src/electronic-ids/pcsc/pcsc-common.hpp b/src/electronic-ids/pcsc/pcsc-common.hpp index d0eb650..2524db1 100644 --- a/src/electronic-ids/pcsc/pcsc-common.hpp +++ b/src/electronic-ids/pcsc/pcsc-common.hpp @@ -29,9 +29,45 @@ #include +using namespace std::string_literals; + namespace electronic_id { +inline void selectFile(const pcsc_cpp::SmartCard::Session& session, + const pcsc_cpp::CommandApdu& command) +{ + const auto response = session.transmit(command); + if (!response.isOK()) { + THROW(SmartCardError, "Failed to select file"); + } +} + +inline pcsc_cpp::byte_vector readBinary(const pcsc_cpp::SmartCard::Session& session, + const uint16_t length, pcsc_cpp::byte_type blockLength) +{ + pcsc_cpp::byte_vector resultBytes; + resultBytes.reserve(length); + while (resultBytes.size() < length) { + pcsc_cpp::byte_type chunk = + pcsc_cpp::byte_type(std::min(length - resultBytes.size(), blockLength)); + auto response = session.transmit( + pcsc_cpp::CommandApdu::readBinary(uint16_t(resultBytes.size()), chunk)); + if (chunk > 0 && response.data.size() != chunk) { + THROW(SmartCardError, + "Length mismatch, expected "s + std::to_string(chunk) + ", received " + + std::to_string(response.data.size()) + " bytes"); + } + resultBytes.insert(resultBytes.end(), response.data.cbegin(), response.data.cend()); + } + if (resultBytes.size() != length) { + THROW(SmartCardError, + "Length mismatch, expected "s + std::to_string(length) + ", received " + + std::to_string(resultBytes.size()) + " bytes"); + } + return resultBytes; +} + inline pcsc_cpp::byte_vector readFile(const pcsc_cpp::SmartCard::Session& session, const pcsc_cpp::CommandApdu& select, pcsc_cpp::byte_type blockLength = 0x00) @@ -40,19 +76,15 @@ inline pcsc_cpp::byte_vector readFile(const pcsc_cpp::SmartCard::Session& sessio if (!response.isOK()) { THROW(SmartCardError, "Failed to select EF file"); } - TLV fci(response.data); - if (fci.tag != 0x62) { - THROW(SmartCardError, "Failed to read EF file length"); - } + auto fci = TLV(response.data).find(0x62); TLV size = fci[0x80]; if (!size) { size = fci[0x81]; } - if (size.length != 2) { + if (!size || size.length != 2) { THROW(SmartCardError, "Failed to read EF file length"); } - return pcsc_cpp::readBinary(session, pcsc_cpp::toSW(*size.begin, *(size.begin + 1)), - blockLength); + return readBinary(session, pcsc_cpp::toSW(*size.begin, *(size.begin + 1)), blockLength); } PCSC_CPP_CONSTEXPR_VECTOR inline pcsc_cpp::byte_vector @@ -69,20 +101,18 @@ addPaddingToPin(pcsc_cpp::byte_vector&& pin, size_t paddingLength, pcsc_cpp::byt } inline void verifyPin(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byte_type p2, - pcsc_cpp::byte_vector&& pin, uint8_t pinMinLength, size_t paddingLength, + pcsc_cpp::byte_vector&& pin, ElectronicID::PinMinMaxLength pinMinMax, pcsc_cpp::byte_type paddingChar) { pcsc_cpp::ResponseApdu response; if (session.readerHasPinPad()) { - const pcsc_cpp::CommandApdu verifyPin {0x00, 0x20, 0x00, p2, - pcsc_cpp::byte_vector(paddingLength, paddingChar)}; - response = session.transmitCTL(verifyPin, 0, pinMinLength); - + const pcsc_cpp::CommandApdu verifyPin { + 0x00, 0x20, 0x00, p2, pcsc_cpp::byte_vector(pinMinMax.second, paddingChar)}; + response = session.transmitCTL(verifyPin, 0, pinMinMax.first); } else { const pcsc_cpp::CommandApdu verifyPin { - 0x00, 0x20, 0x00, p2, addPaddingToPin(std::move(pin), paddingLength, paddingChar)}; - + 0x00, 0x20, 0x00, p2, addPaddingToPin(std::move(pin), pinMinMax.second, paddingChar)}; response = session.transmit(verifyPin); } @@ -112,6 +142,7 @@ inline void verifyPin(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byt throw VerifyPinFailed(INVALID_PIN_LENGTH, &response); // Fail, retry not allowed. case toSW(COMMAND_NOT_ALLOWED, 0x83): + case toSW(COMMAND_NOT_ALLOWED, 0x84): throw VerifyPinFailed(PIN_BLOCKED, &response); default: if (response.sw1 == VERIFICATION_FAILED) { @@ -127,50 +158,9 @@ inline void verifyPin(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byt } } -inline pcsc_cpp::byte_vector internalAuthenticate(const pcsc_cpp::SmartCard::Session& session, - const pcsc_cpp::byte_vector& hash, - const std::string& cardType) -{ - pcsc_cpp::CommandApdu internalAuth {0x00, 0x88, 0x00, 0x00, hash, 0}; - const auto response = session.transmit(internalAuth); - - if (response.sw1 == pcsc_cpp::ResponseApdu::WRONG_LENGTH) { - THROW(SmartCardError, - cardType - + ": Wrong data length in command INTERNAL AUTHENTICATE argument: " + response); - } - if (!response.isOK()) { - THROW(SmartCardError, - cardType + ": Command INTERNAL AUTHENTICATE failed with error " + response); - } - - return response.data; -} - -inline pcsc_cpp::byte_vector computeSignature(const pcsc_cpp::SmartCard::Session& session, - const pcsc_cpp::byte_vector& hash, - const std::string& cardType) -{ - pcsc_cpp::CommandApdu computeSignature {0x00, 0x2A, 0x9E, 0x9A, hash, 0}; - const auto response = session.transmit(computeSignature); - - if (response.sw1 == pcsc_cpp::ResponseApdu::WRONG_LENGTH) { - THROW(SmartCardError, - cardType + ": Wrong data length in command COMPUTE SIGNATURE argument: " + response); - } - if (!response.isOK()) { - THROW(SmartCardError, - cardType + ": Command COMPUTE SIGNATURE failed with error " + response); - } - - return response.data; -} - -inline pcsc_cpp::byte_type selectSecurityEnv(const pcsc_cpp::SmartCard::Session& session, - pcsc_cpp::byte_type env, - pcsc_cpp::byte_type signatureAlgo, - pcsc_cpp::byte_type keyReference, - const std::string& cardType) +inline void selectSecurityEnv(const pcsc_cpp::SmartCard::Session& session, pcsc_cpp::byte_type env, + pcsc_cpp::byte_type signatureAlgo, pcsc_cpp::byte_type keyReference, + const std::string& cardType) { const auto response = session.transmit( {0x00, 0x22, 0x41, env, {0x80, 0x01, signatureAlgo, 0x84, 0x01, keyReference}}); @@ -178,7 +168,6 @@ inline pcsc_cpp::byte_type selectSecurityEnv(const pcsc_cpp::SmartCard::Session& if (!response.isOK()) { THROW(SmartCardError, cardType + ": Command SET ENV failed with error " + response); } - return signatureAlgo; } } // namespace electronic_id diff --git a/tests/common/selectcard.hpp b/tests/common/selectcard.hpp index 74ba8f1..a09dbfe 100644 --- a/tests/common/selectcard.hpp +++ b/tests/common/selectcard.hpp @@ -2,6 +2,8 @@ #include "electronic-id/electronic-id.hpp" +#include "pcsc-cpp/pcsc-cpp-utils.hpp" + #include #include diff --git a/tests/integration/test-signing.cpp b/tests/integration/test-signing.cpp index 709e99d..48cffa4 100644 --- a/tests/integration/test-signing.cpp +++ b/tests/integration/test-signing.cpp @@ -49,7 +49,7 @@ static void signing(HashAlgorithm hashAlgo) GTEST_ASSERT_GE(cardInfo->signingPinRetriesLeft().first, 0U); byte_vector pin; - if (cardInfo->name() == "EstEID IDEMIA v1") + if (cardInfo->name() == "EstEID IDEMIA v1" || cardInfo->name() == "EstEIDThales") pin = {'1', '2', '3', '4', '5'}; // EstIDEMIA test card default PIN2 else if (cardInfo->name() == "LatEID IDEMIA v1" || cardInfo->name() == "LatEID IDEMIA v2") pin = {'1', '2', '3', '4', '5', '6'}; // LatIDEMIA test card default PIN2 diff --git a/tests/mock/select-certificate-script-FIN-V3.hpp b/tests/mock/select-certificate-script-FIN-V3.hpp index c88a02f..f8aa668 100644 --- a/tests/mock/select-certificate-script-FIN-V3.hpp +++ b/tests/mock/select-certificate-script-FIN-V3.hpp @@ -379,7 +379,7 @@ const PcscMock::ApduScript FINEID_V3_SELECT_SIGN_CERTIFICATE_AND_SIGNING = { {0x90, 0x00}}, // Compute signature - {{0x00, 0x2a, 0x9e, 0x9a, 0x40}, + {{0x00, 0x2a, 0x9e, 0x9a, 0x00}, {0x97, 0x8b, 0x91, 0x3d, 0xc8, 0x83, 0x54, 0xa6, 0xbc, 0x61, 0x8a, 0xa6, 0x58, 0x14, 0x87, 0x97, 0xe3, 0x06, 0xfc, 0x28, 0xea, 0x8b, 0x55, 0xf4, 0x97, 0x50, 0xea, 0xaa, 0x6b, 0x18, 0x41, 0x7c, 0xe7, 0x9d, 0x92, 0xba, 0x37, 0x47, 0x66, 0x45, 0x13, 0x46, diff --git a/tests/mock/select-certificate-script-FIN-V4.hpp b/tests/mock/select-certificate-script-FIN-V4.hpp index efaa0d8..b3363be 100644 --- a/tests/mock/select-certificate-script-FIN-V4.hpp +++ b/tests/mock/select-certificate-script-FIN-V4.hpp @@ -152,7 +152,7 @@ const PcscMock::ApduScript FINEID_V4_SELECT_AUTH_CERTIFICATE_AND_AUTHENTICATE { {0x90, 0x00}}, // Compute signature - {{0x00, 0x2a, 0x9e, 0x9a, 0x60}, + {{0x00, 0x2a, 0x9e, 0x9a, 0x00}, {0xd4, 0x27, 0xbb, 0xd4, 0x56, 0x7f, 0x89, 0x99, 0x95, 0xb0, 0x74, 0x84, 0x82, 0xba, 0xc9, 0x11, 0xa1, 0x3e, 0x7f, 0xb0, 0x97, 0x21, 0x5f, 0x41, 0x46, 0x06, 0x28, 0x8a, 0x37, 0x5a, 0xed, 0x58, 0xc4, 0x06, 0xf5, 0x0b, 0x30, 0xa2, 0x82, 0x3c, 0xb2, 0x38, @@ -293,7 +293,7 @@ const PcscMock::ApduScript FINEID_V4_SELECT_SIGN_CERTIFICATE_AND_SIGNING { {0x90, 0x00}}, // Compute signature - {{0x00, 0x2a, 0x9e, 0x9a, 0x60}, + {{0x00, 0x2a, 0x9e, 0x9a, 0x00}, {0x0a, 0x3e, 0x09, 0x68, 0x5f, 0xe5, 0x2c, 0x43, 0x8a, 0x3a, 0x54, 0x1b, 0x01, 0x39, 0x3b, 0xb6, 0x10, 0x85, 0x34, 0xed, 0x40, 0x42, 0x53, 0x18, 0x2a, 0x6e, 0xad, 0x2f, 0x6f, 0x06, 0x34, 0x16, 0x77, 0x6c, 0xc1, 0xaf, 0x3f, 0xe3, 0x4e, 0xd5, 0xbb, 0x82,