Add SSL_[CTX_]_set_compliance_policy.
These functions aid in meeting specific compliance goals and allows
configuration of things like TLS 1.3 cipher suites, which are otherwise
not configurable.
Change-Id: I668afc734a19ecd4b996eaa23be73ce259b13fa2
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/52625
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index f0ca7f7..b990b41 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -5104,6 +5104,44 @@
OPENSSL_EXPORT uint16_t SSL_CIPHER_get_value(const SSL_CIPHER *cipher);
+// Compliance policy configurations
+//
+// A TLS connection has a large number of different parameters. Some are well
+// known, like cipher suites, but many are obscure and configuration functions
+// for them may not exist. These policy controls allow broad configuration
+// goals to be specified so that they can flow down to all the different
+// parameters of a TLS connection.
+
+enum ssl_compliance_policy_t BORINGSSL_ENUM_INT {
+ // ssl_policy_fips_202205 configures a TLS connection to use:
+ // * TLS 1.2 or 1.3
+ // * For TLS 1.2, only ECDHE_[RSA|ECDSA]_WITH_AES_*_GCM_SHA*.
+ // * For TLS 1.3, only AES-GCM
+ // * P-256 or P-384 for key agreement.
+ // * For server signatures, only PKCS#1/PSS with SHA256/384/512, or ECDSA
+ // with P-256 or P-384.
+ //
+ // Note: this policy can be configured even if BoringSSL has not been built in
+ // FIPS mode. Call |FIPS_mode| to check that.
+ //
+ // Note: this setting aids with compliance with NIST requirements but does not
+ // guarantee it. Careful reading of SP 800-52r2 is recommended.
+ ssl_compliance_policy_fips_202205,
+};
+
+// 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.
+OPENSSL_EXPORT int SSL_CTX_set_compliance_policy(
+ SSL_CTX *ctx, enum ssl_compliance_policy_t policy);
+
+// SSL_set_compliance_policy acts the same as |SSL_CTX_set_compliance_policy|,
+// but only configures a single |SSL*|.
+OPENSSL_EXPORT int SSL_set_compliance_policy(
+ SSL *ssl, enum ssl_compliance_policy_t policy);
+
+
// Nodejs compatibility section (hidden).
//
// These defines exist for node.js, with the hope that we can eliminate the
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index e630121..bb2462f 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -235,7 +235,12 @@
// Add TLS 1.3 ciphers. Order ChaCha20-Poly1305 relative to AES-GCM based on
// hardware support.
if (hs->max_version >= TLS1_3_VERSION) {
- if (!EVP_has_aes_hardware() &&
+ const bool include_chacha20 = ssl_tls13_cipher_meets_policy(
+ TLS1_CK_CHACHA20_POLY1305_SHA256 & 0xffff,
+ ssl->config->only_fips_cipher_suites_in_tls13);
+
+ if (!EVP_has_aes_hardware() && //
+ include_chacha20 && //
!CBB_add_u16(&child, TLS1_CK_CHACHA20_POLY1305_SHA256 & 0xffff)) {
return false;
}
@@ -243,7 +248,8 @@
!CBB_add_u16(&child, TLS1_CK_AES_256_GCM_SHA384 & 0xffff)) {
return false;
}
- if (EVP_has_aes_hardware() &&
+ if (EVP_has_aes_hardware() && //
+ include_chacha20 && //
!CBB_add_u16(&child, TLS1_CK_CHACHA20_POLY1305_SHA256 & 0xffff)) {
return false;
}
diff --git a/ssl/internal.h b/ssl/internal.h
index fbf9745..2400f90 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -660,10 +660,15 @@
size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher);
// ssl_choose_tls13_cipher returns an |SSL_CIPHER| corresponding with the best
-// available from |cipher_suites| compatible with |version| and |group_id|. It
-// returns NULL if there isn't a compatible cipher.
+// available from |cipher_suites| compatible with |version|, |group_id|, and
+// |only_fips|. It returns NULL if there isn't a compatible cipher.
const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
- uint16_t group_id);
+ uint16_t group_id, bool only_fips);
+
+// ssl_tls13_cipher_meets_policy returns true if |cipher_id| is acceptable given
+// |only_fips|. (For now there's only a single policy and so the policy argument
+// is just a bool.)
+bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, bool only_fips);
// Transcript layer.
@@ -3087,6 +3092,10 @@
// permute_extensions is whether to permute extensions when sending messages.
bool permute_extensions : 1;
+
+ // only_fips_cipher_suites_in_tls13 constrains the selection of cipher suites
+ // in TLS 1.3 such that only FIPS approved ones will be selected.
+ bool only_fips_cipher_suites_in_tls13 : 1;
};
// From RFC 8446, used in determining PSK modes.
@@ -3694,6 +3703,10 @@
// If enable_early_data is true, early data can be sent and accepted.
bool enable_early_data : 1;
+ // only_fips_cipher_suites_in_tls13 constrains the selection of cipher suites
+ // in TLS 1.3 such that only FIPS approved ones will be selected.
+ bool only_fips_cipher_suites_in_tls13 : 1;
+
private:
~ssl_ctx_st();
friend OPENSSL_EXPORT void SSL_CTX_free(SSL_CTX *);
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index cddeb3f..d0fc6c0 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -691,8 +691,25 @@
const bool security_128_is_fine_;
};
+bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, bool only_fips) {
+ if (!only_fips) {
+ return true;
+ }
+
+ switch (cipher_id) {
+ case TLS1_CK_AES_128_GCM_SHA256 & 0xffff:
+ case TLS1_CK_AES_256_GCM_SHA384 & 0xffff:
+ return true;
+ case TLS1_CK_CHACHA20_POLY1305_SHA256 & 0xffff:
+ return false;
+ default:
+ assert(false);
+ return false;
+ }
+}
+
const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
- uint16_t group_id) {
+ uint16_t group_id, bool only_fips) {
if (CBS_len(&cipher_suites) % 2 != 0) {
return nullptr;
}
@@ -715,6 +732,11 @@
continue;
}
+ if (!ssl_tls13_cipher_meets_policy(SSL_CIPHER_get_protocol_id(candidate),
+ only_fips)) {
+ continue;
+ }
+
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.
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 5e96bef..0b3febf 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -519,7 +519,8 @@
allow_unknown_alpn_protos(false),
false_start_allowed_without_alpn(false),
handoff(false),
- enable_early_data(false) {
+ enable_early_data(false),
+ only_fips_cipher_suites_in_tls13(false) {
CRYPTO_MUTEX_init(&lock);
CRYPTO_new_ex_data(&ex_data);
}
@@ -639,6 +640,8 @@
ssl->config->retain_only_sha256_of_client_certs =
ctx->retain_only_sha256_of_client_certs;
ssl->config->permute_extensions = ctx->permute_extensions;
+ ssl->config->only_fips_cipher_suites_in_tls13 =
+ ctx->only_fips_cipher_suites_in_tls13;
if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) ||
!ssl->config->alpn_client_proto_list.CopyFrom(
@@ -3079,3 +3082,93 @@
ctx->legacy_ocsp_callback_arg = arg;
return 1;
}
+
+namespace fips202205 {
+
+// (References are to SP 800-52r2):
+
+// Section 3.4.2.2
+// "at least one of the NIST-approved curves, P-256 (secp256r1) and P384
+// (secp384r1), shall be supported as described in RFC 8422."
+//
+// Section 3.3.1
+// "The server shall be configured to only use cipher suites that are
+// composed entirely of NIST approved algorithms"
+static const int kCurves[] = {NID_X9_62_prime256v1, NID_secp384r1};
+
+static const uint16_t kSigAlgs[] = {
+ SSL_SIGN_RSA_PKCS1_SHA256,
+ SSL_SIGN_RSA_PKCS1_SHA384,
+ SSL_SIGN_RSA_PKCS1_SHA512,
+ // Table 4.1:
+ // "The curve should be P-256 or P-384"
+ SSL_SIGN_ECDSA_SECP256R1_SHA256,
+ SSL_SIGN_ECDSA_SECP384R1_SHA384,
+ SSL_SIGN_RSA_PSS_RSAE_SHA256,
+ SSL_SIGN_RSA_PSS_RSAE_SHA384,
+ SSL_SIGN_RSA_PSS_RSAE_SHA512,
+};
+
+static const char kTLS12Ciphers[] =
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:"
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:"
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:"
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+
+static int Configure(SSL_CTX *ctx) {
+ ctx->only_fips_cipher_suites_in_tls13 = true;
+
+ return
+ // Section 3.1:
+ // "Servers that support government-only applications shall be
+ // configured to use TLS 1.2 and should be configured to use TLS 1.3
+ // as well. These servers should not be configured to use TLS 1.1 and
+ // shall not use TLS 1.0, SSL 3.0, or SSL 2.0.
+ SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) &&
+ SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) &&
+ // Sections 3.3.1.1.1 and 3.3.1.1.2 are ambiguous about whether
+ // HMAC-SHA-1 cipher suites are permitted with TLS 1.2. However, later the
+ // Encrypt-then-MAC extension is required for all CBC cipher suites and so
+ // it's easier to drop them.
+ SSL_CTX_set_strict_cipher_list(ctx, kTLS12Ciphers) &&
+ SSL_CTX_set1_curves(ctx, kCurves, OPENSSL_ARRAY_SIZE(kCurves)) &&
+ SSL_CTX_set_signing_algorithm_prefs(ctx, kSigAlgs,
+ OPENSSL_ARRAY_SIZE(kSigAlgs)) &&
+ SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs,
+ OPENSSL_ARRAY_SIZE(kSigAlgs));
+}
+
+static int Configure(SSL *ssl) {
+ ssl->config->only_fips_cipher_suites_in_tls13 = true;
+
+ // See |Configure(SSL_CTX)|, above, for reasoning.
+ 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_curves(ssl, kCurves, OPENSSL_ARRAY_SIZE(kCurves)) &&
+ SSL_set_signing_algorithm_prefs(ssl, kSigAlgs,
+ OPENSSL_ARRAY_SIZE(kSigAlgs)) &&
+ SSL_set_verify_algorithm_prefs(ssl, kSigAlgs,
+ OPENSSL_ARRAY_SIZE(kSigAlgs));
+}
+
+} // namespace fips202205
+
+int SSL_CTX_set_compliance_policy(SSL_CTX *ctx,
+ enum ssl_compliance_policy_t policy) {
+ switch (policy) {
+ case ssl_compliance_policy_fips_202205:
+ return fips202205::Configure(ctx);
+ default:
+ return 0;
+ }
+}
+
+int SSL_set_compliance_policy(SSL *ssl, enum ssl_compliance_policy_t policy) {
+ switch (policy) {
+ case ssl_compliance_policy_fips_202205:
+ return fips202205::Configure(ssl);
+ default:
+ return 0;
+ }
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4c1c955..65ed278 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -19032,6 +19032,197 @@
}
}
+func addCompliancePolicyTests() {
+ for _, protocol := range []protocol{tls, quic} {
+ for _, suite := range testCipherSuites {
+ var isFIPSCipherSuite bool
+ switch suite.id {
+ case TLS_AES_128_GCM_SHA256,
+ TLS_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ isFIPSCipherSuite = true
+ }
+
+ var certFile string
+ var keyFile string
+ var certs []Certificate
+ if hasComponent(suite.name, "ECDSA") {
+ certFile = ecdsaP256CertificateFile
+ keyFile = ecdsaP256KeyFile
+ certs = []Certificate{ecdsaP256Certificate}
+ } else {
+ certFile = rsaCertificateFile
+ keyFile = rsaKeyFile
+ certs = []Certificate{rsaCertificate}
+ }
+
+ maxVersion := uint16(VersionTLS13)
+ if !isTLS13Suite(suite.name) {
+ if protocol == quic {
+ continue
+ }
+ maxVersion = VersionTLS12
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Server-" + suite.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ CipherSuites: []uint16{suite.id},
+ },
+ certFile: certFile,
+ keyFile: keyFile,
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: !isFIPSCipherSuite,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Client-" + suite.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ CipherSuites: []uint16{suite.id},
+ Certificates: certs,
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: !isFIPSCipherSuite,
+ })
+ }
+
+ // Check that a TLS 1.3 client won't accept ChaCha20 even if the server
+ // picks it without it being in the client's cipher list.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Client-ReallyWontAcceptChaCha",
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ Bugs: ProtocolBugs{
+ SendCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
+ },
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CIPHER_RETURNED:",
+ })
+
+ for _, curve := range testCurves {
+ var isFIPSCurve bool
+ switch curve.id {
+ case CurveP256, CurveP384:
+ isFIPSCurve = true
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Server-" + curve.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: !isFIPSCurve,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Client-" + curve.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: !isFIPSCurve,
+ })
+ }
+
+ for _, sigalg := range testSignatureAlgorithms {
+ var isFIPSSigAlg bool
+ switch sigalg.id {
+ case signatureRSAPKCS1WithSHA256,
+ signatureRSAPKCS1WithSHA384,
+ signatureRSAPKCS1WithSHA512,
+ signatureECDSAWithP256AndSHA256,
+ signatureECDSAWithP384AndSHA384,
+ signatureRSAPSSWithSHA256,
+ signatureRSAPSSWithSHA384,
+ signatureRSAPSSWithSHA512:
+ isFIPSSigAlg = true
+ }
+
+ if sigalg.cert == testCertECDSAP224 {
+ // This can work in TLS 1.2, but not with TLS 1.3.
+ // For consistency it's not permitted in FIPS mode.
+ isFIPSSigAlg = false
+ }
+
+ maxVersion := uint16(VersionTLS13)
+ if hasComponent(sigalg.name, "PKCS1") {
+ if protocol == quic {
+ continue
+ }
+ maxVersion = VersionTLS12
+ }
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Server-" + sigalg.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
+ },
+ flags: []string{
+ "-fips-202205",
+ "-cert-file", path.Join(*resourceDir, getShimCertificate(sigalg.cert)),
+ "-key-file", path.Join(*resourceDir, getShimKey(sigalg.cert)),
+ },
+ shouldFail: !isFIPSSigAlg,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ protocol: protocol,
+ name: "Compliance-fips202205-" + protocol.String() + "-Client-" + sigalg.name,
+ config: Config{
+ MinVersion: VersionTLS12,
+ MaxVersion: maxVersion,
+ SignSignatureAlgorithms: []signatureAlgorithm{sigalg.id},
+ Certificates: []Certificate{getRunnerCertificate(sigalg.cert)},
+ },
+ flags: []string{
+ "-fips-202205",
+ },
+ shouldFail: !isFIPSSigAlg,
+ })
+ }
+ }
+}
+
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
defer wg.Done()
@@ -19274,6 +19465,7 @@
addDelegatedCredentialTests()
addEncryptedClientHelloTests()
addHintMismatchTests()
+ addCompliancePolicyTests()
toAppend, err := convertToSplitHandshakeTests(testCases)
if err != nil {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index a6409d6..2c7fa08 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -385,6 +385,7 @@
&TestConfig::quic_early_data_context),
IntFlag("-early-write-after-message",
&TestConfig::early_write_after_message),
+ BoolFlag("-fips-202205", &TestConfig::fips_202205),
};
std::sort(flags.begin(), flags.end(), [](const Flag &a, const Flag &b) {
return strcmp(a.name, b.name) < 0;
@@ -1765,6 +1766,11 @@
if (enable_ech_grease) {
SSL_set_enable_ech_grease(ssl.get(), 1);
}
+ 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 (!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 f1e26ff..1a21ac1 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -193,6 +193,7 @@
bool wait_for_debugger = false;
std::string quic_early_data_context;
int early_write_after_message = 0;
+ bool fips_202205 = false;
int argc;
char **argv;
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index af2120c..bd0f820 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -192,11 +192,15 @@
}
// The cipher suite must be one we offered. We currently offer all supported
- // TLS 1.3 ciphers, so check the version.
+ // TLS 1.3 ciphers unless policy controls limited it. So we check the version
+ // and that it's ok per policy.
const SSL_CIPHER *cipher = SSL_get_cipher_by_value(server_hello.cipher_suite);
if (cipher == nullptr ||
SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) ||
- SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl)) {
+ SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl) ||
+ !ssl_tls13_cipher_meets_policy(
+ SSL_CIPHER_get_value(cipher),
+ ssl->config->only_fips_cipher_suites_in_tls13)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return ssl_hs_error;
@@ -372,7 +376,7 @@
}
// Check the cipher suite, in case this is after HelloRetryRequest.
- if (SSL_CIPHER_get_value(hs->new_cipher) != server_hello.cipher_suite) {
+ if (SSL_CIPHER_get_protocol_id(hs->new_cipher) != server_hello.cipher_suite) {
OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return ssl_hs_error;
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 9a65e0f..eca63f7 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -116,7 +116,8 @@
const uint16_t version = ssl_protocol_version(ssl);
- return ssl_choose_tls13_cipher(cipher_suites, version, group_id);
+ return ssl_choose_tls13_cipher(cipher_suites, version, group_id,
+ ssl->config->only_fips_cipher_suites_in_tls13);
}
static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {