Add ssl_compliance_policy_cnsa_202407

This policy implements a part of RFC 9151.

Change-Id: I2c0a0e922b9287c8e29bdce4633fdee2a9f07fb8
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/69887
Auto-Submit: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 2f14754..1dbc1e7 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -5637,6 +5637,14 @@
   // 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/ssl/handshake_client.cc b/ssl/handshake_client.cc
index c532345..9788b46 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -244,23 +244,36 @@
   // 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<const uint16_t> ciphers =
+        ssl->config->tls13_cipher_policy == ssl_compliance_policy_cnsa_202407
+            ? bssl::Span<const uint16_t>(kCiphersCNSA)
+            : (has_aes_hw ? bssl::Span<const uint16_t>(kCiphersAESHardware)
+                          : bssl::Span<const uint16_t>(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/ssl/s3_both.cc b/ssl/s3_both.cc
index 172de90..7db2730 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -659,36 +659,49 @@
   }
 }
 
-// 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<bool, bool> 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 *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<CipherScorer*>(&cnsa_scorer)
+                                   : static_cast<CipherScorer*>(&aes_hw_scorer);
+  CipherScorer::Score best_score = CipherScorer::kMinScore;
 
   while (CBS_len(&cipher_suites) > 0) {
     uint16_t cipher_suite;
@@ -750,7 +767,7 @@
       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/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index c86b51b..2e78599 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -3403,6 +3403,21 @@
 
 }  // 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 @@
       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 @@
       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/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 2cd1549..1476b38 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -19928,6 +19928,38 @@
 				})
 			}
 		}
+
+		// 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/ssl/test/test_config.cc b/ssl/test/test_config.cc
index ae4f87b..2db2a89 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -478,6 +478,7 @@
                 &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 @@
   if (enable_ech_grease) {
     SSL_set_enable_ech_grease(ssl.get(), 1);
   }
-  if (static_cast<int>(fips_202205) + static_cast<int>(wpa_202304) > 1) {
+  if (static_cast<int>(fips_202205) + static_cast<int>(wpa_202304) +
+          static_cast<int>(cnsa_202407) >
+      1) {
     fprintf(stderr, "Multiple policy options given\n");
     return nullptr;
   }
@@ -2110,6 +2113,11 @@
     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<const uint8_t *>(ech_config_list.data()),
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 607f58d..0afeb49 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -214,6 +214,7 @@
   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;