added basic suisse id functions; refs #28
parent
1de9676582
commit
cbef3c6e8c
10 changed files with 2645 additions and 284 deletions
@ -0,0 +1,164 @@ |
||||
/*! @file
|
||||
|
||||
@id $Id$ |
||||
*/ |
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
// #include <pcsc.hxx>
|
||||
// #include <cryptaux.hxx>
|
||||
// #include <openssl.hxx>
|
||||
|
||||
#include <string> |
||||
#include <memory> |
||||
#include <cctype> |
||||
#include <stdexcept> |
||||
#include <sstream> |
||||
#include <iostream> |
||||
#include <iomanip> |
||||
|
||||
#define CARDOS_LOG(X) std::cout<<X<<std::endl |
||||
#include <cardos.hxx> |
||||
|
||||
class Commands: public cardos::Commands { |
||||
|
||||
public: |
||||
|
||||
void help() { |
||||
std::cout |
||||
<<"Commands: "<<std::endl |
||||
<<" COMMAND DATA PURPOSE"<<std::endl |
||||
<<" (h)elp Show this help"<<std::endl |
||||
<<" (q)quit Quit"<<std::endl |
||||
<<" (l)ist List all reader"<<std::endl |
||||
<<" (r)eader <num> Select reader by number"<<std::endl |
||||
<<" (s)erial Show token serial number"<<std::endl |
||||
<<" (a)pdu <hex> Send APDU hex data"<<std::endl |
||||
<<" (m)aster(f)ile Select master file"<<std::endl |
||||
<<" (p)kcs(15) Select PKCS#15 file"<<std::endl |
||||
<<" (s)ig(g) Select SigG file"<<std::endl |
||||
<<" (s)elect(f)ile <hex> Select file below master"<<std::endl |
||||
<<" (s)elect(p)15(f)ile <hex> Select file below PKCS#15"<<std::endl |
||||
<<" (s)elect(s)igg(f)ile <hex> Select file below SigG"<<std::endl |
||||
<<" (r)ead(b)in(f)ile Read selected binary file"<<std::endl |
||||
<<" (r)ead(a)bsolute(r)ecord Read absolute record"<<std::endl |
||||
<<" (r)ead(f)irst(r)ecord Read first record"<<std::endl |
||||
<<" (r)ead(n)ext(r)ecord Read next record"<<std::endl |
||||
<<" (p)in(s)status <id> Query status of PIN <id>"<<std::endl |
||||
<<" (l)ogon <id> <pin> logon to key with PIN"<<std::endl |
||||
<<" (l)ogon(p)15 <pin> logon with user PIN"<<std::endl |
||||
<<" (l)ogonpu(k) <pin> logon with user PUK"<<std::endl |
||||
<<" (l)ogon(s)igg <pin> logon with SigG PIN"<<std::endl |
||||
<<" (l)ogon(t)transport <pin> logon with transport PIN"<<std::endl |
||||
<<" (g)et(p)in(l)engths get PIN min max lengths"<<std::endl |
||||
<<"Note: \"(h)elp\" means: type \"help\" or simply \"h\""<<std::endl; |
||||
} |
||||
|
||||
int run() { |
||||
std::string cmd; |
||||
while (std::cout<<"cmd> ", std::cin>>cmd) try { |
||||
if (cmd=="help"||cmd=="h") help(); |
||||
else if (cmd=="quit"||cmd=="q") return 0; |
||||
else if (cmd=="list"||cmd=="l") listReader(); |
||||
else if (cmd=="reader"||cmd=="r") selectReader(); |
||||
else if (cmd=="serial"||cmd=="s") serial(); |
||||
else if (cmd=="apdu"||cmd=="a") sendAPDU(apdu()); |
||||
else if (cmd=="masterfile"||cmd=="mf") selectMF(); |
||||
else if (cmd=="pkcs15"||cmd=="p15") selectPkcs15(); |
||||
else if (cmd=="sigg"||cmd=="sg") selectSigG(); |
||||
else if (cmd=="selectfile"||cmd=="sf") selectMfFile(apdu()); |
||||
else if (cmd=="selectp15file"||cmd=="spf") selectPkcs15File(apdu()); |
||||
else if (cmd=="selectsiggfile"||cmd=="ssf") selectSigGFile(apdu()); |
||||
else if (cmd=="readbinfile"||cmd=="rbf") _ber=readBinFile(); |
||||
else if (cmd=="readabsoluterecord"||cmd=="rar") _ber=readRecord(); |
||||
else if (cmd=="readfirstrecord"||cmd=="rfr") |
||||
_ber=readRecord(0, 0, FIRST_RECORD); |
||||
else if (cmd=="readnextrecord"||cmd=="rnr") |
||||
_ber=readRecord(0, 0, NEXT_RECORD); |
||||
else if (cmd=="pinstatus"||cmd=="ps") pinStatus(id()); |
||||
else if (cmd=="logon"||cmd=="l") logon(id(), pin()); |
||||
else if (cmd=="logonp15"||cmd=="lp") logonPkcs15(pin()); |
||||
else if (cmd=="logonppuk"||cmd=="lk") logonPuk(pin()); |
||||
else if (cmd=="logonsigg"||cmd=="ls") logonSigG(pin()); |
||||
else if (cmd=="logontransport"||cmd=="lt") logonTransport(pin()); |
||||
else if (cmd=="getpinlengths"||cmd=="gpl") getPinLengths(); |
||||
else if (cmd=="print"||cmd=="p") std::cout<<_ber.print()<<std::endl; |
||||
else help(); |
||||
} catch (const std::exception& e) { |
||||
std::cout<<"**** Error: "<<e.what()<<std::endl; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void listReader() { |
||||
_readers = _c.scan(); |
||||
std::cout<<"Found "<<_readers.size()<<" readers" |
||||
<<(_readers.size()?":":".")<<std::endl; |
||||
int i(0); |
||||
for (pcsc::Connection::Strings::const_iterator it(_readers.begin()); |
||||
it!=_readers.end(); ++it, ++i) |
||||
std::cout<<" "<<i<<". "<<*it<<std::endl; |
||||
} |
||||
|
||||
void selectReader() { |
||||
listReader(); |
||||
if (_readers.size()<0) return; |
||||
int num(-1); |
||||
if (std::cin>>num && num>=0 && num<_readers.size()) { |
||||
_reader = _c.reader(_readers[num]); |
||||
std::cout<<"Active Reader: "<<_readers[num]<<std::endl; |
||||
} else throw std::runtime_error("no valid reader selected"); |
||||
} |
||||
|
||||
void getPinLengths() { |
||||
selectPkcs15File("4408"); |
||||
_ber.clear(); |
||||
while (true) { |
||||
std::string res(send(0x00, 0xB2, 0, NEXT_RECORD)); |
||||
if (cardos::Commands::retCode(res)!=0x9000) break; |
||||
cardos::BerValue record(cardos::Commands::retData(res).substr(2)); |
||||
_ber += record; |
||||
std::cout<<record[0][0].value() |
||||
<<": len=" |
||||
<<record[2][0][2].toULong() |
||||
<<"-"<<record[2][0][4].toULong()<<std::endl; |
||||
} |
||||
} |
||||
|
||||
private: |
||||
|
||||
std::string apdu() { |
||||
std::string data; |
||||
if (!(std::cin>>data)) |
||||
throw std::runtime_error("please enter bytes in hex"); |
||||
return data; |
||||
} |
||||
|
||||
std::string pin() { |
||||
std::string data; |
||||
if (!(std::cin>>data)) |
||||
throw std::runtime_error("please enter pin"); |
||||
return data; |
||||
} |
||||
|
||||
unsigned char id() { |
||||
std::string data; |
||||
if (!(std::cin>>data) || data.size()!=2 || |
||||
data.find_first_not_of("0123456789abcdef")!=std::string::npos) |
||||
throw std::runtime_error("please enter one byte in hex"); |
||||
return crypto::hexToBin(data)[0]; |
||||
} |
||||
|
||||
private: |
||||
|
||||
pcsc::Connection _c; |
||||
pcsc::Connection::Strings _readers; |
||||
|
||||
cardos::BerValues _ber; |
||||
|
||||
}; |
||||
|
||||
int main(int, char**) { |
||||
std::cout<<"Type \"help\" for help."<<std::endl; |
||||
return Commands().run(); |
||||
} |
@ -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 file’s 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 MF’s 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 file’s 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 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>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 card’s 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 DF’s 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 |
@ -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 |
Loading…
Reference in new issue