diff --git a/CMakeLists.txt b/CMakeLists.txt index eb14cd3..98918a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,8 @@ add_executable(${MOCK_TEST_EXE} tests/mock/select-certificate-script-LAT-V1.hpp tests/mock/select-certificate-script-LAT-V2.hpp tests/mock/test-autoselect-card.cpp + tests/mock/test-find-masked-atr.cpp + tests/mock/test-is-card-supported.cpp tests/mock/test-get-certificate.cpp tests/mock/test-pkcs11-token.cpp ) diff --git a/include/electronic-id/electronic-id.hpp b/include/electronic-id/electronic-id.hpp index cf72a55..b1a44d0 100644 --- a/include/electronic-id/electronic-id.hpp +++ b/include/electronic-id/electronic-id.hpp @@ -24,6 +24,9 @@ #include "enums.hpp" +#include +#include + namespace electronic_id { @@ -101,6 +104,10 @@ class ElectronicID pcsc_cpp::SmartCard::ptr card; }; +using ElectronicIDConstructor = std::function; + +std::optional findMaskedATR(const pcsc_cpp::byte_vector& atr); + bool isCardSupported(const pcsc_cpp::byte_vector& atr); ElectronicID::ptr getElectronicID(const pcsc_cpp::Reader& reader); diff --git a/src/electronic-id.cpp b/src/electronic-id.cpp index da93ea0..1558142 100644 --- a/src/electronic-id.cpp +++ b/src/electronic-id.cpp @@ -41,8 +41,6 @@ using namespace std::string_literals; namespace { -using ElectronicIDConstructor = std::function; - template constexpr auto constructor(const Reader& reader) { @@ -94,9 +92,6 @@ const std::map SUPPORTED_ATRS { 0x04, 0x44, 0xec, 0xc1, 0x73, 0x94, 0x01, 0x80, 0x82, 0x90, 0x00, 0x12}, constructor}, // BelEID - {{0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, 0x01, 0x01, 0x01, 0xad, 0x13, 0x11}, - constructor}, - // BelEID {{0x3B, 0x98, 0x94, 0x40, 0x0A, 0xA5, 0x03, 0x01, 0x01, 0x01, 0xAD, 0x13, 0x10}, constructor}, // BelEID @@ -112,7 +107,41 @@ const std::map SUPPORTED_ATRS { constructor}, }; -inline std::string byteVectorToHexString(const byte_vector& bytes) +// Holds ATR pattern, mask, and constructor for variable ATR cards. +struct MaskedATREntry +{ + // Single template parameter enforces equal size pattern and mask arrays at compile time. + template + constexpr MaskedATREntry(const byte_type (&_pat)[N], const byte_type (&_mask)[N], + ElectronicIDConstructor&& _constructor) : + pattern(std::begin(_pat), std::end(_pat)), mask(std::begin(_mask), std::end(_mask)), + constructor(std::move(_constructor)) + { + } + + bool operator==(const byte_vector& atr) const + { + return std::equal(atr.cbegin(), atr.cend(), pattern.cbegin(), pattern.cend(), + [mask_ptr = mask.data()](byte_type a, byte_type p) mutable { + bool result = (a & *mask_ptr) == (p & *mask_ptr); + ++mask_ptr; + return result; + }); + } + + byte_vector pattern; + byte_vector mask; + ElectronicIDConstructor constructor; +}; + +const std::vector MASKED_ATRS = { + // BelEID v1.7 + {{0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, 0x01, 0x01, 0x01, 0xad, 0x13, 0x11}, + {0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + constructor}, +}; + +std::string byteVectorToHexString(const byte_vector& bytes) { std::ostringstream hexStringBuilder; @@ -137,22 +166,40 @@ const auto SUPPORTED_ALGORITHMS = std::map { namespace electronic_id { +std::optional findMaskedATR(const byte_vector& atr) +{ + if (auto i = std::find(MASKED_ATRS.cbegin(), MASKED_ATRS.cend(), atr); + i != MASKED_ATRS.cend()) { + return i->constructor; + } + return std::nullopt; +} + bool isCardSupported(const pcsc_cpp::byte_vector& atr) { - return SUPPORTED_ATRS.count(atr); + if (SUPPORTED_ATRS.count(atr)) { + return true; + } + + // If exact ATR match is not found, fall back to masked ATR lookup. + return findMaskedATR(atr).has_value(); } ElectronicID::ptr getElectronicID(const pcsc_cpp::Reader& reader) { - try { - const auto& eidConstructor = SUPPORTED_ATRS.at(reader.cardAtr); - return eidConstructor(reader); - } catch (const std::out_of_range&) { - // It should be verified that the card is supported with isCardSupported() before - // calling getElectronicID(), so it is a programming error if out_of_range occurs here. - THROW(ProgrammingError, - "Card with ATR '" + byteVectorToHexString(reader.cardAtr) + "' is not supported"); + if (auto it = SUPPORTED_ATRS.find(reader.cardAtr); it != SUPPORTED_ATRS.end()) { + return it->second(reader); } + + // If exact ATR match is not found, fall back to masked ATR lookup. + if (auto eIDConstructor = findMaskedATR(reader.cardAtr)) { + return (*eIDConstructor)(reader); + } + + // It should be verified that the card is supported with isCardSupported() before + // calling getElectronicID(), so it is a programming error to reach this point. + THROW(ProgrammingError, + "Card with ATR '" + byteVectorToHexString(reader.cardAtr) + "' is not supported"); } bool ElectronicID::isSupportedSigningHashAlgorithm(const HashAlgorithm hashAlgo) const diff --git a/tests/mock/test-find-masked-atr.cpp b/tests/mock/test-find-masked-atr.cpp new file mode 100644 index 0000000..e949e5e --- /dev/null +++ b/tests/mock/test-find-masked-atr.cpp @@ -0,0 +1,47 @@ +/* + * 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 "electronic-id/electronic-id.hpp" + +#include + +using namespace electronic_id; + +const pcsc_cpp::byte_vector BEL_EID_V1_7_ATR {0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, + 0x01, 0x01, 0x01, 0xad, 0x13, 0x11}; +const pcsc_cpp::byte_vector INVALID_ATR {0xaa, 0xbb, 0xcc, 0x40, 0x0a, 0xa5, 0x03, + 0x01, 0x01, 0x01, 0xad, 0x13, 0x11}; + +TEST(electronic_id_test, findMaskedATRSuccessWithSupportedMaskedATR) +{ + EXPECT_TRUE(findMaskedATR(BEL_EID_V1_7_ATR).has_value()); +} + +TEST(electronic_id_test, findMaskedATRFailureWithUnSupportedATR) +{ + EXPECT_FALSE(findMaskedATR(INVALID_ATR).has_value()); +} + +TEST(electronic_id_test, isCardSupportedSuccessWithSupportedMaskedATR) +{ + EXPECT_TRUE(isCardSupported(BEL_EID_V1_7_ATR)); +} diff --git a/tests/mock/test-is-card-supported.cpp b/tests/mock/test-is-card-supported.cpp new file mode 100644 index 0000000..b1da7b7 --- /dev/null +++ b/tests/mock/test-is-card-supported.cpp @@ -0,0 +1,43 @@ +/* + * 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 "electronic-id/electronic-id.hpp" + +#include + +using namespace electronic_id; + +const pcsc_cpp::byte_vector EstEIDIDEMIAV1_ATR {0x3b, 0xdb, 0x96, 0x00, 0x80, 0xb1, 0xfe, 0x45, + 0x1f, 0x83, 0x00, 0x12, 0x23, 0x3f, 0x53, 0x65, + 0x49, 0x44, 0x0f, 0x90, 0x00, 0xf1}; +const pcsc_cpp::byte_vector INVALID_ATR {0xaa, 0xbb, 0xcc, 0x40, 0x0a, 0xa5, 0x03, + 0x01, 0x01, 0x01, 0xad, 0x13, 0x11}; + +TEST(electronic_id_test, isCardSupportedSuccessWithSupportedATR) +{ + EXPECT_TRUE(isCardSupported(EstEIDIDEMIAV1_ATR)); +} + +TEST(electronic_id_test, isCardSupportedFailureWithUnsupportedATR) +{ + EXPECT_FALSE(isCardSupported(INVALID_ATR)); +}