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;