2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
10 #include <folly/wangle/ssl/SSLContextManager.h>
12 #include <folly/wangle/ssl/ClientHelloExtStats.h>
13 #include <folly/wangle/ssl/DHParam.h>
14 #include <folly/wangle/ssl/PasswordInFile.h>
15 #include <folly/wangle/ssl/SSLCacheOptions.h>
16 #include <folly/wangle/ssl/SSLSessionCacheManager.h>
17 #include <folly/wangle/ssl/SSLUtil.h>
18 #include <folly/wangle/ssl/TLSTicketKeyManager.h>
19 #include <folly/wangle/ssl/TLSTicketKeySeeds.h>
21 #include <folly/Conv.h>
22 #include <folly/ScopeGuard.h>
23 #include <folly/String.h>
25 #include <openssl/asn1.h>
26 #include <openssl/ssl.h>
28 #include <folly/io/async/EventBase.h>
30 #define OPENSSL_MISSING_FEATURE(name) \
32 throw std::runtime_error("missing " #name " support in openssl"); \
37 using std::shared_ptr;
40 * SSLContextManager helps to create and manage all SSL_CTX,
41 * SSLSessionCacheManager and TLSTicketManager for a listening
42 * VIP:PORT. (Note, in SNI, a listening VIP:PORT can have >1 SSL_CTX(s)).
44 * Other responsibilities:
45 * 1. It also handles the SSL_CTX selection after getting the tlsext_hostname
46 * in the client hello message.
49 * 1. Each listening VIP:PORT serving SSL should have one SSLContextManager.
50 * It maps to Acceptor in the wangle vocabulary.
52 * 2. Create a SSLContextConfig object (e.g. by parsing the JSON config).
54 * 3. Call SSLContextManager::addSSLContextConfig() which will
55 * then create and configure the SSL_CTX
57 * Note: Each Acceptor, with SSL support, should have one SSLContextManager to
58 * manage all SSL_CTX for the VIP:PORT.
65 X509* getX509(SSL_CTX* ctx) {
66 SSL* ssl = SSL_new(ctx);
67 SSL_set_connect_state(ssl);
68 X509* x509 = SSL_get_certificate(ssl);
69 CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509);
74 void set_key_from_curve(SSL_CTX* ctx, const std::string& curveName) {
75 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
76 #ifndef OPENSSL_NO_ECDH
77 EC_KEY* ecdh = nullptr;
81 * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
82 * from RFC 4492 section 5.1.1, or explicitly described curves over
83 * binary fields. OpenSSL only supports the "named curves", which provide
84 * maximum interoperability.
87 nid = OBJ_sn2nid(curveName.c_str());
89 LOG(FATAL) << "Unknown curve name:" << curveName.c_str();
92 ecdh = EC_KEY_new_by_curve_name(nid);
93 if (ecdh == nullptr) {
94 LOG(FATAL) << "Unable to create curve:" << curveName.c_str();
98 SSL_CTX_set_tmp_ecdh(ctx, ecdh);
104 // Helper to create TLSTicketKeyManger and aware of the needed openssl
106 std::unique_ptr<TLSTicketKeyManager> createTicketManagerHelper(
107 std::shared_ptr<folly::SSLContext> ctx,
108 const TLSTicketKeySeeds* ticketSeeds,
109 const SSLContextConfig& ctxConfig,
112 std::unique_ptr<TLSTicketKeyManager> ticketManager;
113 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
114 if (ticketSeeds && ctxConfig.sessionTicketEnabled) {
115 ticketManager = folly::make_unique<TLSTicketKeyManager>(ctx.get(), stats);
116 ticketManager->setTLSTicketKeySeeds(
117 ticketSeeds->oldSeeds,
118 ticketSeeds->currentSeeds,
119 ticketSeeds->newSeeds);
121 ctx->setOptions(SSL_OP_NO_TICKET);
124 if (ticketSeeds && ctxConfig.sessionTicketEnabled) {
125 OPENSSL_MISSING_FEATURE(TLSTicket);
128 return ticketManager;
131 std::string flattenList(const std::list<std::string>& list) {
134 for (auto& item : list) {
147 SSLContextManager::~SSLContextManager() {}
149 SSLContextManager::SSLContextManager(
150 EventBase* eventBase,
151 const std::string& vipName,
155 eventBase_(eventBase),
159 void SSLContextManager::addSSLContextConfig(
160 const SSLContextConfig& ctxConfig,
161 const SSLCacheOptions& cacheOptions,
162 const TLSTicketKeySeeds* ticketSeeds,
163 const folly::SocketAddress& vipAddress,
164 const std::shared_ptr<SSLCacheProvider>& externalCache) {
166 unsigned numCerts = 0;
167 std::string commonName;
168 std::string lastCertPath;
169 std::unique_ptr<std::list<std::string>> subjectAltName;
170 auto sslCtx = std::make_shared<SSLContext>(ctxConfig.sslVersion);
171 for (const auto& cert : ctxConfig.certificates) {
173 sslCtx->loadCertificate(cert.certPath.c_str());
174 } catch (const std::exception& ex) {
175 // The exception isn't very useful without the certificate path name,
176 // so throw a new exception that includes the path to the certificate.
177 string msg = folly::to<string>("error loading SSL certificate ",
179 folly::exceptionStr(ex));
181 throw std::runtime_error(msg);
184 // Verify that the Common Name and (if present) Subject Alternative Names
185 // are the same for all the certs specified for the SSL context.
187 X509* x509 = getX509(sslCtx->getSSLCtx());
188 auto guard = folly::makeGuard([x509] { X509_free(x509); });
189 auto cn = SSLUtil::getCommonName(x509);
191 throw std::runtime_error(folly::to<string>("Cannot get CN for X509 ",
194 auto altName = SSLUtil::getSubjectAltName(x509);
195 VLOG(2) << "cert " << cert.certPath << " CN: " << *cn;
198 VLOG(2) << "cert " << cert.certPath << " SAN: " << flattenList(*altName);
200 VLOG(2) << "cert " << cert.certPath << " SAN: " << "{none}";
204 subjectAltName = std::move(altName);
206 if (commonName != *cn) {
207 throw std::runtime_error(folly::to<string>("X509 ", cert.certPath,
208 " does not have same CN as ",
211 if (altName == nullptr) {
212 if (subjectAltName != nullptr) {
213 throw std::runtime_error(folly::to<string>("X509 ", cert.certPath,
214 " does not have same SAN as ",
218 if ((subjectAltName == nullptr) || (*altName != *subjectAltName)) {
219 throw std::runtime_error(folly::to<string>("X509 ", cert.certPath,
220 " does not have same SAN as ",
225 lastCertPath = cert.certPath;
227 // TODO t4438250 - Add ECDSA support to the crypto_ssl offload server
228 // so we can avoid storing the ECDSA private key in the
229 // address space of the Internet-facing process. For
230 // now, if cert name includes "-EC" to denote elliptic
231 // curve, we load its private key even if the server as
232 // a whole has been configured for async crypto.
233 if (ctxConfig.isLocalPrivateKey ||
234 (cert.certPath.find("-EC") != std::string::npos)) {
235 // The private key lives in the same process
237 // This needs to be called before loadPrivateKey().
238 if (!cert.passwordPath.empty()) {
239 auto sslPassword = std::make_shared<PasswordInFile>(cert.passwordPath);
240 sslCtx->passwordCollector(sslPassword);
244 sslCtx->loadPrivateKey(cert.keyPath.c_str());
245 } catch (const std::exception& ex) {
246 // Throw an error that includes the key path, so the user can tell
247 // which key had a problem.
248 string msg = folly::to<string>("error loading private SSL key ",
250 folly::exceptionStr(ex));
252 throw std::runtime_error(msg);
256 if (!ctxConfig.isLocalPrivateKey) {
257 enableAsyncCrypto(sslCtx);
260 // Let the server pick the highest performing cipher from among the client's
263 // Let's use a unique private key for all DH key exchanges.
265 // Because some old implementations choke on empty fragments, most SSL
266 // applications disable them (it's part of SSL_OP_ALL). This
267 // will improve performance and decrease write buffer fragmentation.
268 sslCtx->setOptions(SSL_OP_CIPHER_SERVER_PREFERENCE |
269 SSL_OP_SINGLE_DH_USE |
270 SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
272 // Configure SSL ciphers list
273 if (!ctxConfig.tls11Ciphers.empty()) {
274 // FIXME: create a dummy SSL_CTX for cipher testing purpose? It can
275 // remove the ordering dependency
277 // Test to see if the specified TLS1.1 ciphers are valid. Note that
278 // these will be overwritten by the ciphers() call below.
279 sslCtx->setCiphersOrThrow(ctxConfig.tls11Ciphers);
282 // Important that we do this *after* checking the TLS1.1 ciphers above,
283 // since we test their validity by actually setting them.
284 sslCtx->ciphers(ctxConfig.sslCiphers);
286 // Use a fix DH param
287 DH* dh = get_dh2048();
288 SSL_CTX_set_tmp_dh(sslCtx->getSSLCtx(), dh);
291 const string& curve = ctxConfig.eccCurveName;
292 if (!curve.empty()) {
293 set_key_from_curve(sslCtx->getSSLCtx(), curve);
296 if (!ctxConfig.clientCAFile.empty()) {
298 sslCtx->setVerificationOption(SSLContext::VERIFY_REQ_CLIENT_CERT);
299 sslCtx->loadTrustedCertificates(ctxConfig.clientCAFile.c_str());
300 sslCtx->loadClientCAList(ctxConfig.clientCAFile.c_str());
301 } catch (const std::exception& ex) {
302 string msg = folly::to<string>("error loading client CA",
303 ctxConfig.clientCAFile, ": ",
304 folly::exceptionStr(ex));
306 throw std::runtime_error(msg);
310 // - start - SSL session cache config
311 // the internal cache never does what we want (per-thread-per-vip).
312 // Disable it. SSLSessionCacheManager will set it appropriately.
313 SSL_CTX_set_session_cache_mode(sslCtx->getSSLCtx(), SSL_SESS_CACHE_OFF);
314 SSL_CTX_set_timeout(sslCtx->getSSLCtx(),
315 cacheOptions.sslCacheTimeout.count());
316 std::unique_ptr<SSLSessionCacheManager> sessionCacheManager;
317 if (ctxConfig.sessionCacheEnabled &&
318 cacheOptions.maxSSLCacheSize > 0 &&
319 cacheOptions.sslCacheFlushSize > 0) {
320 sessionCacheManager =
321 folly::make_unique<SSLSessionCacheManager>(
322 cacheOptions.maxSSLCacheSize,
323 cacheOptions.sslCacheFlushSize,
331 // - end - SSL session cache config
333 std::unique_ptr<TLSTicketKeyManager> ticketManager =
334 createTicketManagerHelper(sslCtx, ticketSeeds, ctxConfig, stats_);
336 // finalize sslCtx setup by the individual features supported by openssl
337 ctxSetupByOpensslFeature(sslCtx, ctxConfig);
341 std::move(sessionCacheManager),
342 std::move(ticketManager),
343 ctxConfig.isDefault);
344 } catch (const std::exception& ex) {
345 string msg = folly::to<string>("Error adding certificate : ",
346 folly::exceptionStr(ex));
348 throw std::runtime_error(msg);
353 #ifdef PROXYGEN_HAVE_SERVERNAMECALLBACK
354 SSLContext::ServerNameCallbackResult
355 SSLContextManager::serverNameCallback(SSL* ssl) {
356 shared_ptr<SSLContext> ctx;
358 const char* sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
360 VLOG(6) << "Server Name (tlsext_hostname) is missing";
361 if (clientHelloTLSExtStats_) {
362 clientHelloTLSExtStats_->recordAbsentHostname();
364 return SSLContext::SERVER_NAME_NOT_FOUND;
366 size_t snLen = strlen(sn);
367 VLOG(6) << "Server Name (SNI TLS extension): '" << sn << "' ";
369 // FIXME: This code breaks the abstraction. Suggestion?
370 AsyncSSLSocket* sslSocket = AsyncSSLSocket::getFromSSL(ssl);
373 DNString dnstr(sn, snLen);
377 // Try exact match first
378 ctx = getSSLCtx(dnstr);
380 sslSocket->switchServerSSLContext(ctx);
381 if (clientHelloTLSExtStats_) {
382 clientHelloTLSExtStats_->recordMatch();
384 return SSLContext::SERVER_NAME_FOUND;
387 ctx = getSSLCtxBySuffix(dnstr);
389 sslSocket->switchServerSSLContext(ctx);
390 if (clientHelloTLSExtStats_) {
391 clientHelloTLSExtStats_->recordMatch();
393 return SSLContext::SERVER_NAME_FOUND;
396 // Give the noMatchFn one chance to add the correct cert
398 while (count++ == 0 && noMatchFn_ && noMatchFn_(sn));
400 VLOG(6) << folly::stringPrintf("Cannot find a SSL_CTX for \"%s\"", sn);
402 if (clientHelloTLSExtStats_) {
403 clientHelloTLSExtStats_->recordNotMatch();
405 return SSLContext::SERVER_NAME_NOT_FOUND;
409 // Consolidate all SSL_CTX setup which depends on openssl version/feature
411 SSLContextManager::ctxSetupByOpensslFeature(
412 shared_ptr<folly::SSLContext> sslCtx,
413 const SSLContextConfig& ctxConfig) {
414 // Disable compression - profiling shows this to be very expensive in
415 // terms of CPU and memory consumption.
417 #ifdef SSL_OP_NO_COMPRESSION
418 sslCtx->setOptions(SSL_OP_NO_COMPRESSION);
421 // Enable early release of SSL buffers to reduce the memory footprint
422 #ifdef SSL_MODE_RELEASE_BUFFERS
423 sslCtx->getSSLCtx()->mode |= SSL_MODE_RELEASE_BUFFERS;
425 #ifdef SSL_MODE_EARLY_RELEASE_BBIO
426 sslCtx->getSSLCtx()->mode |= SSL_MODE_EARLY_RELEASE_BBIO;
429 // This number should (probably) correspond to HTTPSession::kMaxReadSize
430 // For now, this number must also be large enough to accommodate our
431 // largest certificate, because some older clients (IE6/7) require the
432 // cert to be in a single fragment.
433 #ifdef SSL_CTRL_SET_MAX_SEND_FRAGMENT
434 SSL_CTX_set_max_send_fragment(sslCtx->getSSLCtx(), 8000);
437 // Specify cipher(s) to be used for TLS1.1 client
438 if (!ctxConfig.tls11Ciphers.empty()) {
439 #ifdef PROXYGEN_HAVE_SERVERNAMECALLBACK
440 // Specified TLS1.1 ciphers are valid
441 sslCtx->addClientHelloCallback(
443 &SSLContext::switchCiphersIfTLS11,
445 std::placeholders::_1,
446 ctxConfig.tls11Ciphers
450 OPENSSL_MISSING_FEATURE(SNI);
454 // NPN (Next Protocol Negotiation)
455 if (!ctxConfig.nextProtocols.empty()) {
456 #ifdef OPENSSL_NPN_NEGOTIATED
457 sslCtx->setRandomizedAdvertisedNextProtocols(ctxConfig.nextProtocols);
459 OPENSSL_MISSING_FEATURE(NPN);
464 #ifdef PROXYGEN_HAVE_SERVERNAMECALLBACK
465 noMatchFn_ = ctxConfig.sniNoMatchFn;
466 if (ctxConfig.isDefault) {
468 throw std::runtime_error(">1 X509 is set as default");
471 defaultCtx_ = sslCtx;
472 defaultCtx_->setServerNameCallback(
473 std::bind(&SSLContextManager::serverNameCallback, this,
474 std::placeholders::_1));
477 if (ctxs_.size() > 1) {
478 OPENSSL_MISSING_FEATURE(SNI);
484 SSLContextManager::insert(shared_ptr<SSLContext> sslCtx,
485 std::unique_ptr<SSLSessionCacheManager> smanager,
486 std::unique_ptr<TLSTicketKeyManager> tmanager,
487 bool defaultFallback) {
488 X509* x509 = getX509(sslCtx->getSSLCtx());
489 auto guard = folly::makeGuard([x509] { X509_free(x509); });
490 auto cn = SSLUtil::getCommonName(x509);
492 throw std::runtime_error("Cannot get CN");
496 * Some notes from RFC 2818. Only for future quick references in case of bugs
498 * RFC 2818 section 3.1:
500 * If a subjectAltName extension of type dNSName is present, that MUST
501 * be used as the identity. Otherwise, the (most specific) Common Name
502 * field in the Subject field of the certificate MUST be used. Although
503 * the use of the Common Name is existing practice, it is deprecated and
504 * Certification Authorities are encouraged to use the dNSName instead.
506 * In some cases, the URI is specified as an IP address rather than a
507 * hostname. In this case, the iPAddress subjectAltName must be present
508 * in the certificate and must exactly match the IP in the URI.
512 // Not sure if we ever get this kind of X509...
513 // If we do, assume '*' is always in the CN and ignore all subject alternative
515 if (cn->length() == 1 && (*cn)[0] == '*') {
516 if (!defaultFallback) {
517 throw std::runtime_error("STAR X509 is not the default");
519 ctxs_.emplace_back(sslCtx);
520 sessionCacheManagers_.emplace_back(std::move(smanager));
521 ticketManagers_.emplace_back(std::move(tmanager));
526 insertSSLCtxByDomainName(cn->c_str(), cn->length(), sslCtx);
528 // Insert by subject alternative name(s)
529 auto altNames = SSLUtil::getSubjectAltName(x509);
531 for (auto& name : *altNames) {
532 insertSSLCtxByDomainName(name.c_str(), name.length(), sslCtx);
536 ctxs_.emplace_back(sslCtx);
537 sessionCacheManagers_.emplace_back(std::move(smanager));
538 ticketManagers_.emplace_back(std::move(tmanager));
542 SSLContextManager::insertSSLCtxByDomainName(const char* dn, size_t len,
543 shared_ptr<SSLContext> sslCtx) {
545 insertSSLCtxByDomainNameImpl(dn, len, sslCtx);
546 } catch (const std::runtime_error& ex) {
550 LOG(ERROR) << ex.what() << " DN=" << dn;
555 SSLContextManager::insertSSLCtxByDomainNameImpl(const char* dn, size_t len,
556 shared_ptr<SSLContext> sslCtx)
559 folly::stringPrintf("Adding CN/Subject-alternative-name \"%s\" for "
562 // Only support wildcard domains which are prefixed exactly by "*." .
563 // "*" appearing at other locations is not accepted.
565 if (len > 2 && dn[0] == '*') {
567 // skip the first '*'
571 throw std::runtime_error(
572 "Invalid wildcard CN/subject-alternative-name \"" + std::string(dn) + "\" "
573 "(only allow character \".\" after \"*\"");
577 if (len == 1 && *dn == '.') {
578 throw std::runtime_error("X509 has only '.' in the CN or subject alternative name "
579 "(after removing any preceding '*')");
582 if (strchr(dn, '*')) {
583 throw std::runtime_error("X509 has '*' in the the CN or subject alternative name "
584 "(after removing any preceding '*')");
587 DNString dnstr(dn, len);
588 const auto v = dnMap_.find(dnstr);
589 if (v == dnMap_.end()) {
590 dnMap_.emplace(dnstr, sslCtx);
591 } else if (v->second == sslCtx) {
592 VLOG(6)<< "Duplicate CN or subject alternative name found in the same X509."
593 " Ignore the later name.";
595 throw std::runtime_error("Duplicate CN or subject alternative name found: \"" +
596 std::string(dnstr.c_str()) + "\"");
600 shared_ptr<SSLContext>
601 SSLContextManager::getSSLCtxBySuffix(const DNString& dnstr) const
605 if ((dot = dnstr.find_first_of(".")) != DNString::npos) {
606 DNString suffixDNStr(dnstr, dot);
607 const auto v = dnMap_.find(suffixDNStr);
608 if (v != dnMap_.end()) {
609 VLOG(6) << folly::stringPrintf("\"%s\" is a willcard match to \"%s\"",
610 dnstr.c_str(), suffixDNStr.c_str());
615 VLOG(6) << folly::stringPrintf("\"%s\" is not a wildcard match",
617 return shared_ptr<SSLContext>();
620 shared_ptr<SSLContext>
621 SSLContextManager::getSSLCtx(const DNString& dnstr) const
623 const auto v = dnMap_.find(dnstr);
624 if (v == dnMap_.end()) {
625 VLOG(6) << folly::stringPrintf("\"%s\" is not an exact match",
627 return shared_ptr<SSLContext>();
629 VLOG(6) << folly::stringPrintf("\"%s\" is an exact match", dnstr.c_str());
634 shared_ptr<SSLContext>
635 SSLContextManager::getDefaultSSLCtx() const {
640 SSLContextManager::reloadTLSTicketKeys(
641 const std::vector<std::string>& oldSeeds,
642 const std::vector<std::string>& currentSeeds,
643 const std::vector<std::string>& newSeeds) {
644 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
645 for (auto& tmgr: ticketManagers_) {
646 tmgr->setTLSTicketKeySeeds(oldSeeds, currentSeeds, newSeeds);