std::shared_ptr<SSLContext> getSSLContext(folly::StringPiece pemCertPath, folly::StringPiece pemKeyPath, folly::StringPiece pemCaPath) { static constexpr std::chrono::minutes kSslReloadInterval{5}; thread_local std::unordered_map<CertPaths, ContextInfo, CertPathsHasher> localContexts; CertPaths paths; paths.pemCertPath = pemCertPath; paths.pemKeyPath = pemKeyPath; paths.pemCaPath = pemCaPath; auto iter = localContexts.find(paths); if (localContexts.find(paths) == localContexts.end()) { // Copy strings. ContextInfo info; info.pemCertPath = pemCertPath.toString(); info.pemKeyPath = pemKeyPath.toString(); info.pemCaPath = pemCaPath.toString(); // Point all StringPiece's to our own strings. paths.pemCertPath = info.pemCertPath; paths.pemKeyPath = info.pemKeyPath; paths.pemCaPath = info.pemCaPath; iter = localContexts.insert(std::make_pair(paths, std::move(info))).first; } auto& contextInfo = iter->second; auto now = std::chrono::steady_clock::now(); if (contextInfo.context == nullptr || now - contextInfo.lastLoadTime > kSslReloadInterval) { try { auto sslContext = std::make_shared<SSLContext>(); sslContext->loadCertificate(pemCertPath.begin()); sslContext->loadPrivateKey(pemKeyPath.begin()); sslContext->loadTrustedCertificates(pemCaPath.begin()); sslContext->loadClientCAList(pemCaPath.begin()); // Disable compression if possible to reduce CPU and memory usage. #ifdef SSL_OP_NO_COMPRESSION sslContext->setOptions(SSL_OP_NO_COMPRESSION); #endif contextInfo.lastLoadTime = now; contextInfo.context = std::move(sslContext); } catch (const apache::thrift::transport::TTransportException& ex) { LOG(ERROR) << "Failed to load certificate, ex: " << ex.what(); } } return contextInfo.context; }
void SSLContextManager::addSSLContextConfig( const SSLContextConfig& ctxConfig, const SSLCacheOptions& cacheOptions, const TLSTicketKeySeeds* ticketSeeds, const folly::SocketAddress& vipAddress, const std::shared_ptr<SSLCacheProvider>& externalCache) { unsigned numCerts = 0; std::string commonName; std::string lastCertPath; std::unique_ptr<std::list<std::string>> subjectAltName; auto sslCtx = std::make_shared<SSLContext>(ctxConfig.sslVersion); for (const auto& cert : ctxConfig.certificates) { try { sslCtx->loadCertificate(cert.certPath.c_str()); } catch (const std::exception& ex) { // The exception isn't very useful without the certificate path name, // so throw a new exception that includes the path to the certificate. string msg = folly::to<string>("error loading SSL certificate ", cert.certPath, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } // Verify that the Common Name and (if present) Subject Alternative Names // are the same for all the certs specified for the SSL context. numCerts++; X509* x509 = getX509(sslCtx->getSSLCtx()); auto guard = folly::makeGuard([x509] { X509_free(x509); }); auto cn = SSLUtil::getCommonName(x509); if (!cn) { throw std::runtime_error(folly::to<string>("Cannot get CN for X509 ", cert.certPath)); } auto altName = SSLUtil::getSubjectAltName(x509); VLOG(2) << "cert " << cert.certPath << " CN: " << *cn; if (altName) { altName->sort(); VLOG(2) << "cert " << cert.certPath << " SAN: " << flattenList(*altName); } else { VLOG(2) << "cert " << cert.certPath << " SAN: " << "{none}"; } if (numCerts == 1) { commonName = *cn; subjectAltName = std::move(altName); } else { if (commonName != *cn) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same CN as ", lastCertPath)); } if (altName == nullptr) { if (subjectAltName != nullptr) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same SAN as ", lastCertPath)); } } else { if ((subjectAltName == nullptr) || (*altName != *subjectAltName)) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same SAN as ", lastCertPath)); } } } lastCertPath = cert.certPath; // TODO t4438250 - Add ECDSA support to the crypto_ssl offload server // so we can avoid storing the ECDSA private key in the // address space of the Internet-facing process. For // now, if cert name includes "-EC" to denote elliptic // curve, we load its private key even if the server as // a whole has been configured for async crypto. if (ctxConfig.isLocalPrivateKey || (cert.certPath.find("-EC") != std::string::npos)) { // The private key lives in the same process // This needs to be called before loadPrivateKey(). if (!cert.passwordPath.empty()) { auto sslPassword = std::make_shared<PasswordInFile>(cert.passwordPath); sslCtx->passwordCollector(sslPassword); } try { sslCtx->loadPrivateKey(cert.keyPath.c_str()); } catch (const std::exception& ex) { // Throw an error that includes the key path, so the user can tell // which key had a problem. string msg = folly::to<string>("error loading private SSL key ", cert.keyPath, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } } } if (!ctxConfig.isLocalPrivateKey) { enableAsyncCrypto(sslCtx); } // Let the server pick the highest performing cipher from among the client's // choices. // // Let's use a unique private key for all DH key exchanges. // // Because some old implementations choke on empty fragments, most SSL // applications disable them (it's part of SSL_OP_ALL). This // will improve performance and decrease write buffer fragmentation. sslCtx->setOptions(SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE | SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); // Configure SSL ciphers list if (!ctxConfig.tls11Ciphers.empty()) { // FIXME: create a dummy SSL_CTX for cipher testing purpose? It can // remove the ordering dependency // Test to see if the specified TLS1.1 ciphers are valid. Note that // these will be overwritten by the ciphers() call below. sslCtx->setCiphersOrThrow(ctxConfig.tls11Ciphers); } // Important that we do this *after* checking the TLS1.1 ciphers above, // since we test their validity by actually setting them. sslCtx->ciphers(ctxConfig.sslCiphers); // Use a fix DH param DH* dh = get_dh2048(); SSL_CTX_set_tmp_dh(sslCtx->getSSLCtx(), dh); DH_free(dh); const string& curve = ctxConfig.eccCurveName; if (!curve.empty()) { set_key_from_curve(sslCtx->getSSLCtx(), curve); } if (!ctxConfig.clientCAFile.empty()) { try { sslCtx->setVerificationOption(SSLContext::VERIFY_REQ_CLIENT_CERT); sslCtx->loadTrustedCertificates(ctxConfig.clientCAFile.c_str()); sslCtx->loadClientCAList(ctxConfig.clientCAFile.c_str()); } catch (const std::exception& ex) { string msg = folly::to<string>("error loading client CA", ctxConfig.clientCAFile, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } } // - start - SSL session cache config // the internal cache never does what we want (per-thread-per-vip). // Disable it. SSLSessionCacheManager will set it appropriately. SSL_CTX_set_session_cache_mode(sslCtx->getSSLCtx(), SSL_SESS_CACHE_OFF); SSL_CTX_set_timeout(sslCtx->getSSLCtx(), cacheOptions.sslCacheTimeout.count()); std::unique_ptr<SSLSessionCacheManager> sessionCacheManager; if (ctxConfig.sessionCacheEnabled && cacheOptions.maxSSLCacheSize > 0 && cacheOptions.sslCacheFlushSize > 0) { sessionCacheManager = folly::make_unique<SSLSessionCacheManager>( cacheOptions.maxSSLCacheSize, cacheOptions.sslCacheFlushSize, sslCtx.get(), vipAddress, commonName, eventBase_, stats_, externalCache); } // - end - SSL session cache config std::unique_ptr<TLSTicketKeyManager> ticketManager = createTicketManagerHelper(sslCtx, ticketSeeds, ctxConfig, stats_); // finalize sslCtx setup by the individual features supported by openssl ctxSetupByOpensslFeature(sslCtx, ctxConfig); try { insert(sslCtx, std::move(sessionCacheManager), std::move(ticketManager), ctxConfig.isDefault); } catch (const std::exception& ex) { string msg = folly::to<string>("Error adding certificate : ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } }