/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef __OPENSSL_HXX__ #define __OPENSSL_HXX__ #include #include #include "openssl/bio.h" #include "openssl/ssl.h" #include #include #include #include /*! @defgroup gopenssl C++ Wrapper around OpenSSL API */ //@{ //! @defgroup openssllib OpenSSL C++ Library //! @defgroup opensslexceptions OpenSSL Exceptions //! @see gopenssl namespace openssl { //============================================================================ //! @addtogroup opensslexceptions //@{ //---------------------------------------------------------------------------- class exception: public std::exception { public: exception(const std::string& reason) throw(): _what("openssl: "+reason) { } ~exception() throw() {} const char* what() const throw() { return _what.c_str(); } private: std::string _what; }; //---------------------------------------------------------------------------- class openssl_error: public exception { public: openssl_error(const std::string& reason) throw(): exception(reason+'\n'+ERR_error_string(ERR_get_error(), 0)) { } }; //---------------------------------------------------------------------------- class pkcs12_error: public openssl_error { public: pkcs12_error(const std::string& reason) throw(): openssl_error("pkcs12: "+reason) { } }; //---------------------------------------------------------------------------- class x509_error: public openssl_error { public: x509_error(const std::string& reason) throw(): openssl_error("x509: "+reason) { } }; //---------------------------------------------------------------------------- class bio_error: public openssl_error { public: bio_error(const std::string& reason) throw(): openssl_error("bio: "+reason) { } }; //---------------------------------------------------------------------------- class ssl_error: public openssl_error { public: ssl_error(const std::string& reason) throw(): openssl_error("ssl: "+reason) { } }; //---------------------------------------------------------------------------- class allocation_failed: public x509_error { public: allocation_failed() throw(): x509_error("memory allocation failed") { } }; //---------------------------------------------------------------------------- class x509_decoding_failed: public x509_error { public: x509_decoding_failed(const std::string& der) throw(): x509_error("certificate decoding failed:\n"+crypto::readable(der)) { } }; //---------------------------------------------------------------------------- class undefined_certificate: public x509_error { public: undefined_certificate() throw(): x509_error("certificate must not be 0") { } }; //---------------------------------------------------------------------------- class x509_parsing_failed: public x509_error { public: x509_parsing_failed() throw(): x509_error("parsing DER encoded certificate failed") { } }; //---------------------------------------------------------------------------- class x509_copy_failed: public x509_error { public: x509_copy_failed() throw(): x509_error("certificate object copy failed") { } }; //---------------------------------------------------------------------------- class pkcs12_reading_failed: public pkcs12_error { public: pkcs12_reading_failed(const std::string& file) throw(): pkcs12_error("reading DER encoded p12 file failed: "+file) { } }; //---------------------------------------------------------------------------- class pkcs12_parsing_failed: public pkcs12_error { public: pkcs12_parsing_failed(const std::string& file) throw(): pkcs12_error("parsing DER encoded p12 file failed: "+file) { } }; //---------------------------------------------------------------------------- class pkcs12_no_private_key: public pkcs12_error { public: pkcs12_no_private_key() throw(): pkcs12_error("no private key") {} }; //---------------------------------------------------------------------------- class pkcs12_no_x509: public pkcs12_error { public: pkcs12_no_x509() throw(): pkcs12_error("no x509 certificate") {} }; //---------------------------------------------------------------------------- class cannot_open_file: public exception { public: cannot_open_file(const std::string& file) throw(): exception("cannot open file: "+file) { } }; //---------------------------------------------------------------------------- class bio_connection_failed: public bio_error { public: bio_connection_failed(const std::string& hostPort) throw(): bio_error("connection failed to: "+hostPort) { } }; //---------------------------------------------------------------------------- class bio_closed_connection: public bio_error { public: bio_closed_connection() throw(): bio_error("closed connection") {} }; //---------------------------------------------------------------------------- class bio_read_error: public bio_error { public: bio_read_error() throw(): bio_error("read error") {} }; //---------------------------------------------------------------------------- class bio_write_error: public bio_error { public: bio_write_error() throw(): bio_error("write error") {} }; //---------------------------------------------------------------------------- class ssl_cannot_create_context: public ssl_error { public: ssl_cannot_create_context() throw(): ssl_error("cannot create context") {} }; //---------------------------------------------------------------------------- class ssl_certificate_file_not_loaded: public ssl_error { public: ssl_certificate_file_not_loaded(const std::string& file) throw(): ssl_error("certificate file not loaded: "+file) { } }; //---------------------------------------------------------------------------- class ssl_certificate_folder_not_loaded: public ssl_error { public: ssl_certificate_folder_not_loaded(const std::string& path) throw(): ssl_error("certificate folder not loaded: "+path) { } }; //@} //! @addtogroup openssllib //@{ //============================================================================ class Init { public: Init() { SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); } }; //============================================================================ class X509 { public: //! Construct empty certificate. X509(): _x509(X509_new()) { if (!_x509) throw allocation_failed(); } //! Initialize from DER encoded cerificate. X509(const std::string& der): _x509(0) { const unsigned char* c((const unsigned char*)der.begin().operator->()); if (!(_x509=d2i_X509(0, &c, der.size())) || (const char*)c!=der.begin().operator->()+der.size()) throw x509_decoding_failed(der); } X509(const X509& o): _x509(0) { unsigned char* d(0); int len(i2d_X509(o._x509, &d)); if (!len) throw x509_copy_failed(); const unsigned char* d2(d); _x509 = d2i_X509(0, &d2, len); OPENSSL_free(d); if (!_x509) throw x509_copy_failed(); } //! Take over OpenSSL allocated certificate. X509(::X509 *x509): _x509(x509) { if (!_x509) throw undefined_certificate(); } ~X509() { X509_free(_x509); } X509& operator=(const X509& o) { X509_free(_x509); _x509 = 0; unsigned char* d(0); int len(i2d_X509(o._x509, &d)); if (!len) throw x509_copy_failed(); const unsigned char* d2(d); _x509 = d2i_X509(0, &d2, len); OPENSSL_free(d); if (!_x509) throw x509_copy_failed(); } //! Get DER encoded subject. std::string subjectDER() const { unsigned char* c(0); int len(i2d_X509_NAME(X509_get_subject_name(_x509), &c)); std::string res((char*)c, len); OPENSSL_free(c); return res; } //! Get DER encoded issuer. std::string issuerDER() const { unsigned char* c(0); int len(i2d_X509_NAME(X509_get_issuer_name(_x509), &c)); std::string res((char*)c, len); OPENSSL_free(c); return res; } //! Get DER encoded value. std::string valueDER() const { unsigned char* c(0); int len(i2d_X509(_x509, &c)); std::string res((char*)c, len); OPENSSL_free(c); return res; } //! Get serial number. std::string serial() const { /* @bug http://albistechnologies.com reports: «could be a failure in openSSL: len too short by 1 if serial number starts with 00 ASN1_INTEGER* ser = X509_get_serialNumber(_x509);» */ ASN1_INTEGER* ser(X509_get_serialNumber(_x509)); //! @todo requires memory free? /*! @todo ser->type?!? http://albistechnologies.com prepends tag and length in the first two char-fields. */ return std::string((char*)ser->data, ser->length); } //! Get id. std::string id() const { unsigned char c[SHA_DIGEST_LENGTH]; SHA1(_x509->cert_info->key->public_key->data, _x509->cert_info->key->public_key->length, c); return std::string((char*)c, SHA_DIGEST_LENGTH); } //! Get common name. std::string commonName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID(name, NID_commonName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } //! Get country name. std::string countryName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID(name, NID_countryName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } //! Get locality name. std::string localityName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID(name, NID_localityName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } //! Get state or province name. std::string stateOrProvinceName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID (name, NID_stateOrProvinceName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } //! Get organization name. std::string organizationName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID (name, NID_organizationName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } //! Get organizational unit name. std::string organizationalUnitName() const { X509_NAME *name(X509_get_subject_name(_x509)); ASN1_STRING* cn (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (name, X509_NAME_get_index_by_NID (name, NID_organizationalUnitName, -1)))); return std::string((char*)M_ASN1_STRING_data(cn), M_ASN1_STRING_length(cn)); } private: ::X509* _x509; }; //============================================================================ class PrivateKey { public: PrivateKey(EVP_PKEY *) { } }; //============================================================================ class PKCS12 { //...............................................................typedefs public: typedef std::vector X509List; //................................................................methods public: //! Read from a PKCS#12 (.p12) file. PKCS12(std::string filename, std::string password): _key(0), _cert(0) { FILE* file(fopen(filename.c_str(), "rb")); if (!file) throw cannot_open_file(filename); ::PKCS12 *p12(d2i_PKCS12_fp(file, 0)); fclose(file); if (!p12) throw pkcs12_reading_failed(filename); try { EVP_PKEY *pkey(0); ::X509 *cert(0); STACK_OF(X509) *ca(0); if (!PKCS12_parse(p12, password.c_str(), &pkey, &cert, &ca)) throw pkcs12_parsing_failed(filename); if (pkey) _key = new PrivateKey(pkey); if (cert) _cert = new X509(cert); for (int i(sk_num(ca)); i>0; --i) _ca.push_back(new X509((::X509*)sk_pop(ca))); PKCS12_free(p12); } catch (...) { PKCS12_free(p12); throw; } } ~PKCS12() { delete _key; delete _cert; for (X509List::iterator it(_ca.begin()); it!=_ca.end(); ++it) delete *it; } bool hasPrivateKey() { return _key; } bool hasCert() { return _cert; } const PrivateKey& privateKey() { if (!_key) throw pkcs12_no_private_key(); return *_key; }; const X509& x509() { if (!_cert) throw pkcs12_no_x509(); return *_cert; }; const X509List& ca() const { return _ca; } private: PrivateKey *_key; X509 *_cert; X509List _ca; }; //============================================================================ class BIO { private: BIO(const BIO&); BIO& operator=(const BIO&); public: BIO(): _bio(0) {} ~BIO() { try { close(); } catch (...) { if (!std::uncaught_exception()) throw; } } BIO& connect(const std::string& hostPort) { close(); if (!(_bio=BIO_new_connect(const_cast(hostPort.c_str()))) || BIO_do_connect(_bio)<=0) throw bio_connection_failed(hostPort); return *this; } BIO& operator>>(std::string& s) { s += read(); return *this; } BIO& operator<<(const std::string& s) { return write(s); } /*! @todo How can I find out, whether there's still data available, or whether a response has been finished and server is waiting for next request, but connection is still open? */ std::string read() { if (!_bio) throw bio_closed_connection(); const int BUFF_SZ(1024); char buff[BUFF_SZ]; int x(BIO_read(_bio, buff, BUFF_SZ)); if (x<=0) if (BIO_should_retry(_bio)) return read(); else throw bio_read_error(); return std::string(buff, x); } BIO& write(const std::string& s) { int x(BIO_write(_bio, s.begin().operator->(), s.size())); if (x<=0) if (BIO_should_retry(_bio)) return write(s); else throw bio_write_error(); else if (x(hostPort.c_str())); if (BIO_do_connect(_bio._bio)<=0) throw bio_connection_failed(hostPort); return _bio; } SSL& close() { _bio.close(); _ssl = 0; //! @todo is this correct? <-- return *this; } bool verifyResult() { return _ssl && SSL_get_verify_result(_ssl)==X509_V_OK; } private: SSL_CTX *_ctx; ::SSL *_ssl; BIO _bio; }; //@} } //@} #endif