/*! @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/des.h" #include "openssl/ssl.h" #include #include #include // BASIC_CONSTRAINTS #include #include #include #include #include //! @todo remove (debug only) /*! @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 encryption_error: public openssl_error { public: encryption_error(const std::string& reason) throw(): openssl_error("encryption: "+reason) { } }; //---------------------------------------------------------------------------- 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 key_error: public openssl_error { public: key_error(const std::string& reason) throw(): openssl_error("private key: "+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 cannot_encrypt: public encryption_error { public: cannot_encrypt(std::string reason) throw(): encryption_error("cannot encrypt text: "+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 key_copy_failed: public key_error { public: key_copy_failed() throw(): key_error("key object copy failed") { } }; //---------------------------------------------------------------------------- class undefined_key: public key_error { public: undefined_key() throw(): key_error("private key must not be 0") { } }; //---------------------------------------------------------------------------- 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(); } }; inline std::string desEnc(const std::string& txt, DES_cblock key1) { std::string res(txt); if (txt.size()%8!=0) throw cannot_encrypt("text size must be a multiple of eight"); DES_key_schedule ks1; DES_set_key_unchecked(&(DES_cblock&)key1, &ks1); for (std::string::size_type pos(0); pos()+pos; out.text = res.begin().operator->()+pos; DES_ecb_encrypt(&in.array, &out.array, &ks1, DES_ENCRYPT); } return res; } inline std::string desDec(const std::string& txt, DES_cblock key1) { std::string res(txt); if (txt.size()%8!=0) throw cannot_encrypt("text size must be a multiple of eight"); DES_key_schedule ks1; DES_set_key_unchecked(&(DES_cblock&)key1, &ks1); for (std::string::size_type pos(0); pos()+pos; out.text = res.begin().operator->()+pos; DES_ecb_encrypt(&in.array, &out.array, &ks1, DES_DECRYPT); } return res; } class CBlock8 { public: CBlock8() { _cb[0] = _cb[1] = _cb[2] = _cb[3] = _cb[4] = _cb[5] = _cb[6] = _cb[7] = 0; } CBlock8(unsigned char c1, unsigned char c2, unsigned char c3, unsigned char c4, unsigned char c5, unsigned char c6, unsigned char c7, unsigned char c8) { _cb[0] = c1; _cb[1] = c2; _cb[2] = c3; _cb[3] = c4; _cb[4] = c5; _cb[5] = c6; _cb[6] = c7; _cb[7] = c8; } //! String must contain exactly 8 char. CBlock8(const std::string& s) { assert(s.size()==8); _cb[0] = s[0]; _cb[1] = s[1]; _cb[2] = s[3]; _cb[3] = s[4]; _cb[4] = s[5]; _cb[5] = s[6]; _cb[6] = s[7]; _cb[7] = s[8]; } operator DES_cblock&() { return _cb; } operator DES_cblock*() { return &_cb; } operator std::string() const { return std::string((char*)_cb, 8); } private: DES_cblock _cb; }; /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string desCbcEnc(std::string txt, CBlock8 key, CBlock8& ivec) { if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0); std::string res(txt.size(), 0); DES_key_schedule ks; DES_set_key_unchecked(key, &ks); DES_ncbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0], txt.size(), &ks, ivec, DES_ENCRYPT); return res; } /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string desCbcEnc(const std::string& txt, const CBlock8& key) { CBlock8 ivec; return desCbcEnc(txt, key, ivec); } /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string desCbcDec(std::string txt, CBlock8 key, CBlock8& ivec) { if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0); std::string res(txt.size(), 0); DES_key_schedule ks; DES_set_key_unchecked(key, &ks); DES_ncbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0], txt.size(), &ks, ivec, DES_DECRYPT); return res; } /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string desCbcDec(const std::string& txt, const CBlock8& key) { CBlock8 ivec; return desCbcDec(txt, key, ivec); } /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string des2edeCbcEnc(std::string txt, CBlock8 key1, CBlock8 key2, CBlock8 ivec = CBlock8()) { if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0); std::string res(txt.size(), 0); DES_key_schedule ks1, ks2; DES_set_key_unchecked(key1, &ks1); DES_set_key_unchecked(key2, &ks2); DES_ede2_cbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0], txt.size(), &ks1, &ks2, ivec, DES_ENCRYPT); return res; } /*! @param txt If the length is not an integral multiple of eight bytes, it is zero filled. The output is always an integral multiple of eight bytes. */ inline std::string des2edeCbcDec(std::string txt, CBlock8 key1, CBlock8 key2, CBlock8 ivec = CBlock8()) { if (txt.size()%8!=0) txt.resize((txt.size()/8+1)*8, 0); std::string res(txt.size(), 0); DES_key_schedule ks1, ks2; DES_set_key_unchecked(key1, &ks1); DES_set_key_unchecked(key2, &ks2); DES_ede2_cbc_encrypt((unsigned char*)&txt[0], (unsigned char*)&res[0], txt.size(), &ks1, &ks2, ivec, DES_DECRYPT); return res; } //! @todo untested inline std::string des2ecbEnc(std::string txt, CBlock8 key1, CBlock8 key2) { std::string res; if (txt.size()%8!=0) throw cannot_encrypt("text size must be a multiple of eight"); DES_key_schedule ks1, ks2; DES_set_key_unchecked(key1, &ks1); DES_set_key_unchecked(key2, &ks2); for (std::string::size_type pos(0); pos()); 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);» @code ASN1_INTEGER* ser(X509_get_serialNumber(_x509)); return std::string((char*)ser->data, ser->length); @endcode - requires memory free? - ser->type?!? http://albistechnologies.com prepends tag and length in the first two char-fields. */ unsigned char* c(0); int len(i2d_X509(_x509, &c)); std::string res((char*)c+15, c[14]); OPENSSL_free(c); return res; } //! 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)); } //! Check whether it's a CA certificate. bool isCa() { static BASIC_CONSTRAINTS* bc(0); if (!bc) { int pos(X509_get_ext_by_NID(_x509, NID_basic_constraints, -1)); if (pos>=0) bc = (BASIC_CONSTRAINTS*)X509V3_EXT_d2i(X509_get_ext(_x509, pos)); } return bc&&bc->ca; } //! 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)); } //! Get key usage flags. int keyUsageFlags() const { int res(X509v3_KU_UNDEF); int pos(X509_get_ext_by_NID(_x509, NID_key_usage, -1)); if (pos>=0) { ASN1_BIT_STRING* ku((ASN1_BIT_STRING*)X509V3_EXT_d2i (X509_get_ext(_x509, pos))); std::string val((char*)M_ASN1_STRING_data(ku), M_ASN1_STRING_length(ku)); if (val.size()<=sizeof(int)) val = std::string(sizeof(int)-val.size(), '\0')+val; assert(val.size()==sizeof(int)); res = *((int*)val.begin().operator->()); } return res; } private: ::X509* _x509; }; //============================================================================ class PrivateKey { public: PrivateKey(): _key(EVP_PKEY_new()) { if (!_key) throw allocation_failed(); } PrivateKey(const PrivateKey& o): _key(0) { copy(o); } PrivateKey(EVP_PKEY* k): _key(k) { if (!_key) throw undefined_key(); } ~PrivateKey() { EVP_PKEY_free(_key); } PrivateKey& operator=(const PrivateKey& o) { copy(o); return *this; } std::string modulus() const { return string(rsa()->n); } std::string publicExponent() const { return string(rsa()->e); } std::string privateExponent() const { return string(rsa()->d); } std::string prime1() const { return string(rsa()->p); } std::string prime2() const { return string(rsa()->q); } std::string exponent1() const { return string(rsa()->dmp1); } std::string exponent2() const { return string(rsa()->dmq1); } std::string coefficient() const { return string(rsa()->iqmp); } private: void copy(const PrivateKey& o) { EVP_PKEY_free(_key); if (!(_key=EVP_PKEY_new())) throw allocation_failed(); rsa(o); dsa(o); dh(o); ec(o); } std::string string(BIGNUM* a) const { std::string res(BN_num_bytes(a), '0'); BN_bn2bin(a, (unsigned char*)res.begin().operator->()); return res; } void rsa(const PrivateKey& o) { //! @todo throw exception if 0? RSA* tmp(o.rsa()); if (tmp&&!EVP_PKEY_set1_RSA(_key, tmp)) throw key_copy_failed(); } void dsa(const PrivateKey& o) { DSA* tmp(o.dsa()); if (tmp&&!EVP_PKEY_set1_DSA(_key, tmp)) throw key_copy_failed(); } void dh(const PrivateKey& o) { DH* tmp(o.dh()); if (tmp&&!EVP_PKEY_set1_DH(_key, tmp)) throw key_copy_failed(); } void ec(const PrivateKey& o) { EC_KEY* tmp(o.ec()); if (tmp&&!EVP_PKEY_set1_EC_KEY(_key, tmp)) throw key_copy_failed(); } RSA* rsa() const { //! @todo throw exception if 0? return EVP_PKEY_get1_RSA(_key); } DSA* dsa() const { return EVP_PKEY_get1_DSA(_key); } DH* dh() const { return EVP_PKEY_get1_DH(_key); } EC_KEY* ec() const { return EVP_PKEY_get1_EC_KEY(_key); } EVP_PKEY* _key; }; //============================================================================ 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() const { return _key; } bool hasCert() const { return _cert; } const PrivateKey& privateKey() const { if (!_key) throw pkcs12_no_private_key(); return *_key; }; const X509& x509() const { 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