Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/cmake-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
with:
vcpkgArguments: gtest:x64-windows openssl:x64-windows
vcpkgTriplet: x64-windows
vcpkgGitCommitId: 0d5cae153065957df7f382de7c1549ccc88027e5
vcpkgGitCommitId: 031ad89ce6c575df35a8e58707ad2c898446c63e

- name: Configure CMake
run: cmake -A x64 "-DCMAKE_TOOLCHAIN_FILE=${env:VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" "-DCMAKE_BUILD_TYPE=${env:BUILD_TYPE}" -S . -B build
Expand Down
6 changes: 3 additions & 3 deletions include/electronic-id/electronic-id.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ class ElectronicID
virtual std::string name() const = 0;
virtual Type type() const = 0;

virtual pcsc_cpp::SmartCard const& smartcard() const { return *card; }
virtual pcsc_cpp::SmartCard const& smartcard() const { return card; }

protected:
ElectronicID(pcsc_cpp::SmartCard::ptr _card) : card(std::move(_card)) {}
ElectronicID(pcsc_cpp::SmartCard&& _card) noexcept : card(std::move(_card)) {}

pcsc_cpp::SmartCard::ptr card;
pcsc_cpp::SmartCard card;
};

using ElectronicIDConstructor = std::function<ElectronicID::ptr(const pcsc_cpp::Reader&)>;
Expand Down
79 changes: 40 additions & 39 deletions lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
#include <vector>

// The rule of five (C++ Core guidelines C.21).
#define PCSC_CPP_DISABLE_COPY_MOVE(Class) \
#define PCSC_CPP_DISABLE_COPY(Class) \
Class(const Class&) = delete; \
Class& operator=(const Class&) = delete; \
Class& operator=(const Class&) = delete
#define PCSC_CPP_DISABLE_COPY_MOVE(Class) \
PCSC_CPP_DISABLE_COPY(Class); \
Class(Class&&) = delete; \
Class& operator=(Class&&) = delete

Expand Down Expand Up @@ -235,7 +237,19 @@ struct CommandApdu

/** Opaque class that wraps the PC/SC smart card resources like card handle and I/O protocol. */
class CardImpl;
using CardImplPtr = std::unique_ptr<CardImpl>;

class SmartCard;

/** Reader provides card reader information, status and gives access to the smart card in it. */
struct Reader
{
[[nodiscard]] SmartCard connectToCard() const;

const ContextPtr ctx;
const string_t name;
const byte_vector cardAtr;
const bool isCardPresent = false;
};

/** PIN pad PIN entry timer timeout */
constexpr uint8_t PIN_PAD_PIN_ENTRY_TIMEOUT = 90; // 1 minute, 30 seconds
Expand All @@ -246,52 +260,37 @@ class SmartCard
public:
enum class Protocol { UNDEFINED, T0, T1 }; // AUTO = T0 | T1

using ptr = std::unique_ptr<SmartCard>;

class TransactionGuard
class Session
{
public:
TransactionGuard(const CardImpl& CardImpl, bool& inProgress);
~TransactionGuard() noexcept;
PCSC_CPP_DISABLE_COPY_MOVE(TransactionGuard);
Session(const CardImpl& CardImpl);
~Session() noexcept;
PCSC_CPP_DISABLE_COPY_MOVE(Session);

ResponseApdu transmit(const CommandApdu& command) const;
ResponseApdu transmitCTL(const CommandApdu& command, uint16_t lang, uint8_t minlen) const;
bool readerHasPinPad() const;

private:
const CardImpl& card;
bool& inProgress;
};

SmartCard(ContextPtr context, string_t readerName, byte_vector atr);
SmartCard(); // Null object constructor.
SmartCard(Reader _reader);
SmartCard() noexcept; // Null object constructor.
SmartCard(SmartCard&& other) noexcept;
~SmartCard() noexcept;
PCSC_CPP_DISABLE_COPY_MOVE(SmartCard);
PCSC_CPP_DISABLE_COPY(SmartCard);
SmartCard& operator=(SmartCard&& other) noexcept = delete;

TransactionGuard beginTransaction();
ResponseApdu transmit(const CommandApdu& command) const;
ResponseApdu transmitCTL(const CommandApdu& command, uint16_t lang, uint8_t minlen) const;
[[nodiscard]] Session beginSession() const;
bool readerHasPinPad() const;

Protocol protocol() const { return _protocol; }
const byte_vector& atr() const { return _atr; }
const string_t& readerName() const { return _readerName; }
Protocol protocol() const;
const byte_vector& atr() const { return reader.cardAtr; }
const string_t& readerName() const { return reader.name; }

private:
ContextPtr ctx;
CardImplPtr card;
string_t _readerName;
byte_vector _atr;
Protocol _protocol = Protocol::UNDEFINED;
bool transactionInProgress = false;
};

