Support WPA 3.1 "enterprise" mode.

It's unwise for organisations to try and define TLS profiles. As in this
case, they usually make security worse. However, since this is already
established and supported by Android, this change raises it to the level
of a supported policy.

Change-Id: Ic66d5eaa33d884e57fc6d8eb922d86882b621e9e
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58626
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index fdea6ae..5b5e3fe 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -5187,6 +5187,10 @@
 // parameters of a TLS connection.
 
 enum ssl_compliance_policy_t BORINGSSL_ENUM_INT {
+  // ssl_compliance_policy_none does nothing. However, since setting this
+  // doesn't undo other policies it's an error to try and set it.
+  ssl_compliance_policy_none,
+
   // 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*.
@@ -5201,12 +5205,32 @@
   // 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_compliance_policy_wpa3_192_202304 configures a TLS connection to use:
+  //   * TLS 1.2 or 1.3.
+  //   * For TLS 1.2, only TLS_ECDHE_[ECDSA|RSA]_WITH_AES_256_GCM_SHA384.
+  //   * For TLS 1.3, only AES-256-GCM.
+  //   * P-384 for key agreement.
+  //   * For handshake signatures, only ECDSA with P-384 and SHA-384, or RSA
+  //     with SHA-384 or SHA-512.
+  //
+  // No limitations on the certificate chain nor leaf public key are imposed,
+  // other than by the supported signature algorithms. But WPA3's "192-bit"
+  // mode requires at least P-384 or 3072-bit along the chain. The caller must
+  // enforce this themselves on the verified chain using functions such as
+  // `X509_STORE_CTX_get0_chain`.
+  //
+  // Note that this setting is less secure than the default. The
+  // implementation risks of using a more obscure primitive like P-384
+  // dominate other considerations.
+  ssl_compliance_policy_wpa3_192_202304,
 };
 
 // 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.
+// configuration function called in order to have defined behaviour. 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/handshake_client.cc b/ssl/handshake_client.cc
index 7fe2a2b..971ebd0 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -215,6 +215,14 @@
   }
 }
 
+static bool ssl_add_tls13_cipher(CBB *cbb, uint16_t cipher_id,
+                                 ssl_compliance_policy_t policy) {
+  if (ssl_tls13_cipher_meets_policy(cipher_id, policy)) {
+    return CBB_add_u16(cbb, cipher_id);
+  }
+  return true;
+}
+
 static bool ssl_write_client_cipher_list(const SSL_HANDSHAKE *hs, CBB *out,
                                          ssl_client_hello_type_t type) {
   const SSL *const ssl = hs->ssl;
@@ -235,26 +243,22 @@
   // Add TLS 1.3 ciphers. Order ChaCha20-Poly1305 relative to AES-GCM based on
   // hardware support.
   if (hs->max_version >= TLS1_3_VERSION) {
-    const bool include_chacha20 = ssl_tls13_cipher_meets_policy(
-        TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff,
-        ssl->config->only_fips_cipher_suites_in_tls13);
-
     const bool has_aes_hw = ssl->config->aes_hw_override
                                 ? ssl->config->aes_hw_override_value
                                 : EVP_has_aes_hardware();
 
-    if (!has_aes_hw &&       //
-        include_chacha20 &&  //
-        !CBB_add_u16(&child, TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff)) {
-      return false;
-    }
-    if (!CBB_add_u16(&child, TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff) ||
-        !CBB_add_u16(&child, TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff)) {
-      return false;
-    }
-    if (has_aes_hw &&        //
-        include_chacha20 &&  //
-        !CBB_add_u16(&child, TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff)) {
+    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;
     }
   }
diff --git a/ssl/internal.h b/ssl/internal.h
index e12b12f..01decb0 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -660,17 +660,17 @@
 
 // ssl_choose_tls13_cipher returns an |SSL_CIPHER| corresponding with the best
 // available from |cipher_suites| compatible with |version|, |group_id|, and
-// |only_fips|. It returns NULL if there isn't a compatible cipher. |has_aes_hw|
+// |policy|. It returns NULL if there isn't a compatible cipher. |has_aes_hw|
 // indicates if the choice should be made as if support for AES in hardware
 // is available.
 const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw,
                                           uint16_t version, uint16_t group_id,
-                                          bool only_fips);
+                                          enum ssl_compliance_policy_t policy);
 
 // 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);
