/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef __SUISSEID_HXX__ #define __SUISSEID_HXX__ #include #include #include #include #include /*! @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: "< 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 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 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 ""; } /// 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(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() <(CKO_PUBLIC_KEY) <attribute(CKA_ID)); cryptoki::ObjectList certs (session().find(cryptoki::AttributeList() <(CKO_CERTIFICATE) <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 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(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 = ""; } } 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 > Cards; //! Auxiliary SuisseID card manager. /** Use this manager to scan your system for SuisseID cards. Usage Example: @code #include #include [...] try { suisseid::Cards cards(suisseid::Scanner().scan()); for (auto card(cards.begin()); card!=cards.end(); ++card) std::cout<<"Found SuisseID: "<<(*card)->name()< (new Post(_pcsc.reader(*reader), _cryptoki))); } return res; } private: pcsc::Connection _pcsc; cryptoki::Library _cryptoki; }; class StatusCycle { public: StatusCycle(mrw::Shared card, unsigned int maxRetries = 20): _card(card), _maxRetries(maxRetries), _counter(0) { } ~StatusCycle() {} bool run() { CRYPTOLOG("log"); _counter = 0; return start(); } protected: mrw::Shared 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; unsigned int _maxRetries; unsigned int _counter; }; //@} } #endif