#pragma once #ifdef BMCWEB_ENABLE_SSL #include #include #include #include #include #include #include #include #include #include #include namespace ensuressl { static void initOpenssl(); static EVP_PKEY *createEcKey(); // Trust chain related errors.` inline bool isTrustChainError(int errnum) { if ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || (errnum == X509_V_ERR_CERT_UNTRUSTED) || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)) { return true; } else { return false; } } inline bool validateCertificate(X509 *const cert) { // Create an empty X509_STORE structure for certificate validation. X509_STORE *x509Store = X509_STORE_new(); if (!x509Store) { BMCWEB_LOG_ERROR << "Error occured during X509_STORE_new call"; return false; } // Load Certificate file into the X509 structure. X509_STORE_CTX *storeCtx = X509_STORE_CTX_new(); if (!storeCtx) { BMCWEB_LOG_ERROR << "Error occured during X509_STORE_CTX_new call"; X509_STORE_free(x509Store); return false; } int errCode = X509_STORE_CTX_init(storeCtx, x509Store, cert, NULL); if (errCode != 1) { BMCWEB_LOG_ERROR << "Error occured during X509_STORE_CTX_init call"; X509_STORE_CTX_free(storeCtx); X509_STORE_free(x509Store); return false; } errCode = X509_verify_cert(storeCtx); if (errCode == 1) { BMCWEB_LOG_INFO << "Certificate verification is success"; X509_STORE_CTX_free(storeCtx); X509_STORE_free(x509Store); return true; } if (errCode == 0) { errCode = X509_STORE_CTX_get_error(storeCtx); X509_STORE_CTX_free(storeCtx); X509_STORE_free(x509Store); if (isTrustChainError(errCode)) { BMCWEB_LOG_DEBUG << "Ignoring Trust Chain error. Reason: " << X509_verify_cert_error_string(errCode); return true; } else { BMCWEB_LOG_ERROR << "Certificate verification failed. Reason: " << X509_verify_cert_error_string(errCode); return false; } } BMCWEB_LOG_ERROR << "Error occured during X509_verify_cert call. ErrorCode: " << errCode; X509_STORE_CTX_free(storeCtx); X509_STORE_free(x509Store); return false; } inline bool verifyOpensslKeyCert(const std::string &filepath) { bool privateKeyValid = false; bool certValid = false; std::cout << "Checking certs in file " << filepath << "\n"; FILE *file = fopen(filepath.c_str(), "r"); if (file != NULL) { EVP_PKEY *pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL); if (pkey != nullptr) { RSA *rsa = EVP_PKEY_get1_RSA(pkey); if (rsa != nullptr) { std::cout << "Found an RSA key\n"; if (RSA_check_key(rsa) == 1) { privateKeyValid = true; } else { std::cerr << "Key not valid error number " << ERR_get_error() << "\n"; } RSA_free(rsa); } else { EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); if (ec != nullptr) { std::cout << "Found an EC key\n"; if (EC_KEY_check_key(ec) == 1) { privateKeyValid = true; } else { std::cerr << "Key not valid error number " << ERR_get_error() << "\n"; } EC_KEY_free(ec); } } if (privateKeyValid) { // If the order is certificate followed by key in input file // then, certificate read will fail. So, setting the file // pointer to point beginning of file to avoid certificate and // key order issue. fseek(file, 0, SEEK_SET); X509 *x509 = PEM_read_X509(file, NULL, NULL, NULL); if (x509 == nullptr) { std::cout << "error getting x509 cert " << ERR_get_error() << "\n"; } else { certValid = validateCertificate(x509); X509_free(x509); } } EVP_PKEY_free(pkey); } fclose(file); } return certValid; } inline void generateSslCertificate(const std::string &filepath) { FILE *pFile = NULL; std::cout << "Generating new keys\n"; initOpenssl(); // std::cerr << "Generating RSA key"; // EVP_PKEY *pRsaPrivKey = create_rsa_key(); std::cerr << "Generating EC key\n"; EVP_PKEY *pRsaPrivKey = createEcKey(); if (pRsaPrivKey != nullptr) { std::cerr << "Generating x509 Certificate\n"; // Use this code to directly generate a certificate X509 *x509; x509 = X509_new(); if (x509 != nullptr) { // get a random number from the RNG for the certificate serial // number If this is not random, regenerating certs throws broswer // errors std::random_device rd; int serial = static_cast(rd()); ASN1_INTEGER_set(X509_get_serialNumber(x509), serial); // not before this moment X509_gmtime_adj(X509_get_notBefore(x509), 0); // Cert is valid for 10 years X509_gmtime_adj(X509_get_notAfter(x509), 60L * 60L * 24L * 365L * 10L); // set the public key to the key we just generated X509_set_pubkey(x509, pRsaPrivKey); // get the subject name X509_NAME *name; name = X509_get_subject_name(x509); X509_NAME_add_entry_by_txt( name, "C", MBSTRING_ASC, reinterpret_cast("US"), -1, -1, 0); X509_NAME_add_entry_by_txt( name, "O", MBSTRING_ASC, reinterpret_cast("OpenBMC"), -1, -1, 0); X509_NAME_add_entry_by_txt( name, "CN", MBSTRING_ASC, reinterpret_cast("testhost"), -1, -1, 0); // set the CSR options X509_set_issuer_name(x509, name); // Sign the certificate with our private key X509_sign(x509, pRsaPrivKey, EVP_sha256()); pFile = fopen(filepath.c_str(), "wt"); if (pFile != nullptr) { PEM_write_PrivateKey(pFile, pRsaPrivKey, NULL, NULL, 0, 0, NULL); PEM_write_X509(pFile, x509); fclose(pFile); pFile = NULL; } X509_free(x509); } EVP_PKEY_free(pRsaPrivKey); pRsaPrivKey = NULL; } // cleanup_openssl(); } EVP_PKEY *createEcKey() { EVP_PKEY *pKey = NULL; int eccgrp = 0; eccgrp = OBJ_txt2nid("prime256v1"); EC_KEY *myecc = EC_KEY_new_by_curve_name(eccgrp); if (myecc != nullptr) { EC_KEY_set_asn1_flag(myecc, OPENSSL_EC_NAMED_CURVE); EC_KEY_generate_key(myecc); pKey = EVP_PKEY_new(); if (pKey != nullptr) { if (EVP_PKEY_assign_EC_KEY(pKey, myecc)) { /* pKey owns pRSA from now */ if (EC_KEY_check_key(myecc) <= 0) { fprintf(stderr, "EC_check_key failed.\n"); } } } } return pKey; } void initOpenssl() { #if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_load_error_strings(); OpenSSL_add_all_algorithms(); RAND_load_file("/dev/urandom", 1024); #endif } inline void ensureOpensslKeyPresentAndValid(const std::string &filepath) { bool pemFileValid = false; pemFileValid = verifyOpensslKeyCert(filepath); if (!pemFileValid) { std::cerr << "Error in verifying signature, regenerating\n"; generateSslCertificate(filepath); } } inline std::shared_ptr getSslContext(const std::string &ssl_pem_file) { std::shared_ptr mSslContext = std::make_shared( boost::asio::ssl::context::tls_server); mSslContext->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use | boost::asio::ssl::context::no_tlsv1 | boost::asio::ssl::context::no_tlsv1_1); // m_ssl_context.set_verify_mode(boost::asio::ssl::verify_peer); mSslContext->use_certificate_file(ssl_pem_file, boost::asio::ssl::context::pem); mSslContext->use_private_key_file(ssl_pem_file, boost::asio::ssl::context::pem); // Set up EC curves to auto (boost asio doesn't have a method for this) // There is a pull request to add this. Once this is included in an asio // drop, use the right way // http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue if (SSL_CTX_set_ecdh_auto(mSslContext->native_handle(), 1) != 1) { BMCWEB_LOG_ERROR << "Error setting tmp ecdh list\n"; } std::string mozillaModern = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "ECDHE-ECDSA-CHACHA20-POLY1305:" "ECDHE-RSA-CHACHA20-POLY1305:" "ECDHE-ECDSA-AES128-GCM-SHA256:" "ECDHE-RSA-AES128-GCM-SHA256:" "ECDHE-ECDSA-AES256-SHA384:" "ECDHE-RSA-AES256-SHA384:" "ECDHE-ECDSA-AES128-SHA256:" "ECDHE-RSA-AES128-SHA256"; if (SSL_CTX_set_cipher_list(mSslContext->native_handle(), mozillaModern.c_str()) != 1) { BMCWEB_LOG_ERROR << "Error setting cipher list\n"; } return mSslContext; } } // namespace ensuressl #endif