Skip to content

Commit a40b9c6

Browse files
committed
Add support for masked ATRs to support BelEID v1_7
WE2-1040 Signed-off-by: Mart Somermaa <[email protected]>
1 parent 7991e0e commit a40b9c6

File tree

5 files changed

+163
-15
lines changed

5 files changed

+163
-15
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ add_executable(${MOCK_TEST_EXE}
8181
tests/mock/select-certificate-script-LAT-V1.hpp
8282
tests/mock/select-certificate-script-LAT-V2.hpp
8383
tests/mock/test-autoselect-card.cpp
84+
tests/mock/test-find-masked-atr.cpp
85+
tests/mock/test-is-card-supported.cpp
8486
tests/mock/test-get-certificate.cpp
8587
tests/mock/test-pkcs11-token.cpp
8688
)

include/electronic-id/electronic-id.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
#include "enums.hpp"
2626

27+
#include <optional>
28+
#include <functional>
29+
2730
namespace electronic_id
2831
{
2932

@@ -101,6 +104,10 @@ class ElectronicID
101104
pcsc_cpp::SmartCard::ptr card;
102105
};
103106

107+
using ElectronicIDConstructor = std::function<ElectronicID::ptr(const pcsc_cpp::Reader&)>;
108+
109+
std::optional<ElectronicIDConstructor> findMaskedATR(const pcsc_cpp::byte_vector& atr);
110+
104111
bool isCardSupported(const pcsc_cpp::byte_vector& atr);
105112

106113
ElectronicID::ptr getElectronicID(const pcsc_cpp::Reader& reader);

src/electronic-id.cpp

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ using namespace std::string_literals;
4141
namespace
4242
{
4343

44-
using ElectronicIDConstructor = std::function<ElectronicID::ptr(const Reader&)>;
45-
4644
template <typename T>
4745
constexpr auto constructor(const Reader& reader)
4846
{
@@ -94,9 +92,6 @@ const std::map<byte_vector, ElectronicIDConstructor> SUPPORTED_ATRS {
9492
0x04, 0x44, 0xec, 0xc1, 0x73, 0x94, 0x01, 0x80, 0x82, 0x90, 0x00, 0x12},
9593
constructor<ElectronicID::Type::HrvEID>},
9694
// BelEID
97-
{{0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, 0x01, 0x01, 0x01, 0xad, 0x13, 0x11},
98-
constructor<ElectronicID::Type::BelEID>},
99-
// BelEID
10095
{{0x3B, 0x98, 0x94, 0x40, 0x0A, 0xA5, 0x03, 0x01, 0x01, 0x01, 0xAD, 0x13, 0x10},
10196
constructor<ElectronicID::Type::BelEID>},
10297
// BelEID
@@ -112,7 +107,22 @@ const std::map<byte_vector, ElectronicIDConstructor> SUPPORTED_ATRS {
112107
constructor<ElectronicID::Type::CzeEID>},
113108
};
114109

115-
inline std::string byteVectorToHexString(const byte_vector& bytes)
110+
// Holds ATR pattern, mask, and constructor for variable ATR cards.
111+
struct MaskedATREntry
112+
{
113+
byte_vector pattern;
114+
byte_vector mask;
115+
ElectronicIDConstructor constructor;
116+
};
117+
118+
const std::vector<MaskedATREntry> MASKED_ATRS = {
119+
// BelEID v1.7
120+
{{0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03, 0x01, 0x01, 0x01, 0xad, 0x13, 0x11},
121+
{0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00},
122+
constructor<ElectronicID::Type::BelEID>},
123+
};
124+
125+
std::string byteVectorToHexString(const byte_vector& bytes)
116126
{
117127
std::ostringstream hexStringBuilder;
118128

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

138+
bool matchATRWithMask(const byte_vector& atr, const byte_vector& pattern, const byte_vector& mask)
139+
{
140+
if (pattern.size() != mask.size()) {
141+
THROW(ProgrammingError,
142+
"MaskedATREntry '" + byteVectorToHexString(pattern)
143+
+ "' pattern size does not match mask size");
144+
}
145+
146+
if (atr.size() != pattern.size()) {
147+
return false;
148+
}
149+
for (size_t i = 0; i < atr.size(); ++i) {
150+
if ((atr[i] & mask[i]) != (pattern[i] & mask[i])) {
151+
return false;
152+
}
153+
}
154+
155+
return true;
156+
}
157+
128158
const auto SUPPORTED_ALGORITHMS = std::map<std::string, HashAlgorithm> {
129159
{"SHA-224"s, HashAlgorithm::SHA224}, {"SHA-256"s, HashAlgorithm::SHA256},
130160
{"SHA-384"s, HashAlgorithm::SHA384}, {"SHA-512"s, HashAlgorithm::SHA512},
@@ -137,22 +167,41 @@ const auto SUPPORTED_ALGORITHMS = std::map<std::string, HashAlgorithm> {
137167
namespace electronic_id
138168
{
139169

170+
std::optional<ElectronicIDConstructor> findMaskedATR(const byte_vector& atr)
171+
{
172+
for (const auto& entry : MASKED_ATRS) {
173+
if (matchATRWithMask(atr, entry.pattern, entry.mask)) {
174+
return entry.constructor;
175+
}
176+
}
177+
return std::nullopt;
178+
}
179+
140180
bool isCardSupported(const pcsc_cpp::byte_vector& atr)
141181
{
142-
return SUPPORTED_ATRS.count(atr);
182+
if (SUPPORTED_ATRS.count(atr)) {
183+
return true;
184+
}
185+
186+
// If exact ATR match is not found, fall back to masked ATR lookup.
187+
return findMaskedATR(atr).has_value();
143188
}
144189

145190
ElectronicID::ptr getElectronicID(const pcsc_cpp::Reader& reader)
146191
{
147-
try {
148-
const auto& eidConstructor = SUPPORTED_ATRS.at(reader.cardAtr);
149-
return eidConstructor(reader);
150-
} catch (const std::out_of_range&) {
151-
// It should be verified that the card is supported with isCardSupported() before
152-
// calling getElectronicID(), so it is a programming error if out_of_range occurs here.
153-
THROW(ProgrammingError,
154-
"Card with ATR '" + byteVectorToHexString(reader.cardAtr) + "' is not supported");
192+
if (auto it = SUPPORTED_ATRS.find(reader.cardAtr); it != SUPPORTED_ATRS.end()) {
193+
return it->second(reader);
155194
}
195+
196+
// If exact ATR match is not found, fall back to masked ATR lookup.
197+
if (auto eIDConstructor = findMaskedATR(reader.cardAtr)) {
198+
return (*eIDConstructor)(reader);
199+
}
200+
201+
// It should be verified that the card is supported with isCardSupported() before
202+
// calling getElectronicID(), so it is a programming error to reach this point.
203+
THROW(ProgrammingError,
204+
"Card with ATR '" + byteVectorToHexString(reader.cardAtr) + "' is not supported");
156205
}
157206

158207
bool ElectronicID::isSupportedSigningHashAlgorithm(const HashAlgorithm hashAlgo) const
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2020-2024 Estonian Information System Authority
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
#include "electronic-id/electronic-id.hpp"
24+
25+
#include <gtest/gtest.h>
26+
27+
using namespace electronic_id;
28+
29+
const pcsc_cpp::byte_vector BEL_EID_V1_7_ATR {0x3b, 0x98, 0x13, 0x40, 0x0a, 0xa5, 0x03,
30+
0x01, 0x01, 0x01, 0xad, 0x13, 0x11};
31+
const pcsc_cpp::byte_vector INVALID_ATR {0xaa, 0xbb, 0xcc, 0x40, 0x0a, 0xa5, 0x03,
32+
0x01, 0x01, 0x01, 0xad, 0x13, 0x11};
33+
34+
TEST(electronic_id_test, findMaskedATRSuccessWithSupportedMaskedATR)
35+
{
36+
EXPECT_TRUE(findMaskedATR(BEL_EID_V1_7_ATR).has_value());
37+
}
38+
39+
TEST(electronic_id_test, findMaskedATRFailureWithUnSupportedATR)
40+
{
41+
EXPECT_FALSE(findMaskedATR(INVALID_ATR).has_value());
42+
}
43+
44+
TEST(electronic_id_test, isCardSupportedSuccessWithSupportedMaskedATR)
45+
{
46+
EXPECT_TRUE(isCardSupported(BEL_EID_V1_7_ATR));
47+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2020-2024 Estonian Information System Authority
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
#include "electronic-id/electronic-id.hpp"
24+
25+
#include <gtest/gtest.h>
26+
27+
using namespace electronic_id;
28+
29+
const pcsc_cpp::byte_vector EstEIDIDEMIAV1_ATR {0x3b, 0xdb, 0x96, 0x00, 0x80, 0xb1, 0xfe, 0x45,
30+
0x1f, 0x83, 0x00, 0x12, 0x23, 0x3f, 0x53, 0x65,
31+
0x49, 0x44, 0x0f, 0x90, 0x00, 0xf1};
32+
const pcsc_cpp::byte_vector INVALID_ATR {0xaa, 0xbb, 0xcc, 0x40, 0x0a, 0xa5, 0x03,
33+
0x01, 0x01, 0x01, 0xad, 0x13, 0x11};
34+
35+
TEST(electronic_id_test, isCardSupportedSuccessWithSupportedATR)
36+
{
37+
EXPECT_TRUE(isCardSupported(EstEIDIDEMIAV1_ATR));
38+
}
39+
40+
TEST(electronic_id_test, isCardSupportedFailureWithUnsupportedATR)
41+
{
42+
EXPECT_FALSE(isCardSupported(INVALID_ATR));
43+
}

0 commit comments

Comments
 (0)