added basic suisse id functions; refs #28

This commit is contained in:
Marc Wäckerlin
2013-10-21 07:10:46 +00:00
parent 1de9676582
commit cbef3c6e8c
10 changed files with 2646 additions and 285 deletions

1649
src/cardos.hxx Normal file
View File

@@ -0,0 +1,1649 @@
/*! @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::cout<<X<<std::endl
#endif
namespace cardos {
/// @defgroup gcardos C++ Access to Siemens CardOS V4.4
/** Implements APDUs for accessing Siemens CardOS V4.4 smartcards. */
//@{
/// @defgroup cardosexception CardOS Exceptions
/// @defgroup cardostypes CardOS Types
/// @defgroup cardoslib CardOS Library
/// @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\n"+s) {}
};
//----------------------------------------------------------------------------
class user_pin_locked: public exception {
public:
user_pin_locked(const std::string& s) throw():
exception("user pin locked\n"+s) {}
};
//----------------------------------------------------------------------------
class wrong_puk: public exception {
public:
wrong_puk(const std::string& s) throw(): exception("wrong puk\n"+s) {}
};
//----------------------------------------------------------------------------
class wrong_result: public exception {
public:
wrong_result(const std::string& reason, const std::string& data) throw():
exception("wrong result,\n"+reason+":\n"+crypto::hex(data)) {}
wrong_result(const std::string& reason) throw():
exception("wrong result,\n"+reason) {}
};
//----------------------------------------------------------------------------
class runtime_error: public exception {
public:
runtime_error(const std::string& reason, const std::string& data) throw():
exception("runtime error,\n"+reason+":\n"+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:\n"
+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:\n"+position
+"\nreason:\n"+reason) {
}
};
//----------------------------------------------------------------------------
class wrong_dataformat: public exception {
public:
wrong_dataformat(const std::string& data,
const std::string& reason) throw():
exception("wrong dataformat:\n"+crypto::hex(data)
+"\nreason:\n"+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") {}
};
//@}
//============================================================================
/// @addtogroup cardostypes
//@{
/// Represents the Smart Card's Tag Length Value Encoded File Format.
class TagLengthValue {
public:
enum Tag {
DIRECTORY_ENTRY = 0x6F,
FILE_TYPE = 0x82,
FILE_IDENTIFIER = 0x86,
NEXT_OFFSET = 0x8A,
VERSION_INFO_CONTAINER = 0xff,
VERSION_INFO_STRING = 0x01
};
public:
/// Initialize from a File's Content
TagLengthValue(const std::string& content):
_size(0), _content(content) {
if (_content.size()<2)
throw wrong_dataformat(_content, "not a TLV, too small");
for (std::string::size_type pos(0); pos!=_content.size();
pos+=_content[pos+1]+2) {
++_size;
if (pos+2>_content.size())
throw wrong_dataformat(_content, "not a TLV, wrong size");
}
}
/// Return RAW File Content
std::string raw() {
return _content;
}
TagLengthValue at(unsigned int i) {
if (i>=_size) throw array_range(i, _size);
std::string::size_type pos(0);
for (unsigned int j(0); j!=i; ++j) {
pos+=_content[pos+1]+2;
}
return TagLengthValue(_content.substr(pos, _content[pos+1]+2));
}
std::string str() {
switch ((int)_content[0]) {
case VERSION_INFO_STRING:
return _content.substr(2, (int)_content[1]);
default:
throw wrong_dataformat(_content, "not a string");
}
}
TagLengthValue tlv() {
switch ((int)_content[0]) {
case VERSION_INFO_CONTAINER:
return TagLengthValue(_content.substr(2, (int)_content[1]));
default:
throw wrong_dataformat(_content, "not a container");
}
}
private:
unsigned int _size;
std::string _content;
};
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,
};
public:
BerValue(std::vector<BerValue> sequence):
_tag(PRIVATE|CONSTRUCTED|SEQUENCE),
_length(0),
_sequence(sequence) {
}
BerValue(const std::string& content) {
if (content.size()<2)
throw wrong_dataformat(content, "not a BER, header size too small: \""
+crypto::binToHex(content)+"\"");
_tag = content[0];
_length = content[1];
_value = content.substr(2, _length);
if (_length+2>content.size())
throw wrong_dataformat(content, "not a BER, content size too"
" small: \""+crypto::binToHex(_value)+"\"");
if (tagType()==END_OF_CONTENT) return; // done
if (isContainer()) {
for (std::string::size_type pos(0); pos+1<_value.size();
pos+=2+_value[pos+1]) { // recursively extract value
_sequence.push_back(BerValue(_value.substr(pos, 2+_value[pos+1])));
}
}
}
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];
}
std::string value() {
return _value;
}
unsigned long toULong() {
unsigned long res(0);
for (std::string::reverse_iterator it(_value.rbegin());
it!=_value.rend(); ++it)
(res<<=8)+=(unsigned)*it;
return res;
}
std::string print(int indent=0, int indentStep = 4) {
std::stringstream ss;
ss<<std::string(indent*indentStep, ' ')<<'[';
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;
unsigned char _length;
std::string _value;
std::vector<BerValue> _sequence;
// BerValue(const std::string& content) {
// if (content.size()<2 ||
// content.size()<2+(std::string::size_type)content[1])
// throw wrong_dataformat(content, "not a BER, value too small");
// if (content.size()>2+(std::string::size_type)content[1]) {
// _tag = UNIVERSAL|CONSTRUCTED|SEQUENCE;
// _length = content.size()-2;
// _value = content.substr(2, _length);
// for (std::string::size_type pos(0); pos!=content.size();
// pos+=content[pos+1]+2) {
// if (pos+2>content.size() ||
// (std::string::size_type)content[pos+1]+2>content.size())
// throw wrong_dataformat(content, "not a BER, wrong size");
// _sequence.push_back
// (BerValue(content.substr(pos, content[pos+1]+2)));
// }
// } else {
// _tag = content[0];
// _length = content[1];
// _value = content.substr(2, _length);
// if (isContainer()) {
// for (std::string::size_type pos(2); pos!=content.size();
// pos+=content[pos+1]+2) {
// if (pos+2>content.size() ||
// (std::string::size_type)content[pos+1]+2>content.size())
// throw wrong_dataformat(content, "not a BER, wrong size");
// _sequence.push_back
// (BerValue(content.substr(pos, content[pos+1]+2)));
// }
// } else {
// _value = content.substr(2, _length);
// }
// }
// }
};
/// Store a sequence of BerValue
class BerValues: public std::vector<BerValue> {
public:
BerValues& operator=(const std::string& content) {
clear();
push_back(BerValue(content));
return *this;
}
BerValues& operator+=(const BerValue& value) {
push_back(value);
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.
This class does not do any transaction handling. Please handle
transactions on a higher level, when you access these
methods. */
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() {
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) {
if (size>0x7FFF) throw std::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) {
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) {
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 cards
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>xxh…xxh</td><td>80h 88h Mode 00h</td><td>xxh</td>
<td>22h…22h</td><td>xxh</td><td>xxh…xxh</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) {
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() {
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()) {
check(send(0x00, 0x24, oldData.size()?0x00:0x01, id, oldData+newData));
}
//! Changes the data of a system key to the new key data
//! provided with the command.
void changeSystemKey() {
assert(!"not implemented");
}
//! Creates a file (only EF or DF)
void createFile(BerValue) {
// check(send(0x00, 0xE0, 0x00, 0x00, BerValue(0x62h,
// data).raw()));
assert(!"not implemented");
}
//! Deactivates a file or a file tree
void deactivateFile() {
assert(!"not implemented");
}
//! Decreases a record value
void decrease() {
assert(!"not implemented");
}
//! Deletes a file (DF or EF)
void deleteFile() {
assert(!"not implemented");
}
enum FileTypes {DF=0x00, EF=0x01, DF_EF=0x02};
//! Reads file information of EFs and/or DFs in the current DF
BerValue directory(FileTypes types, unsigned char offset=0) {
return BerValue(check(send(0x80, 0x16, types, offset)));
}
//! Enables an already loaded and activated but disabled license package.
void enablePackage() {
assert(!"not implemented");
}
//! Erases the file system in the EEPROM.
void eraseFiles() {
assert(!"not implemented");
}
//! Performs a challenge/response test
void externalAuthenticate() {
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() {
assert(!"not implemented");
}
//! Generates a key pair for the RSA/RSA2 algorithms within the card
void generateKeyPair() {
assert(!"not implemented");
}
//! Generates an internal random number (i.e. SC_Challenge)
void getChallange() {
assert(!"not implemented");
}
//! Reads system information
std::string getData(unsigned char mode) {
return check(send(0x00, 0xCA, 0x01, mode));
}
//! Product name, version, release date, copyright string
std::string getDataProductName() {
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() {
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) {
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) {
std::string data;
data.resize(2);
data[0] = size>>8;
data[1] = size&0xff;
return check(send(0x08, 0x32, 0x00, efId, data)); // 0x08 or 0x80?
// old code was 0x80, manual says 0x08 (manual exactly says: "8xh")
}
//! Manufacturer use only
void initializeEeprom() {
assert(!"not implemented");
}
//! Manufacturer use only
void initializeEnd() {
assert(!"not implemented");
}
//! Performs cryptographic algorithms for authentication
void internalAuthenticate() {
assert(!"not implemented");
}
//! Activates a package
void loadExecutable() {
assert(!"not implemented");
}
//! Opens or closes a logical channel
std::string manageChannel(unsigned char mode, unsigned char channelId) {
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() {
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() {
assert(!"not implemented");
}
//! Performs a cryptographic operation
void performSecurityOperation() {
assert(!"not implemented");
}
//! Controls the command sequence transactions
void performTransactonOperation() {
assert(!"not implemented");
}
//! Personalizer use only
void personalize() {
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() {
check(send(0x80, 0x10, 0x00, 0x00));
}
//! Installs / administrates / overwrites different objects
void putData() {
assert(!"not implemented");
}
//! Read a BINARY file
std::string readBinary(unsigned short offset = 0) {
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) {
return check(send(0x00, 0xB2, record, (sfi&0xF8)|mode));
}
/// Read all records from a record oriented file
BerValues readBerFile() {
BerValues content;
while (true) {
std::string res(send(0x00, 0xB2, 0, NEXT_RECORD));
if (cardos::Commands::retCode(res)!=0x9000) break;
content += BerValue(retData(res).substr(2));
}
return content;
}
//! Resets the error counter of a BS object to its maximum
void resetRetryCounter() {
assert(!"not implemented");
}
//! Resets the security status of the current DF
void resetSecurityCounter() {
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
BerValue selectFile(std::string file,
FileSelectionMode mode
= DF_OR_EF_USING_PATH_FROM_MF,
FileSelectionReturn ret
= RETURN_NOTHING) {
return BerValue(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() {
assert(!"not implemented");
}
//! Enables or disables the usage of an existing transaction buffer
void setTransactionState() {
assert(!"not implemented");
}
//! Signs a hashed input using a decryption key with the RSA_SIG
//! algorithm.
void signByDecryptionKey() {
assert(!"not implemented");
}
//! Uninstalls a package of the smart card
void uninstallPackage() {
assert(!"not implemented");
}
/// Read the previously file from smart card
std::string readBinFile() {
return check(send(0x00, 0xB0, 0x00, 0x00));
}
//! Updates a BINARY file
void updateBinary(std::string data, unsigned short offset=0) {
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) {
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) {
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) {
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) {
selectSigG();
logon(transportPin(), pin);
}
/// Logon with SigG (Signaturgesetz) secure PIN
void logonSigG(std::string pin) {
selectSigG();
logon(sigGPin(), pin);
}
/// Logon with PKCS#15 user PUK to unlock user PIN
void logonPuk(std::string pin) {
selectMF();
logon(puk(), pin);
}
/// Logon with PKCS#15 user PIN
void logonPkcs15(std::string pin) {
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) {
selectSigG();
if (transportState()) { // first time use, reset transport state
logonTransport(oldPin);
changeReferenceData(sigGPin(), newPin);
unsetTransportState();
} else { // ordinary PIN change
changeReferenceData(sigGPin(), newPin, oldPin);
}
}
/// Change PKCS#15 user PIN
void changePkcs15Pin(std::string newPin, std::string oldPin) {
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 changePin(std::string newPin, std::string oldPin) {
try {
changePkcs15Pin(newPin, oldPin);
try {
changeSigGPin(newPin, oldPin);
} catch (...) {
changePkcs15Pin(oldPin, newPin); // undo PKCS#15 PIN change
throw; // change SigG PIN failed
}
} catch (...) {
throw; // change PKCS#15 PIN failed
}
}
/// Select a file in the PKCS#15 part on the smart card
void selectPkcs15File(std::string file) {
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) {
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) {
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f00"+file)));
}
/// Select the PKCS#15 part on the smart card
void selectPkcs15() {
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f005015")));
}
/// Select the SigG (Signaturgesetz) part on the smart card
void selectSigG() {
check(send(0x00, 0xA4, 0x08, 0x0C, crypto::hexToBin("3f001fff")));
}
/// Select the MFpart on the smart card
void selectMF() {
check(send(0x00, 0xA4, 0x00, 0x0C));
}
/// @return binary serial number
std::string serial() {
return getDataChipProduction().serial;
}
/// @return @c true if card is still in transport state
bool transportState() {
selectSigGFile("fe15");
return std::string(4, '\0')==readBinary();
}
/// Mark card as initiakized and no more in transport state
void unsetTransportState() {
selectSigGFile("fe15");
increase();
}
/*! @return number of remaining transport PIN retries or -1 if locked */
int transportPinRetries() {
selectSigG();
return verify(transportPin());
}
/*! @return number of remaining PKCS#15 PIN retries or -1 if locked */
int pkcs15PinRetries() {
selectMF();
return verify(pkcs15Pin());
}
/*! @return number of remaining SigG PIN retries or -1 if locked */
int sigGPinRetries() {
selectSigG();
return verify(sigGPin());
}
/*! @return number of remaining PUK retries or -1 if locked */
int pukRetries() {
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 std::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:\n"
" 1 Attempt to create more than 254 records in a file.\n"
" 2 Package uses SDK version which is not compatible to"
" API version\n"
" 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)\n"
"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!=0x9000) throw std::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 std::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 std::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;
};
//@}
//============================================================================
class Access {
public:
enum TokenVersion {PZ2007, PZ2009, PZ2010};
private:
Access(); // not available, reader is required
public:
std::string serialNumber() {
return check(_reader->transmit(0x00, 0xCA, 0x01, 0x81),
"read serial number").substr(8, 8);
}
bool firstUse() {
#ifndef Q_OS_MAC
pcsc::Connection::Reader::Transaction lock(_reader);
#endif
// SigG Part:
// 1. select file "transport protection state" /1/ #236
check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C, "\x1F\xFF\xFE\x15", 4),
"select file \"transport protection state\"");
// 2. read PIN_T's current use counter value
std::string res(check(_reader->transmit(0x00, 0xB2, 0x00, 0x04),
"read PIN_T counter"));
if (res.size()!=6) throw wrong_result("cannot check for first use");
return res.substr(0, 4)==std::string("\0\0\0\0", 4); // uninitialized: 0
}
//! get file version info
/*! @return Content of file "Version Info" or "<unknown>" if
there's no such file */
void unlock(const std::string puk, const std::string pin,
bool force=false) {
// #ifndef Q_OS_MAC
// pcsc::Connection::Reader::Transaction lock(_reader);
// #endif
// check(_reader->transmit(0x00, 0xA4, 0x00, 0x0C), "select MF");
// try {
// check(_reader->transmit(0x00, 0x20, 0x00, 0x02, puk), "verify PUK");
// } catch (std::exception& e) {
// throw wrong_puk(std::string("verify user PUK failed: ")+e.what());
// }
// if (force) { // kill old dig sig
// while (checkDigSigPin().valid())
// try {
// check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C,
// "\x1F\xFF\xFE\x15", 4),
// "select SigG");
// check(_reader->transmit(0x00, 0x20, 0x00, 0x81, pin),
// "verify for correct old PIN in SigG");
// break; // ok, pin still vaild, user is a winner
// } catch (...) {} // normally ends up here, retry until broken
// } else {
// if (checkDigSigPin().valid()) try {
// check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C,
// "\x1F\xFF\xFE\x15", 4),
// "select SigG");
// check(_reader->transmit(0x00, 0x20, 0x00, 0x81, pin),
// "verify for correct old PIN in SigG");
// } catch (std::exception& e) {
// throw wrong_pin(std::string("verify SigG PIN failed: ")+e.what());
// }
// }
// check(_reader->transmit(0x00, 0xA4, 0x00, 0x0C), "select MF");
// check(_reader->transmit(0x00, 0x24, 0x01, 0x81, pin), "reset user PIN");
}
void changePin(const std::string oldPin, const std::string newPin) {
// #ifndef Q_OS_MAC
// pcsc::Connection::Reader::Transaction lock(_reader);
// #endif
// if (version()==PZ2007||firstUse()) {
// changePinSigG(oldPin, newPin);
// changePinPkcs15(oldPin, newPin);
// } else {
// if (checkUserPin().locked())
// throw user_pin_locked("pin change is not possible");
// changePinPkcs15(oldPin, newPin);
// if (!checkDigSigPin().locked())
// changePinSigG(oldPin, newPin);
// }
}
void changePinSigG(const std::string oldPin, const std::string newPin) {
// #ifndef Q_OS_MAC
// pcsc::Connection::Reader::Transaction lock(_reader);
// #endif
// bool first(firstUse());
// // select DF SigG /1/ #236
// check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C, "\x1F\xFF\xFE\x15", 4),
// "select DF SigG");
// try {
// if (first) // verify PIN_T /1/ #248 (optional!)
// check(_reader->transmit(0x00, 0x20, 0x00, 0xF1, oldPin),
// "verify PIN_T");
// else // verify PIN (not in transport state)
// check(_reader->transmit(0x00, 0x20, 0x00, 0x81, oldPin),
// "verify PIN");
// } catch (const std::exception& e) {
// throw wrong_pin(std::string("verify SigG-PIN failed: ")+e.what());
// }
// // change reference data SigG-PIN /1/ #125,81
// if (first)
// check(_reader->transmit(0x00, 0x24, 0x01, 0x81, newPin),
// "change reference data SigG-PIN first time");
// else
// check(_reader->transmit(0x00, 0x24, 0x00, 0x81, oldPin+newPin),
// "change reference data SigG-PIN");
// // mark pin as changed, no more transport state
// for (int i(0); true; ++i) try { // «bugfix» for freddy: multiple tries
// if (first)
// check(_reader->transmit(0x80, 0x32, 0x00, 0x00, "\x01", 1),
// "mark pin as changed");
// break; // jump out of the loop, all everything is ok
// } catch (...) { // retry up to 10 times
// if (i>=10) throw;
// }
}
void changePinPkcs15(const std::string oldPin, const std::string newPin) {
// #ifndef Q_OS_MAC
// pcsc::Connection::Reader::Transaction lock(_reader);
// #endif
// // select MF
// check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C, "\x3F\x00", 2),
// "select MF");
// try {
// // change reference data | Use PIN ID
// check(_reader->transmit(0x00, 0x24, 0x00, 0x01, oldPin+newPin),
// "set new PIN");
// } catch (const std::exception& e) {
// throw wrong_pin(std::string("verify PKCS#15-PIN failed: ")
// +e.what());
// }
// // select DF PKCS#15
// check(_reader->transmit(0x00, 0xA4, 0x08, 0x0C, "\x50\x15", 2),
// "select DF PKCS#15");
}
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;
}
private:
friend class CardOsTest;
//! ANSI Padding: Fill to 8 byte blocks with @c 0x00
static std::string ansiPadding(std::string data) {
while (data.size()%8!=0) data += (char)0x00;
return data;
}
//! ISO Padding: mark end with @c 0x80, then do @ref ansiPadding
static std::string isoPadding(std::string data) {
data += (char)0x80; // command+ISO_Padding_Byte(0x80)
return ansiPadding(data);
}
static std::string des3enc(const std::string& data,
const openssl::CBlock8& key1,
const openssl::CBlock8& key2) {
// initial vector is always 0
return openssl::des2edeCbcEnc(isoPadding(data), key1, key2);
}
// retail mac signature
static std::string sign(const std::string& data,
const openssl::CBlock8& key1,
const openssl::CBlock8& key2) {
// initial vector is 0 anyway...
openssl::CBlock8 ivec;
openssl::desCbcEnc(isoPadding(data), key1, ivec);
return openssl::desCbcEnc(openssl::desCbcDec(ivec, key2), key1);
}
const std::string& check(const std::string& res,
const std::string& position) const {
// static unsigned long SUCCESS(0x9000);
// unsigned long c(retCode(res));
// if (c!=SUCCESS) throw card_transmission_failed(position,
// Commands::error(c));
return res;
}
//========================================================================
private:
mrw::Shared<pcsc::Connection::Reader> _reader;
};
}
//@}
#endif

View File

@@ -11,6 +11,8 @@
#include <string>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <algorithm>
/*! @defgroup gcrypto Auxiliary Crypto-Functions */
//@{
@@ -65,6 +67,44 @@ namespace crypto {
else
return "\""+data+"\"";
}
//! Convert binary to readable hexadecimal
inline std::string binToHex(char c) {
std::stringstream ss;
ss<<std::hex<<std::setw(2)<<std::setfill('0')
<<(unsigned int)(unsigned char)(c);
return ss.str();
}
//! Convert binary to readable hexadecimal
inline std::string binToHex(const std::string& s) {
std::string result;
for (std::string::const_iterator c(s.begin()); c!=s.end(); ++c)
result += binToHex(*c);
return result;
}
//! Convert readable lowercase hexadecimal to binary
inline std::string hexToBin(std::string s) {
const static std::string HEX("0123456789abcdef");
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
if (s.size()%2)
throw std::runtime_error
("input string \""+s
+"\" must have an even number of hexadecimal numbers");
if (s.find_first_not_of(HEX)!=std::string::npos)
throw std::runtime_error
("input string \""+s+"\" has non hexadecimal value at position "
+((std::stringstream&)(std::stringstream()
<<s.find_first_not_of(HEX))).str());
std::string res;
for (std::string::const_iterator it(s.begin()); it!=s.end(); ++it) {
unsigned char c(HEX.find(*it));
if (++it!=s.end()) (c <<= 4) += HEX.find(*it);
res += std::string(1, (char)c);
}
return res;
}
}
//@}

View File

@@ -6,7 +6,7 @@
## 45678901234567890123456789012345678901234567890123456789012345678901234567890
include_HEADERS = pcsc.hxx cryptoki.hxx openssl.hxx cryptaux.hxx \
openssl-engine.hxx
openssl-engine.hxx suisseid.hxx cardos.hxx
if !MINGW32
if MAC

View File

@@ -435,7 +435,7 @@ namespace pcsc {
check(SCardConnect(_connection->id(), strconv(name).c_str(),
mode, protocol,
&_id, &_protocol),
"connect smartcard \""+name);
"connect smartcard \""+name+"\"");
}
//! forbidden

200
src/suisseid.hxx Normal file
View File

@@ -0,0 +1,200 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef __SUISSEID_HXX__
#define __SUISSEID_HXX__
#include <cardos.hxx>
#include <cryptoki.hxx>
#include <pcsc.hxx>
#include <mrw/vector.hxx>
#include <mrw/shared.hxx>
/*! @defgroup gsuisseid C+ Wrapper to access SuisseID smart cards
This library allows access to the Swiss digital identity cards
(SuisseID).
You need to include @ref suisseid.hxx, then start with class @ref
suisseid::Scanner to scan for a list of SuisseID cards on the system.
@see http://www.suisseid.ch
@see http://postsuisseid.ch */
//@{
namespace suisseid {
//! Represents a SuisseID Card
/*! This is the parent class for special classes for the respecive
SuisseID providers. */
class Card: public cardos::Commands {
public:
enum Status {
TRANSPORT
};
public:
Card(mrw::Shared<pcsc::Connection::Reader> reader,
mrw::Shared<cryptoki::Slot> slot):
cardos::Commands(reader),
_slot(slot) {
}
virtual ~Card() {}
virtual unsigned int minimalPinLength() = 0;
virtual unsigned int maximalPinLength() = 0;
//! Name of the token/slot
const std::string& name() {
return _reader->name;
}
/// Version of the card
virtual std::string version() {
return "<unknown>";
}
private:
mrw::Shared<cryptoki::Slot> _slot;
};
//! Instance of a Post SuisseID smartcard.
/*! A SuisseID card issued by Swiss Post.
@see http://postsuisseid.ch */
class PostSuisseID: public Card {
public:
PostSuisseID(mrw::Shared<pcsc::Connection::Reader> reader,
mrw::Shared<cryptoki::Slot> slot):
Card(reader, slot), _minPinLen(0), _maxPinLen(-1) {
}
virtual unsigned int minimalPinLength() {
if (_minPinLen==0) evaluatePinLengths();
return _minPinLen;
}
virtual unsigned int maximalPinLength() {
if (_maxPinLen==-1) evaluatePinLengths();
return _maxPinLen;
}
std::string version() {
if (_version.size()) return _version; // cache the version
pcsc::Connection::Reader::Transaction lock(_reader);
try {
selectMfFile("5649");
return _version = cardos::BerValue(readBinary())[0].value();
} catch (...) {
return _version = "<unknown>";
}
}
private:
void evaluatePinLengths() {
pcsc::Connection::Reader::Transaction lock(_reader);
selectPkcs15File("4408");
cardos::BerValues res(readBerFile());
for (cardos::BerValues::iterator it(res.begin()); it!=res.end(); ++it)
if ((*it)[0][0].value()=="PIN" ||
(*it)[0][0].value()=="Digital Signature PIN") {
if ((*it)[2][0][2].toULong()>_minPinLen)
_minPinLen = (*it)[2][0][2].toULong();
if ((*it)[2][0][4].toULong()<_maxPinLen)
_maxPinLen = (*it)[2][0][4].toULong();
}
}
private:
std::string _version; // version is cached
unsigned int _minPinLen; // minimal PIN length is cached
unsigned int _maxPinLen; // maximal PIN length is cached
};
//! List of cards, returned by @ref suisseid::Scanner::scan.
typedef std::vector<mrw::Shared<Card> > Cards;
//! Auxiliary SuisseID card manager.
/** Use this manager to scan your system for SuisseID cards.
Usage Example:
@code
#include <suisseid.hxx>
#include <iostream>
[...]
try {
suisseid::Cards cards(suisseid::Scanner().scan());
for (auto card(cards.begin()); card!=cards.end(); ++card)
std::cout<<"Found SuisseID: "<<(*card)->name()<<std::endl;
return 0;
} catch (std::exception& x) {
std::cerr<<"**** ERROR in "<<*argv<<": "<<x.what()<<std::endl;
return 1;
}
@endcode */
class Scanner {
public:
Scanner(const std::string& lib="libcvP11.so"):
_cryptoki(lib) {
}
Scanner(const pcsc::Connection& pcsc,
const std::string& lib="libcvP11.so"):
_pcsc(pcsc),
_cryptoki(lib) {
}
Scanner(const cryptoki::Library& cryptoki):
_cryptoki(cryptoki) {
}
Scanner(const pcsc::Connection& pcsc,
const cryptoki::Library& cryptoki):
_pcsc(pcsc),
_cryptoki(cryptoki) {
}
/// Scan for available known SuisseID cards on the system.
/** @return List of detected SuisseID smart cards. */
Cards scan() {
Cards res;
// By now, scan only for PostSuisseID; in future use factory pattern
pcsc::Connection::Strings readers
(_pcsc.getReadersWithAtr("4b53776973735369676e"));
for (pcsc::Connection::Strings::iterator reader(readers.begin());
reader!=readers.end(); ++reader) {
cryptoki::SlotList slots(_cryptoki.slotList(true, *reader));
if (slots.size()==1)
res.push_back(dynamic_cast<Card*>
(new PostSuisseID(_pcsc.reader(*reader), slots[0])));
}
return res;
}
private:
pcsc::Connection _pcsc;
cryptoki::Library _cryptoki;
};
}
//@}
#endif