/*! @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 = " \r ef cardos::Commands::transportState " ] ;
transportPinRetries
[ URL = " \r ef cardos::Commands::transportPinRetries " ] ;
pkcs15PinRetries
[ URL = " \r ef cardos::Commands::pkcs15PinRetries " ] ;
sigGPinRetries
[ URL = " \r ef cardos::Commands::sigGPinRetries " ] ;
pukRetries
[ URL = " \r ef cardos::Commands::pukRetries " ] ;
changePin
[ URL = " \r ef 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 ) 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
class Certificate : public std : : string {
public :
Certificate ( const std : : string & v ,
const std : : string & l ,
const cryptoki : : Attribute & i ) :
std : : string ( v ) ,
_label ( l ) ,
_id ( i ) {
}
const std : : string & label ( ) {
return _label ;
}
const cryptoki : : Attribute & id ( ) {
return _id ;
}
private :
std : : string _label ;
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_ID ) ) ) ;
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 Certificate ( cert - > attribute ( CKA_VALUE ) . value ,
cert - > attribute ( CKA_LABEL ) . value ,
id ) ;
}
}
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 ( std : : shared_ptr < pcsc : : Connection : : Reader > reader ,
const cryptoki : : Library & cryptoki ) :
Card ( reader , cryptoki ) , _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 :
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
/** @param reinstall whether to force reinstallation of existing
certificates
@ return @ c true on success */
virtual bool installCerts ( bool reinstall = 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 ( ) ;
}
std : : shared_ptr < Card > _card ;
unsigned int _maxRetries ;
unsigned int _counter ;
} ;
//@}
}
# endif