Snap for 11421525 from 49948d1655433282c52a9f3068b2a76371d0fd3a to simpleperf-release

Change-Id: I248e422b8966cf713d951df83e00a23517f5003f
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index c33f2e1..7fc6bfd 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,6 @@
 {
   "git": {
-    "sha1": "6c334a2cf5853fb0aa93b5eb0318c031fc2f6f98"
-  }
-}
+    "sha1": "25c183db36d302b69fd9648432e2c679301cb18e"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 27efee5..935a7ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,7 +36,7 @@
     host_supported: true,
     crate_name: "webpki",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.22.0",
+    cargo_pkg_version: "0.22.4",
     srcs: ["src/lib.rs"],
     edition: "2018",
     features: [
@@ -58,7 +58,7 @@
     host_supported: true,
     crate_name: "webpki",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.22.0",
+    cargo_pkg_version: "0.22.4",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -73,6 +73,8 @@
     rustlibs: [
         "libbase64_rust",
         "libring",
+        "libserde",
+        "libserde_json",
         "libuntrusted",
     ],
 }
@@ -82,7 +84,7 @@
     host_supported: true,
     crate_name: "dns_name_tests",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.22.0",
+    cargo_pkg_version: "0.22.4",
     srcs: ["tests/dns_name_tests.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -97,6 +99,8 @@
     rustlibs: [
         "libbase64_rust",
         "libring",
+        "libserde",
+        "libserde_json",
         "libuntrusted",
         "libwebpki",
     ],
@@ -107,7 +111,7 @@
     host_supported: true,
     crate_name: "integration",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.22.0",
+    cargo_pkg_version: "0.22.4",
     srcs: ["tests/integration.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -122,6 +126,8 @@
     rustlibs: [
         "libbase64_rust",
         "libring",
+        "libserde",
+        "libserde_json",
         "libuntrusted",
         "libwebpki",
     ],
diff --git a/Cargo.toml b/Cargo.toml
index 4d687bb..5bec9cb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,32 +3,52 @@
 # When uploading crates to the registry Cargo will automatically
 # "normalize" Cargo.toml files for maximal compatibility
 # with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies
+# to registry (e.g., crates.io) dependencies.
 #
-# If you believe there's an error in this file please file an
-# issue against the rust-lang/cargo repository. If you're
-# editing this file be aware that the upstream Cargo.toml
-# will likely look very different (and much more reasonable)
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
 
 [package]
 edition = "2018"
+rust-version = "1.61.0"
 name = "webpki"
-version = "0.22.0"
+version = "0.22.4"
 authors = ["Brian Smith <brian@briansmith.org>"]
-include = ["Cargo.toml", "LICENSE", "README.md", "src/calendar.rs", "src/cert.rs", "src/der.rs", "src/end_entity.rs", "src/error.rs", "src/name.rs", "src/name/dns_name.rs", "src/name/ip_address.rs", "src/name/verify.rs", "src/signed_data.rs", "src/time.rs", "src/trust_anchor.rs", "src/verify_cert.rs", "src/lib.rs", "src/data/**/*", "tests/dns_name_tests.rs", "tests/integration.rs", "tests/misc/serial_neg.der", "tests/misc/serial_zero.der", "tests/netflix/ca.der", "tests/netflix/ee.der", "tests/netflix/inter.der", "tests/ed25519/ca.der", "tests/ed25519/ee.der", "third-party/chromium/**/*"]
+include = [
+    "Cargo.toml",
+    "LICENSE",
+    "README.md",
+    "src/**/*.rs",
+    "src/data/**/*",
+    "tests/dns_name_tests.rs",
+    "tests/integration.rs",
+    "tests/misc/serial_neg.der",
+    "tests/misc/serial_zero.der",
+    "tests/netflix/ca.der",
+    "tests/netflix/ee.der",
+    "tests/netflix/inter.der",
+    "tests/ed25519/ca.der",
+    "tests/ed25519/ee.der",
+    "third-party/chromium/**/*",
+]
 description = "Web PKI X.509 Certificate Verification."
-documentation = "https://briansmith.org/rustdoc/webpki/"
 readme = "README.md"
-categories = ["cryptography", "no-std"]
+categories = [
+    "cryptography",
+    "no-std",
+]
 license-file = "LICENSE"
 repository = "https://github.com/briansmith/webpki"
+
 [package.metadata.docs.rs]
 all-features = true
+
 [profile.bench]
 opt-level = 3
 lto = true
 codegen-units = 1
-debug = false
+debug = 0
 debug-assertions = false
 rpath = false
 
@@ -36,21 +56,30 @@
 opt-level = 3
 lto = true
 codegen-units = 1
-debug = false
+debug = 0
 debug-assertions = false
 rpath = false
 
 [lib]
 name = "webpki"
+
 [dependencies.ring]
-version = "0.16.19"
+version = "0.17.2"
 default-features = false
 
 [dependencies.untrusted]
-version = "0.7.1"
+version = "0.9"
+
 [dev-dependencies.base64]
 version = "0.9.1"
 
+[dev-dependencies.serde]
+version = "1.0"
+features = ["derive"]
+
+[dev-dependencies.serde_json]
+version = "1.0"
+
 [features]
 alloc = ["ring/alloc"]
 std = ["alloc"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 28a5601..04618a0 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -16,13 +16,13 @@
 authors = ["Brian Smith <brian@briansmith.org>"]
 categories = ["cryptography", "no-std"]
 description = "Web PKI X.509 Certificate Verification."
-documentation = "https://briansmith.org/rustdoc/webpki/"
 edition = "2018"
 license-file = "LICENSE"
 name = "webpki"
 readme = "README.md"
 repository = "https://github.com/briansmith/webpki"
-version = "0.22.0"
+rust-version = "1.61.0"
+version = "0.22.4"
 
 include = [
     "Cargo.toml",
@@ -30,21 +30,7 @@
     "LICENSE",
     "README.md",
 
-    "src/calendar.rs",
-    "src/cert.rs",
-    "src/der.rs",
-    "src/end_entity.rs",
-    "src/error.rs",
-    "src/name.rs",
-    "src/name/dns_name.rs",
-    "src/name/ip_address.rs",
-    "src/name/verify.rs",
-    "src/signed_data.rs",
-    "src/time.rs",
-    "src/trust_anchor.rs",
-    "src/verify_cert.rs",
-    "src/lib.rs",
-
+    "src/**/*.rs",
     "src/data/**/*",
 
     "tests/dns_name_tests.rs",
@@ -71,11 +57,13 @@
 std = ["alloc"]
 
 [dependencies]
-ring = { version = "0.16.19", default-features = false }
-untrusted = "0.7.1"
+ring = { version = "0.17.2", default-features = false }
+untrusted = "0.9"
 
 [dev-dependencies]
 base64 = "0.9.1"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
 
 [profile.bench]
 opt-level = 3
@@ -92,3 +80,9 @@
 lto = true
 debug-assertions = false
 codegen-units = 1
+
+[workspace]
+members = [
+    # Intentionally not a default member.
+    "rcgen-tests",
+]
diff --git a/METADATA b/METADATA
index a5376ed..9156ed1 100644
--- a/METADATA
+++ b/METADATA
@@ -1,19 +1,20 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/rust/crates/webpki
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
 name: "webpki"
 description: "Web PKI X.509 Certificate Verification."
 third_party {
-  url {
-    type: HOMEPAGE
-    value: "https://crates.io/crates/webpki"
-  }
-  url {
-    type: ARCHIVE
-    value: "https://static.crates.io/crates/webpki/webpki-0.22.0.crate"
-  }
-  version: "0.22.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 7
-    day: 19
+    year: 2024
+    month: 2
+    day: 6
+  }
+  homepage: "https://crates.io/crates/webpki"
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/webpki/webpki-0.22.4.crate"
+    version: "0.22.4"
   }
 }
diff --git a/src/budget.rs b/src/budget.rs
new file mode 100644
index 0000000..ea73a7d
--- /dev/null
+++ b/src/budget.rs
@@ -0,0 +1,60 @@
+// Copyright 2015 Brian Smith.
+// Portions Copyright 2033 Daniel McCarney.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::ErrorExt;
+
+pub(super) struct Budget {
+    signatures: usize,
+    build_chain_calls: usize,
+}
+
+impl Budget {
+    #[inline]
+    pub fn consume_signature(&mut self) -> Result<(), ErrorExt> {
+        checked_sub(
+            &mut self.signatures,
+            ErrorExt::MaximumSignatureChecksExceeded,
+        )
+    }
+
+    #[inline]
+    pub fn consume_build_chain_call(&mut self) -> Result<(), ErrorExt> {
+        checked_sub(
+            &mut self.build_chain_calls,
+            ErrorExt::MaximumPathBuildCallsExceeded,
+        )
+    }
+}
+
+fn checked_sub(value: &mut usize, underflow_error: ErrorExt) -> Result<(), ErrorExt> {
+    *value = value.checked_sub(1).ok_or(underflow_error)?;
+    Ok(())
+}
+
+impl Default for Budget {
+    fn default() -> Self {
+        Self {
+            // This limit is taken from the remediation for golang CVE-2018-16875.  However,
+            // note that golang subsequently implemented AKID matching due to this limit
+            // being hit in real applications (see <https://github.com/spiffe/spire/issues/1004>).
+            // So this may actually be too aggressive.
+            signatures: 100,
+
+            // This limit is taken from mozilla::pkix, see:
+            // <https://github.com/nss-dev/nss/blob/bb4a1d38dd9e92923525ac6b5ed0288479f3f3fc/lib/mozpkix/lib/pkixbuild.cpp#L381-L393>
+            build_chain_calls: 200_000,
+        }
+    }
+}
diff --git a/src/cert.rs b/src/cert.rs
index 7c76f2e..792f49f 100644
--- a/src/cert.rs
+++ b/src/cert.rs
@@ -12,7 +12,7 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-use crate::{der, signed_data, Error};
+use crate::{der, equal, signed_data, Error};
 
 pub enum EndEntityOrCa<'a> {
     EndEntity,
@@ -66,7 +66,7 @@
         // TODO: In mozilla::pkix, the comparison is done based on the
         // normalized value (ignoring whether or not there is an optional NULL
         // parameter for RSA-based algorithms), so this may be too strict.
-        if signature != signed_data.algorithm {
+        if !equal(signature, signed_data.algorithm) {
             return Err(Error::SignatureAlgorithmMismatch);
         }
 
diff --git a/src/end_entity.rs b/src/end_entity.rs
index 8c0650a..cfe9ef1 100644
--- a/src/end_entity.rs
+++ b/src/end_entity.rs
@@ -13,7 +13,7 @@
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 use crate::{
-    cert, name, signed_data, verify_cert, DnsNameRef, Error, SignatureAlgorithm, Time,
+    cert, name, signed_data, verify_cert, DnsNameRef, Error, ErrorExt, SignatureAlgorithm, Time,
     TlsClientTrustAnchors, TlsServerTrustAnchors,
 };
 
@@ -79,6 +79,25 @@
         &self.inner
     }
 
+    /// Backward-SemVer-compatible wrapper around `verify_is_valid_tls_server_cert_ext`.
+    ///
+    /// Errors that aren't representable as an `Error` are mapped to `Error::UnknownIssuer`.
+    pub fn verify_is_valid_tls_server_cert(
+        &self,
+        supported_sig_algs: &[&SignatureAlgorithm],
+        trust_anchors: &TlsServerTrustAnchors,
+        intermediate_certs: &[&[u8]],
+        time: Time,
+    ) -> Result<(), Error> {
+        self.verify_is_valid_tls_server_cert_ext(
+            supported_sig_algs,
+            trust_anchors,
+            intermediate_certs,
+            time,
+        )
+        .map_err(ErrorExt::into_error_lossy)
+    }
+
     /// Verifies that the end-entity certificate is valid for use by a TLS
     /// server.
     ///
@@ -89,13 +108,13 @@
     /// intermediate certificates that the server sent in the TLS handshake.
     /// `time` is the time for which the validation is effective (usually the
     /// current time).
-    pub fn verify_is_valid_tls_server_cert(
+    pub fn verify_is_valid_tls_server_cert_ext(
         &self,
         supported_sig_algs: &[&SignatureAlgorithm],
         &TlsServerTrustAnchors(trust_anchors): &TlsServerTrustAnchors,
         intermediate_certs: &[&[u8]],
         time: Time,
-    ) -> Result<(), Error> {
+    ) -> Result<(), ErrorExt> {
         verify_cert::build_chain(
             verify_cert::EKU_SERVER_AUTH,
             supported_sig_algs,
@@ -103,10 +122,28 @@
             intermediate_certs,
             &self.inner,
             time,
-            0,
         )
     }
 
+    /// Backward-SemVer-compatible wrapper around `verify_is_valid_tls_client_cert_ext`.
+    ///
+    /// Errors that aren't representable as an `Error` are mapped to `Error::UnknownIssuer`.
+    pub fn verify_is_valid_tls_client_cert(
+        &self,
+        supported_sig_algs: &[&SignatureAlgorithm],
+        trust_anchors: &TlsClientTrustAnchors,
+        intermediate_certs: &[&[u8]],
+        time: Time,
+    ) -> Result<(), Error> {
+        self.verify_is_valid_tls_client_cert_ext(
+            supported_sig_algs,
+            trust_anchors,
+            intermediate_certs,
+            time,
+        )
+        .map_err(ErrorExt::into_error_lossy)
+    }
+
     /// Verifies that the end-entity certificate is valid for use by a TLS
     /// client.
     ///
@@ -121,13 +158,13 @@
     /// `cert` is the purported end-entity certificate of the client. `time` is
     /// the time for which the validation is effective (usually the current
     /// time).
-    pub fn verify_is_valid_tls_client_cert(
+    pub fn verify_is_valid_tls_client_cert_ext(
         &self,
         supported_sig_algs: &[&SignatureAlgorithm],
         &TlsClientTrustAnchors(trust_anchors): &TlsClientTrustAnchors,
         intermediate_certs: &[&[u8]],
         time: Time,
-    ) -> Result<(), Error> {
+    ) -> Result<(), ErrorExt> {
         verify_cert::build_chain(
             verify_cert::EKU_CLIENT_AUTH,
             supported_sig_algs,
@@ -135,13 +172,12 @@
             intermediate_certs,
             &self.inner,
             time,
-            0,
         )
     }
 
     /// Verifies that the certificate is valid for the given DNS host name.
     pub fn verify_is_valid_for_dns_name(&self, dns_name: DnsNameRef) -> Result<(), Error> {
-        name::verify_cert_dns_name(&self, dns_name)
+        name::verify_cert_dns_name(self, dns_name)
     }
 
     /// Verifies that the certificate is valid for at least one of the given DNS
@@ -182,7 +218,7 @@
     /// `DigitallySigned.algorithm` of TLS type `SignatureAndHashAlgorithm`. In
     /// TLS 1.2 a single `SignatureAndHashAlgorithm` may map to multiple
     /// `SignatureAlgorithm`s. For example, a TLS 1.2
-    /// `ignatureAndHashAlgorithm` of (ECDSA, SHA-256) may map to any or all
+    /// `SignatureAndHashAlgorithm` of (ECDSA, SHA-256) may map to any or all
     /// of {`ECDSA_P256_SHA256`, `ECDSA_P384_SHA256`}, depending on how the TLS
     /// implementation is configured.
     ///
diff --git a/src/error.rs b/src/error.rs
index deeb9a8..3cb7697 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -106,3 +106,39 @@
 /// Requires the `std` feature.
 #[cfg(feature = "std")]
 impl ::std::error::Error for Error {}
+
+/// An error that occurs during certificate validation or name validation.
+///
+/// `ErrorExt` effectively extends `Error` to support reporting new errors. Because `Error` is not
+/// declared `#[non_exhaustive]` it could not be directly extended in a backward-compatible way.
+#[non_exhaustive]
+pub enum ErrorExt {
+    Error(Error),
+    MaximumSignatureChecksExceeded,
+    /// The maximum number of internal path building calls has been reached. Path complexity is too great.
+    MaximumPathBuildCallsExceeded,
+}
+
+impl ErrorExt {
+    pub(crate) fn is_fatal(&self) -> bool {
+        match self {
+            Self::Error(_) => false,
+            Self::MaximumSignatureChecksExceeded | Self::MaximumPathBuildCallsExceeded => true,
+        }
+    }
+
+    pub(crate) fn into_error_lossy(self) -> Error {
+        match self {
+            Self::Error(e) => e,
+            Self::MaximumSignatureChecksExceeded | Self::MaximumPathBuildCallsExceeded => {
+                Error::UnknownIssuer
+            }
+        }
+    }
+}
+
+impl From<Error> for ErrorExt {
+    fn from(error: Error) -> Self {
+        Self::Error(error)
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index ce9e71a..40bb959 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,7 +24,6 @@
 //! | `alloc` | Enable features that require use of the heap. Currently all RSA signature algorithms require this feature. |
 //! | `std` | Enable features that require libstd. Implies `alloc`. |
 
-#![doc(html_root_url = "https://briansmith.org/rustdoc/")]
 #![cfg_attr(not(feature = "std"), no_std)]
 #![allow(
     clippy::doc_markdown,
@@ -42,6 +41,8 @@
 #[cfg_attr(test, macro_use)]
 extern crate alloc;
 
+mod budget;
+
 #[macro_use]
 mod der;
 
@@ -58,7 +59,7 @@
 
 pub use {
     end_entity::EndEntityCert,
-    error::Error,
+    error::{Error, ErrorExt},
     name::{DnsNameRef, InvalidDnsNameError},
     signed_data::{
         SignatureAlgorithm, ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P384_SHA256,
@@ -94,3 +95,9 @@
 #[deprecated(note = "use TlsClientTrustAnchors")]
 #[allow(unknown_lints, clippy::upper_case_acronyms)]
 pub type TLSClientTrustAnchors<'a> = TlsClientTrustAnchors<'a>;
+
+// We don't operate on secret data so a convenient comparison function is warranted.
+#[must_use]
+fn equal(a: untrusted::Input, b: untrusted::Input) -> bool {
+    a.as_slice_less_safe() == b.as_slice_less_safe()
+}
diff --git a/src/name/dns_name.rs b/src/name/dns_name.rs
index e40e703..e4f18f2 100644
--- a/src/name/dns_name.rs
+++ b/src/name/dns_name.rs
@@ -125,7 +125,7 @@
     pub fn to_owned(&self) -> DnsName {
         // DnsNameRef is already guaranteed to be valid ASCII, which is a
         // subset of UTF-8.
-        let s: &str = self.clone().into();
+        let s: &str = (*self).into();
         DnsName(s.to_ascii_lowercase())
     }
 }
diff --git a/src/name/verify.rs b/src/name/verify.rs
index 6082c19..699aea2 100644
--- a/src/name/verify.rs
+++ b/src/name/verify.rs
@@ -18,7 +18,7 @@
 };
 use crate::{
     cert::{Cert, EndEntityOrCa},
-    der, Error,
+    der, equal, Error,
 };
 
 pub fn verify_cert_dns_name(
@@ -26,7 +26,7 @@
     dns_name: DnsNameRef,
 ) -> Result<(), Error> {
     let cert = cert.inner();
-    let dns_name = untrusted::Input::from(dns_name.as_ref().as_ref());
+    let dns_name = untrusted::Input::from(dns_name.as_ref());
     iterate_names(
         cert.subject,
         cert.subject_alt_name,
@@ -152,7 +152,7 @@
             input: &mut untrusted::Reader<'b>,
         ) -> Result<GeneralName<'b>, Error> {
             let general_subtree = der::expect_tag_and_get_value(input, der::Tag::Sequence)?;
-            general_subtree.read_all(Error::BadDer, |subtree| general_name(subtree))
+            general_subtree.read_all(Error::BadDer, general_name)
         }
 
         let base = match general_subtree(&mut constraints) {
@@ -234,7 +234,7 @@
     subtrees: Subtrees,
 ) -> bool {
     match subtrees {
-        Subtrees::PermittedSubtrees => name == constraint,
+        Subtrees::PermittedSubtrees => equal(name, constraint),
         Subtrees::ExcludedSubtrees => true,
     }
 }
diff --git a/src/signed_data.rs b/src/signed_data.rs
index 834f907..ccd834e 100644
--- a/src/signed_data.rs
+++ b/src/signed_data.rs
@@ -12,7 +12,7 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-use crate::{der, Error};
+use crate::{der, equal, Error};
 use ring::signature;
 
 /// X.509 certificates and related items that are signed are almost always
@@ -48,6 +48,7 @@
 ///     signatureAlgorithm AlgorithmIdentifier,
 ///     signatureValue BIT STRING
 /// }
+/// ```
 ///
 /// OCSP responses (RFC 6960) look like this:
 /// ```ASN.1
@@ -312,7 +313,7 @@
 
 impl AlgorithmIdentifier {
     fn matches_algorithm_id_value(&self, encoded: untrusted::Input) -> bool {
-        encoded == self.asn1_id_value
+        equal(encoded, self.asn1_id_value)
     }
 }
 
@@ -460,10 +461,12 @@
         let tsd = parse_test_signed_data(file_contents);
         let signature = untrusted::Input::from(&tsd.signature);
         assert_eq!(
-            Err(expected_error),
-            signature.read_all(Error::BadDer, |input| {
-                der::bit_string_with_no_unused_bits(input)
-            })
+            expected_error,
+            signature
+                .read_all(Error::BadDer, |input| {
+                    der::bit_string_with_no_unused_bits(input)
+                })
+                .unwrap_err()
         );
     }
 
@@ -481,10 +484,11 @@
         let tsd = parse_test_signed_data(file_contents);
         let spki = untrusted::Input::from(&tsd.spki);
         assert_eq!(
-            Err(expected_error),
+            expected_error,
             spki.read_all(Error::BadDer, |input| {
                 der::expect_tag_and_get_value(input, der::Tag::Sequence)
             })
+            .unwrap_err()
         );
     }
 
@@ -755,7 +759,7 @@
             if line == end_section {
                 break;
             }
-            base64.push_str(&line);
+            base64.push_str(line);
         }
 
         base64::decode(&base64).unwrap()
diff --git a/src/verify_cert.rs b/src/verify_cert.rs
index c68e6cf..fe7ef9d 100644
--- a/src/verify_cert.rs
+++ b/src/verify_cert.rs
@@ -12,9 +12,14 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
+use core::default::Default;
+
 use crate::{
+    budget::Budget,
     cert::{self, Cert, EndEntityOrCa},
-    der, name, signed_data, time, Error, SignatureAlgorithm, TrustAnchor,
+    der, equal,
+    error::ErrorExt,
+    name, signed_data, time, Error, SignatureAlgorithm, TrustAnchor,
 };
 
 pub fn build_chain(
@@ -24,8 +29,30 @@
     intermediate_certs: &[&[u8]],
     cert: &Cert,
     time: time::Time,
+) -> Result<(), ErrorExt> {
+    build_chain_inner(
+        required_eku_if_present,
+        supported_sig_algs,
+        trust_anchors,
+        intermediate_certs,
+        cert,
+        time,
+        0,
+        &mut Budget::default(),
+    )
+}
+
+#[allow(clippy::too_many_arguments)]
+fn build_chain_inner(
+    required_eku_if_present: KeyPurposeId,
+    supported_sig_algs: &[&SignatureAlgorithm],
+    trust_anchors: &[TrustAnchor],
+    intermediate_certs: &[&[u8]],
+    cert: &Cert,
+    time: time::Time,
     sub_ca_count: usize,
-) -> Result<(), Error> {
+    budget: &mut Budget,
+) -> Result<(), ErrorExt> {
     let used_as_ca = used_as_ca(&cert.ee_or_ca);
 
     check_issuer_independent_properties(
@@ -43,7 +70,7 @@
             const MAX_SUB_CA_COUNT: usize = 6;
 
             if sub_ca_count >= MAX_SUB_CA_COUNT {
-                return Err(Error::UnknownIssuer);
+                return Err(Error::UnknownIssuer.into());
             }
         }
         UsedAsCa::No => {
@@ -55,47 +82,46 @@
 
     match loop_while_non_fatal_error(trust_anchors, |trust_anchor: &TrustAnchor| {
         let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
-        if cert.issuer != trust_anchor_subject {
-            return Err(Error::UnknownIssuer);
+        if !equal(cert.issuer, trust_anchor_subject) {
+            return Err(Error::UnknownIssuer.into());
         }
 
-        let name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
-
-        untrusted::read_all_optional(name_constraints, Error::BadDer, |value| {
-            name::check_name_constraints(value, &cert)
-        })?;
-
         let trust_anchor_spki = untrusted::Input::from(trust_anchor.spki);
 
         // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
 
-        check_signatures(supported_sig_algs, cert, trust_anchor_spki)?;
+        check_signatures(supported_sig_algs, cert, trust_anchor_spki, budget)?;
+
+        check_signed_chain_name_constraints(cert, trust_anchor)?;
 
         Ok(())
     }) {
         Ok(()) => {
             return Ok(());
         }
-        Err(..) => {
+        Err(e) => {
+            if e.is_fatal() {
+                return Err(e);
+            }
             // If the error is not fatal, then keep going.
         }
     }
 
     loop_while_non_fatal_error(intermediate_certs, |cert_der| {
         let potential_issuer =
-            cert::parse_cert(untrusted::Input::from(*cert_der), EndEntityOrCa::Ca(&cert))?;
+            cert::parse_cert(untrusted::Input::from(cert_der), EndEntityOrCa::Ca(cert))?;
 
-        if potential_issuer.subject != cert.issuer {
-            return Err(Error::UnknownIssuer);
+        if !equal(potential_issuer.subject, cert.issuer) {
+            return Err(Error::UnknownIssuer.into());
         }
 
         // Prevent loops; see RFC 4158 section 5.2.
         let mut prev = cert;
         loop {
-            if potential_issuer.spki.value() == prev.spki.value()
-                && potential_issuer.subject == prev.subject
+            if equal(potential_issuer.spki.value(), prev.spki.value())
+                && equal(potential_issuer.subject, prev.subject)
             {
-                return Err(Error::UnknownIssuer);
+                return Err(Error::UnknownIssuer.into());
             }
             match &prev.ee_or_ca {
                 EndEntityOrCa::EndEntity => {
@@ -107,16 +133,13 @@
             }
         }
 
-        untrusted::read_all_optional(potential_issuer.name_constraints, Error::BadDer, |value| {
-            name::check_name_constraints(value, &cert)
-        })?;
-
         let next_sub_ca_count = match used_as_ca {
             UsedAsCa::No => sub_ca_count,
             UsedAsCa::Yes => sub_ca_count + 1,
         };
 
-        build_chain(
+        budget.consume_build_chain_call()?;
+        build_chain_inner(
             required_eku_if_present,
             supported_sig_algs,
             trust_anchors,
@@ -124,6 +147,7 @@
             &potential_issuer,
             time,
             next_sub_ca_count,
+            budget,
         )
     })
 }
@@ -132,10 +156,12 @@
     supported_sig_algs: &[&SignatureAlgorithm],
     cert_chain: &Cert,
     trust_anchor_key: untrusted::Input,
-) -> Result<(), Error> {
+    budget: &mut Budget,
+) -> Result<(), ErrorExt> {
     let mut spki_value = trust_anchor_key;
     let mut cert = cert_chain;
     loop {
+        budget.consume_signature()?;
         signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?;
 
         // TODO: check revocation
@@ -154,6 +180,32 @@
     Ok(())
 }
 
+fn check_signed_chain_name_constraints(
+    cert_chain: &Cert,
+    trust_anchor: &TrustAnchor,
+) -> Result<(), Error> {
+    let mut cert = cert_chain;
+    let mut name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
+
+    loop {
+        untrusted::read_all_optional(name_constraints, Error::BadDer, |value| {
+            name::check_name_constraints(value, cert)
+        })?;
+
+        match &cert.ee_or_ca {
+            EndEntityOrCa::Ca(child_cert) => {
+                name_constraints = cert.name_constraints;
+                cert = child_cert;
+            }
+            EndEntityOrCa::EndEntity => {
+                break;
+            }
+        }
+    }
+
+    Ok(())
+}
+
 fn check_issuer_independent_properties(
     cert: &Cert,
     time: time::Time,
@@ -302,7 +354,7 @@
         Some(input) => {
             loop {
                 let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
-                if value == required_eku_if_present.oid_value {
+                if equal(value, required_eku_if_present.oid_value) {
                     input.skip_to_end();
                     break;
                 }
@@ -322,7 +374,10 @@
             // important that id-kp-OCSPSigning is explicit so that a normal
             // end-entity certificate isn't able to sign trusted OCSP responses
             // for itself or for other certificates issued by its issuing CA.
-            if required_eku_if_present.oid_value == EKU_OCSP_SIGNING.oid_value {
+            if equal(
+                required_eku_if_present.oid_value,
+                EKU_OCSP_SIGNING.oid_value,
+            ) {
                 return Err(Error::RequiredEkuNotFound);
             }
 
@@ -333,8 +388,8 @@
 
 fn loop_while_non_fatal_error<V>(
     values: V,
-    f: impl Fn(V::Item) -> Result<(), Error>,
-) -> Result<(), Error>
+    mut f: impl FnMut(V::Item) -> Result<(), ErrorExt>,
+) -> Result<(), ErrorExt>
 where
     V: IntoIterator,
 {
@@ -343,10 +398,13 @@
             Ok(()) => {
                 return Ok(());
             }
-            Err(..) => {
+            Err(e) => {
+                if e.is_fatal() {
+                    return Err(e);
+                }
                 // If the error is not fatal, then keep going.
             }
         }
     }
-    Err(Error::UnknownIssuer)
+    Err(Error::UnknownIssuer.into())
 }
diff --git a/tests/dns_name_tests.rs b/tests/dns_name_tests.rs
index b3a3adc..0e51999 100644
--- a/tests/dns_name_tests.rs
+++ b/tests/dns_name_tests.rs
@@ -250,7 +250,7 @@
     (b"1.2.3.4\n", false),
     // Nulls not allowed
     (b"\0", false),
-    (b"\01.2.3.4", false),
+    (b"\x001.2.3.4", false),
     (b"1.2.3.4\0", false),
     (b"1.2.3.4\0.5", false),
     // Range
@@ -389,7 +389,7 @@
     (b"::1\0:2", false),
     (b"::1\0", false),
     (b"::1.2.3.4\0", false),
-    (b"::1.2\02.3.4", false),
+    (b"::1.2\x002.3.4", false),
 ];
 
 #[test]
diff --git a/third-party/bettertls/LICENSE b/third-party/bettertls/LICENSE
new file mode 100644
index 0000000..4947287
--- /dev/null
+++ b/third-party/bettertls/LICENSE
@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/third-party/bettertls/README.md b/third-party/bettertls/README.md
new file mode 100644
index 0000000..9c12dc8
--- /dev/null
+++ b/third-party/bettertls/README.md
@@ -0,0 +1,17 @@
+# BetterTLS Test Suite
+
+Generated using the Netflix [bettertls] project.
+
+[bettertls]: https://github.com/Netflix/bettertls
+
+## Pathbuilding 
+
+To regenerate pathbuilding test data:
+
+1. Install Go
+2. Generate the JSON testdata export for the path building suite:
+
+```bash
+GOBIN=$PWD go install github.com/Netflix/bettertls/test-suites/cmd/bettertls@latest
+./bettertls export-tests --suite pathbuilding --out ./pathbuilding.tests.json
+```