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

597 lines
18 KiB

/*! @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++ library 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
*/
//@{
/*! @defgroup suisseidlib SuisseID Library */
/*! @defgroup suisseidtypes SuisseID C++ Types and Auxiliary */
/*! @defgroup suisseidconsts SuisseID C++ Constants */
/*! @defgroup suisseidexceptions SuisseID Exceptions */
/** @example suisse-id-demo.cxx
Usage of @ref gsuisseid This is a comprehensive example how you
can access a SuisseID and access to certificates on that card.
First implement a status cycle, here for @c std::cin and @c
std::cout as user interface in the @c suisse-id-demo.hxx header
file:
@include suisse-id-demo.hxx
Then instanciate and use this class from your code: */
//@}
/// @ref gsuisseid @copydoc gsuisseid
namespace suisseid {
/** @page init Initialize Card and Check Status
An idea on how the smart card status could be evaluated is the
following state machine:
@dot
digraph {
transportState
[URL="\ref cardos::Commands::transportState"];
transportPinRetries
[URL="\ref cardos::Commands::transportPinRetries"];
pkcs15PinRetries
[URL="\ref cardos::Commands::pkcs15PinRetries"];
sigGPinRetries
[URL="\ref cardos::Commands::sigGPinRetries"];
pukRetries
[URL="\ref cardos::Commands::pukRetries"];
changePin
[URL="\ref cardos::Commands::changePin"];
broken [label="replace card"];
start -> transportState;
transportPinRetries -> broken [label="-1"];
certsValid -> broken [label="false"];
pukRetries -> broken [label="-1"];
sigGPinRetries -> broken [label="-1"];
transportState -> transportPinRetries [label="true"];
transportPinRetries -> changePin [label=">-1"];
changePin -> transportState;
transportState -> haveCerts [label="false"];
haveCerts -> installCerts [label="false"];
installCerts -> transportState;
haveCerts -> certsValid [label="true"];
certsValid -> pkcs15PinRetries [label="true"];
pkcs15PinRetries -> sigGPinRetries [label=">-1"];
pkcs15PinRetries -> pukRetries [label="-1"];
pukRetries -> changePin [label=">-1"];
sigGPinRetries -> valid [label=">-1"];
{valid broken} -> end;
{rank=same; valid broken}
{rank=same; transportPinRetries haveCerts}
{rank=same; certsValid installCerts changePin}
}
@enddot
*/
//============================================================================
/*! @addtogroup suisseidexceptions */
//@{
//----------------------------------------------------------------------------
class exception: public std::exception {
public:
exception(const std::string& reason) throw():
_what("suisseid: "+reason) {
CRYPTOLOG("ERROR: "<<what());
}
~exception() throw() {}
const char* what() const throw() {
return _what.c_str();
}
private:
std::string _what;
};
//----------------------------------------------------------------------------
class no_certfound: public exception {
public:
no_certfound(const std::string& label) throw():
exception("no certificate with label \""+label+"\" found") {}
};
//----------------------------------------------------------------------------
class no_auth: public exception {
public:
no_auth() throw():
exception("no authentication certificate found") {}
};
//----------------------------------------------------------------------------
class no_digsig: public exception {
public:
no_digsig() throw():
exception("no digital signature certificate found") {}
};
//----------------------------------------------------------------------------
class slot_not_found: public exception {
public:
slot_not_found(const std::string& name) throw():
exception("matching cryptoki slot for "+name+" not found") {}
};
//@}
/*! @addtogroup suisseidtypes */
//@{
/// DER encoded binary certificate
typedef std::string Certificate;
/// List of DER encoded binary certificates
typedef std::vector<Certificate> Certificates;
//@}
/*! @addtogroup suisseidconsts */
//@{
/// Label of the key for digital signature certificate.
const std::string NON_REP = "SwissSign_nonRep ";
/// Label of the key for authentication certificate
const std::string DIG_SIG = "SwissSign_digSig ";
/// Label of the key required for getting the certificates
const std::string DATA_ENC = "SwissSign_dataEnc ";
//@}
/*! @addtogroup suisseidlib */
//@{
//! Represents a SuisseID Card
/*! This is the parent class for special classes for the respecive
SuisseID providers. */
class Card: public cardos::Commands {
public:
/// Status of the card's certificates
/** @note by now, only @c MISSING and @c VALID is supported */
enum CertStatus {
MISSING, ///< certificate is missing, needs initiatlization
EXPIRES_SOON, ///< certificate will soon expire, needs renewal
EXPIRED, ///< certificate is expired, needs new purchase
REVOKED, ///< certificate has been revoked and is invalid
VALID ///< certificate is valid
};
public:
/// Instanciation is done within suisseid::Scanner
/** Instance requires a connenction to the reader an a cryptoky
library. This is passes automatically when this class is
instanciated through suisseid::Scanner. */
Card(mrw::Shared<pcsc::Connection::Reader> reader,
const cryptoki::Library& cryptoki):
cardos::Commands(reader),
_cryptoki(cryptoki) {
}
virtual ~Card() {}
/// Find the matching cryptoki::Slot for further access
/** @throws slot_not_found if not exactly one matching slot exists */
cryptoki::Slot slot() {
cryptoki::SlotList slots(_cryptoki.slotList(true, name()));
if (slots.size()==1) return slots[0];
throw slot_not_found(name());
}
/// Get the reader, needed for example to lock a transaction
/** @begincode
pcsc::Connection::Reader::Transaction lock(card.reader());
[... do some low level stuff ...]
@endcode */
mrw::Shared<pcsc::Connection::Reader> reader() {
return _reader;
}
/// Minimum allowed PIN length for this card.
virtual unsigned int minimalPinLength() = 0;
/// Maximum allowed PIN length for this card.
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>";
}
/// Status of the certificates on the card
virtual CertStatus certStatus() {
return MISSING;
}
/// Starts and returns a cryptoki::Session.
cryptoki::Session session() {
return cryptoki::Session(slot());
}
/// Starts a cryptoki::Session and returns cryptoki::Session::Info.
cryptoki::Session::Info sessionInfo() {
return session().getsessioninfo();
}
/// @returns Certificates in DER format.
Certificates certificates() {
Certificates res;
cryptoki::ObjectList certs
(session().find(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)));
for (cryptoki::ObjectList::iterator cert(certs.begin());
cert!=certs.end(); ++cert)
res.push_back(cert->attribute(CKA_VALUE).value);
return res;
}
virtual Certificate certificate(const std::string& keylabel) {
cryptoki::ObjectList keys // find keys with digsig-label
(session().find(cryptoki::AttributeList()
<<cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_PUBLIC_KEY)
<<cryptoki::Attribute(CKA_LABEL, keylabel)));
for (cryptoki::ObjectList::iterator key(keys.begin());
key!=keys.end(); ++key) {
cryptoki::Attribute id(key->attribute(CKA_ID));
cryptoki::ObjectList certs
(session().find(cryptoki::AttributeList()
<<cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)
<<id));
for (cryptoki::ObjectList::iterator cert(certs.begin());
cert!=certs.end(); ++cert) { // return first matching cert
return cert->attribute(CKA_VALUE).value;
}
}
throw no_certfound(keylabel);
}
virtual Certificate authenticationCertificate() = 0;
virtual Certificate digitalSignatureCertificate() = 0;
protected:
cryptoki::Library _cryptoki;
};
//! Instance of a Post SuisseID smartcard.
/*! A SuisseID card issued by Swiss Post.
@see http://postsuisseid.ch */
class Post: public Card {
public:
/// @copydoc Card::Card
Post(mrw::Shared<pcsc::Connection::Reader> reader,
const cryptoki::Library& cryptoki):
Card(reader, cryptoki), _minPinLen(0), _maxPinLen(-1) {
}
virtual unsigned int minimalPinLength() {
if (_minPinLen==0) evaluatePinLengths();
return _minPinLen;
}
virtual unsigned int maximalPinLength() {
if (_maxPinLen==-1) evaluatePinLengths();
return _maxPinLen;
}
virtual std::string version() {
if (_version.size()) return _version; // cache the version
return versionFromMFFile("5649");
}
virtual CertStatus certStatus() {
cryptoki::ObjectList certs
(session().find(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)));
if (certs.size()==0) return MISSING;
return VALID;
}
virtual Certificate authenticationCertificate() try {
return certificate(DIG_SIG);
} catch (const no_certfound&) {
throw no_auth();
}
virtual Certificate digitalSignatureCertificate() try {
return certificate(NON_REP);
} catch (const no_certfound&) {
throw no_digsig();
}
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].string()=="PIN" ||
(*it)[0][0].string()=="Digital Signature PIN") {
if ((*it)[2][0][2].ulong()>_minPinLen)
_minPinLen = (*it)[2][0][2].ulong();
if ((*it)[2][0][4].ulong()<_maxPinLen)
_maxPinLen = (*it)[2][0][4].ulong();
}
}
std::string versionFromMFFile(const std::string& file) {
pcsc::Connection::Reader::Transaction lock(_reader);
try {
selectMfFile(file);
return _version = cardos::BerValues(readBinary())[0].string();
} catch (...) {
return _version = "<unknown>";
}
}
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() {
CRYPTOLOG("log");
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 Post(_pcsc.reader(*reader), _cryptoki)));
}
return res;
}
private:
pcsc::Connection _pcsc;
cryptoki::Library _cryptoki;
};
class StatusCycle {
public:
StatusCycle(mrw::Shared<Card> card, unsigned int maxRetries = 20):
_card(card), _maxRetries(maxRetries), _counter(0) {
}
~StatusCycle() {}
bool run() {
CRYPTOLOG("log");
_counter = 0;
return start();
}
protected:
mrw::Shared<Card> card() {
return _card;
}
/// @name Slots
//@{
struct PinPukChange {
std::string oldpin;
std::string newpin;
bool valid() {
return oldpin.size() && newpin.size();
}
};
virtual PinPukChange pinChange() {
CRYPTOLOG("log");
return PinPukChange();
}
virtual PinPukChange pinChangeTransportPin() {
CRYPTOLOG("log");
return pinChange();
}
virtual PinPukChange pinChangePuk() {
CRYPTOLOG("log");
return pinChange();
}
virtual void transportPinLocked() {
CRYPTOLOG("log");
}
virtual void pkcs15PinLocked() {
CRYPTOLOG("log");
}
virtual void sigGPinLocked() {
CRYPTOLOG("log");
}
virtual void pukLocked() {
CRYPTOLOG("log");
}
virtual void certsExpireSoon() {
CRYPTOLOG("log");
}
virtual void certsExpired() {
CRYPTOLOG("log");
}
virtual void certsRevoked() {
CRYPTOLOG("log");
}
/// install certificates on the card
/** @param bool whether to force reinstallation of existin certificates
@return @c true on success */
virtual bool installCerts(bool = true) {
CRYPTOLOG("log");
return false;
}
//@}
private:
bool start() {
CRYPTOLOG("log");
if (++_counter>_maxRetries) return false;
if (_card->transportState())
return unlockTransportState();
else
return checkPkcs15PinStatus();
}
bool unlockTransportState() {
CRYPTOLOG("log");
if (_card->transportPinRetries()<0)
return transportPinLocked(), false;
else
return changeTransportPin();
}
bool changeTransportPin() {
CRYPTOLOG("log");
PinPukChange pins(pinChangeTransportPin());
if (!pins.valid()) return false;
_card->changePins(pins.newpin, pins.oldpin);
_card->unsetTransportState();
return start();
}
bool checkPkcs15PinStatus() {
CRYPTOLOG("log");
if (_card->pkcs15PinRetries()<0)
return pkcs15PinLocked(), unlockPkcs15();
if (_card->pukRetries()<0)
return pukLocked(), false;
return checkCertificates();
}
bool checkCertificates() {
CRYPTOLOG("log");
switch (_card->certStatus()) {
case Card::MISSING: return installCerts() && start();
case Card::EXPIRES_SOON: certsExpireSoon(); break;
case Card::EXPIRED: return certsExpired(), false;
case Card::REVOKED: return certsRevoked(), false;
case Card::VALID: break;
}
return checkSigGPinStatus();
}
bool checkSigGPinStatus() {
CRYPTOLOG("log");
if (_card->sigGPinRetries()<0)
return sigGPinLocked(), false;
return true;
}
bool unlockPkcs15() {
CRYPTOLOG("log");
if (_card->pukRetries()<0)
return pukLocked(), false;
PinPukChange pins(pinChangePuk());
if (!pins.valid()) return false;
_card->changePins(pins.newpin, pins.oldpin);
return start();
}
mrw::Shared<Card> _card;
unsigned int _maxRetries;
unsigned int _counter;
};
//@}
}
#endif