/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef __SUISSEID_HXX__ #define __SUISSEID_HXX__ #include #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::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: "< 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 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 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(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 "<(CKO_PUBLIC_KEY) <attribute(CKA_ID)); CRYPTOLOG("get certs for key with id "<(CKO_CERTIFICATE) <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 "< 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: "< > 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::Connection::reader(*reader), _cryptoki))); } return res; } private: cryptoki::Library _cryptoki; }; class StatusCycle { public: StatusCycle(std::shared_ptr 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() { 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; unsigned int _maxRetries; unsigned int _counter; }; //@} } #endif