/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef __OPENSSL_HXX__ #define __OPENSSL_HXX__ #include #include #include #include "openssl/bio.h" #include "openssl/des.h" #include "openssl/ssl.h" #include #include #include #ifndef OPENSSL_VERSION_NUMBER # error OpenSSL Version Number not Found #elif OPENSSL_VERSION_NUMBER < 0x10000000L # define OPENSSL_0 # define V0_CONST const # define CV_STACK # define CV_X509 #else # define OPENSSL_1 # define V0_CONST const # define CV_STACK (_STACK*) # define CV_X509 (STACK_OF(X509)*) #endif #include // BASIC_CONSTRAINTS #include #include #include #include namespace pcsc { std::string version(); } #ifndef OPENSSL_LOG #include #if __GNUC__ >= 2 //! Openssl Logging /*! If you want to change openssl logging mechanism, just redefine your own OPENSSL_LOG macro before #include <openssl.hxx>. Define it empty for no logging at all. By default logs to std::clog. */ #define OPENSSL_LOG(X) std::clog<#include <openssl.hxx>. Define it empty for no logging at all. By default logs to std::clog. */ #define OPENSSL_LOG(X) std::clog<()+pos; out.text = res.begin().operator->()+pos; DES_ecb_encrypt(&in.array, &out.array, &ks1, DES_ENCRYPT); } return res; } //! Decrypt a DES encrypted string. /*! @param txt text to decrypt - size must be a multiple of 8 @param key1 DES key for decryption */ inline std::string desDec(const std::string& txt, CBlock8 key1) { OPENSSL_LOG("log"); 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(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; } //! DES CBC Encryption /*! @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) { OPENSSL_LOG("log"); 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; } //! DES CBC Encryption with empty vector /*! @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) { OPENSSL_LOG("log"); CBlock8 ivec; return desCbcEnc(txt, key, ivec); } //! DES CBC Decryption /*! @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) { OPENSSL_LOG("log"); 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; } //! DES CBC Decryption with empty vector /*! @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) { OPENSSL_LOG("log"); 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()) { OPENSSL_LOG("log"); 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()) { OPENSSL_LOG("log"); 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) { OPENSSL_LOG("log"); 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) { OPENSSL_LOG("log"); unsigned char* d(0); int len(i2d_X509(o._x509, &d)); if (!len) throw x509_copy_failed(); V0_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) { OPENSSL_LOG("log"); if (!_x509) throw undefined_certificate(); } ~X509() { OPENSSL_LOG("log"); X509_free(_x509); } X509& operator=(const X509& o) { OPENSSL_LOG("log"); X509_free(_x509); _x509 = 0; unsigned char* d(0); int len(i2d_X509(o._x509, &d)); if (!len) throw x509_copy_failed(); V0_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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); /* @bug tcp://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?!? tcp://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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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() { OPENSSL_LOG("log"); BASIC_CONSTRAINTS* bc(0); 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 { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); 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; }; //============================================================================ //! Private certificate key class PrivateKey { public: PrivateKey(): _key(EVP_PKEY_new()) { OPENSSL_LOG("log"); if (!_key) throw allocation_failed(); } PrivateKey(const PrivateKey& o): _key(0) { OPENSSL_LOG("log"); copy(o); } PrivateKey(EVP_PKEY* k): _key(k) { OPENSSL_LOG("log"); if (!_key) throw undefined_key(); } ~PrivateKey() { OPENSSL_LOG("log"); EVP_PKEY_free(_key); } PrivateKey& operator=(const PrivateKey& o) { OPENSSL_LOG("log"); copy(o); return *this; } std::string modulus() const { OPENSSL_LOG("log"); return string(rsa()->n); } std::string publicExponent() const { OPENSSL_LOG("log"); return string(rsa()->e); } std::string privateExponent() const { OPENSSL_LOG("log"); return string(rsa()->d); } std::string prime1() const { OPENSSL_LOG("log"); return string(rsa()->p); } std::string prime2() const { OPENSSL_LOG("log"); return string(rsa()->q); } std::string exponent1() const { OPENSSL_LOG("log"); return string(rsa()->dmp1); } std::string exponent2() const { OPENSSL_LOG("log"); return string(rsa()->dmq1); } std::string coefficient() const { OPENSSL_LOG("log"); return string(rsa()->iqmp); } private: void copy(const PrivateKey& o) { OPENSSL_LOG("log"); 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 { OPENSSL_LOG("log"); std::string res(BN_num_bytes(a), '0'); BN_bn2bin(a, (unsigned char*)res.begin().operator->()); return res; } void rsa(const PrivateKey& o) { OPENSSL_LOG("log"); //! @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) { OPENSSL_LOG("log"); DSA* tmp(o.dsa()); if (tmp&&!EVP_PKEY_set1_DSA(_key, tmp)) throw key_copy_failed(); } void dh(const PrivateKey& o) { OPENSSL_LOG("log"); DH* tmp(o.dh()); if (tmp&&!EVP_PKEY_set1_DH(_key, tmp)) throw key_copy_failed(); } /* Not available on mac osx void ec(const PrivateKey& o) { OPENSSL_LOG("log"); EC_KEY* tmp(o.ec()); if (tmp&&!EVP_PKEY_set1_EC_KEY(_key, tmp)) throw key_copy_failed(); } */ RSA* rsa() const { OPENSSL_LOG("log"); //! @todo throw exception if 0? return EVP_PKEY_get1_RSA(_key); } DSA* dsa() const { OPENSSL_LOG("log"); return EVP_PKEY_get1_DSA(_key); } DH* dh() const { OPENSSL_LOG("log"); return EVP_PKEY_get1_DH(_key); } /* Not available on mac osx EC_KEY* ec() const { OPENSSL_LOG("log"); return EVP_PKEY_get1_EC_KEY(_key); }*/ EVP_PKEY* _key; }; //============================================================================ //! PKCS#12 certificate file handler 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) { OPENSSL_LOG("log"); 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(CV_STACK ca)); i>0; --i) _ca.push_back(new X509((::X509*)sk_pop(CV_STACK ca))); PKCS12_free(p12); } catch (...) { PKCS12_free(p12); throw; } } ~PKCS12() { OPENSSL_LOG("log"); delete _key; delete _cert; for (X509List::iterator it(_ca.begin()); it!=_ca.end(); ++it) delete *it; } bool hasPrivateKey() const { OPENSSL_LOG("log"); return _key; } bool hasCert() const { OPENSSL_LOG("log"); return _cert; } const PrivateKey& privateKey() const { OPENSSL_LOG("log"); if (!_key) throw pkcs12_no_private_key(); return *_key; }; const X509& x509() const { OPENSSL_LOG("log"); if (!_cert) throw pkcs12_no_x509(); return *_cert; }; const X509List& ca() const { OPENSSL_LOG("log"); return _ca; } private: PrivateKey *_key; X509 *_cert; X509List _ca; }; //============================================================================ //! PKCS#7 certificate file handler class PKCS7 { //...............................................................typedefs public: typedef std::vector X509List; //................................................................methods public: /* //! Read from a PKCS#7 (.p7) file. PKCS7(std::string filename) { FILE* file(fopen(filename.c_str(), "rb")); if (!file) throw cannot_open_file(filename); ::PKCS7 *p7(d2i_PKCS7_fp(file, 0)); fclose(file); if (!p7) throw pkcs7_reading_failed(filename); try { if (PKCS7_type_is_signed(p7)) while (p7->d.sign->cert->num>0) _certs.push_back(new X509((::X509*)sk_pop(p7->d.sign->cert))); else //! @todo to be implemented: check for other types throw pkcs7_unsupported_format(); PKCS7_free(p7); } catch (...) { PKCS7_free(p7); throw; } }*/ //! Read PKCS#7 from memory. PKCS7(const std::string& memory) { OPENSSL_LOG("log"); BIO* mem(BIO_new_mem_buf((void*)memory.data(), memory.size())); ::PKCS7 *p7(d2i_PKCS7_bio(mem, 0)); BIO_free(mem); if (!p7) throw pkcs7_parsing_failed(); try { if (PKCS7_type_is_signed(p7)) while ((CV_STACK p7->d.sign->cert)->num>0) _certs.push_back(new X509((::X509*)sk_pop(CV_STACK p7->d.sign->cert))); else //! @todo to be implemented: check for other types throw pkcs7_unsupported_format(); PKCS7_free(p7); } catch (...) { PKCS7_free(p7); throw; } } ~PKCS7() { OPENSSL_LOG("log"); for (X509List::iterator it(_certs.begin()); it!=_certs.end(); ++it) delete *it; } const X509List& certs() const { OPENSSL_LOG("log"); return _certs; } private: X509List _certs; }; //============================================================================ //! TCP Connection class TCP { private: TCP(const TCP&); TCP& operator=(const TCP&); public: TCP(): _bio(0) { OPENSSL_LOG("log"); } TCP(const std::string& hostPort): _bio(0) { OPENSSL_LOG("log"); connect(hostPort); } virtual ~TCP() { OPENSSL_LOG("log"); try { close(); } catch (...) { if (!std::uncaught_exception()) throw; } } virtual TCP& connect(const std::string& hostPort) { OPENSSL_LOG("log"); close(); if (!(_bio=BIO_new_connect(const_cast(hostPort.c_str()))) || BIO_do_connect(_bio)<=0) throw tcp_connection_failed(hostPort); _hostPort = hostPort; return *this; } virtual TCP& connect() { if (!_hostPort.size()) throw tcp_server_not_specified(); close(); connect(_hostPort); return *this; } operator bool() const { OPENSSL_LOG("log"); return _bio>0; } TCP& operator>>(std::string& s) { OPENSSL_LOG("log"); s += read(); return *this; } TCP& operator<<(const std::string& s) { OPENSSL_LOG("log"); 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() { OPENSSL_LOG("log"); if (_bio<=0) throw tcp_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 { close(); if (x==0) return std::string(); else throw tcp_read_error(); } else if (x==1024) return std::string(buff, x)+read(); return std::string(buff, x); } TCP& write(const std::string& s) { OPENSSL_LOG("log"); if (_bio<=0) throw tcp_closed_connection(); int x(BIO_write(_bio, s.begin().operator->(), s.size())); if (x<=0) if (BIO_should_retry(_bio)) return write(s); else { close(); throw tcp_write_error(); } else if (x0) BIO_free_all(_bio); _bio = 0; return *this; } protected: ::BIO* _bio; std::string _hostPort; }; //! Read authorized certificate from a single pem file. class TrustStore { public: TrustStore(const std::string& pathToPemFile): _file(pathToPemFile) { OPENSSL_LOG("log"); } const std::string& file() const { OPENSSL_LOG("log"); return _file; } private: std::string _file; }; class CertificateFolder { public: CertificateFolder(const std::string& certificateFolder): _path(certificateFolder) { OPENSSL_LOG("log"); } const std::string& path() const { OPENSSL_LOG("log"); return _path; } private: std::string _path; }; class SSL: public TCP { private: SSL(); SSL(const SSL&); SSL& operator=(const SSL&); public: SSL(const TrustStore& file): _ctx(SSL_CTX_new(SSLv23_client_method())), _ssl(0) { OPENSSL_LOG("log"); if (!_ctx) throw ssl_cannot_create_context(); if (!SSL_CTX_load_verify_locations(_ctx, file.file().c_str(), 0)) throw ssl_certificate_file_not_loaded(file.file()); } SSL(const CertificateFolder& folder): _ctx(SSL_CTX_new(SSLv23_client_method())), _ssl(0) { OPENSSL_LOG("log"); if (!_ctx) throw ssl_cannot_create_context(); if (!SSL_CTX_load_verify_locations(_ctx, 0, folder.path().c_str())) throw ssl_certificate_folder_not_loaded(folder.path()); } ~SSL() { OPENSSL_LOG("log"); close(); SSL_CTX_free(_ctx); } virtual SSL& connect(const std::string& hostPort) { OPENSSL_LOG("log"); close(); if (!(_bio=BIO_new_ssl_connect(_ctx))) throw tcp_connection_failed(hostPort); BIO_get_ssl(_bio, &_ssl); if (!_ssl) throw ssl_no_connection(); SSL_set_mode(_ssl, SSL_MODE_AUTO_RETRY); BIO_set_conn_hostname(_bio, const_cast(hostPort.c_str())); if (BIO_do_connect(_bio)<=0) throw tcp_connection_failed(hostPort); verify(); return *this; } virtual SSL& close() { OPENSSL_LOG("log"); TCP::close(); _ssl = 0; //! @todo is this correct? <-- return *this; } protected: void verify() { OPENSSL_LOG("log"); if (!_ssl) throw ssl_no_connection(); int res(SSL_get_verify_result(_ssl)); if (res!=X509_V_OK) throw ssl_verification_failed(res); } private: SSL_CTX *_ctx; ::SSL *_ssl; }; //@} } inline std::ostream& operator<<(std::ostream& os, openssl::TCP& is) { return os<>(std::istream& is, openssl::TCP& os) { std::string s; is>>s; os<