Add new compliance policy profiles for CNSA 1.0 and 2.0

Bug: 495413980
Change-Id: I70d389c33b00ba7cdafcac1c91284c3e6a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/91567
Commit-Queue: Lily Chen <chlily@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Auto-Submit: Lily Chen <chlily@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 455de88..9adbb99 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -6270,13 +6270,38 @@
   // 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_compliance_policy_cnsa1_202603 configures a TLS connection to use:
+  //   * TLS 1.2 or TLS 1.3.
+  //   * For TLS 1.2, only TLS_ECDHE_[ECDSA|RSA]_WITH_AES_256_GCM_SHA384.
+  //   * For TLS 1.3, only AES-256-GCM.
+  //   * ML-KEM-1024 or P-384 for key agreement, preferring ML-KEM-1024 if the
+  //     client supports it.
+  //   * For handshake signatures, only ECDSA with P-384 and SHA-384, or RSA
+  //     with SHA-384.
+  //
+  // Note: this setting aids with compliance with CNSA requirements but does not
+  // guarantee it. Careful reading of RFC 9151 is recommended.
+  ssl_compliance_policy_cnsa1_202603,
+
+  // ssl_compliance_policy_cnsa2_202603 configures a TLS connection to use:
+  //   * Only TLS 1.3, with AES-256-GCM.
+  //   * Only ML-KEM-1024 for key agreement.
+  //   * For handshake signatures, only ECDSA with P-384 and SHA-384, or RSA
+  //     with SHA-384.
+  //
+  // Note: this setting aids with compliance with CNSA requirements but does not
+  // guarantee it. Careful reading of draft-becker-cnsa2-tls-profile is
+  // recommended.
+  ssl_compliance_policy_cnsa2_202603,
 };
 
 // SSL_CTX_set_compliance_policy configures various aspects of |ctx| based on
 // the given policy requirements. Subsequently calling other functions that
 // configure |ctx| may override |policy|, or may not. This should be the final
-// configuration function called in order to have defined behaviour. It's a
-// fatal error if |policy| is |ssl_compliance_policy_none|.
+// configuration function called in order to have defined behaviour matching the
+// configuration profile documented for |policy| above. It's a fatal error if
+// |policy| is |ssl_compliance_policy_none|.
 OPENSSL_EXPORT int SSL_CTX_set_compliance_policy(
     SSL_CTX *ctx, enum ssl_compliance_policy_t policy);
 
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index 78e3818..d3036c3 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -620,6 +620,8 @@
       }
 
     case ssl_compliance_policy_wpa3_192_202304:
+    case ssl_compliance_policy_cnsa1_202603:
+    case ssl_compliance_policy_cnsa2_202603:
       switch (cipher_id) {
         case SSL_CIPHER_AES_256_GCM_SHA384:
           return true;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index abbba93..602cf8d 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -3496,6 +3496,87 @@
 
 }  // namespace cnsa202407
 
