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
36 changes: 30 additions & 6 deletions lib/libpcsc-cpp/include/pcsc-cpp/pcsc-cpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ struct CommandApdu
constexpr operator const byte_vector&() const { return d; }

/**
* A helper function to create a SELECT command APDU.
* A helper function to create a SELECT FILE command APDU.
*
* The ISO 7816-4 Section 6.11 SELECT command has the form:
* The ISO 7816-4 Section 6.11 SELECT FILE command has the form:
* CLA = 0x00
* INS = 0xA4
* P1 = varies, see below.
Expand All @@ -193,6 +193,7 @@ struct CommandApdu
*
* The P1 parameter for the SELECT command controls the selection mode,
* we use the following modes:
* 0x02 = Select EF under current DF,
* 0x04 = Select AID (application identifier),
* direct selection by DF (dedicated file, directory) name.
* 0x08 = Select from MF (master file, root directory).
Expand All @@ -203,6 +204,32 @@ struct CommandApdu
return {0x00, 0xA4, p1, 0x0C, std::move(file)};
}

/**
* A helper function to create a SELECT EF command APDU.
*
* Same as select() but with P2 set to 0x04 and returns the file identifier as data.
*/
static PCSC_CPP_CONSTEXPR_VECTOR CommandApdu selectEF(byte_type p1, byte_vector file)
{
return {0x00, 0xA4, p1, 0x04, std::move(file), 0x00};
}

/**
* A helper function to create a READ BINARY command APDU.
*
* The ISO 7816-4 Section 6.1 READ BINARY command has the form:
* CLA = 0x00
* INS = 0xB0
* P1, P2 = if bit8=0 in P1, then P1||P2 is the offset of the first byte to be read in data units from the
* beginning of the file.
* Lc and Data field = Empty
* Le = Number of bytes to be read
*/
static PCSC_CPP_CONSTEXPR_VECTOR CommandApdu readBinary(uint16_t pos, byte_type le)
{
return {0x00, 0xb0, byte_type(pos >> 8), byte_type(pos), le};
}

byte_vector d;
};

Expand Down Expand Up @@ -279,11 +306,8 @@ std::vector<Reader> listReaders();
/** Transmit APDU command and verify that expected response is received. */
void transmitApduWithExpectedResponse(const SmartCard& card, const CommandApdu& command);

/** Read data length from currently selected file header, file must be ASN.1-encoded. */
size_t readDataLengthFromAsn1(const SmartCard& card);

/** Read lenght bytes from currently selected binary file in blockLength-sized chunks. */
byte_vector readBinary(const SmartCard& card, const size_t length, byte_type blockLength);
byte_vector readBinary(const SmartCard& card, const uint16_t length, byte_type blockLength = 0x00);

// Errors.

Expand Down
91 changes: 15 additions & 76 deletions lib/libpcsc-cpp/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,16 @@
#include "pcsc-cpp/pcsc-cpp.hpp"
#include "pcsc-cpp/pcsc-cpp-utils.hpp"

#include <sstream>
#include <algorithm>
#include <iomanip>
#include <sstream>

using namespace pcsc_cpp;
using namespace std::string_literals;

#ifdef HIBYTE
#undef HIBYTE
#endif
#ifdef LOBYTE
#undef LOBYTE
#endif

constexpr byte_type HIBYTE(size_t w) noexcept
{
return static_cast<byte_type>((w >> 8) & 0xff);
}
constexpr byte_type LOBYTE(size_t w) noexcept
{
return static_cast<byte_type>(w & 0xff);
}

namespace
{

const byte_type DER_SEQUENCE_TYPE_TAG = 0x30;
const byte_type DER_TWO_BYTE_LENGTH = 0x82;

class UnexpectedResponseError : public Error
{
public:
Expand Down Expand Up @@ -93,68 +75,25 @@ void transmitApduWithExpectedResponse(const SmartCard& card, const CommandApdu&
}
}

size_t readDataLengthFromAsn1(const SmartCard& card)
{
// p1 - offset size first byte, 0
// p2 - offset size second byte, 0
// le - number of bytes to read, need 4 bytes from start for length
const CommandApdu readBinary4Bytes {0x00, 0xb0, 0x00, 0x00, 0x04};

auto response = card.transmit(readBinary4Bytes);

// Verify expected DER header, first byte must be SEQUENCE.
if (response.data[0] != DER_SEQUENCE_TYPE_TAG) {
// TODO: more specific exception
THROW(Error,
"readDataLengthFromAsn1(): First byte must be SEQUENCE (0x30), but is "s
+ int2hexstr(response.data[0]));
}

// TODO: support other lenghts besides 2.
// Assume 2-byte length, so second byte must be 0x82.
if (response.data[1] != DER_TWO_BYTE_LENGTH) {
// TODO: more specific exception
THROW(Error,
"readDataLengthFromAsn1(): Second byte must be two-byte length indicator "s
"(0x82), but is "s
+ int2hexstr(response.data[1]));
}

// Read 2-byte length field at offset 2 and 3 and add the 4 DER length bytes.
const auto length = size_t((response.data[2] << 8) + response.data[3] + 4);
if (length < 128 || length > 0x0f00) {
// TODO: more specific exception
THROW(Error,
"readDataLengthFromAsn1(): Unexpected data length in DER header: "s
+ std::to_string(length));
}

return length;
}

byte_vector readBinary(const SmartCard& card, const size_t length, byte_type blockLength)
byte_vector readBinary(const SmartCard& card, const uint16_t length, byte_type blockLength)
{
auto lengthCounter = length;
auto resultBytes = byte_vector {};

for (size_t offset = 0; lengthCounter != 0;
offset += blockLength, lengthCounter -= blockLength) {

if (blockLength > lengthCounter) {
blockLength = byte_type(lengthCounter);
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));
if (chunk > 0 && response.data.size() != chunk) {
THROW(Error,
"Length mismatch, expected "s + std::to_string(chunk) + ", received "
+ std::to_string(response.data.size()) + " bytes");
}

CommandApdu readBinary {0x00, 0xb0, HIBYTE(offset), LOBYTE(offset), blockLength};
auto response = card.transmit(readBinary);

resultBytes.insert(resultBytes.end(), response.data.cbegin(), response.data.cend());
}

if (resultBytes.size() != length) {
// TODO: more specific exception
THROW(Error, "readBinary(): Invalid length: "s + std::to_string(resultBytes.size()));
THROW(Error,
"Length mismatch, expected "s + std::to_string(length) + ", received "
+ std::to_string(resultBytes.size()) + " bytes");
}

return resultBytes;
}

Expand Down
5 changes: 0 additions & 5 deletions src/electronic-ids/TLV.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,6 @@ struct TLV
}
return TLV({});
}
template <typename... Tags>
static PCSC_CPP_CONSTEXPR_VECTOR TLV path(const byte_vector& data, uint32_t tag, Tags... tags)
{
return path(TLV(data), tag, tags...);
}

constexpr operator bool() const noexcept { return begin < end; }
};
Expand Down
9 changes: 5 additions & 4 deletions src/electronic-ids/pcsc/EIDIDEMIA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const auto ADF1_AID = CommandApdu::select(
const auto ADF2_AID = CommandApdu::select(0x04,
{0x51, 0x53, 0x43, 0x44, 0x20, 0x41, 0x70, 0x70, 0x6C,
0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E});
const auto AUTH_CERT = CommandApdu::select(0x09, {0xAD, 0xF1, 0x34, 0x01});
const auto SIGN_CERT = CommandApdu::select(0x09, {0xAD, 0xF2, 0x34, 0x1F});
const auto AUTH_CERT = CommandApdu::selectEF(0x09, {0xAD, 0xF1, 0x34, 0x01});
const auto SIGN_CERT = CommandApdu::selectEF(0x09, {0xAD, 0xF2, 0x34, 0x1F});

} // namespace

