Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
42 changes: 42 additions & 0 deletions lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
#include <vector>

// 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
Expand Down Expand Up @@ -119,6 +122,7 @@ struct ResponseApdu
}
};

struct VerifyApdu;
/**
* Struct that wraps command APDUs.
*
Expand Down Expand Up @@ -162,6 +166,8 @@ struct CommandApdu
d.push_back(le);
}

virtual ~CommandApdu() noexcept = default;

constexpr operator const byte_vector&() const { return d; }

/**
Expand Down Expand Up @@ -213,9 +219,45 @@ 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 VerifyApdu verify(byte_type p2, byte_vector&& pin,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding PCSC_CPP_CONSTEXPR_VECTOR, can and should polymorphic types (with a virtual destructor) be constexpr? CommandApdu and VerifyApdu are polymorphic now and I'm not sure constexpr really applies to them any more.

On the other hand, we could avoid making CommandApdu destructor virtual as the temporary VerifyApdu object is deleted inside verifyPin() directly, not through a CommandApdu pointer or reference.

size_t paddingLength,
pcsc_cpp::byte_type paddingChar);

byte_vector d;
};

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)); }
Copy link
Member

@mrts mrts Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use memset_explicit() to ensure that the compiler will not optimize the memory overwrite away? See this and this. However, it is officially available since C23, so I'm unsure if our toolchain supports this on all platforms. In Windows we could use SecureZeroMemory(). Note that neither memset_explicit() or SecureZeroMemory() is constexpr.

We should use memset_explicit() here as well:

  1. https://github.com/web-eid/web-eid-app/blob/main/src/controller/utils/erasedata.hpp#L32-L34
  2. https://github.com/web-eid/web-eid-app/blob/main/src/ui/webeiddialog.cpp#L252-L254

(and elsewhere where we erase the pin).

};

PCSC_CPP_CONSTEXPR_VECTOR VerifyApdu CommandApdu::verify(byte_type p2, byte_vector&& pin,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be inline?

size_t paddingLength,
pcsc_cpp::byte_type paddingChar)
{
if (!pin.empty() && pin.capacity() < paddingLength + 5) {
Copy link
Member

@mrts mrts Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail if pin.size() > paddingLength. You can argue that this will not happen, but we should consider edge cases.

For example, assume pin.size() == 12 and paddingLength == 8, so no padding, payload is 12 and required capacity is 12 + 5 = 17. But if you only check for paddingLength + 5 = 13, there will be a reallocation.

And by ES.45: Avoid “magic constants”; use symbolic constants I would recommend replacing 5 with APDU_HEADER_AND_LC_SIZE or something like that. If you run clang-tidy, you will see a warning.

throw std::invalid_argument(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the THROW macro to include source location metadata:

Suggested change
throw std::invalid_argument(
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 {0x00, 0x20, 0x00, p2, std::move(pin)};
}

/** Opaque class that wraps the PC/SC smart card resources like card handle and I/O protocol. */
class CardImpl;

Expand Down
24 changes: 5 additions & 19 deletions src/electronic-ids/pcsc/pcsc-common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,33 +87,19 @@ 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)
{
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the caller accidentally provides a non-empty PIN for the pin-pad reader case, this change causes it to be embedded into the APDU. Previous code with the dummy padding-only PIN was safer. Or am I mistaken here?

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,
Expand Down
16 changes: 8 additions & 8 deletions tests/mock/test-get-certificate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Loading