diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h index 2f1475410..1dbc1e7dc 100644 --- a/src/include/openssl/ssl.h +++ b/src/include/openssl/ssl.h @@ -5637,6 +5637,14 @@ enum ssl_compliance_policy_t BORINGSSL_ENUM_INT { // implementation risks of using a more obscure primitive like P-384 // dominate other considerations. ssl_compliance_policy_wpa3_192_202304, + + // ssl_compliance_policy_cnsa_202407 confingures a TLS connection to use: + // * For TLS 1.3, AES-256-GCM over AES-128-GCM over ChaCha20-Poly1305. + // + // I.e. it ensures that AES-GCM will be used whenever the client supports it. + // The cipher suite configuration mini-language can be used to similarly + // configure prior TLS versions if they are enabled. + ssl_compliance_policy_cnsa_202407, }; // SSL_CTX_set_compliance_policy configures various aspects of |ctx| based on diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc index c53234557..9788b46ed 100644 --- a/src/ssl/handshake_client.cc +++ b/src/ssl/handshake_client.cc @@ -244,23 +244,36 @@ static bool ssl_write_client_cipher_list(const SSL_HANDSHAKE *hs, CBB *out, // Add TLS 1.3 ciphers. Order ChaCha20-Poly1305 relative to AES-GCM based on // hardware support. if (hs->max_version >= TLS1_3_VERSION) { + static const uint16_t kCiphersNoAESHardware[] = { + TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff, + TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff, + TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff, + }; + static const uint16_t kCiphersAESHardware[] = { + TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff, + TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff, + TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff, + }; + static const uint16_t kCiphersCNSA[] = { + TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff, + TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff, + TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff, + }; + const bool has_aes_hw = ssl->config->aes_hw_override ? ssl->config->aes_hw_override_value : EVP_has_aes_hardware(); + const bssl::Span ciphers = + ssl->config->tls13_cipher_policy == ssl_compliance_policy_cnsa_202407 + ? bssl::Span(kCiphersCNSA) + : (has_aes_hw ? bssl::Span(kCiphersAESHardware) + : bssl::Span(kCiphersNoAESHardware)); - if ((!has_aes_hw && // - !ssl_add_tls13_cipher(&child, - TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff, - ssl->config->tls13_cipher_policy)) || - !ssl_add_tls13_cipher(&child, TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff, - ssl->config->tls13_cipher_policy) || - !ssl_add_tls13_cipher(&child, TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff, - ssl->config->tls13_cipher_policy) || - (has_aes_hw && // - !ssl_add_tls13_cipher(&child, - TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff, - ssl->config->tls13_cipher_policy))) { - return false; + for (auto cipher : ciphers) { + if (!ssl_add_tls13_cipher(&child, cipher, + ssl->config->tls13_cipher_policy)) { + return false; + } } } diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc index 172de90d9..7db273073 100644 --- a/src/ssl/s3_both.cc +++ b/src/ssl/s3_both.cc @@ -659,36 +659,49 @@ void tls_next_message(SSL *ssl) { } } -// CipherScorer produces a "score" for each possible cipher suite offered by -// the client. class CipherScorer { public: - CipherScorer(bool has_aes_hw) : aes_is_fine_(has_aes_hw) {} + using Score = int; + static constexpr Score kMinScore = 0; - typedef std::tuple Score; + virtual Score Evaluate(const SSL_CIPHER *cipher) const = 0; +}; - // MinScore returns a |Score| that will compare less than the score of all - // cipher suites. - Score MinScore() const { - return Score(false, false); - } +// AesHwCipherScorer scores cipher suites based on whether AES is supported in +// hardware. +class AesHwCipherScorer : public CipherScorer { + public: + explicit AesHwCipherScorer(bool has_aes_hw) : aes_is_fine_(has_aes_hw) {} - Score Evaluate(const SSL_CIPHER *a) const { - return Score( + Score Evaluate(const SSL_CIPHER *a) const override { + return // Something is always preferable to nothing. - true, + 1 + // Either AES is fine, or else ChaCha20 is preferred. - aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305); + ((aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305) ? 1 : 0); } private: const bool aes_is_fine_; }; +// CNsaCipherScorer prefers AES-256-GCM over AES-128-GCM over anything else. +class CNsaCipherScorer : public CipherScorer { + Score Evaluate(const SSL_CIPHER *a) const override { + if (a->id == TLS1_3_CK_AES_256_GCM_SHA384) { + return 3; + } else if (a->id == TLS1_3_CK_AES_128_GCM_SHA256) { + return 2; + } + return 1; + } +}; + bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, enum ssl_compliance_policy_t policy) { switch (policy) { case ssl_compliance_policy_none: + case ssl_compliance_policy_cnsa_202407: return true; case ssl_compliance_policy_fips_202205: @@ -728,8 +741,12 @@ const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw, } const SSL_CIPHER *best = nullptr; - CipherScorer scorer(has_aes_hw); - CipherScorer::Score best_score = scorer.MinScore(); + AesHwCipherScorer aes_hw_scorer(has_aes_hw); + CNsaCipherScorer cnsa_scorer; + CipherScorer *const scorer = (policy == ssl_compliance_policy_cnsa_202407) + ? static_cast(&cnsa_scorer) + : static_cast(&aes_hw_scorer); + CipherScorer::Score best_score = CipherScorer::kMinScore; while (CBS_len(&cipher_suites) > 0) { uint16_t cipher_suite; @@ -750,7 +767,7 @@ const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw, continue; } - const CipherScorer::Score candidate_score = scorer.Evaluate(candidate); + const CipherScorer::Score candidate_score = scorer->Evaluate(candidate); // |candidate_score| must be larger to displace the current choice. That way // the client's order controls between ciphers with an equal score. if (candidate_score > best_score) { diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc index c86b51bd8..2e7859994 100644 --- a/src/ssl/ssl_lib.cc +++ b/src/ssl/ssl_lib.cc @@ -3403,6 +3403,21 @@ static int Configure(SSL *ssl) { } // namespace wpa202304 +namespace cnsa202407 { + +static int Configure(SSL_CTX *ctx) { + ctx->tls13_cipher_policy = ssl_compliance_policy_cnsa_202407; + return 1; +} + +static int Configure(SSL *ssl) { + ssl->config->tls13_cipher_policy = + ssl_compliance_policy_cnsa_202407; + return 1; +} + +} + int SSL_CTX_set_compliance_policy(SSL_CTX *ctx, enum ssl_compliance_policy_t policy) { switch (policy) { @@ -3410,6 +3425,8 @@ int SSL_CTX_set_compliance_policy(SSL_CTX *ctx, return fips202205::Configure(ctx); case ssl_compliance_policy_wpa3_192_202304: return wpa202304::Configure(ctx); + case ssl_compliance_policy_cnsa_202407: + return cnsa202407::Configure(ctx); default: return 0; } @@ -3421,6 +3438,8 @@ int SSL_set_compliance_policy(SSL *ssl, enum ssl_compliance_policy_t policy) { return fips202205::Configure(ssl); case ssl_compliance_policy_wpa3_192_202304: return wpa202304::Configure(ssl); + case ssl_compliance_policy_cnsa_202407: + return cnsa202407::Configure(ssl); default: return 0; } diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go index 2cd15494f..1476b3857 100644 --- a/src/ssl/test/runner/runner.go +++ b/src/ssl/test/runner/runner.go @@ -19928,6 +19928,38 @@ func addCompliancePolicyTests() { }) } } + + // AES-256-GCM is the most preferred. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: "Compliance-cnsa202407-" + protocol.String() + "-AES-256-preferred", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384}, + }, + flags: []string{ + "-cnsa-202407", + }, + expectations: connectionExpectations{cipher: TLS_AES_256_GCM_SHA384}, + }) + + // AES-128-GCM is preferred over ChaCha20-Poly1305. + testCases = append(testCases, testCase{ + testType: serverTest, + protocol: protocol, + name: "Compliance-cnsa202407-" + protocol.String() + "-AES-128-preferred", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256}, + }, + flags: []string{ + "-cnsa-202407", + }, + expectations: connectionExpectations{cipher: TLS_AES_128_GCM_SHA256}, + }) } } diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc index ae4f87b41..2db2a89d9 100644 --- a/src/ssl/test/test_config.cc +++ b/src/ssl/test/test_config.cc @@ -478,6 +478,7 @@ const Flag *FindFlag(const char *name) { &TestConfig::early_write_after_message), BoolFlag("-fips-202205", &TestConfig::fips_202205), BoolFlag("-wpa-202304", &TestConfig::wpa_202304), + BoolFlag("-cnsa-202407", &TestConfig::cnsa_202407), BoolFlag("-no-check-client-certificate-type", &TestConfig::no_check_client_certificate_type), BoolFlag("-no-check-ecdsa-curve", &TestConfig::no_check_ecdsa_curve), @@ -2096,7 +2097,9 @@ bssl::UniquePtr TestConfig::NewSSL( if (enable_ech_grease) { SSL_set_enable_ech_grease(ssl.get(), 1); } - if (static_cast(fips_202205) + static_cast(wpa_202304) > 1) { + if (static_cast(fips_202205) + static_cast(wpa_202304) + + static_cast(cnsa_202407) > + 1) { fprintf(stderr, "Multiple policy options given\n"); return nullptr; } @@ -2110,6 +2113,11 @@ bssl::UniquePtr TestConfig::NewSSL( fprintf(stderr, "SSL_set_compliance_policy failed\n"); return nullptr; } + if (cnsa_202407 && !SSL_set_compliance_policy( + ssl.get(), ssl_compliance_policy_cnsa_202407)) { + fprintf(stderr, "SSL_set_compliance_policy failed\n"); + return nullptr; + } if (!ech_config_list.empty() && !SSL_set1_ech_config_list( ssl.get(), reinterpret_cast(ech_config_list.data()), diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h index 607f58dc1..0afeb4980 100644 --- a/src/ssl/test/test_config.h +++ b/src/ssl/test/test_config.h @@ -214,6 +214,7 @@ struct TestConfig { int early_write_after_message = 0; bool fips_202205 = false; bool wpa_202304 = false; + bool cnsa_202407 = false; bool no_check_client_certificate_type = false; bool no_check_ecdsa_curve = false; int expect_selected_credential = -1;