Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
7 changes: 7 additions & 0 deletions include/electronic-id/electronic-id.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

#include "enums.hpp"

#include <optional>
#include <functional>

namespace electronic_id
{

Expand Down Expand Up @@ -101,6 +104,10 @@ class ElectronicID
pcsc_cpp::SmartCard::ptr card;
};

using ElectronicIDConstructor = std::function<ElectronicID::ptr(const pcsc_cpp::Reader&)>;

std::optional<ElectronicIDConstructor> findMaskedATR(const pcsc_cpp::byte_vector& atr);

bool isCardSupported(const pcsc_cpp::byte_vector& atr);

ElectronicID::ptr getElectronicID(const pcsc_cpp::Reader& reader);
Expand Down
79 changes: 64 additions & 15 deletions src/electronic-id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ using namespace std::string_literals;
namespace
{

using ElectronicIDConstructor = std::function<ElectronicID::ptr(const Reader&)>;

template <typename T>
constexpr auto constructor(const Reader& reader)
{
Expand Down Expand Up @@ -94,9 +92,6 @@ const std::map<byte_vector, ElectronicIDConstructor> SUPPORTED_ATRS {
0x04, 0x44, 0xec, 0xc1, 0x73, 0x94, 0x01, 0x80, 0x82, 0x90, 0x00, 0x12},
constructor<ElectronicID::Type::HrvEID>},
// BelEID
{{0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, 0x01, 0x01, 0x01, 0xad, 0x13, 0x11},
constructor<ElectronicID::Type::BelEID>},
// BelEID
{{0x3B, 0x98, 0x94, 0x40, 0x0A, 0xA5, 0x03, 0x01, 0x01, 0x01, 0xAD, 0x13, 0x10},
constructor<ElectronicID::Type::BelEID>},
// BelEID
Expand All @@ -112,7 +107,22 @@ const std::map<byte_vector, ElectronicIDConstructor> SUPPORTED_ATRS {
constructor<ElectronicID::Type::CzeEID>},
};

inline std::string byteVectorToHexString(const byte_vector& bytes)
// Holds ATR pattern, mask, and constructor for variable ATR cards.
struct MaskedATREntry
{
byte_vector pattern;
byte_vector mask;
ElectronicIDConstructor constructor;
};

const std::vector<MaskedATREntry> 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<ElectronicID::Type::BelEID>},
};

std::string byteVectorToHexString(const byte_vector& bytes)
{
std::ostringstream hexStringBuilder;

Expand All @@ -125,6 +135,26 @@ inline std::string byteVectorToHexString(const byte_vector& bytes)
return hexStringBuilder.str();
}

bool matchATRWithMask(const byte_vector& atr, const byte_vector& pattern, const byte_vector& mask)
{
if (pattern.size() != mask.size()) {
THROW(ProgrammingError,
"MaskedATREntry '" + byteVectorToHexString(pattern)
+ "' pattern size does not match mask size");
}

if (atr.size() != pattern.size()) {
return false;
}
for (size_t i = 0; i < atr.size(); ++i) {
if ((atr[i] & mask[i]) != (pattern[i] & mask[i])) {
return false;
}
}

return true;
}

const auto SUPPORTED_ALGORITHMS = std::map<std::string, HashAlgorithm> {
{"SHA-224"s, HashAlgorithm::SHA224}, {"SHA-256"s, HashAlgorithm::SHA256},
{"SHA-384"s, HashAlgorithm::SHA384}, {"SHA-512"s, HashAlgorithm::SHA512},
Expand All @@ -137,22 +167,41 @@ const auto SUPPORTED_ALGORITHMS = std::map<std::string, HashAlgorithm> {
namespace electronic_id
{

std::optional<ElectronicIDConstructor> findMaskedATR(const byte_vector& atr)
{
for (const auto& entry : MASKED_ATRS) {
if (matchATRWithMask(atr, entry.pattern, entry.mask)) {
return entry.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
Expand Down
47 changes: 47 additions & 0 deletions tests/mock/test-find-masked-atr.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

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));
}
43 changes: 43 additions & 0 deletions tests/mock/test-is-card-supported.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

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));
}
Loading