+namespace cnsa1_202603 {
+
+// Approximates CNSA 1.0 (RFC 9151).
+
+static const uint16_t kGroups[] = {SSL_GROUP_MLKEM1024, SSL_GROUP_SECP384R1};
+
+// Prefer ML-KEM-1024 if the client supports it.
+static const uint32_t kOptions = SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+static const uint16_t kSigAlgs[] = {
+    SSL_SIGN_ECDSA_SECP384R1_SHA384,
+    SSL_SIGN_RSA_PSS_RSAE_SHA384,
+    SSL_SIGN_RSA_PKCS1_SHA384,
+};
+
+static const char kTLS12Ciphers[] =
+    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:"
+    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+
+static int Configure(SSL_CTX *ctx) {
+  ctx->compliance_policy = ssl_compliance_policy_cnsa1_202603;
+
+  return SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) &&
+         SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) &&
+         SSL_CTX_set_strict_cipher_list(ctx, kTLS12Ciphers) &&
+         SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) &&
+         SSL_CTX_set_options(ctx, kOptions) &&
+         SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs,
+                                             std::size(kSigAlgs)) &&
+         SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
+}
+
+static int Configure(SSL *ssl) {
+  ssl->config->compliance_policy = ssl_compliance_policy_cnsa1_202603;
+
+  return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) &&
+         SSL_set_max_proto_version(ssl, TLS1_3_VERSION) &&
+         SSL_set_strict_cipher_list(ssl, kTLS12Ciphers) &&
+         SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) &&
+         SSL_set_options(ssl, kOptions) &&
+         SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) &&
+         SSL_set_verify_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs));
+}
+
+}  // namespace cnsa1_202603
+
+namespace cnsa2_202603 {
+
+// Approximates CNSA 2.0 (draft-becker-cnsa2-tls-profile).
+
+static const uint16_t kGroups[] = {SSL_GROUP_MLKEM1024};
+
+static const uint16_t kSigAlgs[] = {
+    SSL_SIGN_ECDSA_SECP384R1_SHA384,
+    SSL_SIGN_RSA_PSS_RSAE_SHA384,
+    SSL_SIGN_RSA_PKCS1_SHA384,
+};
+
+static int Configure(SSL_CTX *ctx) {
+  ctx->compliance_policy = ssl_compliance_policy_cnsa2_202603;
+
+  return SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) &&
+         SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) &&
+         SSL_CTX_set1_group_ids(ctx, kGroups, std::size(kGroups)) &&
+         SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs,
+                                             std::size(kSigAlgs)) &&
+         SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
+}
+
+static int Configure(SSL *ssl) {
+  ssl->config->compliance_policy = ssl_compliance_policy_cnsa2_202603;
+
+  return SSL_set_min_proto_version(ssl, TLS1_3_VERSION) &&
+         SSL_set_max_proto_version(ssl, TLS1_3_VERSION) &&
+         SSL_set1_group_ids(ssl, kGroups, std::size(kGroups)) &&
+         SSL_set_signing_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs)) &&
+         SSL_set_verify_algorithm_prefs(ssl, kSigAlgs, std::size(kSigAlgs));
+}
+
+}  // namespace cnsa2_202603
+
 int SSL_CTX_set_compliance_policy(SSL_CTX *ctx,
                                   enum ssl_compliance_policy_t policy) {
   switch (policy) {
@@ -3505,6 +3586,10 @@
       return wpa202304::Configure(ctx);
     case ssl_compliance_policy_cnsa_202407:
       return cnsa202407::Configure(ctx);
+    case ssl_compliance_policy_cnsa1_202603:
+      return cnsa1_202603::Configure(ctx);
+    case ssl_compliance_policy_cnsa2_202603:
+      return cnsa2_202603::Configure(ctx);
     default:
       return 0;
   }
@@ -3522,6 +3607,10 @@
       return wpa202304::Configure(ssl);
     case ssl_compliance_policy_cnsa_202407:
       return cnsa202407::Configure(ssl);
+    case ssl_compliance_policy_cnsa1_202603:
+      return cnsa1_202603::Configure(ssl);
+    case ssl_compliance_policy_cnsa2_202603:
+      return cnsa2_202603::Configure(ssl);
     default:
       return 0;
   }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 8614ac6..6b5441a 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -11127,7 +11127,9 @@
 
   for (const auto policy : {ssl_compliance_policy_fips_202205,      //
                             ssl_compliance_policy_wpa3_192_202304,  //
-                            ssl_compliance_policy_cnsa_202407}) {
+                            ssl_compliance_policy_cnsa_202407,      //
+                            ssl_compliance_policy_cnsa1_202603,     //
+                            ssl_compliance_policy_cnsa2_202603}) {
     SSL_CTX_set_compliance_policy(ctx.get(), policy);
     EXPECT_EQ(SSL_CTX_get_compliance_policy(ctx.get()), policy);
     SSL_set_compliance_policy(ssl.get(), policy);
diff --git a/ssl/test/runner/compliance_policy_tests.go b/ssl/test/runner/compliance_policy_tests.go
index 1b8791e..8a4b8c2 100644
--- a/ssl/test/runner/compliance_policy_tests.go
+++ b/ssl/test/runner/compliance_policy_tests.go
@@ -29,11 +29,19 @@
 			}
 
 			var isWPACipherSuite bool
