ssl: Use default ALPN handler This default ALPN handler is called when ALPN protocols are configured without a ALPN selection callback on a server. Update-Note: Calling `SSL_CTX_set_alpn_protos` or `SSL_set_alpn_protos` without calling `SSL_CTX_set_alpn_select_cb` will no longer fail ALPN anymore. Instead, the protocol will be picked by walking down the client protocol list. Bug: 411171274 Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I81cb3820c7aa1f97fe90fd7c7fe5b4616a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/98987 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index d9eca9b..56682da 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -3425,7 +3425,13 @@ // `protos`. `protos` must be in wire-format (i.e. a series of non-empty, 8-bit // length-prefixed strings), or the empty string to disable ALPN. It returns // zero on success and one on failure. Configuring a non-empty string enables -// ALPN on a client. +// ALPN on a client. On a client, it will advertise these protocols. On a +// server, it will use them to automatically select a protocol if the client +// supports ALPN and no select callback is set. +// +// Those configuring HTTP/2 must also configure the cipher suite set on the +// SSL_CTX so that the selected cipher is compliant with RFC 7540 Section 9.2. +// Servers should install their own ALPN callback to comply with the RFC. // // WARNING: this function is dangerous because it breaks the usual return value // convention. @@ -3436,7 +3442,13 @@ // `protos` must be in wire-format (i.e. a series of non-empty, 8-bit // length-prefixed strings), or the empty string to disable ALPN. It returns // zero on success and one on failure. Configuring a non-empty string enables -// ALPN on a client. +// ALPN on a client. On a client, it will advertise these protocols. On a +// server, it will use them to automatically select a protocol if the client +// supports ALPN and no select callback is set. +// +// Those configuring HTTP/2 must also configure the cipher suite set on the +// SSL_CTX so that the selected cipher is compliant with RFC 7540 Section 9.2. +// Servers should install their own ALPN callback to comply with the RFC. // // WARNING: this function is dangerous because it breaks the usual return value // convention.
diff --git a/ssl/extensions.cc b/ssl/extensions.cc index 022714a..8787d9f 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc
@@ -16,6 +16,7 @@ #include <assert.h> #include <limits.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> @@ -38,6 +39,7 @@ #include <openssl/nid.h> #include <openssl/pool.h> #include <openssl/rand.h> +#include <openssl/span.h> #include "../crypto/bytestring/internal.h" #include "../crypto/internal.h" @@ -1031,7 +1033,7 @@ if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_signature_algorithms) || !CBB_add_u16_length_prefixed(out_compressible, &contents) || !CBB_add_u16_length_prefixed(&contents, &sigalgs_cbb)) { - return false; + return false; } // Add a fake signature algorithm. See RFC 8701. if (hs->ssl->ctx->grease_sigalgs_enabled && @@ -1501,14 +1503,51 @@ return false; } +static int default_alpn_select_cb(SSL *ssl, const uint8_t **out, + uint8_t *out_len, const uint8_t *in, + unsigned inlen, void *arg) { + auto *ssl_impl = FromOpaque(ssl); + SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get(); + if (hs == nullptr) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + bssl::Span<const uint8_t> client_protos(in, inlen); + CBS cbs = client_protos; + CBS proto; + while (CBS_len(&cbs) != 0) { + if (!CBS_get_u8_length_prefixed(&cbs, &proto) || CBS_len(&proto) == 0) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + if (ssl_alpn_list_contains_protocol(hs->config->alpn_client_proto_list, + proto)) { + *out = CBS_data(&proto); + *out_len = static_cast<uint8_t>(CBS_len(&proto)); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} + bool ssl_negotiate_alpn(SSL_HANDSHAKE *hs, uint8_t *out_alert, const SSL_CLIENT_HELLO *client_hello) { SSLImpl *const ssl = hs->ssl; CBS contents; - if (ssl->ctx->alpn_select_cb == nullptr || - !ssl_client_hello_get_extension( - client_hello, &contents, - TLSEXT_TYPE_application_layer_protocol_negotiation)) { + const bool has_extension = ssl_client_hello_get_extension( + client_hello, &contents, + TLSEXT_TYPE_application_layer_protocol_negotiation); + + auto alpn_select_cb = ssl->ctx->alpn_select_cb; + void *alpn_select_cb_arg = ssl->ctx->alpn_select_cb_arg; + + if (alpn_select_cb == nullptr && + !hs->config->alpn_client_proto_list.empty()) { + alpn_select_cb = default_alpn_select_cb; + alpn_select_cb_arg = nullptr; + } + + if (!has_extension || alpn_select_cb == nullptr) { if (SSL_is_quic(ssl)) { // ALPN is required when QUIC is used. OPENSSL_PUT_ERROR(SSL, SSL_R_NO_APPLICATION_PROTOCOL); @@ -1533,12 +1572,11 @@ // `protocol_name_list` fits in `unsigned` because TLS extensions use 16-bit // lengths. - const uint8_t *selected; - uint8_t selected_len; - int ret = ssl->ctx->alpn_select_cb( + const uint8_t *selected = nullptr; + uint8_t selected_len = 0; + int ret = alpn_select_cb( ssl, &selected, &selected_len, CBS_data(&protocol_name_list), - static_cast<unsigned>(CBS_len(&protocol_name_list)), - ssl->ctx->alpn_select_cb_arg); + static_cast<unsigned>(CBS_len(&protocol_name_list)), alpn_select_cb_arg); // ALPN is required when QUIC is used. if (SSL_is_quic(ssl) && (ret == SSL_TLSEXT_ERR_NOACK || ret == SSL_TLSEXT_ERR_ALERT_WARNING)) {
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index d9185ff..b1a8941 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -21,6 +21,7 @@ #include <algorithm> #include <iterator> #include <limits> +#include <optional> #include <string> #include <utility> #include <vector> @@ -43,6 +44,7 @@ #include <openssl/pem.h> #include <openssl/rand.h> #include <openssl/sha.h> +#include <openssl/span.h> #include <openssl/ssl.h> #include <openssl/x509.h> @@ -9589,6 +9591,90 @@ check_alpn_proto({}); } +TEST(SSLTest, DeclarativeServerALPN) { + bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method())); + bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(client_ctx); + ASSERT_TRUE(server_ctx); + bssl::UniquePtr<X509> cert = GetTestCertificate(); + bssl::UniquePtr<EVP_PKEY> key = GetTestKey(); + ASSERT_TRUE(cert); + ASSERT_TRUE(key); + ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get())); + ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get())); + SSL_CTX_set_custom_verify(client_ctx.get(), SSL_VERIFY_PEER, + AcceptAnyCertificate); + + // Helper to run connection and check negotiated ALPN. + auto check_negotiation = + [&](bssl::Span<const uint8_t> client_protos, + bssl::Span<const uint8_t> server_protos, + std::optional<bssl::Span<const uint8_t>> expected_alpn) { + ASSERT_EQ( + 0, SSL_CTX_set_alpn_protos(client_ctx.get(), client_protos.data(), + client_protos.size())); + ASSERT_EQ( + 0, SSL_CTX_set_alpn_protos(server_ctx.get(), server_protos.data(), + server_protos.size())); + + bssl::UniquePtr<SSL> client, server; + bool success = ConnectClientAndServer( + &client, &server, client_ctx.get(), server_ctx.get()); + if (!expected_alpn.has_value()) { + EXPECT_FALSE(success); + return; + } + + ASSERT_TRUE(success); + const uint8_t *alpn = nullptr; + unsigned alpn_len = 0; + SSL_get0_alpn_selected(client.get(), &alpn, &alpn_len); + EXPECT_EQ(Bytes(*expected_alpn), Bytes(alpn, alpn_len)); + SSL_get0_alpn_selected(server.get(), &alpn, &alpn_len); + EXPECT_EQ(Bytes(*expected_alpn), Bytes(alpn, alpn_len)); + }; + const auto kBar = bssl::StringAsBytes("bar"); + const auto kH2 = bssl::StringAsBytes("h2"); + + // 1. Basic match: Client ["foo", "bar"], Server ["bar", "baz"] -> + // negotiates "bar" + const uint8_t kClient1[] = {3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}; + const uint8_t kServer1[] = {3, 'b', 'a', 'r', 3, 'b', 'a', 'z'}; + check_negotiation(kClient1, kServer1, kBar); + + // 2. Client preference: Client ["bar", "foo"], Server ["foo", "bar"] -> + // negotiates "bar" + const uint8_t kClient2[] = {3, 'b', 'a', 'r', 3, 'f', 'o', 'o'}; + const uint8_t kServer2[] = {3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}; + check_negotiation(kClient2, kServer2, kBar); + + // 3. No overlap -> Handshake fails + const uint8_t kClient3[] = {3, 'f', 'o', 'o'}; + const uint8_t kServer3[] = {3, 'b', 'a', 'r'}; + check_negotiation(kClient3, kServer3, std::nullopt); + + // 4. HTTP/2 example. + SSL_CTX_set_min_proto_version(client_ctx.get(), TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(client_ctx.get(), TLS1_2_VERSION); + SSL_CTX_set_min_proto_version(server_ctx.get(), TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(server_ctx.get(), TLS1_2_VERSION); + + const uint8_t kClientH2[] = {2, 'h', '2', 8, 'h', 't', + 't', 'p', '/', '1', '.', '1'}; + const uint8_t kServerH2[] = {2, 'h', '2'}; + + ASSERT_TRUE( + SSL_CTX_set_cipher_list(client_ctx.get(), "ECDHE-RSA-AES128-SHA256")); + ASSERT_TRUE( + SSL_CTX_set_cipher_list(server_ctx.get(), "ECDHE-RSA-AES128-SHA256")); + + // Yes, this is a violation of RFC 7540, Section 9.2. + // However, we made a conscious decision to negotiate "h2" anyway. + // Users should install their own ALPN callback to enforce the cipher suite + // restrictions. + check_negotiation(kClientH2, kServerH2, kH2); +} + TEST(SSLTest, AcceptedPeerCertTypesConfig) { bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); ASSERT_TRUE(ctx);