Add mode for MLS client auth, with EKU checking

Bug: 394579214

Change-Id: Ib52af3d1672e45c5c3bb136fc6e1f63210e713b2
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/77089
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/gen/sources.bzl b/gen/sources.bzl
index b116e71..c5c1fa9 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -2212,6 +2212,14 @@
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test",
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-chain.pem",
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-any.test",
     "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-clientAuth-strict.test",
@@ -2428,6 +2436,7 @@
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth.test",
@@ -2442,6 +2451,7 @@
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth.test",
@@ -2450,6 +2460,7 @@
     "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict-leaf.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth.test",
+    "pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth-strict.test",
     "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth.test",
     "pki/testdata/verify_certificate_chain_unittest/target-has-512bit-rsa-key/chain.pem",
diff --git a/gen/sources.cmake b/gen/sources.cmake
index 2d774f8..8d7b69e 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -2256,6 +2256,14 @@
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-chain.pem
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-any.test
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-clientAuth-strict.test
@@ -2472,6 +2480,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth.test
+  pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth.test
@@ -2486,6 +2495,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth.test
+  pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth.test
@@ -2494,6 +2504,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict-leaf.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth.test
+  pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth-strict.test
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth.test
   pki/testdata/verify_certificate_chain_unittest/target-has-512bit-rsa-key/chain.pem
diff --git a/gen/sources.gni b/gen/sources.gni
index 7762d0a..ea49630 100644
--- a/gen/sources.gni
+++ b/gen/sources.gni
@@ -2212,6 +2212,14 @@
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test",
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-chain.pem",
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-any.test",
   "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-clientAuth-strict.test",
@@ -2428,6 +2436,7 @@
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth.test",
@@ -2442,6 +2451,7 @@
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth.test",
@@ -2450,6 +2460,7 @@
   "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict-leaf.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth.test",
+  "pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth-strict.test",
   "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth.test",
   "pki/testdata/verify_certificate_chain_unittest/target-has-512bit-rsa-key/chain.pem",
diff --git a/gen/sources.json b/gen/sources.json
index ce099e0..47c6c55 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -2193,6 +2193,14 @@
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test",
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-chain.pem",
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-any.test",
       "pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-clientAuth-strict.test",
@@ -2409,6 +2417,7 @@
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth.test",
@@ -2423,6 +2432,7 @@
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth.test",
@@ -2431,6 +2441,7 @@
       "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict-leaf.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth.test",
+      "pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth-strict.test",
       "pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth.test",
       "pki/testdata/verify_certificate_chain_unittest/target-has-512bit-rsa-key/chain.pem",
diff --git a/gen/sources.mk b/gen/sources.mk
index a1116fa..7e6187a 100644
--- a/gen/sources.mk
+++ b/gen/sources.mk
@@ -2191,6 +2191,14 @@
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-clientauth/serverauth.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test \
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-chain.pem \
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-any.test \
   pki/testdata/verify_certificate_chain_unittest/intermediate-eku-server-gated-crypto/sha1-eku-clientAuth-strict.test \
@@ -2407,6 +2415,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/clientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-any/serverauth.test \
@@ -2421,6 +2430,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/clientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-many/serverauth.test \
@@ -2429,6 +2439,7 @@
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict-leaf.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/clientauth.test \
+  pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth-strict.test \
   pki/testdata/verify_certificate_chain_unittest/target-eku-none/serverauth.test \
   pki/testdata/verify_certificate_chain_unittest/target-has-512bit-rsa-key/chain.pem \
diff --git a/include/openssl/pki/verify.h b/include/openssl/pki/verify.h
index 06ca1af..1396e80 100644
--- a/include/openssl/pki/verify.h
+++ b/include/openssl/pki/verify.h
@@ -72,6 +72,7 @@
     CLIENT_AUTH_STRICT,
     SERVER_AUTH_STRICT_LEAF,
     CLIENT_AUTH_STRICT_LEAF,
