host/cxpslib/cxpssslcontext.h (459 lines of code) (raw):

/// /// \file cxpssslcontext.h /// /// \brief /// #ifndef CXPSSSLCONTEXT_H #define CXPSSSLCONTEXT_H #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/asio/ssl.hpp> #include <boost/algorithm/string.hpp> #include <boost/shared_ptr.hpp> #include <boost/lexical_cast.hpp> #include <openssl/ssl.h> #include "errorexception.h" #include "atloc.h" #include "connection.h" #include "cxps.h" #include "fingerprintmgr.h" #include "scopeguard.h" #ifdef CXPS_x64 #include <Wincrypt.h> #include <atlconv.h> #include <Windows.h> #include "cxpslogger.h" #define USER_CERT_STORE L"My" #define GUID_REGEX "^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" #endif inline int opensslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx); inline int opensslVerifyCallback2(int preverifyOk, X509_STORE_CTX* ctx); inline int opensslVerifyClientCertCallback(int preverifyOk, X509_STORE_CTX* ctx); /// \breif SSL conext needed for secure transfer class CxpsSslContext { public: typedef boost::shared_ptr<CxpsSslContext> ptr; /// \brief constructs SslConext fro use on server side CxpsSslContext(boost::asio::io_service& ioService, std::string const& certFile, std::string const& keyFile, std::string const& dhFile, std::string const& passphrase, std::string const& caCertThumbprint) : m_sslContext(boost::asio::ssl::context::tlsv12), m_passphrase(passphrase), m_caCertThumbprint(caCertThumbprint) { int ret = 0; m_sslContext.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 | boost::asio::ssl::context::no_tlsv1_1 | boost::asio::ssl::context::single_dh_use ); m_sslContext.set_password_callback(boost::bind(&CxpsSslContext::getPassphrase, this)); try { m_sslContext.use_certificate_chain_file(certFile); m_sslContext.use_private_key_file(keyFile, boost::asio::ssl::context::pem); m_sslContext.use_tmp_dh_file(dhFile); SSL_CTX* sslCtx = m_sslContext.native_handle(); if (!caCertThumbprint.empty()) { m_sslContext.set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::context::verify_fail_if_no_peer_cert); SSL_CTX_set_ex_data(sslCtx, 1, this); SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, &::opensslVerifyClientCertCallback); } ret = SSL_CTX_set_cipher_list(sslCtx, "HIGH:!ADH:!AECDH"); } catch (std::exception const& e) { throw ERROR_EXCEPTION << "failed to load ssl certificate files. ssl returned '" << e.what() << "'. Make sure all paths and file names are correct in the conf file"; } catch (...) { throw ERROR_EXCEPTION << "unknown exception while loading ssl pem files"; } if (!ret) { throw ERROR_EXCEPTION << "failed to set the cipher list by excluding the deprecated list"; } } /// \brief constructs SSlContext for use on client side CxpsSslContext(boost::asio::io_service& ioService, std::string const& clientFile) : m_sslContext(boost::asio::ssl::context::tlsv12) { try { m_sslContext.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 | boost::asio::ssl::context::no_tlsv1_1 | boost::asio::ssl::context::single_dh_use ); if (!clientFile.empty()) { m_sslContext.set_verify_mode(boost::asio::ssl::context::verify_peer | boost::asio::ssl::context::verify_fail_if_no_peer_cert); m_sslContext.load_verify_file(clientFile); } SSL_CTX* sslCtx = m_sslContext.native_handle(); if (!clientFile.empty()) { SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, &::opensslVerifyCallback); } } catch (std::exception const& e) { throw ERROR_EXCEPTION << "failed to load ssl files. ssl returned '" << e.what() << "'. Make sure all paths and file names are correct in the conf file"; } catch (...) { throw ERROR_EXCEPTION << "caught unknown error while loading ssl pem files"; } } /// \brief constructs SSlContext for use on client side using client certs CxpsSslContext(boost::asio::io_service& ioService, std::string const& certFile, std::string const& keyFile, std::string const& serverCertThumbprint) : m_sslContext(boost::asio::ssl::context::tlsv12), m_serverCertThumbprint(serverCertThumbprint) { try { m_sslContext.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 | boost::asio::ssl::context::no_tlsv1_1 | boost::asio::ssl::context::single_dh_use ); m_sslContext.use_certificate_chain_file(certFile); m_sslContext.use_private_key_file(keyFile, boost::asio::ssl::context::pem); SSL_CTX* sslCtx = m_sslContext.native_handle(); SSL_CTX_set_ex_data(sslCtx, 1, this); SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, &::opensslVerifyCallback2); } catch (std::exception const& e) { throw ERROR_EXCEPTION << "failed to load ssl files. ssl returned '" << e.what() << "'. Make sure all paths and file names are correct in the conf file"; } catch (...) { throw ERROR_EXCEPTION << "caught unknown error while loading ssl pem files"; } } /// \brief gets the ssl context needed by the connection for secure transfer /// /// \return reference to the boost::asio:ssl::context boost::asio::ssl::context& context() { return m_sslContext; } bool verifyFingerprint(sslSocket_t::native_handle_type nativeSsl) { // FIXME: use g_fingerprintMgr // std::string fingerprint("c5f3f1ec4e1bfd12cecae937fe30dbfdbf34b794"); return true; // boost::algorithm::iequals(fingerprint, getFingerprint(nativeSsl)); } bool opensslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx) { return true; } std::string getFingerprint(sslSocket_t::native_handle_type nativeSsl) { X509* cert = SSL_get_peer_certificate(nativeSsl); ON_BLOCK_EXIT(&X509_free, cert); if (0 == cert) { return std::string(); } EVP_MD const* evpSha1 = EVP_sha1(); unsigned char md[EVP_MAX_MD_SIZE]; unsigned int len; X509_digest(cert, evpSha1, md, &len); std::stringstream certFingerprint; for (unsigned int i = 0; i < len; ++i) { certFingerprint << std::hex << std::setfill('0') << std::setw(2) << (int)md[i]; } return certFingerprint.str(); } std::string getCertificate(sslSocket_t::native_handle_type nativeSsl) { std::string certificate; X509* cert = SSL_get_peer_certificate(nativeSsl); ON_BLOCK_EXIT(&X509_free, cert); if (0 == cert) { return certificate; } BIO* memBio = BIO_new(BIO_s_mem()); if (0 == memBio) { return certificate; } ON_BLOCK_EXIT(&BIO_free, memBio); BUF_MEM* memBuf; BIO_get_mem_ptr(memBio, &memBuf); PEM_write_bio_X509_AUX(memBio, cert); certificate.assign(memBuf->data, memBuf->length); return certificate; } std::string getCurrentCipherSuite(sslSocket_t::native_handle_type nativeSsl) { std::string sslCipherStr; SSL_CIPHER const* sslCipher = SSL_get_current_cipher(nativeSsl); if (0 != sslCipher) { sslCipherStr = "Cipher - name: "; sslCipherStr += SSL_CIPHER_get_name(sslCipher); int algoBits; int secretBits = SSL_CIPHER_get_bits(sslCipher, &algoBits); sslCipherStr += ", bits: "; sslCipherStr += boost::lexical_cast<std::string>(secretBits); sslCipherStr += ", "; sslCipherStr += boost::lexical_cast<std::string>(algoBits); sslCipherStr += ", version: "; const char* tmp = SSL_CIPHER_get_version(sslCipher); if (0 != tmp) { sslCipherStr += tmp; } else { sslCipherStr += "unknonw"; } sslCipherStr += ", description: "; char buf[256] = { 0 }; tmp = SSL_CIPHER_description(sslCipher, buf, sizeof(buf)); if (0 != buf[0]) { sslCipherStr += buf; } else { sslCipherStr += "not found"; } } return sslCipherStr; } std::string getServerCertThumbprint() { return m_serverCertThumbprint; } std::string getCaCertThumbprint() { return m_caCertThumbprint; } std::string getCertBiosId() { return m_certBiosId; } void setCertBiosId(std::string biosId) { m_certBiosId = biosId; } protected: /// \breif gets the passphrase needed to access certifcates /// /// \return std::string hold passphrase std::string getPassphrase() { return m_passphrase; } private: boost::asio::ssl::context m_sslContext; ///< boost asio ssl context used for secure transfer std::string m_passphrase; std::string m_serverCertThumbprint; ///< thumbprint used to validate server cert in client std::string m_caCertThumbprint; ///< thumbprint used to validate server cert in client std::string m_certBiosId; }; inline int opensslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx) { X509* cert = X509_STORE_CTX_get_current_cert(ctx); if (0 == cert) { return 0; } // FIXME: #if 0 if (0 == preverifyOk && (X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT == X509_STORE_CTX_get_error(ctx) || X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY == X509_STORE_CTX_get_error(ctx) || X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE == X509_STORE_CTX_get_error(ctx))) { X509_STORE_CTX_set_error(ctx, X509_V_OK); // may not be needed but play it safe preverifyOk = 1; } #else X509_STORE_CTX_set_error(ctx, X509_V_OK); // may not be needed but play it safe preverifyOk = 1; #endif return preverifyOk; } inline int opensslVerifyCallback2(int preverifyOk, X509_STORE_CTX* ctx) { X509* cert = X509_STORE_CTX_get_current_cert(ctx); if (0 == cert) { return 0; } if (0 == preverifyOk && (X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT == X509_STORE_CTX_get_error(ctx) || X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY == X509_STORE_CTX_get_error(ctx) || X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE == X509_STORE_CTX_get_error(ctx))) { SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); if (0 == ssl) { return preverifyOk; } SSL_CTX* sslCtx = ::SSL_get_SSL_CTX(ssl); if (0 == sslCtx) { return preverifyOk; } if (!SSL_CTX_get_ex_data(sslCtx, 1)) { return preverifyOk; } CxpsSslContext* cxpsctx = static_cast<CxpsSslContext*>(SSL_CTX_get_ex_data(sslCtx, 1)); std::string fingerprint = g_fingerprintMgr.getFingerprint(cert); if ((X509_cmp_time(X509_get_notAfter(cert), 0) > 0) && boost::iequals(fingerprint, cxpsctx->getServerCertThumbprint())) { X509_STORE_CTX_set_error(ctx, X509_V_OK); // may not be needed but play it safe preverifyOk = 1; } } return preverifyOk; } #ifdef CXPS_x64 static int verify_cb(int ok, X509_STORE_CTX *ctx) { X509 *currentCert = X509_STORE_CTX_get_current_cert(ctx); int depth = X509_STORE_CTX_get_error_depth(ctx); char subject_name[256]; X509_NAME_oneline(X509_get_subject_name(currentCert), subject_name, 256); CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_3, AT_LOC<<"verify_cb certificate Name:" << subject_name << "\tDepth :" << depth); if (!ok) { int certError = X509_STORE_CTX_get_error(ctx); CXPS_LOG_ERROR(AT_LOC<<"Error depth: " << depth << "Cert Error "<< certError<<"\t"); } return(ok); } // helper function to check if there are any memory leaks in closing cert store static void cxpsCertCloseStore(HCERTSTORE hStore) { bool ret = CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG); if (!ret) { DWORD error = GetLastError(); // CRYPT_E_PENDING_CLOSE indicates some contexts are still open CXPS_LOG_ERROR(AT_LOC << "Error freeing cert store: " << error); } } #endif inline int opensslVerifyClientCertCallback(int preverifyOk, X509_STORE_CTX* ctx) { #ifdef CXPS_x64 USES_CONVERSION; HCERTSTORE hStore = NULL; PCCERT_CONTEXT pCertContext = NULL; X509* cert = X509_STORE_CTX_get_current_cert(ctx); if (0 == cert) { CXPS_LOG_ERROR(AT_LOC<<"cert verfication failed as current cert not present"); return preverifyOk; } SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); if (0 == ssl) { CXPS_LOG_ERROR(AT_LOC<<"ssl details fetch failed"); return preverifyOk; } SSL_CTX* sslCtx = ::SSL_get_SSL_CTX(ssl); if (0 == sslCtx) { CXPS_LOG_ERROR(AT_LOC<<"ssl context from ssl failed"); return preverifyOk; } if (!SSL_CTX_get_ex_data(sslCtx, 1)) { CXPS_LOG_ERROR(AT_LOC<<"ssl ex_data fetch failed"); return preverifyOk; } std::string fingerprint = g_fingerprintMgr.getFingerprint(cert); char subject_name[256]; X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256); CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_2, AT_LOC<< "client cert name=" << subject_name << ", fingerprint=" << fingerprint); if (X509_cmp_time(X509_get_notAfter(cert), 0) <= 0) { CXPS_LOG_ERROR(AT_LOC<<"Cert is expired, Not After Date=" << X509_get_notAfter(cert) <<", Not Before Date="<< X509_get_notBefore(cert)); return preverifyOk; } std::string certSubjectName = std::string(subject_name); if (!(certSubjectName.find("CN=") == std::string::npos || certSubjectName.find("cn=") == std::string::npos)) { CXPS_LOG_ERROR(AT_LOC<<"subject name does not contain CN=, cert subject name="<<certSubjectName); return preverifyOk; } std::string certificateBiosId; size_t biosIdIndex = certSubjectName.find_first_of("="); if (std::string::npos != biosIdIndex) { certificateBiosId = certSubjectName.substr(biosIdIndex + 1); } boost::regex guidRegex(GUID_REGEX); if (!boost::regex_match(certificateBiosId, guidRegex)) { CXPS_LOG_ERROR(AT_LOC<<"guid not found in subject name="<<certSubjectName); return preverifyOk; } CxpsSslContext* cxpsctx = static_cast<CxpsSslContext*>(SSL_CTX_get_ex_data(sslCtx, 1)); CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_3, AT_LOC<<"opening the LocalMachine\\My cert store"); if (!(hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG, USER_CERT_STORE))) { CXPS_LOG_ERROR(AT_LOC<<" LocalMachine\\My cert store opening failed"); return preverifyOk; } CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_3, AT_LOC<<"searching the thumbprint in the store."); #ifdef _DEBUG ON_BLOCK_EXIT(boost::bind(&cxpsCertCloseStore, hStore)); #else ON_BLOCK_EXIT(boost::bind<void>(&CertCloseStore, hStore, 0)); #endif std::string cacertThumbprint; size_t cnt = cxpsctx->getCaCertThumbprint().length() / 2; for (size_t i = 0; cnt > i; ++i) { uint32_t s = 0; std::stringstream ss; ss << std::hex << cxpsctx->getCaCertThumbprint().substr(i * 2, 2); ss >> s; cacertThumbprint.push_back(static_cast<unsigned char>(s)); } CRYPT_HASH_BLOB hashBlob; hashBlob.cbData = cacertThumbprint.length(); hashBlob.pbData = (BYTE *)cacertThumbprint.c_str(); // check the thumbprint in the certificate. if (!(pCertContext = CertFindCertificateInStore(hStore, (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING), 0, CERT_FIND_SHA1_HASH, &hashBlob, NULL))) { CXPS_LOG_ERROR(AT_LOC<<"No certificate with ca thumbprint in local store :" << cxpsctx->getCaCertThumbprint()<<"\t"); return preverifyOk; } ON_BLOCK_EXIT(boost::bind<void>(&CertFreeCertificateContext, pCertContext)); CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_3, AT_LOC<<"Making the certificate chain"); X509_STORE *store = X509_STORE_new(); if (store == NULL) { CXPS_LOG_ERROR(AT_LOC<<"creation of cert store to check certificate validation failed"); return preverifyOk; } ON_BLOCK_EXIT(boost::bind(&X509_STORE_free, store)); X509 *matchingCert = d2i_X509(NULL, (const unsigned char **)&pCertContext->pbCertEncoded, pCertContext->cbCertEncoded); if (matchingCert == NULL) { CXPS_LOG_ERROR(AT_LOC<<"Cert in local store with same thumbprint's conversion to X509 Failed"); return preverifyOk; } ON_BLOCK_EXIT(boost::bind(&X509_free, matchingCert)); if (!X509_STORE_add_cert(store, matchingCert)) { CXPS_LOG_ERROR(AT_LOC<<"Local Cert addition in Store Failed"); return preverifyOk; } X509_STORE_CTX * storectx= X509_STORE_CTX_new(); if (storectx == NULL) { CXPS_LOG_ERROR(AT_LOC<<"creation of cert store context to check certificate validation failed"); return preverifyOk; } ON_BLOCK_EXIT(boost::bind(&X509_STORE_CTX_free, storectx)); X509_STORE_set_verify_cb(store, verify_cb); if (X509_STORE_CTX_init(storectx, store, cert, NULL) == 0) { CXPS_LOG_ERROR(AT_LOC << "Context Setup for Verification Failed"); return preverifyOk; } if (X509_verify_cert(storectx) <= 0) { CXPS_LOG_ERROR(AT_LOC<<"Verification of Cert Chain Failed"); return preverifyOk; } preverifyOk = 1; X509_STORE_CTX_set_error(ctx, X509_V_OK); cxpsctx->setCertBiosId(certificateBiosId); CXPS_LOG_MONITOR(MONITOR_LOG_LEVEL_1, AT_LOC<<"client cert verification successful"); #endif return preverifyOk; } #endif // CXPSSSLCONTEXT_H