+// |policy|.
+bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id,
+                                   enum ssl_compliance_policy_t policy);
 
 
 // Transcript layer.
@@ -3063,6 +3063,10 @@
   // structure for the client to use when negotiating ECH.
   Array<uint8_t> client_ech_config_list;
 
+  // tls13_cipher_policy limits the set of ciphers that can be selected when
+  // negotiating a TLS 1.3 connection.
+  enum ssl_compliance_policy_t tls13_cipher_policy = ssl_compliance_policy_none;
+
   // verify_mode is a bitmask of |SSL_VERIFY_*| values.
   uint8_t verify_mode = SSL_VERIFY_NONE;
 
@@ -3112,10 +3116,6 @@
   // 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;
-
   // aes_hw_override if set indicates we should override checking for aes
   // hardware support, and use the value in aes_hw_override_value instead.
   bool aes_hw_override : 1;
@@ -3684,6 +3684,10 @@
   int (*legacy_ocsp_callback)(SSL *ssl, void *arg) = nullptr;
   void *legacy_ocsp_callback_arg = nullptr;
 
+  // tls13_cipher_policy limits the set of ciphers that can be selected when
+  // negotiating a TLS 1.3 connection.
+  enum ssl_compliance_policy_t tls13_cipher_policy = ssl_compliance_policy_none;
+
   // verify_sigalgs, if not empty, is the set of signature algorithms
   // accepted from the peer in decreasing order of preference.
   bssl::Array<uint16_t> verify_sigalgs;
@@ -3731,10 +3735,6 @@
   // 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;
-
   // aes_hw_override if set indicates we should override checking for AES
   // hardware support, and use the value in aes_hw_override_value instead.
   bool aes_hw_override : 1;
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index f8352f3..6d33c6d 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -685,26 +685,44 @@
   const bool aes_is_fine_;
 };
 
-bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id, bool only_fips) {
-  if (!only_fips) {
-    return true;
+bool ssl_tls13_cipher_meets_policy(uint16_t cipher_id,
+                                   enum ssl_compliance_policy_t policy) {
+  switch (policy) {
+    case ssl_compliance_policy_none:
+      return true;
+
+    case ssl_compliance_policy_fips_202205:
+      switch (cipher_id) {
+        case TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff:
+        case TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff:
+          return true;
+        case TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff:
+          return false;
+        default:
+          assert(false);
+          return false;
+      }
+
+    case ssl_compliance_policy_wpa3_192_202304:
+      switch (cipher_id) {
+        case TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff:
+          return true;
+        case TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff:
+        case TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff:
+          return false;
+        default:
+          assert(false);
+          return false;
+      }
   }
 
-  switch (cipher_id) {
-    case TLS1_3_CK_AES_128_GCM_SHA256 & 0xffff:
-    case TLS1_3_CK_AES_256_GCM_SHA384 & 0xffff:
-      return true;
-    case TLS1_3_CK_CHACHA20_POLY1305_SHA256 & 0xffff:
-      return false;
-    default:
-      assert(false);
-      return false;
-  }
+  assert(false);
+  return false;
 }
 
 const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, bool has_aes_hw,
                                           uint16_t version, uint16_t group_id,
-                                          bool only_fips) {
+                                          enum ssl_compliance_policy_t policy) {
   if (CBS_len(&cipher_suites) % 2 != 0) {
     return nullptr;
   }
@@ -728,7 +746,7 @@
     }
 
     if (!ssl_tls13_cipher_meets_policy(SSL_CIPHER_get_protocol_id(candidate),
-                                       only_fips)) {
+                                       policy)) {
       continue;
     }
 
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 6b45496..838761a 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -536,7 +536,6 @@
       false_start_allowed_without_alpn(false),
       handoff(false),
       enable_early_data(false),
-      only_fips_cipher_suites_in_tls13(false),
       aes_hw_override(false),
       aes_hw_override_value(false) {
   CRYPTO_MUTEX_init(&lock);
@@ -658,10 +657,9 @@
   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;
   ssl->config->aes_hw_override = ctx->aes_hw_override;
   ssl->config->aes_hw_override_value = ctx->aes_hw_override_value;
+  ssl->config->tls13_cipher_policy = ctx->tls13_cipher_policy;
 
   if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) ||
       !ssl->config->alpn_client_proto_list.CopyFrom(
@@ -3175,7 +3173,7 @@
     "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
 
 static int Configure(SSL_CTX *ctx) {
-  ctx->only_fips_cipher_suites_in_tls13 = true;
+  ctx->tls13_cipher_policy = ssl_compliance_policy_fips_202205;
 
   return
       // Section 3.1:
@@ -3198,7 +3196,7 @@
 }
 
 static int Configure(SSL *ssl) {
-  ssl->config->only_fips_cipher_suites_in_tls13 = true;
+  ssl->config->tls13_cipher_policy = ssl_compliance_policy_fips_202205;
 
   // See |Configure(SSL_CTX)|, above, for reasoning.
   return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) &&
@@ -3213,11 +3211,59 @@
 
 }  // namespace fips202205
 
+namespace wpa202304 {
+
+// See WPA version 3.1, section 3.5.
+
+static const int kCurves[] = {NID_secp384r1};
+
+static const uint16_t kSigAlgs[] = {
+    SSL_SIGN_RSA_PKCS1_SHA384,        //
+    SSL_SIGN_RSA_PKCS1_SHA512,        //
+    SSL_SIGN_ECDSA_SECP384R1_SHA384,  //
+    SSL_SIGN_RSA_PSS_RSAE_SHA384,     //
+    SSL_SIGN_RSA_PSS_RSAE_SHA512,     //
+};
+
+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->tls13_cipher_policy = ssl_compliance_policy_wpa3_192_202304;
+
+  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_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->tls13_cipher_policy = ssl_compliance_policy_wpa3_192_202304;
+
+  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 wpa202304
+
 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);
+    case ssl_compliance_policy_wpa3_192_202304:
+      return wpa202304::Configure(ctx);
     default:
       return 0;
   }
@@ -3227,6 +3273,8 @@
   switch (policy) {
     case ssl_compliance_policy_fips_202205:
       return fips202205::Configure(ssl);
+    case ssl_compliance_policy_wpa3_192_202304:
+      return wpa202304::Configure(ssl);
     default:
       return 0;
   }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index ce06779..cb97498 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -222,9 +222,13 @@
 // algorithms.
 var supportedSignatureAlgorithms = []signatureAlgorithm{
 	signatureRSAPSSWithSHA256,
+	signatureRSAPSSWithSHA384,
 	signatureRSAPKCS1WithSHA256,
 	signatureECDSAWithP256AndSHA256,
+	signatureECDSAWithP384AndSHA384,
 	signatureRSAPKCS1WithSHA1,
+	signatureRSAPKCS1WithSHA256,
+	signatureRSAPKCS1WithSHA384,
 	signatureECDSAWithSHA1,
 	signatureEd25519,
 }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index fc8c6ff..b361e19 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -19194,13 +19194,21 @@
 				isFIPSCipherSuite = true
 			}
 
+			var isWPACipherSuite 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
+			}
+
 			var certFile string
 			var keyFile string
 			var certs []Certificate
 			if hasComponent(suite.name, "ECDSA") {
-				certFile = ecdsaP256CertificateFile
-				keyFile = ecdsaP256KeyFile
-				certs = []Certificate{ecdsaP256Certificate}
+				certFile = ecdsaP384CertificateFile
+				keyFile = ecdsaP384KeyFile
+				certs = []Certificate{ecdsaP384Certificate}
 			} else {
 				certFile = rsaCertificateFile
 				keyFile = rsaKeyFile
@@ -19215,38 +19223,48 @@
 				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,
-			})
+			policies := []struct {
+				flag          string
+				cipherSuiteOk bool
+			}{
+				{"-fips-202205", isFIPSCipherSuite},
+				{"-wpa-202304", isWPACipherSuite},
+			}
 
-			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,
-			})
+			for _, policy := range policies {
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + suite.name,
+					config: Config{
+						MinVersion:   VersionTLS12,
+						MaxVersion:   maxVersion,
+						CipherSuites: []uint16{suite.id},
+					},
+					certFile: certFile,
+					keyFile:  keyFile,
+					flags: []string{
+						policy.flag,
+					},
+					shouldFail: !policy.cipherSuiteOk,
+				})
+
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + suite.name,
+					config: Config{
+						MinVersion:   VersionTLS12,
+						MaxVersion:   maxVersion,
+						CipherSuites: []uint16{suite.id},
+						Certificates: certs,
+					},
+					flags: []string{
+						policy.flag,
+					},
+					shouldFail: !policy.cipherSuiteOk,
+				})
+			}
 		}
 
 		// Check that a TLS 1.3 client won't accept ChaCha20 even if the server
@@ -19276,35 +19294,51 @@
 				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,
-			})
+			var isWPACurve bool
+			switch curve.id {
+			case CurveP384:
+				isWPACurve = true
+			}
 
-			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,
-			})
+			policies := []struct {
+				flag    string
+				curveOk bool
+			}{
+				{"-fips-202205", isFIPSCurve},
+				{"-wpa-202304", isWPACurve},
+			}
+
+			for _, policy := range policies {
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + curve.name,
+					config: Config{
+						MinVersion:       VersionTLS12,
+						MaxVersion:       VersionTLS13,
+						CurvePreferences: []CurveID{curve.id},
+					},
+					flags: []string{
+						policy.flag,
+					},
+					shouldFail: !policy.curveOk,
+				})
+
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + curve.name,
+					config: Config{
+						MinVersion:       VersionTLS12,
+						MaxVersion:       VersionTLS13,
+						CurvePreferences: []CurveID{curve.id},
+					},
+					flags: []string{
+						policy.flag,
+					},
+					shouldFail: !policy.curveOk,
+				})
+			}
 		}
 
 		for _, sigalg := range testSignatureAlgorithms {
@@ -19321,6 +19355,16 @@
 				isFIPSSigAlg = true
 			}
 
+			var isWPASigAlg bool
+			switch sigalg.id {
+			case signatureRSAPKCS1WithSHA384,
+				signatureRSAPKCS1WithSHA512,
+				signatureECDSAWithP384AndSHA384,
+				signatureRSAPSSWithSHA384,
+				signatureRSAPSSWithSHA512:
+				isWPASigAlg = 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.
@@ -19335,38 +19379,48 @@
 				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,
-			})
+			policies := []struct {
+				flag     string
+				sigAlgOk bool
+			}{
+				{"-fips-202205", isFIPSSigAlg},
+				{"-wpa-202304", isWPASigAlg},
+			}
 
-			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,
-			})
+			for _, policy := range policies {
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + sigalg.name,
+					config: Config{
+						MinVersion:                VersionTLS12,
+						MaxVersion:                maxVersion,
+						VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
+					},
+					flags: []string{
+						policy.flag,
+						"-cert-file", path.Join(*resourceDir, getShimCertificate(sigalg.cert)),
+						"-key-file", path.Join(*resourceDir, getShimKey(sigalg.cert)),
+					},
+					shouldFail: !policy.sigAlgOk,
+				})
+
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + sigalg.name,
+					config: Config{
+						MinVersion:              VersionTLS12,
+						MaxVersion:              maxVersion,
+						SignSignatureAlgorithms: []signatureAlgorithm{sigalg.id},
+						Certificates:            []Certificate{getRunnerCertificate(sigalg.cert)},
+					},
+					flags: []string{
+						policy.flag,
+					},
+					shouldFail: !policy.sigAlgOk,
+				})
+			}
 		}
 	}
 }
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index ba10a33..0ee5580 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -390,6 +390,7 @@
       IntFlag("-early-write-after-message",
               &TestConfig::early_write_after_message),
       BoolFlag("-fips-202205", &TestConfig::fips_202205),
+      BoolFlag("-wpa-202304", &TestConfig::wpa_202304),
   };
   std::sort(flags.begin(), flags.end(), [](const Flag &a, const Flag &b) {
     return strcmp(a.name, b.name) < 0;
@@ -1765,11 +1766,20 @@
   if (enable_ech_grease) {
     SSL_set_enable_ech_grease(ssl.get(), 1);
   }
+  if (static_cast<int>(fips_202205) + static_cast<int>(wpa_202304) > 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 (!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 5cc5926..e8c473a 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -195,6 +195,7 @@
   std::string quic_early_data_context;
   int early_write_after_message = 0;
   bool fips_202205 = false;
+  bool wpa_202304 = false;
 
   int argc;
   char **argv;
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 92e26f2..1971596 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -200,7 +200,7 @@
       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)) {
+          ssl->config->tls13_cipher_policy)) {
     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 c97e3f5..9d26f4e0 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -120,7 +120,7 @@
       cipher_suites,
       ssl->config->aes_hw_override ? ssl->config->aes_hw_override_value
                                    : EVP_has_aes_hardware(),
-      version, group_id, ssl->config->only_fips_cipher_suites_in_tls13);
+      version, group_id, ssl->config->tls13_cipher_policy);
 }
 
 static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {