bud_error_t bud_config_new_ssl_ctx(bud_config_t* config, bud_context_t* context) { SSL_CTX* ctx; int ecdh_nid; EC_KEY* ecdh; bud_error_t err; int options; int r; const char* ticket_key; size_t max_len; if (context->backend != NULL) { if (context->backend->keepalive == -1) context->backend->keepalive = kBudDefaultKeepalive; r = bud_config_str_to_addr(context->backend->host, context->backend->port, &context->backend->addr); if (r != 0) return bud_error_num(kBudErrPton, r); } /* Decode ticket_key */ ticket_key = context->ticket_key == NULL ? config->frontend.ticket_key : context->ticket_key; if (ticket_key != NULL) { max_len = sizeof(context->ticket_key_storage); if (bud_base64_decode(context->ticket_key_storage, max_len, ticket_key, strlen(ticket_key)) < max_len) { return bud_error(kBudErrSmallTicketKey); } } /* Choose method, tlsv1_2 by default */ if (config->frontend.method == NULL) { if (strcmp(config->frontend.security, "tls1.1") == 0) config->frontend.method = TLSv1_1_server_method(); else if (strcmp(config->frontend.security, "tls1.0") == 0) config->frontend.method = TLSv1_server_method(); else if (strcmp(config->frontend.security, "tls1.2") == 0) config->frontend.method = TLSv1_2_server_method(); else if (strcmp(config->frontend.security, "ssl3") == 0) config->frontend.method = SSLv3_server_method(); else config->frontend.method = SSLv23_server_method(); } ctx = SSL_CTX_new(config->frontend.method); if (ctx == NULL) return bud_error_str(kBudErrNoMem, "SSL_CTX"); /* Disable sessions, they won't work with cluster anyway */ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); if (config->frontend.max_send_fragment) SSL_CTX_set_max_send_fragment(ctx, config->frontend.max_send_fragment); if (ticket_key != NULL) { SSL_CTX_set_tlsext_ticket_keys(ctx, context->ticket_key_storage, sizeof(context->ticket_key_storage)); } /* ECDH curve selection */ if (context->ecdh != NULL || config->frontend.ecdh != NULL) { if (context->ecdh != NULL) ecdh_nid = OBJ_sn2nid(context->ecdh); else ecdh_nid = OBJ_sn2nid(config->frontend.ecdh); if (ecdh_nid == NID_undef) { ecdh = NULL; err = bud_error_str(kBudErrECDHNotFound, context->ecdh == NULL ? config->frontend.ecdh : context->ecdh); goto fatal; } ecdh = EC_KEY_new_by_curve_name(ecdh_nid); if (ecdh == NULL) { err = bud_error_str(kBudErrNoMem, "EC_KEY"); goto fatal; } SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } ecdh = NULL; /* Cipher suites */ if (context->ciphers != NULL) SSL_CTX_set_cipher_list(ctx, context->ciphers); else if (config->frontend.ciphers != NULL) SSL_CTX_set_cipher_list(ctx, config->frontend.ciphers); /* Disable SSL2 */ options = SSL_OP_NO_SSLv2 | SSL_OP_ALL; if (!config->frontend.ssl3) options |= SSL_OP_NO_SSLv3; if (config->frontend.server_preference) options |= SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_CTX_set_options(ctx, options); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (config->context_count != 0) { SSL_CTX_set_tlsext_servername_callback(ctx, bud_config_select_sni_context); SSL_CTX_set_tlsext_servername_arg(ctx, config); } #endif /* SSL_CTRL_SET_TLSEXT_SERVERNAME_CB */ #ifdef OPENSSL_NPN_NEGOTIATED context->npn_line = bud_config_encode_npn(config, context->npn, &context->npn_line_len, &err); if (!bud_is_ok(err)) goto fatal; if (context->npn_line != NULL) { SSL_CTX_set_next_protos_advertised_cb(ctx, bud_config_advertise_next_proto, context); } #else /* !OPENSSL_NPN_NEGOTIATED */ err = bud_error(kBudErrNPNNotSupported); goto fatal; #endif /* OPENSSL_NPN_NEGOTIATED */ SSL_CTX_set_tlsext_status_cb(ctx, bud_client_stapling_cb); context->ctx = ctx; return bud_ok(); fatal: if (ecdh != NULL) EC_KEY_free(ecdh); SSL_CTX_free(ctx); return err; }
// Consolidate all SSL_CTX setup which depends on openssl version/feature void SSLContextManager::ctxSetupByOpensslFeature( shared_ptr<folly::SSLContext> sslCtx, const SSLContextConfig& ctxConfig) { // Disable compression - profiling shows this to be very expensive in // terms of CPU and memory consumption. // #ifdef SSL_OP_NO_COMPRESSION sslCtx->setOptions(SSL_OP_NO_COMPRESSION); #endif // Enable early release of SSL buffers to reduce the memory footprint #ifdef SSL_MODE_RELEASE_BUFFERS sslCtx->getSSLCtx()->mode |= SSL_MODE_RELEASE_BUFFERS; #endif #ifdef SSL_MODE_EARLY_RELEASE_BBIO sslCtx->getSSLCtx()->mode |= SSL_MODE_EARLY_RELEASE_BBIO; #endif // This number should (probably) correspond to HTTPSession::kMaxReadSize // For now, this number must also be large enough to accommodate our // largest certificate, because some older clients (IE6/7) require the // cert to be in a single fragment. #ifdef SSL_CTRL_SET_MAX_SEND_FRAGMENT SSL_CTX_set_max_send_fragment(sslCtx->getSSLCtx(), 8000); #endif // Specify cipher(s) to be used for TLS1.1 client if (!ctxConfig.tls11Ciphers.empty()) { #ifdef PROXYGEN_HAVE_SERVERNAMECALLBACK // Specified TLS1.1 ciphers are valid sslCtx->addClientHelloCallback( std::bind( &SSLContext::switchCiphersIfTLS11, sslCtx.get(), std::placeholders::_1, ctxConfig.tls11Ciphers ) ); #else OPENSSL_MISSING_FEATURE(SNI); #endif } // NPN (Next Protocol Negotiation) if (!ctxConfig.nextProtocols.empty()) { #ifdef OPENSSL_NPN_NEGOTIATED sslCtx->setRandomizedAdvertisedNextProtocols(ctxConfig.nextProtocols); #else OPENSSL_MISSING_FEATURE(NPN); #endif } // SNI #ifdef PROXYGEN_HAVE_SERVERNAMECALLBACK noMatchFn_ = ctxConfig.sniNoMatchFn; if (ctxConfig.isDefault) { if (defaultCtx_) { throw std::runtime_error(">1 X509 is set as default"); } defaultCtx_ = sslCtx; defaultCtx_->setServerNameCallback( std::bind(&SSLContextManager::serverNameCallback, this, std::placeholders::_1)); } #else if (ctxs_.size() > 1) { OPENSSL_MISSING_FEATURE(SNI); } #endif }
/* * Configure callbacks and other properties that can't be set directly * in the server/client CONF. */ static void configure_handshake_ctx(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, SSL_CTX *client_ctx, const SSL_TEST_CTX *test, const SSL_TEST_EXTRA_CONF *extra, CTX_DATA *server_ctx_data, CTX_DATA *server2_ctx_data, CTX_DATA *client_ctx_data) { unsigned char *ticket_keys; size_t ticket_key_len; TEST_check(SSL_CTX_set_max_send_fragment(server_ctx, test->max_fragment_size) == 1); if (server2_ctx != NULL) { TEST_check(SSL_CTX_set_max_send_fragment(server2_ctx, test->max_fragment_size) == 1); } TEST_check(SSL_CTX_set_max_send_fragment(client_ctx, test->max_fragment_size) == 1); switch (extra->client.verify_callback) { case SSL_TEST_VERIFY_ACCEPT_ALL: SSL_CTX_set_cert_verify_callback(client_ctx, &verify_accept_cb, NULL); break; case SSL_TEST_VERIFY_REJECT_ALL: SSL_CTX_set_cert_verify_callback(client_ctx, &verify_reject_cb, NULL); break; default: break; } /* link the two contexts for SNI purposes */ switch (extra->server.servername_callback) { case SSL_TEST_SERVERNAME_IGNORE_MISMATCH: SSL_CTX_set_tlsext_servername_callback(server_ctx, servername_ignore_cb); SSL_CTX_set_tlsext_servername_arg(server_ctx, server2_ctx); break; case SSL_TEST_SERVERNAME_REJECT_MISMATCH: SSL_CTX_set_tlsext_servername_callback(server_ctx, servername_reject_cb); SSL_CTX_set_tlsext_servername_arg(server_ctx, server2_ctx); break; default: break; } /* * The initial_ctx/session_ctx always handles the encrypt/decrypt of the * session ticket. This ticket_key callback is assigned to the second * session (assigned via SNI), and should never be invoked */ if (server2_ctx != NULL) SSL_CTX_set_tlsext_ticket_key_cb(server2_ctx, do_not_call_session_ticket_cb); if (extra->server.broken_session_ticket) { SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, broken_session_ticket_cb); } #ifndef OPENSSL_NO_NEXTPROTONEG if (extra->server.npn_protocols != NULL) { parse_protos(extra->server.npn_protocols, &server_ctx_data->npn_protocols, &server_ctx_data->npn_protocols_len); SSL_CTX_set_next_protos_advertised_cb(server_ctx, server_npn_cb, server_ctx_data); } if (extra->server2.npn_protocols != NULL) { parse_protos(extra->server2.npn_protocols, &server2_ctx_data->npn_protocols, &server2_ctx_data->npn_protocols_len); TEST_check(server2_ctx != NULL); SSL_CTX_set_next_protos_advertised_cb(server2_ctx, server_npn_cb, server2_ctx_data); } if (extra->client.npn_protocols != NULL) { parse_protos(extra->client.npn_protocols, &client_ctx_data->npn_protocols, &client_ctx_data->npn_protocols_len); SSL_CTX_set_next_proto_select_cb(client_ctx, client_npn_cb, client_ctx_data); } #endif if (extra->server.alpn_protocols != NULL) { parse_protos(extra->server.alpn_protocols, &server_ctx_data->alpn_protocols, &server_ctx_data->alpn_protocols_len); SSL_CTX_set_alpn_select_cb(server_ctx, server_alpn_cb, server_ctx_data); } if (extra->server2.alpn_protocols != NULL) { TEST_check(server2_ctx != NULL); parse_protos(extra->server2.alpn_protocols, &server2_ctx_data->alpn_protocols, &server2_ctx_data->alpn_protocols_len); SSL_CTX_set_alpn_select_cb(server2_ctx, server_alpn_cb, server2_ctx_data); } if (extra->client.alpn_protocols != NULL) { unsigned char *alpn_protos = NULL; size_t alpn_protos_len; parse_protos(extra->client.alpn_protocols, &alpn_protos, &alpn_protos_len); /* Reversed return value convention... */ TEST_check(SSL_CTX_set_alpn_protos(client_ctx, alpn_protos, alpn_protos_len) == 0); OPENSSL_free(alpn_protos); } /* * Use fixed session ticket keys so that we can decrypt a ticket created with * one CTX in another CTX. Don't address server2 for the moment. */ ticket_key_len = SSL_CTX_set_tlsext_ticket_keys(server_ctx, NULL, 0); ticket_keys = OPENSSL_zalloc(ticket_key_len); TEST_check(ticket_keys != NULL); TEST_check(SSL_CTX_set_tlsext_ticket_keys(server_ctx, ticket_keys, ticket_key_len) == 1); OPENSSL_free(ticket_keys); /* The default log list includes EC keys, so CT can't work without EC. */ #if !defined(OPENSSL_NO_CT) && !defined(OPENSSL_NO_EC) TEST_check(SSL_CTX_set_default_ctlog_list_file(client_ctx)); switch (extra->client.ct_validation) { case SSL_TEST_CT_VALIDATION_PERMISSIVE: TEST_check(SSL_CTX_enable_ct(client_ctx, SSL_CT_VALIDATION_PERMISSIVE)); break; case SSL_TEST_CT_VALIDATION_STRICT: TEST_check(SSL_CTX_enable_ct(client_ctx, SSL_CT_VALIDATION_STRICT)); break; case SSL_TEST_CT_VALIDATION_NONE: break; } #endif }