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
58 changes: 57 additions & 1 deletion 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 @@ -162,7 +165,14 @@ struct CommandApdu
d.push_back(le);
}

constexpr operator const byte_vector&() const { return d; }
PCSC_CPP_CONSTEXPR_VECTOR CommandApdu(const CommandApdu& other, byte_type le) : d(other.d)
{
size_t pos = d.size() <= 5 ? 4 : 5 + d[4]; // Case 1/2 or 3/4
d.resize(pos + 1);
d[pos] = le;
}

virtual ~CommandApdu() noexcept = default;

/**
* A helper function to create a SELECT FILE command APDU.
Expand Down Expand Up @@ -213,6 +223,52 @@ 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 CommandApdu verify(byte_type p2, byte_vector&& pin,
size_t paddingLength,
pcsc_cpp::byte_type paddingChar)
{
if (!pin.empty() && pin.capacity() < paddingLength + 5) {
throw std::invalid_argument(
"PIN buffer does not have enough capacity to pad without reallocation");
}
if (pin.size() < paddingLength) {
pin.insert(pin.end(), paddingLength - pin.size(), paddingChar);
}
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)); }
};
return VerifyApdu {0x00, 0x20, 0x00, p2, std::move(pin)};
}

/**
* A helper function to create a GET RESPONSE command APDU.
*
* The ISO 7816-4 Section 7.1 GET RESPONSE command has the form:
* CLA = 0x00
* INS = 0xC0
* P1, P2 = ‘0000’ (other values are RFU)
* Lc and Data field = Empty
* Le = Maximum length of data expected in response
*/
static PCSC_CPP_CONSTEXPR_VECTOR CommandApdu getResponse(byte_type le = 0x00)
{
return {0x00, 0xc0, 0x00, 0x00, le};
}

byte_vector d;
};

Expand Down
141 changes: 58 additions & 83 deletions lib/libpcsc-cpp/src/SmartCard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
#include <arpa/inet.h>
#endif

#include <algorithm>
#include <array>
#include <map>
#include <utility>

// TODO: Someday, maybe SCARD_SHARE_SHARED vs SCARD_SHARE_EXCLUSIVE and SCARD_RESET_CARD on
Expand All @@ -41,20 +41,6 @@
namespace
{

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)
{
std::map<K, V> result;
for (auto p = data.cbegin(); DWORD(std::distance(data.cbegin(), p)) < size;) {
auto tag = K(*p++);
V value {};
for (unsigned int i = 0, len = *p++; i < len; ++i)
value |= V(*p++) << 8 * i;
result[tag] = transform(value);
}
return result;
}

constexpr uint32_t VENDOR_HID_GLOBAL = 0x076B;
constexpr uint32_t OMNIKEY_3x21 = 0x3031;
constexpr uint32_t OMNIKEY_6121 = 0x6632;
Expand Down Expand Up @@ -89,27 +75,33 @@ class CardImpl

try {
DWORD size = 0;
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);
features.data(), DWORD(features.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);
for (auto& f : features) {
f.value = ntohl(f.value);
}

if (auto ioctl = features.find(FEATURE_GET_TLV_PROPERTIES); ioctl != features.cend()) {
if (auto ioctl = feature(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; });
if (auto vendor = properties.find(TLV_PROPERTY_wIdVendor);
vendor != properties.cend())
id_vendor = vendor->second;
if (auto product = properties.find(TLV_PROPERTY_wIdProduct);
product != properties.cend())
id_product = product->second;
SCard(Control, cardHandle, ioctl->value, nullptr, 0U, buf.data(), DWORD(buf.size()),
&size);
for (auto p = buf.cbegin(); DWORD(std::distance(buf.cbegin(), p)) < size;) {
auto tag = TLV_PROPERTIES(*p++);
BYTE len = *p++;
if (DWORD(std::distance(buf.cbegin(), p)) + len > size) {
break; // Malformed TLV data.
}
uint32_t value {};
for (BYTE i = 0; i < len; ++i)
value |= uint32_t(*p++) << 8 * i;
if (tag == TLV_PROPERTY_wIdVendor)
id_vendor = value;
if (tag == TLV_PROPERTY_wIdProduct)
id_product = value;
}
}
} catch (const ScardError&) {
// Ignore driver errors during card feature requests.
Expand Down Expand Up @@ -138,33 +130,20 @@ class CardImpl
return false;
if (getenv("SMARTCARDPP_NOPINPAD"))
return false;
return features.contains(FEATURE_VERIFY_PIN_START)
|| features.contains(FEATURE_VERIFY_PIN_DIRECT);
return feature(FEATURE_VERIFY_PIN_START) != features.cend()
|| feature(FEATURE_VERIFY_PIN_DIRECT) != features.cend();
}

ResponseApdu transmitBytes(const byte_vector& commandBytes) const
ResponseApdu transmitBytes(const CommandApdu& commandApdu) const
{
byte_vector responseBytes(ResponseApdu::MAX_SIZE, 0);
auto responseLength = DWORD(responseBytes.size());

// TODO: debug("Sending: " + bytes2hexstr(commandBytes))

SCard(Transmit, cardHandle, &_protocol, commandBytes.data(), DWORD(commandBytes.size()),
SCard(Transmit, cardHandle, &_protocol, commandApdu.d.data(), DWORD(commandApdu.d.size()),
nullptr, responseBytes.data(), &responseLength);

auto response = toResponse(std::move(responseBytes), responseLength);

if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) {
getResponseWithLE(response, commandBytes);
}
if (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) {
getMoreResponseData(response);
}

return response;
return toResponse(std::move(responseBytes), responseLength);
}

