This library provides a simple and nice C++ wrapper around these libraries, so that programmers can concentrate on functionality. It offers general support for PCSC-lite, OpenSSL, PKCS#11, plus specific functionality for the SuisseID.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1507 lines
52 KiB

/*! @file
@id $Id: cardos.hxx 3826 2012-05-21 12:25:29Z marc $
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef __CARDOS_HXX__
#define __CARDOS_HXX__
#include <pcsc.hxx>
#include <cryptaux.hxx>
#include <openssl.hxx>
#include <mrw/string.hxx>
#include <stdexcept>
#ifndef CARDOS_LOG
#define CARDOS_LOG(X) // no logging by default
// use e.g. #define CARDOS_LOG(X) std::clog<<X<<std::endl
#endif
/** @defgroup gcardos C++ Access to Siemens CardOS 4.4
Implements APDUs for accessing Siemens CardOS V4.4 smartcards. */
//@{
/// @defgroup cardosexception CardOS Exceptions
/// @defgroup cardostypes CardOS Types
/// @defgroup cardoslib CardOS Library
//@}
/// @ref gcardos @copydoc gcardos
namespace cardos {
/// @addtogroup cardosexception CardOS Exceptions
//@{
//============================================================================
//----------------------------------------------------------------------------
class exception: public pcsc::exception {
public:
exception(const std::string& reason) throw():
pcsc::exception("cardos: "+reason) {
}
};
//----------------------------------------------------------------------------
class wrong_pin: public exception {
public:
wrong_pin(const std::string& s) throw(): exception("wrong pin "+s) {}
};
//----------------------------------------------------------------------------
class user_pin_locked: public exception {
public:
user_pin_locked(const std::string& s) throw():
exception("user pin locked "+s) {}
};
//----------------------------------------------------------------------------
class wrong_puk: public exception {
public:
wrong_puk(const std::string& s) throw(): exception("wrong puk "+s) {}
};
//----------------------------------------------------------------------------
class wrong_result: public exception {
public:
wrong_result(const std::string& reason, const std::string& data) throw():
exception("wrong result, "+reason+": "+crypto::hex(data)) {}
wrong_result(const std::string& reason) throw():
exception("wrong result, "+reason) {}
};
//----------------------------------------------------------------------------
class runtime_error: public exception {
public:
runtime_error(const std::string& reason) throw():
exception("runtime error, "+reason) {}
runtime_error(const std::string& reason, const std::string& data) throw():
exception("runtime error, "+reason+": "+crypto::hex(data)) {}
};
//----------------------------------------------------------------------------
class unexpected_challenge_length: public exception {
public:
unexpected_challenge_length(const std::string& data) throw():
exception("challenge should be 8 bytes, challenge is: "
+crypto::hex(data)) {}
};
//----------------------------------------------------------------------------
class card_transmission_failed: public exception {
public:
card_transmission_failed(const std::string& position,
const std::string& reason) throw():
exception("transmission to card failed: "+position
+" reason: "+reason) {
}
};
//----------------------------------------------------------------------------
class wrong_dataformat: public exception {
public:
wrong_dataformat(const std::string& data,
const std::string& reason) throw():
exception("wrong dataformat: "+crypto::hex(data)
+" reason: "+reason) {
}
};
//----------------------------------------------------------------------------
class array_range: public exception {
public:
array_range(unsigned int i, unsigned int j) throw():
exception("array index out of range: "+mrw::string(i)
+"; length: "+mrw::string(j)) {
}
};
//----------------------------------------------------------------------------
class pin_locked: public exception {
public:
pin_locked() throw(): exception("pin is locked and cannot be changed") {}
};
//@}
//============================================================================
class BerValue {
public:
enum Class {
UNIVERSAL = 0x00,
APPLICATION = 0x40,
CONTEXT_SPECIFIC = 0x80,
PRIVATE = 0xC0
};
enum PC {
PRIMITIVE = 0x00,
CONSTRUCTED = 0x20
};
enum Type {
END_OF_CONTENT = 0x00,
BOOLEAN = 0x01,
INTEGER = 0x02,
BIT_STRING = 0x03,
OCTET_STRING = 0x04,
EMPTY = 0x05,
OBJECT_IDENTIFIER = 0x06,
OBJECT_DESCRIPTOR = 0x07,
EXTERNAL = 0x08,
REAL = 0x09,
ENUMERATED = 0x0A,
EMBEDDED_PDV = 0x0B,
UTF8_STRING = 0x0C,
RELATIVE_OID = 0x0D,
SEQUENCE = 0x10,
SET = 0x11,
NUMERIC_STRING = 0x12,
PRINTABLE_STRING = 0x13,
T61_STRING = 0x14,
VIDEOTEX_STRING = 0x15,
IA5_STRING = 0x16,
UTC_TIME = 0x17,
GENERALIZED_TIME = 0x18,
GRAPHIC_STRING = 0x19,
VISIBLE_STRING = 0x1A,
GENERAL_STRING = 0x1B,
UNIVERSAL_STRING = 0x1C,
CHARACTER_STRING = 0x1D,
BMP_STRING = 0x1E,
};
protected: // use BerValues instead
friend class BerValues;
BerValue(const std::vector<BerValue>& sequence):
_tag(PRIVATE|CONSTRUCTED|SEQUENCE),
_sequence(sequence) {
}
BerValue(std::string& content) {
if (content.size()<2)
throw wrong_dataformat(content, "not a BER, header size too small: \""
+crypto::binToHex(content)+"\"");
_tag = content[0];
unsigned char length = content[1];
_value = content.substr(2, length);
if (content.size()<std::string::size_type(length)+2)
throw wrong_dataformat(content, "not a BER, content size too"
" small: \""+crypto::binToHex(_value)+"\"");
content.erase(0, 2+length);
if (tagType()==END_OF_CONTENT) return; // done
if (isContainer())
while (_value.size()) _sequence.push_back(BerValue(_value));
}
public:
BerValue(unsigned char tag, const std::string& value):
_tag(tag), _value(value) {
if (isContainer())
while (_value.size()) _sequence.push_back(BerValue(_value));
}
BerValue(unsigned char tag, const std::vector<BerValue>& values):
_tag(tag), _sequence(values) {
}
unsigned char tagClass() {
return _tag&0xC0;
}
unsigned char tagPC() {
return _tag&0x20;
}
unsigned char tagType() {
return _tag&0x1F;
}
bool isContainer() {
return tagPC()==CONSTRUCTED;
}
unsigned int size() {
return _sequence.size();
}
unsigned char tag() {
return _tag;
}
BerValue operator[](unsigned int i) {
if (i>=_sequence.size()) throw array_range(i, _sequence.size());
return _sequence[i];
}
operator std::string() {
std::string res;
res.push_back(_tag);
if (isContainer()) {
for (std::vector<BerValue>::iterator it(_sequence.begin());
it!=_sequence.end(); ++it) {
res += *it;
}
} else {
(res += (char)_value.size()) += _value;
}
return res;
}
std::string string() {
return _value;
}
unsigned long ulong() {
return crypto::ulongFromBinary(_value);
}
std::string print(int indent=0, int indentStep = 4) {
std::stringstream ss;
ss<<std::string(indent*indentStep, ' ')<<'['<<crypto::binToHex(_tag)<<'=';
switch (tagClass()) {
case UNIVERSAL: ss<<"UNIVERSAL,"; break;
case APPLICATION: ss<<"APPLICATION,"; break;
case CONTEXT_SPECIFIC: ss<<"CONTEXT_SPECIFIC,"; break;
case PRIVATE: ss<<"PRIVATE,"; break;
default: ss<<(unsigned int)tagClass();
}
switch (tagPC()) {
case PRIMITIVE: ss<<"PRIMITIVE,"; break;
case CONSTRUCTED: ss<<"CONSTRUCTED,"; break;
default: ss<<(unsigned int)tagPC();
}
switch (tagType()) {
case END_OF_CONTENT: ss<<"END_OF_CONTENT"; break;
case BOOLEAN: ss<<"BOOLEAN"; break;
case INTEGER: ss<<"INTEGER"; break;
case BIT_STRING: ss<<"BIT_STRING"; break;
case OCTET_STRING: ss<<"OCTET_STRING"; break;
case EMPTY: ss<<"EMPTY"; break;
case OBJECT_IDENTIFIER: ss<<"OBJECT_IDENTIFIER"; break;
case OBJECT_DESCRIPTOR: ss<<"OBJECT_DESCRIPTOR"; break;
case EXTERNAL: ss<<"EXTERNAL"; break;
case REAL: ss<<"REAL"; break;
case ENUMERATED: ss<<"ENUMERATED"; break;
case EMBEDDED_PDV: ss<<"EMBEDDED_PDV"; break;
case UTF8_STRING: ss<<"UTF8_STRING"; break;
case RELATIVE_OID: ss<<"RELATIVE_OID"; break;
case SEQUENCE: ss<<"SEQUENCE"; break;
case SET: ss<<"SET"; break;
case NUMERIC_STRING: ss<<"NUMERIC_STRING"; break;
case PRINTABLE_STRING: ss<<"PRINTABLE_STRING"; break;
case T61_STRING: ss<<"T61_STRING"; break;
case VIDEOTEX_STRING: ss<<"VIDEOTEX_STRING"; break;
case IA5_STRING: ss<<"IA5_STRING"; break;
case UTC_TIME: ss<<"UTC_TIME"; break;
case GENERALIZED_TIME: ss<<"GENERALIZED_TIME"; break;
case GRAPHIC_STRING: ss<<"GRAPHIC_STRING"; break;
case VISIBLE_STRING: ss<<"VISIBLE_STRING"; break;
case GENERAL_STRING: ss<<"GENERAL_STRING"; break;
case UNIVERSAL_STRING: ss<<"UNIVERSAL_STRING"; break;
case CHARACTER_STRING: ss<<"CHARACTER_STRING"; break;
case BMP_STRING: ss<<"BMP_STRING"; break;
default: ss<<(unsigned int)tagType();
}
ss<<"] = ";
if (isContainer()) {
int i(0);
ss<<"{"<<std::endl;
for (std::vector<BerValue>::iterator it(_sequence.begin());
it!=_sequence.end(); ++it) {
ss<<std::string((indent+1)*indentStep, ' ')
<<"["<<i++<<"] {"
<<std::endl
<<it->print(indent+2, indentStep)
<<std::endl
<<std::string((indent+1)*indentStep, ' ')<<'}'
<<std::endl;
}
ss<<std::string(indent*indentStep, ' ')<<'}';
} else {
ss<<"0x"<<crypto::binToHex(_value)<<" = \"";
for (std::string::const_iterator it(_value.begin());
it!=_value.end(); ++it)
ss<<(isprint(*it)?*it:'.');
ss<<"\"";
}
return ss.str();
}
private:
unsigned char _tag;
std::string _value;
std::vector<BerValue> _sequence;
};
/// Store a sequence of BerValue
class BerValues: public std::vector<BerValue> {
public:
BerValues() {}
BerValues(const std::string& content) {
std::string contentCopy(content);
while (contentCopy.size()) push_back(BerValue(contentCopy));
}
BerValues& operator=(std::string& content) {
clear();
std::string contentCopy(content);
while (contentCopy.size()) push_back(BerValue(contentCopy));
return *this;
}
BerValues& operator+=(const std::string& content) {
std::string contentCopy(content);
while (contentCopy.size()) push_back(BerValue(contentCopy));
return *this;
}
BerValues& operator+=(const BerValues& values) {
insert(end(), values.begin(), values.end());
return *this;
}
std::string print(int indent=0, int indentStep = 4) {
std::stringstream ss;
if (size()==1) {
ss<<std::string(indent*indentStep, ' ')<<at(0).print();
} else {
int i(0);
ss<<"{"<<std::endl;
for (iterator it(begin()); it!=end(); ++it) {
ss<<std::string((indent+1)*indentStep, ' ')
<<"["<<i++<<"] {"
<<std::endl
<<it->print(indent+2, indentStep)
<<std::endl
<<std::string((indent+1)*indentStep, ' ')<<'}'
<<std::endl;
}
ss<<std::string(indent*indentStep, ' ')<<'}';
}
return ss.str();
}
};
//@}
//============================================================================
/// @addtogroup cardoslib
//@{
/// Implements CardOS V4.4 commands.
/** Directly sends CardOS V4.4 commands to a smart card using APDUs. */
class Commands {
public:
//------------------------------------------------------------------------
/// @name Setup Smart Card Reader
//@{
/// Uninitialized class
/** Use @ref reader to setup assign smart card reader. */
Commands() {}
/// Initialize with given smart card reader.
Commands(mrw::Shared<pcsc::Connection::Reader> reader):
_reader(reader) {
}
/// Set smart card reader.
void reader(mrw::Shared<pcsc::Connection::Reader> reader) {
_reader = reader;
}
//@}
//------------------------------------------------------------------------
/// @name Basic CardOS Commands
//@{
//! Activates a deactivated file or file tree.
/*! This command reactivates the current file in the file
system. If there were any child files (file tree), they can
subsequently be selected and thus used again. The command
will not return an error, if the current file is already
active.
@prereq The command can only be executed, if the right
referenced by the files AC ACTIVATE is granted in
the security status of the current DF. The command
cannot be applied to CODE files. */
void activateFile() {
CRYPTOLOG("log");
check(send(0x00, 0x44, 0x00, 0x00));
}
//! Manages the transaction buffer for command transactions
//! and/or command sequence transactions
/*! The command allocates or frees a transaction buffer
In case of mode Allocate a possibly existing old buffer
block is marked as unused and a new block with size HI-LO is
initialized. The ID of the new buffer is returned as
response. After the command has been executed successfully,
all the EEPROM contents, which will be affected by a command
with the setting AutoTR=ON, will be stored in the allocated
EEPROM buffer, so that in case of an unforeseen interruption
the former EEPROM state can be restored after the next reset
(see also SET TRANSACTION STATE command).
In case of mode Free, the buffer with the ID in P2 will not
be used anymore.
The recommended buffer size is 300 bytes. A small buffer
will increase the EEPROM stress.
Buffers should be allocated and freed again by the
application to reduce the EEPROM stress.
<table>
<caption>Bytes P1-P2</caption>
<thead>
<tr><td colspan="2">P1 (MODE)</td><td>P2</td><td>Meaning</td></tr>
<tr><td>Bit 7</td><td>Bits 6 0</td><td/><td/></tr>
</thead>
<tr>
<td>1</td><td>HI value</td><td>LO value</td>
<td>Allocate buffer with size HI-LO</td>
</tr><tr>
<td>0</td><td>rfu</td><td>ID</td>
<td>Free buffer with ID in P2</td>
</tr>
</table>
@prereq The command can only be executed, if the right
referenced by the MFs AC ALLOCATE is granted.
@returns 1 byte: ID of allocated buffer
@see freeTransactionBuffer */
std::string allocateTransactionBuffer(unsigned short size) {
CRYPTOLOG("log");
if (size>0x7FFF) throw runtime_error("requested buffer too large");
return check(send(0x80, 0x12, 8|size>>8, size&0xFF));
}
//! Free transaction buffer
/*! @see allocateTransactionBuffer */
void freeTransactionBuffer(unsigned char id) {
CRYPTOLOG("log");
check(send(0x80, 0x12, 0x00, id));
}
//! Creates a new record in a record oriented file
/*! This command creates a new record in the currently selected
file, or, if P2 is not 00h, in the file, which is referenced
by the Short File Identifier (SFI). The command can only be
used on LINEAR FIXED, CYCLIC FIXED and LINEAR TLV files.
In the parameter EF-ID a file ID can be specified with an
SFI. See table 3.4-1 for its format. The thus referenced
file will then be selected.
EF-ID Byte P2:
- 0x00 use current EF
- bits 7-3 = ppppp, bits 2-0=0 use SFI ppppp (11111 not allowed)
- other value rfu
If an SFI is present the actual FID to be used is built by
adding the constant value FE00h to the specified SFI.
The Record_Data contain the contents of the new record. The
length of the Record_Data must not be more than 254 bytes.
For LINEAR FIXED or CYCLIC FIXED files the length of the
Record_Data must match the length, which was specified by
the belonging CREATE FILE command.
For LINEAR TLV files the command data field must have a
valid TLV format.
If there is not sufficient file memory to store the new
record, an error message is generated for all files, except
for a CYCLIC FIXED file, where the previous record will be
overwritten (same behavior as UPDATE RECORD in PREV mode).
For all of file types the newly written record becomes the
current record. For CYCLIC FIXED files the newly written
record becomes the logical first record.
For LINEAR TLV files it is assumed that the length field of
the record consists of one byte. The tag byte is not
interpreted by APPEND RECORD.
@prereq The command can only be executed, if the right
referenced by the files AC APPEND is granted in the
security status of the current DF. */
void appendRecord(unsigned char efId, std::string data) {
CRYPTOLOG("log");
check(send(0x00, 0xE2, 0x00, efId, data));
}
//! Computes a digital signature of internally hashed data
//! provided by the application and the card itself in order to
//! authenticate the card to the application.
/*! The CARD AUTHENTICATE command allows to check a card’s
authenticity before personalization takes place. It returns
a digital signature, which is computed over an internal hash
value.
Inputs for the hash value calculation are:
- The System_Challenge (s. Command Data Field)
- the command header of the CARD AUTHENTICATE command
(CLA, INS, P1, P2)
- the global life cycle phase (s. GET DATA command, mode 83h)
- the number of loaded Packages (s. GET DATA command, mode 88h)
- the system internal parameters (9 bytes consisting 22h) and
- optionally, the Chip Unique Identification Number
(as indicated in mode byte P1, @refs getData, mode 81h,
Bytes 11-16).
The commands supports 2 modes indicated by the parameter P1:
- 0x01: The Chip Unique Identification Number is not used for
the calculation of the hash value
- 0x02: Include the Chip Unique Identification Number for the
calculation of hash value
The mode 01h can be used to compute a constant signature for
different cards of the same CardOS version, depending on
their global life cycle phase and loaded system packages
that use the system internal data The command uses a
1536-bit private RSA2_SIG_SHA-1 key stored in ROM, see
chapter 2.4.2.4
<table>
<caption>Format of the input for hash value calculation</caption>
<thead><tr>
<th>System_Challenge</th>
<th>Command Header<br/>CLA INS P1 P2</th>
<th>Global Life Cycle Phase</th>
<th>Internal Parameters</th>
<th>Number of loaded Packages</th>
<th>Chip Unique Identification Number (opt.)</th>
</tr></thead>
<tr>
<td>xxhxxh</td><td>80h 88h Mode 00h</td><td>xxh</td>
<td>22h22h</td><td>xxh</td><td>xxhxxh</td>
</tr><tr>
<td>n bytes</td><td>4 bytes</td><td>1 byte</td><td>9 bytes</td>
<td>1 byte</td><td>6 bytes/empty</td>
</tr>
</table>
@prereq The System_Challenge provided by the application
must be greater or equal to 16 bytes. Since the
command does not support chaining, the length of the
System_Challenge is limited by the IO buffer size.
@note The application must use the corresponding RSA2 public
key to verify the Digital Signature received from the
card and thus establishes the cards authenticity. The
RSA2 public key is contained in the CardOS V4.4 PRNs.
@return Digital Signature */
std::string cardAuthenticate(bool hashInclIdentNum,
std::string systemChallange) {
CRYPTOLOG("log");
return check(send(0x80, 0x88, hashInclIdentNum?0x01:0x02, 0x00,
systemChallange));
}
//! Changes the object data of any BS object except for PIN TEST objects
void changeKeyData() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Changes the object data of a PIN TEST object
/** Changes a PIN. */
void changeReferenceData(unsigned char id, std::string newData,
std::string oldData=std::string()) {
CRYPTOLOG("log");
check(send(0x00, 0x24, oldData.size()?0x00:0x01, 0x80|id,
oldData+newData));
}
//! Changes the data of a system key to the new key data
//! provided with the command.
void changeSystemKey() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Creates a file (only EF or DF)
void createFile(std::string path="", const std::string data="") {
CRYPTOLOG("log");
// pcsc::Connection::Reader::Transaction lock(_reader);
// if (path.size()) select(path);
// check(send(0x00, 0xE0, 0x00, 0x00,
// check(send(0x00, 0xE0, 0x00, 0x00, BerValue(0x62h,
// data).raw()));
assert(!"not implemented");
}
//! Creates a EF file
void createBinary(std::string path="", std::string id="",
const std::string data="") {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
if (path.size()) select(path);
BerValues c;
c += BerValue(0x80, crypto::toBinary(data.size()));
std::string idbin(crypto::hexToBin(id));
if (idbin.size()!=2) throw runtime_error("file id must be two bytes");
c += BerValue(0x83, idbin);
check(send(0x00, 0xE0, 0x00, 0x00, BerValue(82, c)));
}
//! Deactivates a file or a file tree
void deactivateFile() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Decreases a record value
void decrease() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Deletes a file (DF or EF)
void deleteFile() {
CRYPTOLOG("log");
assert(!"not implemented");
}
enum FileTypes {DF=0x00, EF=0x01, DF_EF=0x02};
//! Reads file information of EFs and/or DFs in the current DF
BerValues directory(std::string path, FileTypes types=DF_EF,
unsigned char offset=0) {
CRYPTOLOG("log");
unsigned char o(offset);
BerValues content;
pcsc::Connection::Reader::Transaction lock(_reader);
if (path.size()) select(path);
/*while (o<(unsigned char)-1)*/ { /// @todo read until done
std::string res(check(send(0x80, 0x16, types, o++)));
//if (cardos::Commands::retCode(res)!=0x9000) break;
content += res;
}
return content;
}
//! Enables an already loaded and activated but disabled license package.
void enablePackage() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Erases the file system in the EEPROM.
void eraseFiles() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Performs a challenge/response test
void externalAuthenticate() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Changes the life cycle phase from MANUFACTURING to
//! ADMINISTRATION after creation of the MF or changes only from
//! MANUFACTURING to INITIALIZATION.
void format() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Generates a key pair for the RSA/RSA2 algorithms within the card
void generateKeyPair() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Generates an internal random number (i.e. SC_Challenge)
void getChallange() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Reads system information
std::string getData(unsigned char mode) {
CRYPTOLOG("log");
return check(send(0x00, 0xCA, 0x01, mode));
}
//! Product name, version, release date, copyright string
std::string getDataProductName() {
CRYPTOLOG("log");
return getData(0x80);
}
struct ChipProductionData {
std::string serial;
unsigned char type;
std::string id;
};
//! Chip production data as supplied by Infineon, PROM area
ChipProductionData getDataChipProduction() {
CRYPTOLOG("log");
std::string code(getData(0x81));
ChipProductionData res = {
code.substr(8, 8),
(unsigned char)code[9],
code.substr(11, 6)
};
return res;
}
//! Transmits an external random number (i.e. System_Random) to
//! the smart card
/*! The command transmits an external random number of n bytes
the so-called System_Random to the smart card. System_Random
will be used for the next response- SM (SIG or ENC_SIG)
calculation. Valid values for n in short APDU mode are in
the range 1 256. In extended APDU mode nmax is dependent
on the preset data field length (nmax = data field length).
The external random number System_Random can be used only
once. System_Random is valid during the same smart card
session until it has been used by response-SM or until it is
overwritten by a new GIVE RANDOM command.
The external random number is stored in the XRAM of the
smart card. */
void giveRandom(std::string random) {
CRYPTOLOG("log");
check(send(0x80, 0x86, 0x00, 0x00, random));
}
//! Increases a record value
/** This command increases the value of the current record of a
cyclic fixed file by the @p size.
EF-ID Byte P2:
- 0x00 use current EF
- bits 7-3 = ppppp, bits 2-0=0 use SFI ppppp (11111 not allowed)
- other value rfu */
std::string increase(unsigned char efId=0, unsigned short size=1) {
CRYPTOLOG("log");
std::string data;
data.resize(2);
data[0] = size>>8;
data[1] = size&0xff;
return check(send(0x80, 0x32, 0x00, efId, data)); // 0x08 or 0x80?
// old code was 0x80, manual says 0x08 (manual exactly says: "8xh")
}
//! Manufacturer use only
void initializeEeprom() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Manufacturer use only
void initializeEnd() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Performs cryptographic algorithms for authentication
void internalAuthenticate() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Activates a package
void loadExecutable() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Opens or closes a logical channel
std::string manageChannel(unsigned char mode, unsigned char channelId) {
CRYPTOLOG("log");
return check(send(0x00, 0x70, mode, channelId));
}
//! Loads a CSE (RESTORE) or sets a component of the CSE (SET)
//! and/or sets an extended headerlist
void manageSecureEnvironment() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Performs a challenge/response test and a subsequent
//! MAC/Signature calculation and, depending on the input, a
//! session key derivation.
void mutualAuthenticate() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Performs a cryptographic operation
void performSecurityOperation() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Controls the command sequence transactions
void performTransactonOperation() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Personalizer use only
void personalize() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Changes from life cycle phase ADMINISTRATION to OPERATIONAL
//! and vice versa.
/*! The command changes theglobal life cycle phase of the smart
card from ADMINISTRATION to OPERATIONAL. This change is
permanently valid for all DFs and will be stored in EEPROM,
i.e. this life cycle phase is still valid after a reset of
the smart card.
The change from life cycle phase OPERATIONAL to
ADMINISTRATION, however, is only temporary and valid only
for the current DF (current life cycle phase) The life cycle
phase is set back to OPERATIONAL when a context change
(selection of a different DF) occurs or after a reset of the
smart card. If the current life cycle phase shall be changed
to ADMINISTRATION in several DFs, a PHASE CONTROL command
has to be issued each time after the selection of one of
those DFs. The temporary change to ADMINISTRATION in one DF
can also be undone (without reset) with another PHASE
CONTROL command.
@prereq Changing from ADMINISTRATION to OPERATIONAL is not
protected. Changing from OPERATIONAL to
ADMINISTRATION is controlled by the right referenced
by the current DFs AC LCYCLE.
@prereq The command can be executed in the life cycle phases
ADMINISTRATION and OPERATIONAL. */
void phaseControl() {
CRYPTOLOG("log");
check(send(0x80, 0x10, 0x00, 0x00));
}
//! Installs / administrates / overwrites different objects
void putData() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Read a BINARY file
std::string readBinary(std::string path="", unsigned short offset = 0) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
if (path.size()) select(path);
return check(send(0x00, 0xB0, offset>>8, offset&0xFF));
}
enum ReadRecordMode {
FIRST_RECORD =0,
LAST_RECORD = 1,
NEXT_RECORD = 2,
PREVIOUS_RECORD = 3,
CURRENT_RECORD = 4,
ABSOLUTE_RECORD = 4
};
//! Read a record oriented file
std::string readRecord(unsigned char record = 0,
unsigned char sfi = 0,
ReadRecordMode mode = ABSOLUTE_RECORD) {
CRYPTOLOG("log");
return check(send(0x00, 0xB2, record, (sfi&0xF8)|mode));
}
/// Read all records from a record oriented file
BerValues readBerFile(std::string path="") {
CRYPTOLOG("log");
BerValues content;
pcsc::Connection::Reader::Transaction lock(_reader);
if (path.size()) select(path);
while (true) {
std::string res(send(0x00, 0xB2, 0, NEXT_RECORD));
if (cardos::Commands::retCode(res)!=0x9000) break;
content += retData(res).substr(2);
}
return content;
}
//! Resets the error counter of a BS object to its maximum
void resetRetryCounter() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Resets the security status of the current DF
void resetSecurityCounter() {
CRYPTOLOG("log");
assert(!"not implemented");
}
enum FileSelectionMode {
DF_OR_EF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x00,
DF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x01,
EF_DIRECTLY_BELOW_CURRENT_DF_USING_FILE_ID = 0x02,
PARENT_DF_OF_CURRENT_DF = 0x03,
DF_BY_NAME = 0x04,
DF_OR_EF_USING_PATH_FROM_MF = 0x08,
DF_OR_EF_USING_PATH_FROM_CURRENT_DF = 0x09
};
enum FileSelectionReturn {
RETURN_FCI_DATA = 0x00,
RETURN_FCP_DATA = 0x04,
RETURN_NOTHING = 0x0C
};
//! Selects a file
BerValues selectFile(std::string file,
FileSelectionMode mode
= DF_OR_EF_USING_PATH_FROM_MF,
FileSelectionReturn ret
= RETURN_NOTHING) {
CRYPTOLOG("log");
return BerValues(check(send(0x00, 0xA4, mode, ret, file)));
}
//! Sets the so-called Data_Field_Length parameter relevant for
//! the effective Command Data Field Length / Response Data
//! Field Length.
void setDataFieldLength() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Enables or disables the usage of an existing transaction buffer
void setTransactionState() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Signs a hashed input using a decryption key with the RSA_SIG
//! algorithm.
void signByDecryptionKey() {
CRYPTOLOG("log");
assert(!"not implemented");
}
//! Uninstalls a package of the smart card
void uninstallPackage() {
CRYPTOLOG("log");
assert(!"not implemented");
}
// same as readBinary
// /// Read the previously file from smart card
// std::string readBinFile() {
// CRYPTOLOG("log");
// return check(send(0x00, 0xB0, 0x00, 0x00));
// }
//! Updates a BINARY file
void updateBinary(std::string data, unsigned short offset=0) {
CRYPTOLOG("log");
check(send(0x00, 0xD6, offset>>8, offset&0xFF, data));
}
//! Overwrites an existing record.
std::string updateRecord(std::string data,
unsigned char record = 0,
unsigned char sfi = 0,
ReadRecordMode mode = ABSOLUTE_RECORD) {
CRYPTOLOG("log");
return check(send(0x00, 0xDC, record, (sfi&0xF8)|mode, data));
}
enum VerifyMode {
SEARCH_IN_MF = 0,
SEARCH_FROM_DF = 0x80
};
//! Performs a PIN test (CHV test)
void verify(std::string pin, unsigned char id,
VerifyMode mode = SEARCH_FROM_DF) {
CRYPTOLOG("log");
check(send(0x00, 0x20, 0x00, id|mode, pin));
}
//! Performs a PIN test (CHV test)
/*! @return number of remaining PIN retries or -1 if PIN is locked */
int verify(unsigned char id, VerifyMode mode = SEARCH_FROM_DF) {
CRYPTOLOG("log");
std::string res(send(0x00, 0x20, 0x00, id|mode));
unsigned int value((((unsigned int)(unsigned char)res[0])*256)
+((unsigned int)(unsigned char)res[1]));
if ((value&0x63C0)==0x63C0) return value&0x0F;
return -1;
}
//@}
//------------------------------------------------------------------------
/// @name Files and IDs on a Smart Card
//@{
/// Path to MF
std::string mf() {
return crypto::hexToBin("3f00");
}
/// Path to PKCS#15
std::string pkcs15() {
return crypto::hexToBin("5015");
}
/// Path to SigG (Signaturgesetz)
std::string sigG() {
return crypto::hexToBin("1fff");
}
/// ID of transport PIN
unsigned char transportPin() {
return 0x71;
}
/// ID of PKCS#15 user PIN
unsigned char pkcs15Pin() {
return 0x01;
}
/// ID of SigG (Signaturgesetz) secure PIN
unsigned char sigGPin() {
return 0x01;
}
/// ID of PUK to unlock PKCS#15 user PIN
unsigned char puk() {
return 0x02;
}
//@}
//------------------------------------------------------------------------
/// @name High Level Smart Card Access
//@{
/// Logon with transport PIN
void logonTransport(std::string pin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigG();
logon(transportPin(), pin);
}
/// Logon with SigG (Signaturgesetz) secure PIN
void logonSigG(std::string pin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigG();
logon(sigGPin(), pin);
}
/// Logon with PKCS#15 user PUK to unlock user PIN
void logonPuk(std::string pin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectMF();
logon(puk(), pin);
}
/// Logon with PKCS#15 user PIN
void logonPkcs15(std::string pin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectMF();
logon(pkcs15Pin(), pin);
}
/// Change SigG (Signaturgesetz) secure PIN
/** If smart card is in transport state, @p oldPin must be
transport PIN and then the card is unlocked and the
transport state is unset. */
void changeSigGPin(std::string newPin, std::string oldPin) {
CRYPTOLOG("log");
if (transportState()) { // first time use, reset transport state
pcsc::Connection::Reader::Transaction lock(_reader);
logonTransport(oldPin);
changeReferenceData(sigGPin(), newPin);
unsetTransportState();
} else { // ordinary PIN change
pcsc::Connection::Reader::Transaction lock(_reader);
logonSigG(oldPin);
selectSigG();
changeReferenceData(sigGPin(), newPin, oldPin);
}
}
/// Change PKCS#15 user PIN
void changePkcs15Pin(std::string newPin, std::string oldPin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectMF();
changeReferenceData(pkcs15Pin(), newPin, oldPin);
}
/// Change PKCS#15 user PIN
/** To keep all PINs synchronized: Detect if it is in transport
state, if so, old PIN must be transport PIN. in any case,
change PIN on PKCS#15 and SigG from the same old PIN to the
same new PIN. */
void changePins(std::string newPin, std::string oldPin) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
if (transportState()) {
changeSigGPin(newPin, oldPin);
changePkcs15Pin(newPin, oldPin);
} else {
if (pkcs15PinRetries()!=-1) changePkcs15Pin(newPin, oldPin);
try {
if (sigGPinRetries()!=-1) changeSigGPin(newPin, oldPin);
} catch (...) {
// undo PKCS#15 PIN change
if (pkcs15PinRetries()!=-1) changePkcs15Pin(oldPin, newPin);
throw;
}
}
}
/// Select a file in the PKCS#15 part on the smart card
void selectPkcs15File(std::string file) {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f005015"+file)));
}
/// Select a file in the SigG (Signaturgesetz) part on the smart card
void selectSigGFile(std::string file) {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f001fff"+file)));
}
/// Select a file in the MF part on the smart card
void selectMfFile(std::string file) {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f00"+file)));
}
/// Generic select file
void select(std::string path) {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin(path)));
}
/// Select the PKCS#15 part on the smart card
void selectPkcs15() {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f005015")));
}
/// Select the SigG (Signaturgesetz) part on the smart card
void selectSigG() {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f001fff")));
}
/// Select the MFpart on the smart card
void selectMF() {
CRYPTOLOG("log");
check(send(0x00, 0xA4, 0x00, 0x0C));
}
/// @return binary serial number
std::string serial() {
CRYPTOLOG("log");
return getDataChipProduction().serial;
}
/// @return @c true if card is still in transport state
bool transportState() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigGFile("fe15");
return std::string(4, '\0')==readRecord();
}
/// Mark card as initiakized and no more in transport state
void unsetTransportState() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigGFile("fe15");
increase();
}
/*! @return number of remaining transport PIN retries or -1 if locked */
int transportPinRetries() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigG();
return verify(transportPin());
}
/*! @return number of remaining PKCS#15 PIN retries or -1 if locked */
int pkcs15PinRetries() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectMF();
return verify(pkcs15Pin());
}
/*! @return number of remaining SigG PIN retries or -1 if locked */
int sigGPinRetries() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectSigG();
return verify(sigGPin());
}
/*! @return number of remaining PUK retries or -1 if locked */
int pukRetries() {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
selectMF();
return verify(puk());
}
//@}
protected:
/// @name More Generic Internal Auxiliary Methods
//@{
void logon(unsigned char id, std::string pin) {
verify(pin, id);
}
int pinStatus(unsigned char id) {
std::string res(send(0x00, 0x20, 0x00, id|0x80));
unsigned int value((((unsigned int)(unsigned char)res[0])*256)
+((unsigned int)(unsigned char)res[1]));
switch (value) {
case 0x6983: CARDOS_LOG("LOCKED"); return -1;
case 0x63Ca: CARDOS_LOG("TEN"); return 10;
case 0x63C9: CARDOS_LOG("NINE"); return 9;
case 0x63C8: CARDOS_LOG("EIGHT"); return 8;
case 0x63C7: CARDOS_LOG("SEVEN"); return 7;
case 0x63C6: CARDOS_LOG("SIX"); return 6;
case 0x63C5: CARDOS_LOG("FIVE"); return 5;
case 0x63C4: CARDOS_LOG("FOUR"); return 4;
case 0x63C3: CARDOS_LOG("THREE"); return 3;
case 0x63C2: CARDOS_LOG("TWO"); return 2;
case 0x63C1: CARDOS_LOG("ONE"); return 1;
case 0x63C0: CARDOS_LOG("ZERO"); return 0;
case 0x6300: CARDOS_LOG("LOCKED"); return -1;
default: CARDOS_LOG("UNKNOWN"); return -1;
}
}
std::string sendAPDU(std::string data) {
if (data.size()==4)
return send(data[0], data[1], data[2], data[3]);
else if (data.size()>4)
return send(data[0], data[1], data[2], data[3], data.substr(4));
else
throw runtime_error("wrong APDU pass at least 4 bytes in hex");
}
//! @return error text of APDU result string
static std::string error(std::string res) {
return error(retCode(res));
}
//! @return error text of return code evaluated by @ref retCode
static std::string error(int ret) {
switch (ret) {
case 0x6283:
return "File is deactivated (see DEACTIVATE FILE command)";
case 0x6285:
return "File is terminated";
case 0x6300:
return "Authentication failed";
case 0x6581:
return "EEPROM error; command aborted";
case 0x6700:
return "LC invalid";
case 0x6881:
return "Logical channel not supported";
case 0x6882:
return "SM mode not supported";
case 0x6884:
return "Chaining Error";
case 0x6981:
return "Command cannot be used for file structure";
case 0x6982:
return "Required access right not granted";
case 0x6983:
return "BS object blocked";
case 0x6984:
return "BS object has invalid format";
case 0x6985:
return
"Conditions of use not satisfied; no random number available";
case 0x6986:
return "no current EF selected";
case 0x6987:
return "Key object for SM not found";
case 0x6988:
return "Key object used for SM has invalid format";
case 0x6a80:
return "Invalid parameters in data field";
case 0x6a81:
return "Function / mode not supported";
case 0x6a82:
return "File not found";
case 0x6a83:
return "Record / object not found";
case 0x6a84:
return
"Not enough memory in file / in file system (e.g. CREATE FILE)"
" available";
case 0x6a85:
return "LC does not fit the TLV structure of the data field";
case 0x6a86:
return "P1 / P2 invalid";
case 0x6a87:
return "LC does not fit P1 /P2";
case 0x6a88:
return "Object not found (GET DATA)";
case 0x6a89:
return "File already exists";
case 0x6a8a:
return "DF name already exists";
case 0x6c00:
return "LE does not fit the data to be sent (e.g. SM)";
case 0x6d00:
return "INS invalid";
case 0x6e00:
return "CLA invalid (Hi nibble)";
case 0x6f00:
return
"Technical Error: "
" 1 Attempt to create more than 254 records in a file. "
" 2 Package uses SDK version which is not compatible to"
" API version "
" 3 Package contains invalid statements (LOAD EXECUTABLE)";
case 0x6f81:
return "File is invalidated because of checksum error (prop.)";
case 0x6f82:
return "Not enough memory available in XRAM";
case 0x6f83:
return
"Transaction error (i.e. command must not be used in a"
" transaction)";
case 0x6f84:
return "General protection fault (prop.)";
case 0x6f85:
return "Internal failure of PK-API (e.g. wrong CCMS format)";
case 0x6f86:
return "Key Object not found";
case 0x6f87:
return
"Internal hardware attack detected, change to life cycle death";
case 0x6f88:
return "Transaction buffer too small";
case 0x6fff:
return
"Internal assertion (invalid internal error) "
"This error is no runtime error but an internal error which can"
" occur because of a programming error only.";
case 0x9000:
return "Command executed correctly";
case 0x9001:
return
"Command executed correctly; EEPROM weakness detected"
" (EEPROM written with second trial; the EEPROM area"
" overwritten has a limited life time only.)";
case 0x9850:
return "Overflow through INCREASE / underflow through DECREASE";
case -1:
return "No return code received";
default:
std::stringstream ss;
if ((ret&&0xff00)==0x6100)
ss<<(ret&&0xff)<<" bytes of response data can be received"
<<" with GET RESPONSE (only T=0 transmission protocol)";
else
ss<<"Unknown CardOS error code: 0x"<<std::hex<<ret;
return ss.str();
}
}
static unsigned int retCode(const std::string& res) {
if (res.size()>=2)
return ((((unsigned int)(unsigned char)res[res.size()-2])*256)
+((unsigned int)(unsigned char)res[res.size()-1]));
else
return -1;
}
static std::string retData(const std::string& res) {
if (res.size()>=2)
return res.substr(0, res.size()-2);
else
return std::string();
}
std::string check(std::string res) {
unsigned int value(retCode(res));
if (value==0x6300) throw wrong_pin(error(value));
if (value!=0x9000) throw runtime_error(error(value));
return retData(res);
}
std::string logResult(const std::string& result) {
std::string txt;
for (std::string::const_iterator it(result.begin());
it!=result.end(); ++it)
txt += isprint(*it)?*it:'.';
if (result.size()>=2) {
CARDOS_LOG(error(result)<<std::endl
<<"RC: "
<<crypto::binToHex(result.substr(result.size()-2, 2)));
if (result.size()>2) {
CARDOS_LOG("Data: "
<<crypto::binToHex(result.substr(0, result.size()-2)));
CARDOS_LOG("Text: "
<<txt.substr(0, result.size()-2));
}
} else {
CARDOS_LOG("RC: ?? Data: "<<crypto::binToHex(result)<<" Text: "<<txt);
}
return result;
}
std::string send(char cla, char ins, char p1, char p2, std::string lc) {
if (!_reader) throw runtime_error("no reader selected");
CARDOS_LOG("APDU: cla="<<crypto::binToHex(cla)
<<" ins="<<crypto::binToHex(ins)
<<" p1="<<crypto::binToHex(p1)
<<" p2="<<crypto::binToHex(p2)
<<" lc="<<crypto::binToHex(lc));
return logResult(_reader->transmit(cla, ins, p1, p2, lc));
}
std::string send(char cla, char ins, char p1, char p2) {
if (!_reader) throw runtime_error("no reader selected");
CARDOS_LOG("APDU: cla="<<crypto::binToHex(cla)
<<" ins="<<crypto::binToHex(ins)
<<" p1="<<crypto::binToHex(p1)
<<" p2="<<crypto::binToHex(p2));
return logResult(_reader->transmit(cla, ins, p1, p2));
}
//@}
protected:
mrw::Shared<pcsc::Connection::Reader> _reader;
};
//@}
}
//@}
#endif