diff options
author | Jeff Vander Stoep <jeffv@google.com> | 2024-02-06 10:16:49 +0100 |
---|---|---|
committer | Jeff Vander Stoep <jeffv@google.com> | 2024-02-06 10:16:49 +0100 |
commit | 49948d1655433282c52a9f3068b2a76371d0fd3a (patch) | |
tree | f9c07211b0ba2f5202bb23d3cc5d9ff80b3eb1db | |
parent | 62eb4c6da10ae8d79a5b45101234c1445b502783 (diff) | |
download | webpki-49948d1655433282c52a9f3068b2a76371d0fd3a.tar.gz |
Upgrade webpki to 0.22.4
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
Test: TreeHugger
Change-Id: I59432f0b36e6e47e5eda1443dd1325ab74cf6e58
-rw-r--r-- | .cargo_vcs_info.json | 7 | ||||
-rw-r--r-- | Android.bp | 14 | ||||
-rw-r--r-- | Cargo.toml | 55 | ||||
-rw-r--r-- | Cargo.toml.orig | 32 | ||||
-rw-r--r-- | METADATA | 25 | ||||
-rw-r--r-- | src/budget.rs | 60 | ||||
-rw-r--r-- | src/cert.rs | 4 | ||||
-rw-r--r-- | src/end_entity.rs | 54 | ||||
-rw-r--r-- | src/error.rs | 36 | ||||
-rw-r--r-- | src/lib.rs | 11 | ||||
-rw-r--r-- | src/name/dns_name.rs | 2 | ||||
-rw-r--r-- | src/name/verify.rs | 8 | ||||
-rw-r--r-- | src/signed_data.rs | 20 | ||||
-rw-r--r-- | src/verify_cert.rs | 120 | ||||
-rw-r--r-- | tests/dns_name_tests.rs | 4 | ||||
-rw-r--r-- | third-party/bettertls/LICENSE | 177 | ||||
-rw-r--r-- | third-party/bettertls/README.md | 17 |
17 files changed, 536 insertions, 110 deletions
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 @@ -36,7 +36,7 @@ rust_library { 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 @@ rust_test { 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 @@ rust_test { rustlibs: [ "libbase64_rust", "libring", + "libserde", + "libserde_json", "libuntrusted", ], } @@ -82,7 +84,7 @@ rust_test { 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 @@ rust_test { rustlibs: [ "libbase64_rust", "libring", + "libserde", + "libserde_json", "libuntrusted", "libwebpki", ], @@ -107,7 +111,7 @@ rust_test { 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 @@ rust_test { rustlibs: [ "libbase64_rust", "libring", + "libserde", + "libserde_json", "libuntrusted", "libwebpki", ], @@ -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 @@ rpath = false 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 @@ include = [ "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 @@ alloc = ["ring/alloc"] 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 @@ rpath = false lto = true debug-assertions = false codegen-units = 1 + +[workspace] +members = [ + # Intentionally not a default member. + "rcgen-tests", +] @@ -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 @@ pub(crate) fn parse_cert_internal<'a>( // 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 @@ impl<'a> EndEntityCert<'a> { &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 @@ impl<'a> EndEntityCert<'a> { /// 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 @@ impl<'a> EndEntityCert<'a> { 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 @@ impl<'a> EndEntityCert<'a> { /// `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 @@ impl<'a> EndEntityCert<'a> { 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 @@ impl<'a> EndEntityCert<'a> { /// `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 @@ impl fmt::Display for Error { /// 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) + } +} @@ -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 @@ mod verify_cert; 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 @@ pub type TLSServerTrustAnchors<'a> = TlsServerTrustAnchors<'a>; #[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 @@ impl<'a> DnsNameRef<'a> { 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 super::{ }; use crate::{ cert::{Cert, EndEntityOrCa}, - der, Error, + der, equal, Error, }; pub fn verify_cert_dns_name( @@ -26,7 +26,7 @@ pub fn verify_cert_dns_name( 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 @@ fn check_presented_id_conforms_to_constraints_in_subtree( 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 @@ fn presented_directory_name_matches_constraint( 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 @@ pub struct SignedData<'a> { /// signatureAlgorithm AlgorithmIdentifier, /// signatureValue BIT STRING /// } +/// ``` /// /// OCSP responses (RFC 6960) look like this: /// ```ASN.1 @@ -312,7 +313,7 @@ struct AlgorithmIdentifier { 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 @@ mod tests { 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 @@ mod tests { 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 @@ mod tests { 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 @@ pub fn build_chain( 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 @@ pub fn build_chain( 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 @@ pub fn build_chain( 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 @@ pub fn build_chain( } } - 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 @@ pub fn build_chain( &potential_issuer, time, next_sub_ca_count, + budget, ) }) } @@ -132,10 +156,12 @@ fn check_signatures( 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 @@ fn check_signatures( 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 @@ fn check_eku( 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 @@ fn check_eku( // 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 check_eku( 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 @@ where 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 @@ static IP_ADDRESS_DNS_VALIDITY: &[(&[u8], bool)] = &[ (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 @@ static IP_ADDRESS_DNS_VALIDITY: &[(&[u8], bool)] = &[ (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 +``` |