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.
 
 
 
 

691 lines
22 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/checkcxx11.hxx>
#include <memory>
/*! @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::changePins"];
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) noexcept:
_what("suisseid: "+reason) {
CRYPTOLOG("ERROR: "<<what());
}
~exception() noexcept {}
const char* what() const noexcept {
return _what.c_str();
}
private:
std::string _what;
};
//----------------------------------------------------------------------------
class no_certfound: public exception {
public:
no_certfound(const std::string& label) noexcept:
exception("no certificate with label \""+label+"\" found") {}
};
//----------------------------------------------------------------------------
class no_auth: public exception {
public:
no_auth() noexcept:
exception("no authentication certificate found") {}
};
//----------------------------------------------------------------------------
class no_digsig: public exception {
public:
no_digsig() noexcept:
exception("no digital signature certificate found") {}
};
//----------------------------------------------------------------------------
class slot_not_found: public exception {
public:
slot_not_found(const std::string& name) noexcept:
exception("matching cryptoki slot for "+name+" not found") {}
};
//@}
/*! @addtogroup suisseidtypes */
//@{
/// DER encoded binary certificate
class Certificate: public std::string {
public:
Certificate(const std::string& v,
const std::string& l,
const std::string& s,
const cryptoki::Attribute& i):
std::string(v),
_label(l),
_subject(s),
_id(i) {
}
const std::string& label() {
return _label;
}
const std::string& subject() {
return _label;
}
const cryptoki::Attribute& id() {
return _id;
}
private:
std::string _label;
std::string _subject;
cryptoki::Attribute _id;
};
/// 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(std::shared_ptr<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
/** @code
pcsc::Connection::Reader::Transaction lock(card.reader());
[... do some low level stuff ...]
@endcode */
std::shared_ptr<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(Certificate(cert->attribute(CKA_VALUE).value,
cert->attribute(CKA_LABEL).value,
cert->attribute(CKA_SUBJECT).value,
cert->attribute(CKA_ID)));
return res;
}
virtual Certificate certificate(const std::string& keylabel) {
# ifdef __APPLE__
/*! @bug Work around Mac OSX 10.10 bug. On Mac OSX 10.10
there is a bug in PCSC: After a reconnect, first
transaction (SCardTransmit) fails with
SCARD_W_RESET_CARD (0x80100068). */
static int applebug(0);
# endif
CRYPTOLOG("get certificate for key "<<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)));
CRYPTOLOG("found "<<keys.size()<<" keys");
for (cryptoki::ObjectList::iterator key(keys.begin());
key!=keys.end(); ++key) {
cryptoki::Attribute id(key->attribute(CKA_ID));
CRYPTOLOG("get certs for key with id "<<crypto::hex(id.value));
cryptoki::ObjectList certs
(session().find(cryptoki::AttributeList()
<<cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)
<<id));
CRYPTOLOG("found "<<certs.size()<<" certificates");
# ifdef __APPLE__
/*! @bug Work around Mac OSX 10.10 bug. On Mac OSX 10.10
there is a bug in PCSC: After a reconnect, first
transaction (SCardTransmit) fails with
SCARD_W_RESET_CARD (0x80100068). */
try {
# endif
for (cryptoki::ObjectList::iterator cert(certs.begin());
cert!=certs.end(); ++cert) // return first matching cert
return Certificate(cert->attribute(CKA_VALUE).value,
cert->attribute(CKA_SUBJECT).value,
cert->attribute(CKA_LABEL).value,
id);
# ifdef __APPLE__
/*! @bug Work around Mac OSX 10.10 bug. On Mac OSX 10.10
there is a bug in PCSC: After a reconnect, first
transaction (SCardTransmit) fails with
SCARD_W_RESET_CARD (0x80100068). */
} catch (std::exception& x) {
if (++applebug>1) {
CRYPTOLOG("failed again after apple bug retry with "<<x.what());
applebug = 0; // failed again, give up, reset counter
throw;
} else {
CRYPTOLOG("failed with "<<x.what());
// try to fix Apple's Mac OS X 10.10 implementation bug
CRYPTOLOG("Mac OS X 10.10 implementation bug: "
"On Mac OSX 10.10 there is a bug in "
"PCSC: After a reconnect, first "
"transaction (SCardTransmit) fails "
"with SCARD_W_RESET_CARD (0x80100068). "
"Retry Nr. "<<applebug);
Certificate c(certificate(keylabel));
applebug = 0; // successful, reset counter
return c;
}
}
# endif
}
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:
enum CardType {
SUISSE_ID,
UNKNOWN
};
/// @copydoc Card::Card
Post(std::shared_ptr<pcsc::Connection::Reader> reader,
const cryptoki::Library& cryptoki):
Card(reader, cryptoki),
_type(UNKNOWN), _minPinLen(0), _maxPinLen((unsigned int)-1) {
}
virtual unsigned int minimalPinLength() {
if (_minPinLen==0) evaluatePinLengths();
return _minPinLen;
}
virtual unsigned int maximalPinLength() {
if (_maxPinLen==(unsigned int)-1) evaluatePinLengths();
return _maxPinLen;
}
virtual std::string version() {
if (_version.size()) return _version; // cache the version
return versionFromMFFile("5649");
}
virtual CertStatus certStatus() {
try {
Certificate auth(authenticationCertificate());
Certificate sig(digitalSignatureCertificate());
return VALID;
} catch (const no_auth& x) {
return MISSING;
} catch (const no_digsig& x) {
return MISSING;
}
}
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() {
CRYPTOLOG("log");
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) {
CRYPTOLOG("log");
pcsc::Connection::Reader::Transaction lock(_reader);
try {
return _version = cardos::BerValue(readBinary(file))[0].string();
} catch (const std::exception& x) {
CRYPTOLOG("exception, no version file: "<<x.what());
return _version = "<unknown>";
}
}
private:
CardType _type;
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<std::shared_ptr<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 cryptoki::Library& cryptoki):
_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::Connection::getReadersWithAtr("4b53776973735369676e"));
CRYPTOLOG("number of pcsc-readers: "<<readers.size());
for (pcsc::Connection::Strings::iterator reader(readers.begin());
reader!=readers.end(); ++reader) {
cryptoki::SlotList slots(_cryptoki.slotList(true, *reader));
CRYPTOLOG("number of cryptoki-readers for "<<*reader
<<": "<<slots.size());
if (slots.size()==1)
res.push_back(std::shared_ptr<Card>
(new Post(pcsc::Connection::reader(*reader),
_cryptoki)));
}
return res;
}
private:
cryptoki::Library _cryptoki;
};
class StatusCycle {
public:
StatusCycle(std::shared_ptr<Card> card, unsigned int maxRetries = 20):
_card(card), _maxRetries(maxRetries), _counter(0) {
}
~StatusCycle() {}
bool run() {
CRYPTOLOG("log");
_counter = 0;
return start();
}
protected:
std::shared_ptr<Card> card() {
return _card;
}
/// @name Slots
//@{
/// Structure to provide old and new pin
struct PinPukChange {
std::string oldpin;
std::string newpin;
bool valid() {
return oldpin.size() && newpin.size();
}
};
/// Pin change required - get pins from user
virtual PinPukChange pinChange() {
CRYPTOLOG("log");
return PinPukChange();
}
/// Transport pin change required - get pins from user
virtual PinPukChange pinChangeTransportPin() {
CRYPTOLOG("log");
return pinChange();
}
/// Puk change required - get pins from user
virtual PinPukChange pinChangePuk() {
CRYPTOLOG("log");
return pinChange();
}
/// Transport pin locked - you may show an error message
virtual void transportPinLocked() {
CRYPTOLOG("log");
}
/// PKCS#15 pin locked - you may show an error message
virtual void pkcs15PinLocked() {
CRYPTOLOG("log");
}
/// SigG pin locked - you may show an error message
virtual void sigGPinLocked() {
CRYPTOLOG("log");
}
/// Puk locked - you may show an error message
virtual void pukLocked() {
CRYPTOLOG("log");
}
/// Certificates will expire soon - you may show an error message
virtual void certsExpireSoon() {
CRYPTOLOG("log");
}
/// Certificates are expired soon - you may show an error message
virtual void certsExpired() {
CRYPTOLOG("log");
}
/// Certificates have been revoked - you may show an error message
virtual void certsRevoked() {
CRYPTOLOG("log");
}
/// install certificates on the card
/** parameter @c bool whether to force reinstallation of existing
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->unlock(pins.newpin, pins.oldpin);
return start();
}
std::shared_ptr<Card> _card;
unsigned int _maxRetries;
unsigned int _counter;
};
//@}
}
#endif