+    RCS_MLS_CLIENT_AUTH,
   };
 
   CertificateVerifyOptions();
diff --git a/pki/common_cert_errors.cc b/pki/common_cert_errors.cc
index 636bfc6..af36eca 100644
--- a/pki/common_cert_errors.cc
+++ b/pki/common_cert_errors.cc
@@ -70,6 +70,9 @@
 DEFINE_CERT_ERROR_ID(kEkuHasProhibitedCodeSigning,
                      "The extended key usage includes code signing which "
                      "is not permitted for this use");
+DEFINE_CERT_ERROR_ID(kEkuIncorrectForRcsMlsClient,
+                     "The extended key usage does not contain only the "
+                     "rcsMlsClient key purpose.");
 DEFINE_CERT_ERROR_ID(kEkuNotPresent,
                      "Certificate does not have extended key usage");
 DEFINE_CERT_ERROR_ID(kCertIsNotTrustAnchor,
diff --git a/pki/common_cert_errors.h b/pki/common_cert_errors.h
index 40b02de..5b7a871 100644
--- a/pki/common_cert_errors.h
+++ b/pki/common_cert_errors.h
@@ -139,6 +139,9 @@
 // The certificate's EKU has Code Signing when it should not.
 OPENSSL_EXPORT extern const CertErrorId kEkuHasProhibitedCodeSigning;
 
+// The certificate's EKU is incorrect for an RcsMlsClient.
+OPENSSL_EXPORT extern const CertErrorId kEkuIncorrectForRcsMlsClient;
+
 // The certificate does not have EKU.
 OPENSSL_EXPORT extern const CertErrorId kEkuNotPresent;
 
diff --git a/pki/extended_key_usage.h b/pki/extended_key_usage.h
index 2556c24..c0915fd 100644
--- a/pki/extended_key_usage.h
+++ b/pki/extended_key_usage.h
@@ -78,6 +78,18 @@
 inline constexpr uint8_t kOCSPSigning[] = {0x2b, 0x06, 0x01, 0x05,
                                            0x05, 0x07, 0x03, 0x09};
 
+// From GSMA RCC.16 v1.0 End-to-End Encryption Specification.
+// id-gsmaRCSE2EE OBJECT IDENTIFIER ::=  { joint-iso-itu-t(2)
+// international-organizations(23) gsma(146) rcs(2) rcsE2EE (1)}
+// (Note this spec incorrectly says id-appleDraftRCSE2EE in place of
+// id-gmsaRCSE2EE in several places)
+//
+// From GSMA RCC.16 v1.0 End-to-End Encryption Specification section A.2.8.8,
+// and A.3.8.7.
+// id-kp-rcsMlsClient OBJECT IDENTIFIER ::= { id-gmsaRCS2EE 3 }
+// In dotted notation: 2.23.146.2.1.3
+inline constexpr uint8_t kRcsMlsClient[] = {0x67, 0x81, 0x12, 0x02, 0x01, 0x03};
+
 // Parses |extension_value|, which contains the extnValue field of an X.509v3
 // Extended Key Usage extension, and populates |eku_oids| with the list of
 // DER-encoded OID values (that is, without tag and length). Returns false if
diff --git a/pki/test_helpers.cc b/pki/test_helpers.cc
index 20f082f..a66b520 100644
--- a/pki/test_helpers.cc
+++ b/pki/test_helpers.cc
@@ -319,6 +319,8 @@
         test->key_purpose = KeyPurpose::SERVER_AUTH_STRICT_LEAF;
       } else if (value == "CLIENT_AUTH_STRICT_LEAF") {
         test->key_purpose = KeyPurpose::CLIENT_AUTH_STRICT_LEAF;
+      } else if (value == "MLS_CLIENT_AUTH") {
+        test->key_purpose = KeyPurpose::RCS_MLS_CLIENT_AUTH;
       } else {
         ADD_FAILURE() << "Unrecognized key_purpose: " << value;
         return false;
diff --git a/pki/testdata/verify_certificate_chain_unittest/generate-all.sh b/pki/testdata/verify_certificate_chain_unittest/generate-all.sh
index 15dc4ff..4941ca3 100755
--- a/pki/testdata/verify_certificate_chain_unittest/generate-all.sh
+++ b/pki/testdata/verify_certificate_chain_unittest/generate-all.sh
@@ -16,6 +16,11 @@
 
 set -e
 
+# As generate-chains.py calls out to the openssl command under the hood
+# this is suboptimal if you don't have openssl installed. We should
+# replace generate-chains.py
+# TODO(bbe): crbug.com/402461221
+
 for dir in */ ; do
   cd "$dir"
 
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test
new file mode 100644
index 0000000..4d092a9
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/any.test
@@ -0,0 +1,5 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: ANY_EKU
+expected_errors:
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem
new file mode 100644
index 0000000..1a7bdf8
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/chain.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIBkDCCATagAwIBAgIBAzAKBggqhkjOPQQDAjAgMR4wHAYDVQQDExVNTFMgQ2Vy
+dCBJbnRlcm1lZGlhdGUwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1
+OVowGDEWMBQGA1UEAxMNTUxTIENlcnQgTGVhZjBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhER
+pxGojS6aHNHZXk0vMEE/3I8P8D4KHlejZTBjMA4GA1UdDwEB/wQEAwIHgDAMBgNV
+HRMBAf8EAjAAMA0GA1UdDgQGBARsZWFmMBcGA1UdIwQQMA6ADGludGVybWVkaWF0
+ZTAbBgNVHSUEFDASBgZngRICAQMGCCsGAQUFBwMCMAoGCCqGSM49BAMCA0gAMEUC
+ICZ2aFiHqdwrk44duDdK3KB/j3o2KNsILy0kSrOL85x9AiEA19Xas5gJfMK02neq
+UCzUZsXgFZDdfQdg05qikpSox1o=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBkzCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1NTFMgQ2Vy
+dCBSb290MCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCAxHjAc
+BgNVBAMTFU1MUyBDZXJ0IEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABOI6fKiM3jFLkLyAn88cvlw4SwxuygRjopP3FFBKHyUQvh3VVvfqSpSC
+Smp50QiajQ6Dg7CTpVZVVH+bguT7JTCjaDBmMA4GA1UdDwEB/wQEAwICBDAPBgNV
+HRMBAf8EBTADAQH/MBUGA1UdDgQOBAxpbnRlcm1lZGlhdGUwDwYDVR0jBAgwBoAE
+cm9vdDAbBgNVHSUEFDASBgZngRICAQMGCCsGAQUFBwMCMAoGCCqGSM49BAMCA0gA
+MEUCIQCab7y2vrjyMny/WJbdTlJilqPfnuDlEirN0T2AVLJfegIgF+/AcAxZq6Q/
+MIbkA98Th1fRCsTBNVpkVB/fTrp4vXk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBVTCB+6ADAgECAgEBMAoGCCqGSM49BAMCMBgxFjAUBgNVBAMTDU1MUyBDZXJ0
+IFJvb3QwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowGDEWMBQG
+A1UEAxMNTUxTIENlcnQgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCZ2
+pdiXUa9jvLeNxNMroxVchCvjhOoKFFgjoh5X20gHqMiw5j5CiXVuTzH/sDkUY6dR
+YVUsQA8Q9InA+d2OiW2jMjAwMA4GA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTAD
+AQH/MA0GA1UdDgQGBARyb290MAoGCCqGSM49BAMCA0kAMEYCIQCBTvSTnUwq0rQr
+0xFmkVYH1NwAP6OFlfQNfSP9XgeMrQIhAO6ILVuc7gQITkDQQ/9CJLjHD6+aHl9w
+ZSABihMw3UoF
+-----END CERTIFICATE-----
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test
new file mode 100644
index 0000000..82d223f
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth-extra/mlsclientauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: MLS_CLIENT_AUTH
+expected_errors:
+----- Certificate i=0 (CN=MLS Cert Leaf) -----
+ERROR: The extended key usage does not contain only the rcsMlsClient key purpose.
+
+----- Certificate i=1 (CN=MLS Cert Intermediate) -----
+ERROR: The extended key usage does not contain only the rcsMlsClient key purpose.
+
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test
new file mode 100644
index 0000000..4d092a9
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/any.test
@@ -0,0 +1,5 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: ANY_EKU
+expected_errors:
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem
new file mode 100644
index 0000000..b6ee783
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/chain.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIBhzCCASygAwIBAgIBAzAKBggqhkjOPQQDAjAgMR4wHAYDVQQDExVNTFMgQ2Vy
+dCBJbnRlcm1lZGlhdGUwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1
+OVowGDEWMBQGA1UEAxMNTUxTIENlcnQgTGVhZjBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhER
+pxGojS6aHNHZXk0vMEE/3I8P8D4KHlejWzBZMA4GA1UdDwEB/wQEAwIHgDAMBgNV
+HRMBAf8EAjAAMA0GA1UdDgQGBARsZWFmMBcGA1UdIwQQMA6ADGludGVybWVkaWF0
+ZTARBgNVHSUECjAIBgZngRICAQMwCgYIKoZIzj0EAwIDSQAwRgIhAIwK8bEHhn7W
+iWgVLTGsuSdILRxhNC/aGQDU1CwwsS3GAiEAivWAQPt30IzsWrwdFPyAl/68xbnP
+SNzmnZtG2HC4WTs=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBiTCCAS+gAwIBAgIBAjAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1NTFMgQ2Vy
+dCBSb290MCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCAxHjAc
+BgNVBAMTFU1MUyBDZXJ0IEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABOI6fKiM3jFLkLyAn88cvlw4SwxuygRjopP3FFBKHyUQvh3VVvfqSpSC
+Smp50QiajQ6Dg7CTpVZVVH+bguT7JTCjXjBcMA4GA1UdDwEB/wQEAwICBDAPBgNV
+HRMBAf8EBTADAQH/MBUGA1UdDgQOBAxpbnRlcm1lZGlhdGUwDwYDVR0jBAgwBoAE
+cm9vdDARBgNVHSUECjAIBgZngRICAQMwCgYIKoZIzj0EAwIDSAAwRQIgOETRj6he
+MGkPl27+ZVNsvmd1rWuiNpb2Zc79M9FlrHUCIQDpkCWKOepaN7Zsaw/XSh4Q2pJ7
+E9JUo6Ea1qNumaEx1g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBVDCB+6ADAgECAgEBMAoGCCqGSM49BAMCMBgxFjAUBgNVBAMTDU1MUyBDZXJ0
+IFJvb3QwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowGDEWMBQG
+A1UEAxMNTUxTIENlcnQgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCZ2
+pdiXUa9jvLeNxNMroxVchCvjhOoKFFgjoh5X20gHqMiw5j5CiXVuTzH/sDkUY6dR
+YVUsQA8Q9InA+d2OiW2jMjAwMA4GA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTAD
+AQH/MA0GA1UdDgQGBARyb290MAoGCCqGSM49BAMCA0gAMEUCIF3vtvNCgqJwgw+0
+ugR3xzy/hDMDO9k8/YZl2jfVReQAAiEA1yezH2MtSHGs1HIZyLjBW+32N5abvlll
+zWPFmkMkxJU=
+-----END CERTIFICATE-----
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test
new file mode 100644
index 0000000..cbe7baa
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/clientauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: CLIENT_AUTH
+expected_errors:
+----- Certificate i=0 (CN=MLS Cert Leaf) -----
+ERROR: The extended key usage does not include client auth
+
+----- Certificate i=1 (CN=MLS Cert Intermediate) -----
+ERROR: The extended key usage does not include client auth
+
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/make-mls-extensions.go b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/make-mls-extensions.go
new file mode 100644
index 0000000..3d9f65a
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/make-mls-extensions.go
@@ -0,0 +1,159 @@
+// Copyright 2025 The BoringSSL Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build ignore
+
+// make-mls-extensions.go generates test certs to test mls extension handling.
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"encoding/pem"
+	"math/big"
+	"os"
+	"time"
+)
+
+var leafKey, intermediateKey, rootKey *ecdsa.PrivateKey
+
+func init() {
+	leafKey = mustParseECDSAKey(leafKeyPEM)
+	intermediateKey = mustParseECDSAKey(intermediateKeyPEM)
+	rootKey = mustParseECDSAKey(rootKeyPEM)
+}
+
+type templateAndKey struct {
+	template x509.Certificate
+	key      *ecdsa.PrivateKey
+}
+
+func mustGenerateCertificate(path string, subject, issuer *templateAndKey) []byte {
+	cert, err := x509.CreateCertificate(rand.Reader, &subject.template, &issuer.template, &subject.key.PublicKey, issuer.key)
+	if err != nil {
+		panic(err)
+	}
+	file, err := os.Create(path)
+	if err != nil {
+		panic(err)
+	}
+	defer file.Close()
+	err = pem.Encode(file, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
+	if err != nil {
+		panic(err)
+	}
+	return cert
+}
+
+func main() {
+	notBefore, err := time.Parse(time.RFC3339, "0000-01-01T00:00:00Z")
+	if err != nil {
+		panic(err)
+	}
+	notAfter, err := time.Parse(time.RFC3339, "9999-12-31T23:59:59Z")
+	if err != nil {
+		panic(err)
+	}
+
+	root := templateAndKey{
+		template: x509.Certificate{
+			SerialNumber:          new(big.Int).SetInt64(1),
+			Subject:               pkix.Name{CommonName: "MLS Cert Root"},
+			NotBefore:             notBefore,
+			NotAfter:              notAfter,
+			BasicConstraintsValid: true,
+			IsCA:                  true,
+			KeyUsage:              x509.KeyUsageCertSign,
+			SignatureAlgorithm:    x509.ECDSAWithSHA256,
+			SubjectKeyId:          []byte("root"),
+		},
+		key: rootKey,
+	}
+	intermediate := templateAndKey{
+		template: x509.Certificate{
+			SerialNumber:          new(big.Int).SetInt64(2),
+			Subject:               pkix.Name{CommonName: "MLS Cert Intermediate"},
+			NotBefore:             notBefore,
+			NotAfter:              notAfter,
+			BasicConstraintsValid: true,
+			IsCA:                  true,
+			KeyUsage:              x509.KeyUsageCertSign,
+			SignatureAlgorithm:    x509.ECDSAWithSHA256,
+			SubjectKeyId:          []byte("intermediate"),
+			UnknownExtKeyUsage:    []asn1.ObjectIdentifier{[]int{2, 23, 146, 2, 1, 3}},
+		},
+		key: intermediateKey,
+	}
+	leaf := templateAndKey{
+		template: x509.Certificate{
+			SerialNumber:          new(big.Int).SetInt64(3),
+			Subject:               pkix.Name{CommonName: "MLS Cert Leaf"},
+			NotBefore:             notBefore,
+			NotAfter:              notAfter,
+			BasicConstraintsValid: true,
+			IsCA:                  false,
+			KeyUsage:              x509.KeyUsageDigitalSignature,
+			SignatureAlgorithm:    x509.ECDSAWithSHA256,
+			SubjectKeyId:          []byte("leaf"),
+			UnknownExtKeyUsage:    []asn1.ObjectIdentifier{[]int{2, 23, 146, 2, 1, 3}},
+		},
+		key: leafKey,
+	}
+
+	// Generate a valid certificate chain from the templates.
+	mustGenerateCertificate("mls_client_root.pem", &root, &root)
+	mustGenerateCertificate("mls_client_intermediate.pem", &intermediate, &root)
+	mustGenerateCertificate("mls_client_leaf.pem", &leaf, &intermediate)
+	intermediateInvalid := intermediate
+	intermediateInvalid.template.UnknownExtKeyUsage = []asn1.ObjectIdentifier{[]int{2, 23, 146, 2, 1, 3},
+		[]int{2, 23, 133, 8, 1}}
+	mustGenerateCertificate("mls_client_intermediate_extra_eku.pem", &intermediateInvalid, &root)
+	leafInvalid := leaf
+	leafInvalid.template.UnknownExtKeyUsage = []asn1.ObjectIdentifier{[]int{2, 23, 146, 2, 1, 3},
+		[]int{2, 23, 133, 8, 1}}
+	mustGenerateCertificate("mls_client_leaf_extra_eku.pem", &leafInvalid, &intermediateInvalid)
+}
+
+const leafKeyPEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgoPUXNXuH9mgiS/nk
+024SYxryxMa3CyGJldiHymLxSquhRANCAASRKti8VW2Rkma+Kt9jQkMNitlCs0l5
+w8u3SSwm7HZREvmcBCJBjVIREacRqI0umhzR2V5NLzBBP9yPD/A+Ch5X
+-----END PRIVATE KEY-----`
+
+const intermediateKeyPEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWHKCKgY058ahE3t6
+vpxVQgzlycgCVMogwjK0y3XMNfWhRANCAATiOnyojN4xS5C8gJ/PHL5cOEsMbsoE
+Y6KT9xRQSh8lEL4d1Vb36kqUgkpqedEImo0Og4Owk6VWVVR/m4Lk+yUw
+-----END PRIVATE KEY-----`
+
+const rootKeyPEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBwND/eHytW0I417J
+Hr+qcPlp5N1jM3ACXys57bPujg+hRANCAAQmdqXYl1GvY7y3jcTTK6MVXIQr44Tq
+ChRYI6IeV9tIB6jIsOY+Qol1bk8x/7A5FGOnUWFVLEAPEPSJwPndjolt
+-----END PRIVATE KEY-----`
+
+func mustParseECDSAKey(in string) *ecdsa.PrivateKey {
+	keyBlock, _ := pem.Decode([]byte(in))
+	if keyBlock == nil || keyBlock.Type != "PRIVATE KEY" {
+		panic("could not decode private key")
+	}
+	key, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
+	if err != nil {
+		panic(err)
+	}
+	return key.(*ecdsa.PrivateKey)
+}
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test
new file mode 100644
index 0000000..f6b4558
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/mlsclientauth.test
@@ -0,0 +1,5 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: MLS_CLIENT_AUTH
+expected_errors:
diff --git a/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test
new file mode 100644
index 0000000..b1c0909
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/intermediate-eku-mlsclientauth/serverauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: SERVER_AUTH
+expected_errors:
+----- Certificate i=0 (CN=MLS Cert Leaf) -----
+ERROR: The extended key usage does not include server auth
+
+----- Certificate i=1 (CN=MLS Cert Intermediate) -----
+ERROR: The extended key usage does not include server auth
+
diff --git a/pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test b/pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test
new file mode 100644
index 0000000..57f822e
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/target-eku-any/mlsclientauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: MLS_CLIENT_AUTH
+expected_errors:
+----- Certificate i=0 (CN=Target) -----
+ERROR: The extended key usage does not contain only the rcsMlsClient key purpose.
+
+----- Certificate i=1 (CN=Intermediate) -----
+ERROR: Certificate does not have extended key usage
+
diff --git a/pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test b/pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test
new file mode 100644
index 0000000..57f822e
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/target-eku-many/mlsclientauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: MLS_CLIENT_AUTH
+expected_errors:
+----- Certificate i=0 (CN=Target) -----
+ERROR: The extended key usage does not contain only the rcsMlsClient key purpose.
+
+----- Certificate i=1 (CN=Intermediate) -----
+ERROR: Certificate does not have extended key usage
+
diff --git a/pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test b/pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test
new file mode 100644
index 0000000..52d1446
--- /dev/null
+++ b/pki/testdata/verify_certificate_chain_unittest/target-eku-none/mlsclientauth.test
@@ -0,0 +1,11 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: MLS_CLIENT_AUTH
+expected_errors:
+----- Certificate i=0 (CN=Target) -----
+ERROR: Certificate does not have extended key usage
+
+----- Certificate i=1 (CN=Intermediate) -----
+ERROR: Certificate does not have extended key usage
+
diff --git a/pki/verify.cc b/pki/verify.cc
index 09373c0..9acf43a 100644
--- a/pki/verify.cc
+++ b/pki/verify.cc
@@ -261,6 +261,9 @@
     case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
       key_purpose = KeyPurpose::CLIENT_AUTH_STRICT_LEAF;
       break;
+    case CertificateVerifyOptions::KeyPurpose::RCS_MLS_CLIENT_AUTH:
+      key_purpose = KeyPurpose::RCS_MLS_CLIENT_AUTH;
+      break;
   }
   CertPathBuilder path_builder(leaf_cert, trust_store, &path_builder_delegate,
                                verification_time, key_purpose,
diff --git a/pki/verify_certificate_chain.cc b/pki/verify_certificate_chain.cc
index d7c5f29..219273f 100644
--- a/pki/verify_certificate_chain.cc
+++ b/pki/verify_certificate_chain.cc
@@ -216,8 +216,11 @@
   bool has_code_signing_eku = false;
   bool has_time_stamping_eku = false;
   bool has_ocsp_signing_eku = false;
+  bool has_rcs_mls_client_eku = false;
+  size_t eku_oid_count = 0;
   if (cert.has_extended_key_usage()) {
     for (const auto &key_purpose_oid : cert.extended_key_usage()) {
+      eku_oid_count++;
       if (key_purpose_oid == der::Input(kAnyEKU)) {
         has_any_eku = true;
       }
@@ -236,9 +239,25 @@
       if (key_purpose_oid == der::Input(kOCSPSigning)) {
         has_ocsp_signing_eku = true;
       }
+      if (key_purpose_oid == der::Input(kRcsMlsClient)) {
+        has_rcs_mls_client_eku = true;
+      }
     }
   }
 
+  if (required_key_purpose == KeyPurpose::RCS_MLS_CLIENT_AUTH) {
+    // Rules for MLS client auth. For the leaf and all intermediates, EKU must
+    // be present and have exactly one EKU which is rcsMlsClient.
+    if (!cert.has_extended_key_usage()) {
+      errors->AddError(cert_errors::kEkuNotPresent);
+    } else if (eku_oid_count != 1 || !has_rcs_mls_client_eku) {
+      errors->AddError(cert_errors::kEkuIncorrectForRcsMlsClient);
+    }
+    return;
+  }
+
+  // Rules TLS client and server authentication variants.
+
   // Apply strict only to leaf certificates in these cases.
   if (required_key_purpose == KeyPurpose::CLIENT_AUTH_STRICT_LEAF) {
     if (!is_target_cert) {
@@ -329,6 +348,7 @@
     case KeyPurpose::ANY_EKU:
     case KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
     case KeyPurpose::SERVER_AUTH_STRICT_LEAF:
+    case KeyPurpose::RCS_MLS_CLIENT_AUTH:
       assert(0);  // NOTREACHED
       return;
     case KeyPurpose::SERVER_AUTH:
@@ -1232,6 +1252,7 @@
       case KeyPurpose::CLIENT_AUTH_STRICT:
       case KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
       case KeyPurpose::SERVER_AUTH_STRICT_LEAF:
+      case KeyPurpose::RCS_MLS_CLIENT_AUTH:
         errors->AddError(cert_errors::kTargetCertShouldNotBeCa);
         break;
     }
diff --git a/pki/verify_certificate_chain.h b/pki/verify_certificate_chain.h
index f2cf603..f37bc5c 100644
--- a/pki/verify_certificate_chain.h
+++ b/pki/verify_certificate_chain.h
@@ -40,10 +40,11 @@
   CLIENT_AUTH,
   SERVER_AUTH_STRICT,  // Skip ANY_EKU when checking, require EKU present in
                        // certificate.
-  SERVER_AUTH_STRICT_LEAF, // Same as above, but only for leaf cert.
+  SERVER_AUTH_STRICT_LEAF,  // Same as above, but only for leaf cert.
   CLIENT_AUTH_STRICT,  // Skip ANY_EKU when checking, require EKU present in
                        // certificate.
-  CLIENT_AUTH_STRICT_LEAF, // Same as above, but only for leaf ce
+  CLIENT_AUTH_STRICT_LEAF,  // Same as above, but only for leaf cert.
+  RCS_MLS_CLIENT_AUTH,      // Client auth for RCS-MLS.
 };
 
 enum class InitialExplicitPolicy {
diff --git a/pki/verify_certificate_chain_typed_unittest.h b/pki/verify_certificate_chain_typed_unittest.h
index c7f9f11..f62a4b9 100644
--- a/pki/verify_certificate_chain_typed_unittest.h
+++ b/pki/verify_certificate_chain_typed_unittest.h
@@ -184,6 +184,12 @@
   this->RunTest("intermediate-eku-clientauth/serverauth-strict-leaf.test");
   this->RunTest("intermediate-eku-clientauth/clientauth-strict.test");
   this->RunTest("intermediate-eku-clientauth/clientauth-strict-leaf.test");
+  this->RunTest("intermediate-eku-mlsclientauth/any.test");
+  this->RunTest("intermediate-eku-mlsclientauth/serverauth.test");
+  this->RunTest("intermediate-eku-mlsclientauth/clientauth.test");
+  this->RunTest("intermediate-eku-mlsclientauth/mlsclientauth.test");
+  this->RunTest("intermediate-eku-mlsclientauth-extra/any.test");
+  this->RunTest("intermediate-eku-mlsclientauth-extra/mlsclientauth.test");
   this->RunTest("intermediate-eku-any-and-clientauth/any.test");
   this->RunTest("intermediate-eku-any-and-clientauth/serverauth.test");
   this->RunTest("intermediate-eku-any-and-clientauth/serverauth-strict.test");
@@ -203,6 +209,7 @@
   this->RunTest("target-eku-any/serverauth-strict.test");
   this->RunTest("target-eku-any/clientauth-strict.test");
   this->RunTest("target-eku-any/clientauth-strict-leaf.test");
+  this->RunTest("target-eku-any/mlsclientauth.test");
   this->RunTest("target-eku-many/any.test");
   this->RunTest("target-eku-many/serverauth.test");
   this->RunTest("target-eku-many/clientauth.test");
@@ -210,12 +217,14 @@
   this->RunTest("target-eku-many/serverauth-strict-leaf.test");
   this->RunTest("target-eku-many/clientauth-strict.test");
   this->RunTest("target-eku-many/clientauth-strict-leaf.test");
+  this->RunTest("target-eku-many/mlsclientauth.test");
   this->RunTest("target-eku-none/any.test");
   this->RunTest("target-eku-none/serverauth.test");
   this->RunTest("target-eku-none/clientauth.test");
   this->RunTest("target-eku-none/serverauth-strict.test");
   this->RunTest("target-eku-none/clientauth-strict.test");
   this->RunTest("target-eku-none/clientauth-strict-leaf.test");
+  this->RunTest("target-eku-none/mlsclientauth.test");
   this->RunTest("root-eku-clientauth/serverauth.test");
   this->RunTest("root-eku-clientauth/serverauth-strict.test");
   this->RunTest("root-eku-clientauth/serverauth-ta-with-constraints.test");