Expand All @@ -69,7 +69,8 @@ void EIDIDEMIA::selectADF2() const
byte_vector EIDIDEMIA::getCertificateImpl(const CertificateType type) const
{
selectMain();
return electronic_id::getCertificate(*card, type.isAuthentication() ? AUTH_CERT : SIGN_CERT);
// Set block lenght to 0xC0 to workaround for the 2018 v2 card, with reader Alcor Micro AU9540
return readFile(*card, type.isAuthentication() ? AUTH_CERT : SIGN_CERT, 0xC0);
}

EIDIDEMIA::KeyInfo EIDIDEMIA::authKeyRef() const
Expand Down Expand Up @@ -143,7 +144,7 @@ ElectronicID::PinRetriesRemainingAndMax EIDIDEMIA::pinRetriesLeft(byte_type pinR
if (!response.isOK()) {
THROW(SmartCardError, "Command GET DATA ODD failed with error " + response);
}
TLV info = TLV::path(response.data, 0x70, 0xBF8100 | ref, 0xA0);
TLV info = TLV::path(TLV(response.data), 0x70, 0xBF8100 | ref, 0xA0);
TLV max = info[0x9A];
TLV tries = info[0x9B];
if (max && tries) {
Expand Down
14 changes: 7 additions & 7 deletions src/electronic-ids/pcsc/FinEID.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ namespace

const auto SELECT_MAIN_AID = CommandApdu::select(
0x04, {0xa0, 0x00, 0x00, 0x00, 0x63, 0x50, 0x4b, 0x43, 0x53, 0x2d, 0x31, 0x35});
const auto SELECT_AUTH_CERT_FILE = CommandApdu::select(0x08, {0x43, 0x31});
const auto SELECT_SIGN_CERT_FILE_V3 = CommandApdu::select(0x08, {0x50, 0x16, 0x43, 0x35});
const auto SELECT_SIGN_CERT_FILE_V4 = CommandApdu::select(0x08, {0x50, 0x16, 0x43, 0x32});
const auto SELECT_AUTH_CERT_FILE = CommandApdu::selectEF(0x08, {0x43, 0x31});
const auto SELECT_SIGN_CERT_FILE_V3 = CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x35});
const auto SELECT_SIGN_CERT_FILE_V4 = CommandApdu::selectEF(0x08, {0x50, 0x16, 0x43, 0x32});

constexpr byte_type PIN_PADDING_CHAR = 0x00;
constexpr byte_type AUTH_PIN_REFERENCE = 0x11;
Expand All @@ -64,8 +64,8 @@ namespace electronic_id
byte_vector FinEIDv3::getCertificateImpl(const CertificateType type) const
{
transmitApduWithExpectedResponse(*card, SELECT_MAIN_AID);
return electronic_id::getCertificate(
*card, type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V3);
return readFile(*card,
type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V3);
}

byte_vector FinEIDv3::signWithAuthKeyImpl(byte_vector&& pin, const byte_vector& hash) const
Expand Down Expand Up @@ -179,8 +179,8 @@ ElectronicID::PinRetriesRemainingAndMax FinEIDv3::pinRetriesLeft(byte_type pinRe
byte_vector FinEIDv4::getCertificateImpl(const CertificateType type) const
{
transmitApduWithExpectedResponse(*card, SELECT_MAIN_AID);
return electronic_id::getCertificate(
*card, type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V4);
return readFile(*card,
type.isAuthentication() ? SELECT_AUTH_CERT_FILE : SELECT_SIGN_CERT_FILE_V4);
}

byte_vector FinEIDv4::signWithAuthKeyImpl(byte_vector&& pin, const byte_vector& hash) const
Expand Down
35 changes: 15 additions & 20 deletions src/electronic-ids/pcsc/LatEIDIDEMIAv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ byte_vector LatEIDIDEMIAV2::getCertificateImpl(const CertificateType type) const
auto info =
readDCODInfo(CERT_FILE_REF, type.isAuthentication() ? data->authCache : data->signCache);
if (TLV id = TLV::path(info, 0x30, 0xA1, 0x30, 0x30, 0x04)) {
return electronic_id::getCertificate(*card, CommandApdu::select(0x02, {id.begin, id.end}));
return readFile(*card, CommandApdu::selectEF(0x02, {id.begin, id.end}));
}
THROW(SmartCardError, "EF.CD reference not found");
}
Expand Down Expand Up @@ -109,42 +109,37 @@ EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::signKeyRef() const
}

template <class C>
const byte_vector& LatEIDIDEMIAV2::readEF_File(byte_vector file, C& cache) const
TLV LatEIDIDEMIAV2::readEF_File(byte_vector file, C& cache) const
{
if (auto it = cache.find(file); it != cache.end()) {
return it->second;
return TLV(it->second);
}
auto response = card->transmit({0x00, 0xA4, 0x02, 0x04, file, 0x00});
if (!response.isOK()) {
THROW(SmartCardError, "Failed to read EF file");
}
TLV size = TLV::path(response.data, 0x62, 0x80);
if (!size || size.length != 2) {
THROW(SmartCardError, "Failed to read EF file length");
}
return cache[std::move(file)] =
readBinary(*card, size_t(*size.begin << 8) + *(size.begin + 1), 0xFF);
return TLV(cache[std::move(file)] = readFile(*card, CommandApdu::selectEF(0x02, file)));
}

template <class C>
const byte_vector& LatEIDIDEMIAV2::readDCODInfo(byte_type type, C& cache) const
TLV LatEIDIDEMIAV2::readDCODInfo(byte_type type, C& cache) const
{
const auto info = readEF_File(EF_OD, cache);
if (auto file = TLV::path(info, type, 0x30, 0x04); file && file.length == 2) {
return readEF_File({file.begin, file.end}, cache);
for (TLV ref(info); ref; ++ref) {
if (ref.tag != type) {
continue;
}
if (auto file = ref[0x30][0x04]; file && file.length == 2) {
return readEF_File({file.begin, file.end}, cache);
}
}
THROW(SmartCardError, "EF.DCOD reference not found");
}

template <class C>
EIDIDEMIA::KeyInfo LatEIDIDEMIAV2::readPrKDInfo(byte_type keyID, C& cache) const
{
auto info = readDCODInfo(PRIV_FILE_REF, cache);
if (info.empty()) {
TLV prKD = readDCODInfo(PRIV_FILE_REF, cache);
if (!prKD) {
THROW(SmartCardError, "EF.PrKD reference not found");
}
TLV prKD(info);
TLV key = TLV::path(prKD.child(), 0x30);
TLV key = prKD[0x30];
key = TLV::path(++key, 0x30, 0x02);
return {key.length == 2 ? *std::next(key.begin) : keyID, prKD.tag == 0xA0};
}
6 changes: 4 additions & 2 deletions src/electronic-ids/pcsc/LatEIDIDEMIAv2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
namespace electronic_id
{

struct TLV;

class LatEIDIDEMIAV2 : public EIDIDEMIA
{
public:
Expand All @@ -52,9 +54,9 @@ class LatEIDIDEMIAV2 : public EIDIDEMIA
KeyInfo signKeyRef() const override;

template <class C>
const byte_vector& readEF_File(byte_vector file, C& cache) const;
TLV readEF_File(byte_vector file, C& cache) const;
template <class C>
const byte_vector& readDCODInfo(byte_type type, C& cache) const;
TLV readDCODInfo(byte_type type, C& cache) const;
template <class C>
KeyInfo readPrKDInfo(byte_type keyID, C& cache) const;

Expand Down
Loading
Loading