ResponseApdu transmitBytesCTL(const byte_vector& commandBytes, uint16_t lang,
ResponseApdu transmitBytesCTL(const CommandApdu& commandApdu, uint16_t lang,
uint8_t minlen) const
{
uint8_t PINFrameOffset = 0;
Expand All @@ -182,20 +161,20 @@ class CardImpl
data->bNumberMessage = CCIDDefaultInvitationMessage;
data->wLangId = lang;
data->bMsgIndex = NoInvitationMessage;
data->ulDataLength = uint32_t(commandBytes.size());
cmd.insert(cmd.cend(), commandBytes.cbegin(), commandBytes.cend());
data->ulDataLength = uint32_t(commandApdu.d.size());
cmd.insert(cmd.cend(), commandApdu.d.cbegin(), commandApdu.d.cend());

DWORD ioctl =
features.at(features.contains(FEATURE_VERIFY_PIN_START) ? FEATURE_VERIFY_PIN_START
: FEATURE_VERIFY_PIN_DIRECT);
auto ioctl = feature(FEATURE_VERIFY_PIN_START);
if (feature(FEATURE_VERIFY_PIN_START) == features.cend())
ioctl = feature(FEATURE_VERIFY_PIN_DIRECT);
byte_vector responseBytes(ResponseApdu::MAX_SIZE, 0);
auto responseLength = DWORD(responseBytes.size());
SCard(Control, cardHandle, ioctl, cmd.data(), DWORD(cmd.size()),
SCard(Control, cardHandle, ioctl->value, cmd.data(), DWORD(cmd.size()),
LPVOID(responseBytes.data()), DWORD(responseBytes.size()), &responseLength);

if (auto finish = features.find(FEATURE_VERIFY_PIN_FINISH); finish != features.cend()) {
if (auto finish = feature(FEATURE_VERIFY_PIN_FINISH); finish != features.cend()) {
responseLength = DWORD(responseBytes.size());
SCard(Control, cardHandle, finish->second, nullptr, 0U, LPVOID(responseBytes.data()),
SCard(Control, cardHandle, finish->value, nullptr, 0U, LPVOID(responseBytes.data()),
DWORD(responseBytes.size()), &responseLength);
}

Expand Down Expand Up @@ -224,10 +203,16 @@ class CardImpl
private:
SCARDHANDLE cardHandle {};
SCARD_IO_REQUEST _protocol {SCARD_PROTOCOL_UNDEFINED, sizeof(SCARD_IO_REQUEST)};
std::map<DRIVER_FEATURES, uint32_t> features;
std::array<PCSC_TLV_STRUCTURE, FEATURE_CCID_ESC_COMMAND> features {};
uint32_t id_vendor {};
uint32_t id_product {};

constexpr decltype(features)::const_iterator feature(DRIVER_FEATURES tag) const
{
return std::find_if(features.cbegin(), features.cend(),
[tag](PCSC_TLV_STRUCTURE tlv) { return tlv.tag == tag; });
}

ResponseApdu toResponse(byte_vector&& responseBytes, size_t responseLength) const
{
if (responseLength > responseBytes.size()) {
Expand Down Expand Up @@ -266,31 +251,6 @@ class CardImpl
+ std::to_string(_protocol.dwProtocol));
}
}

void getMoreResponseData(ResponseApdu& response) const
{
byte_vector getResponseCommand {0x00, 0xc0, 0x00, 0x00, 0x00};

ResponseApdu newResponse {response.sw1, response.sw2};

while (newResponse.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) {
getResponseCommand[4] = newResponse.sw2;
newResponse = transmitBytes(getResponseCommand);
response.data.insert(response.data.end(), newResponse.data.cbegin(),
newResponse.data.cend());
}

response.sw1 = ResponseApdu::OK;
response.sw2 = 0;
}

void getResponseWithLE(ResponseApdu& response, byte_vector command) const
{
size_t pos = command.size() <= 5 ? 4 : 5 + command[4]; // Case 1/2 or 3/4
command.resize(pos + 1);
command[pos] = response.sw2;
response = transmitBytes(command);
}
};

SmartCard::Session::Session(const CardImpl& card) : card(card)
Expand All @@ -309,7 +269,22 @@ SmartCard::Session::~Session() noexcept

ResponseApdu SmartCard::Session::transmit(const CommandApdu& command) const
{
return card.transmitBytes(command);
auto response = card.transmitBytes(command);
if (response.sw1 == ResponseApdu::WRONG_LE_LENGTH) {
response = card.transmitBytes(CommandApdu(command, response.sw2));
}
if (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) {
auto getResponseCommand = CommandApdu::getResponse();
while (response.sw1 == ResponseApdu::MORE_DATA_AVAILABLE) {
getResponseCommand.d[4] = response.sw2;
auto newResponse = card.transmitBytes(getResponseCommand);
response.sw1 = newResponse.sw1;
response.sw2 = newResponse.sw2;
response.data.insert(response.data.end(), newResponse.data.cbegin(),
newResponse.data.cend());
}
}
return response;
}

ResponseApdu SmartCard::Session::transmitCTL(const CommandApdu& command, uint16_t lang,
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,
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
Loading
Loading