/** Reader provides card reader information, status and gives access to the smart card in it. */
struct Reader
{
SmartCard::ptr connectToCard() const { return std::make_unique<SmartCard>(ctx, name, cardAtr); }

const ContextPtr ctx;
const string_t name;
const byte_vector cardAtr;
const bool isCardPresent;
Reader reader;
std::unique_ptr<CardImpl> card;
};

/**
Expand All @@ -304,10 +303,12 @@ std::vector<Reader> listReaders();
// Utility functions.

/** Transmit APDU command and verify that expected response is received. */
void transmitApduWithExpectedResponse(const SmartCard& card, const CommandApdu& command);
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& card, const uint16_t length, byte_type blockLength = 0x00);
byte_vector readBinary(const SmartCard::Session& session, const uint16_t length,
byte_type blockLength = 0x00);

// Errors.

Expand Down
136 changes: 65 additions & 71 deletions lib/libpcsc-cpp/src/SmartCard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,35 +41,6 @@
namespace
{

using namespace pcsc_cpp;

constexpr SmartCard::Protocol convertToSmartCardProtocol(const DWORD protocol)
{
switch (protocol) {
case SCARD_PROTOCOL_UNDEFINED:
return SmartCard::Protocol::UNDEFINED;
case SCARD_PROTOCOL_T0:
return SmartCard::Protocol::T0;
case SCARD_PROTOCOL_T1:
return SmartCard::Protocol::T1;
default:
THROW(Error, "Unsupported card protocol: " + std::to_string(protocol));
}
}

std::pair<SCARDHANDLE, DWORD> connectToCard(const SCARDCONTEXT ctx, const string_t& readerName)
{
const unsigned requestedProtocol =
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1; // Let PCSC auto-select protocol.
DWORD protocolOut = SCARD_PROTOCOL_UNDEFINED;
SCARDHANDLE cardHandle = 0;

SCard(Connect, ctx, readerName.c_str(), DWORD(SCARD_SHARE_SHARED), requestedProtocol,
&cardHandle, &protocolOut);

return {cardHandle, protocolOut};
}

template <class K, class V = uint32_t, class D, size_t dsize, typename Func>
constexpr std::map<K, V> parseTLV(const std::array<D, dsize>& data, DWORD size, Func transform)
{
Expand All @@ -93,21 +64,35 @@ constexpr uint32_t OMNIKEY_6121 = 0x6632;
namespace pcsc_cpp
{

SmartCard Reader::connectToCard() const
{
return {*this};
}

class CardImpl
{
public:
explicit CardImpl(std::pair<SCARDHANDLE, DWORD> cardParams) :
cardHandle(cardParams.first), _protocol {cardParams.second, sizeof(SCARD_IO_REQUEST)}
explicit CardImpl(const Reader& reader)
{
// TODO: debug("Protocol: " + to_string(protocol()))
constexpr unsigned requestedProtocol =
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1; // Let PCSC auto-select protocol.
SCard(Connect, reader.ctx->handle(), reader.name.c_str(), DWORD(SCARD_SHARE_SHARED),
requestedProtocol, &cardHandle, &_protocol.dwProtocol);

try {
DWORD size = 0;
std::array<BYTE, 256> buf {};
SCard(Control, cardHandle, DWORD(CM_IOCTL_GET_FEATURE_REQUEST), nullptr, 0U, buf.data(),
DWORD(buf.size()), &size);
features = parseTLV<DRIVER_FEATURES>(buf, size, [](uint32_t t) { return ntohl(t); });
std::array<PCSC_TLV_STRUCTURE, FEATURE_CCID_ESC_COMMAND> list {};
SCard(Control, cardHandle, DWORD(CM_IOCTL_GET_FEATURE_REQUEST), nullptr, 0U,
list.data(), DWORD(list.size() * sizeof(PCSC_TLV_STRUCTURE)), &size);
if (size == 0 || size % sizeof(PCSC_TLV_STRUCTURE)) {
return; // No features available or malformed response.
}
for (const auto& f : list) {
features[DRIVER_FEATURES(f.tag)] = ntohl(f.value);
}

if (auto ioctl = features.find(FEATURE_GET_TLV_PROPERTIES); ioctl != features.cend()) {
std::array<BYTE, 256> buf {};
SCard(Control, cardHandle, ioctl->second, nullptr, 0U, buf.data(),
DWORD(buf.size()), &size);
auto properties = parseTLV<TLV_PROPERTIES>(buf, size, [](uint32_t t) { return t; });
Expand All @@ -124,7 +109,7 @@ class CardImpl
}
}

~CardImpl()
~CardImpl() noexcept
{
if (cardHandle) {
// Cannot throw in destructor, so cannot use the SCard() macro here.
Expand Down Expand Up @@ -210,11 +195,24 @@ class CardImpl

void endTransaction() const { SCard(EndTransaction, cardHandle, DWORD(SCARD_LEAVE_CARD)); }

DWORD protocol() const { return _protocol.dwProtocol; }
SmartCard::Protocol protocol() const
{
switch (_protocol.dwProtocol) {
using enum SmartCard::Protocol;
case SCARD_PROTOCOL_UNDEFINED:
return UNDEFINED;
case SCARD_PROTOCOL_T0:
return T0;
case SCARD_PROTOCOL_T1:
return T1;
default:
THROW(Error, "Unsupported card protocol: " + std::to_string(_protocol.dwProtocol));
}
}

private:
SCARDHANDLE cardHandle;
const SCARD_IO_REQUEST _protocol;
SCARDHANDLE cardHandle {};
SCARD_IO_REQUEST _protocol {SCARD_PROTOCOL_UNDEFINED, sizeof(SCARD_IO_REQUEST)};
std::map<DRIVER_FEATURES, uint32_t> features;
uint32_t id_vendor {};
uint32_t id_product {};
Expand Down Expand Up @@ -243,7 +241,8 @@ class CardImpl
break;
default:
THROW(Error,
"Error response: '" + response + "', protocol " + std::to_string(protocol()));
"Error response: '" + response + "', protocol "
+ std::to_string(_protocol.dwProtocol));
}

if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) {
Expand Down Expand Up @@ -271,64 +270,59 @@ class CardImpl
}
};

SmartCard::TransactionGuard::TransactionGuard(const CardImpl& card, bool& inProgress) :
card(card), inProgress(inProgress)
SmartCard::Session::Session(const CardImpl& card) : card(card)
{
card.beginTransaction();
inProgress = true;
}

SmartCard::TransactionGuard::~TransactionGuard() noexcept
SmartCard::Session::~Session() noexcept
{
inProgress = false;
try {
card.endTransaction();
} catch (...) {
// Ignore exceptions in destructor.
}
}

SmartCard::SmartCard(ContextPtr context, string_t readerName, byte_vector atr) :
ctx(std::move(context)),
card(std::make_unique<CardImpl>(connectToCard(ctx->handle(), readerName))),
_readerName(std::move(readerName)), _atr(std::move(atr)),
_protocol(convertToSmartCardProtocol(card->protocol()))
ResponseApdu SmartCard::Session::transmit(const CommandApdu& command) const
{
// TODO: debug("Card ATR -> " + bytes2hexstr(atr))
return card.transmitBytes(command);
}

SmartCard::SmartCard() = default;
SmartCard::~SmartCard() noexcept = default;
ResponseApdu SmartCard::Session::transmitCTL(const CommandApdu& command, uint16_t lang,
uint8_t minlen) const
{
return card.transmitBytesCTL(command, lang, minlen);
}

SmartCard::TransactionGuard SmartCard::beginTransaction()
bool SmartCard::Session::readerHasPinPad() const
{
REQUIRE_NON_NULL(card)
return {*card, transactionInProgress};
return card.readerHasPinPad();
}

bool SmartCard::readerHasPinPad() const
SmartCard::SmartCard(Reader _reader) :
reader(std::move(_reader)), card(std::make_unique<CardImpl>(reader))
{
return card ? card->readerHasPinPad() : false;
}

ResponseApdu SmartCard::transmit(const CommandApdu& command) const
SmartCard::SmartCard() noexcept = default;
SmartCard::SmartCard(SmartCard&& other) noexcept = default;
SmartCard::~SmartCard() noexcept = default;

SmartCard::Session SmartCard::beginSession() const
{
REQUIRE_NON_NULL(card)
if (!transactionInProgress) {
THROW(std::logic_error, "Call SmartCard::transmit() inside a transaction");
}

return card->transmitBytes(command);
return {*card};
}

ResponseApdu SmartCard::transmitCTL(const CommandApdu& command, uint16_t lang, uint8_t minlen) const
SmartCard::Protocol SmartCard::protocol() const
{
REQUIRE_NON_NULL(card)
if (!transactionInProgress) {
THROW(std::logic_error, "Call SmartCard::transmit() inside a transaction");
}
return card ? card->protocol() : Protocol::UNDEFINED;
}

return card->transmitBytesCTL(command, lang, minlen);
bool SmartCard::readerHasPinPad() const
{
return card ? card->readerHasPinPad() : false;
}

} // namespace pcsc_cpp
10 changes: 6 additions & 4 deletions lib/libpcsc-cpp/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,23 @@ std::string operator+(std::string lhs, const byte_vector& rhs)
return hexStringBuilder.str();
}

void transmitApduWithExpectedResponse(const SmartCard& card, const CommandApdu& command)
void transmitApduWithExpectedResponse(const SmartCard::Session& session, const CommandApdu& command)
{
const auto response = card.transmit(command);
const auto response = session.transmit(command);
if (!response.isOK()) {
throw UnexpectedResponseError(command, response, __FILE__, __LINE__, __func__);
}
}

byte_vector readBinary(const SmartCard& card, const uint16_t length, byte_type blockLength)
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<size_t>(length - resultBytes.size(), blockLength));
auto response = card.transmit(CommandApdu::readBinary(uint16_t(resultBytes.size()), chunk));
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 "
Expand Down
Loading
Loading