+			var isCNSA1CipherSuite bool
 			switch suite.id {
 			case TLS_AES_256_GCM_SHA384,
 				TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
 				TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
 				isWPACipherSuite = true
+				isCNSA1CipherSuite = true
+			}
+
+			var isCNSA2CipherSuite bool
+			switch suite.id {
+			case TLS_AES_256_GCM_SHA384:
+				isCNSA2CipherSuite = true
 			}
 
 			var cert Credential
@@ -57,9 +65,17 @@
 			}{
 				{"-fips-202205", isFIPSCipherSuite},
 				{"-wpa-202304", isWPACipherSuite},
+				{"-cnsa1-202603", isCNSA1CipherSuite},
+				{"-cnsa2-202603", isCNSA2CipherSuite},
 			}
 
 			for _, policy := range policies {
+				shouldFail := !policy.cipherSuiteOk
+				// The CNSA2 policy requires TLS 1.3.
+				if policy.flag == "-cnsa2-202603" && maxVersion == VersionTLS12 {
+					shouldFail = true
+				}
+
 				testCases = append(testCases, testCase{
 					testType: serverTest,
 					protocol: protocol,
@@ -73,7 +89,7 @@
 					flags: []string{
 						policy.flag,
 					},
-					shouldFail: !policy.cipherSuiteOk,
+					shouldFail: shouldFail,
 				})
 
 				testCases = append(testCases, testCase{
@@ -89,7 +105,7 @@
 					flags: []string{
 						policy.flag,
 					},
-					shouldFail: !policy.cipherSuiteOk,
+					shouldFail: shouldFail,
 				})
 			}
 		}
@@ -127,12 +143,26 @@
 				isWPACurve = true
 			}
 
+			var isCNSA1Curve bool
+			switch curve.id {
+			case CurveP384, CurveMLKEM1024:
+				isCNSA1Curve = true
+			}
+
+			var isCNSA2Curve bool
+			switch curve.id {
+			case CurveMLKEM1024:
+				isCNSA2Curve = true
+			}
+
 			policies := []struct {
 				flag    string
 				curveOk bool
 			}{
 				{"-fips-202205", isFIPSCurve},
 				{"-wpa-202304", isWPACurve},
+				{"-cnsa1-202603", isCNSA1Curve},
+				{"-cnsa2-202603", isCNSA2Curve},
 			}
 
 			for _, policy := range policies {
@@ -168,6 +198,27 @@
 			}
 		}
 
+		// For CNSA1 as a server, if the client supports ML-KEM-1024 (even if not
+		// the first choice, or the first shared choice), the server will select
+		// ML-KEM-1024, even if the client provides key shares for other groups.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     "Compliance-cnsa1-202603-" + protocol.String() + "-PrefersMLKEM1024",
+			config: Config{
+				MinVersion:       VersionTLS13,
+				MaxVersion:       VersionTLS13,
+				CurvePreferences: []CurveID{CurveP256, CurveP384, CurveMLKEM1024},
+				DefaultCurves:    []CurveID{CurveP256, CurveP384},
+			},
+			flags: []string{
+				"-cnsa1-202603",
+			},
+			expectations: connectionExpectations{
+				curveID: CurveMLKEM1024,
+			},
+		})
+
 		for _, sigalg := range testSignatureAlgorithms {
 			// The TLS 1.0 and TLS 1.1 default signature algorithm does not
 			// apply to these tests.
@@ -198,6 +249,14 @@
 				isWPASigAlg = true
 			}
 
+			var isCNSASigAlg bool
+			switch sigalg.id {
+			case signatureRSAPKCS1WithSHA384,
+				signatureECDSAWithP384AndSHA384,
+				signatureRSAPSSWithSHA384:
+				isCNSASigAlg = true
+			}
+
 			maxVersion := uint16(VersionTLS13)
 			if hasComponent(sigalg.name, "PKCS1") {
 				if protocol == quic {
@@ -212,10 +271,17 @@
 			}{
 				{"-fips-202205", isFIPSSigAlg},
 				{"-wpa-202304", isWPASigAlg},
+				{"-cnsa1-202603", isCNSASigAlg},
+				{"-cnsa2-202603", isCNSASigAlg},
 			}
 
 			cert := sigalg.baseCert.WithSignatureAlgorithms(sigalg.id)
 			for _, policy := range policies {
+				shouldFail := !policy.sigAlgOk
+				// The CNSA2 policy requires TLS 1.3.
+				if policy.flag == "-cnsa2-202603" && maxVersion == VersionTLS12 {
+					shouldFail = true
+				}
 				testCases = append(testCases, testCase{
 					testType: serverTest,
 					protocol: protocol,
@@ -229,7 +295,7 @@
 					// preferences from the FIPS policy.
 					shimCertificate: sigalg.baseCert,
 					flags:           []string{policy.flag},
-					shouldFail:      !policy.sigAlgOk,
+					shouldFail:      shouldFail,
 				})
 
 				testCases = append(testCases, testCase{
@@ -244,7 +310,7 @@
 					flags: []string{
 						policy.flag,
 					},
-					shouldFail: !policy.sigAlgOk,
+					shouldFail: shouldFail,
 				})
 			}
 		}
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index f35fd84..efbbe3b 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -556,6 +556,8 @@
         BoolFlag("-fips-202205", &TestConfig::fips_202205),
         BoolFlag("-wpa-202304", &TestConfig::wpa_202304),
         BoolFlag("-cnsa-202407", &TestConfig::cnsa_202407),
+        BoolFlag("-cnsa1-202603", &TestConfig::cnsa1_202603),
+        BoolFlag("-cnsa2-202603", &TestConfig::cnsa2_202603),
         SetValueFlag("-expect-peer-match-trust-anchor",
                      &TestConfig::expect_peer_match_trust_anchor, true),
         SetValueFlag("-expect-no-peer-match-trust-anchor",
@@ -2399,27 +2401,6 @@
   if (enable_ech_grease) {
     SSL_set_enable_ech_grease(ssl.get(), 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;
-  }
-  if (fips_202205 && !SSL_set_compliance_policy(
-                         ssl.get(), ssl_compliance_policy_fips_202205)) {
-    fprintf(stderr, "SSL_set_compliance_policy failed\n");
-    return nullptr;
-  }
-  if (wpa_202304 && !SSL_set_compliance_policy(
-                        ssl.get(), ssl_compliance_policy_wpa3_192_202304)) {
-    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(), ech_config_list.data(),
                                 ech_config_list.size())) {
@@ -2629,5 +2610,31 @@
     return nullptr;
   }
 
+  // The compliance policy must be the last thing configured to have defined
+  // behavior.
+  struct {
+    const bool *setting;
+    ssl_compliance_policy_t policy;
+  } compliance_options[] = {
+      {&fips_202205, ssl_compliance_policy_fips_202205},
+      {&wpa_202304, ssl_compliance_policy_wpa3_192_202304},
+      {&cnsa_202407, ssl_compliance_policy_cnsa_202407},
+      {&cnsa1_202603, ssl_compliance_policy_cnsa1_202603},
+      {&cnsa2_202603, ssl_compliance_policy_cnsa2_202603},
+  };
+  bool set_compliance_option = false;
+  for (const auto &option : compliance_options) {
+    if (*option.setting) {
+      if (set_compliance_option) {
+        fprintf(stderr, "Multiple policy options given\n");
+        return nullptr;
+      }
+      if (!SSL_set_compliance_policy(ssl.get(), option.policy)) {
+        fprintf(stderr, "SSL_set_compliance_policy failed\n");
+        return nullptr;
+      }
+      set_compliance_option = true;
+    }
+  }
   return ssl;
 }
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 34d00eb..66bb155 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -242,6 +242,8 @@
   bool fips_202205 = false;
   bool wpa_202304 = false;
   bool cnsa_202407 = false;
+  bool cnsa1_202603 = false;
+  bool cnsa2_202603 = false;
   std::optional<bool> expect_peer_match_trust_anchor;
   std::optional<std::vector<uint8_t>> expect_peer_available_trust_anchors;
   std::optional<std::vector<uint8_t>> requested_trust_anchors;