diff --git a/Cargo.lock b/Cargo.lock index 9bb6dcb4..d2defb98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,7 +504,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", + "rustls 0.23.46", "tokio", "tokio-rustls", "tower-service", @@ -954,7 +954,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.46", "socket2", "thiserror", "tokio", @@ -976,7 +976,7 @@ dependencies = [ "rand_pcg", "ring", "rustc-hash", - "rustls", + "rustls 0.23.46", "rustls-pki-types", "slab", "thiserror", @@ -1089,9 +1089,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.46", "rustls-pki-types", - "rustls-platform-verifier 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls-platform-verifier 0.7.1", "serde", "serde_json", "sync_wrapper", @@ -1153,7 +1153,18 @@ dependencies = [ "aws-lc-rs", "once_cell", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.16", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.24.0-dev.2" +source = "git+https://github.com/rustls/rustls.git?branch=main#35a8f439137d45ed18962807d38684b0c6bc6e3f" +dependencies = [ + "rustls-pki-types", + "rustls-webpki 0.104.0-alpha.8", "subtle", "zeroize", ] @@ -1183,17 +1194,18 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" dependencies = [ - "android_logger", - "base64 0.22.1", "core-foundation", "core-foundation-sys", "jni", "log", - "rustls", + "once_cell", + "rustls 0.23.46", "rustls-native-certs", - "rustls-platform-verifier-android 0.2.0", - "rustls-webpki", + "rustls-platform-verifier-android 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls-webpki 0.103.16", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -1202,19 +1214,19 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +version = "0.8.0" dependencies = [ + "android_logger", + "base64 0.22.1", "core-foundation", "core-foundation-sys", "jni", "log", - "once_cell", - "rustls", + "rustls 0.24.0-dev.2", "rustls-native-certs", - "rustls-platform-verifier-android 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustls-webpki", + "rustls-platform-verifier-android 0.2.0", + "rustls-ring", + "rustls-util", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -1225,7 +1237,7 @@ dependencies = [ name = "rustls-platform-verifier-android" version = "0.2.0" dependencies = [ - "rustls", + "rustls 0.23.46", "similar-asserts", "tokio", "webpki-ccadb", @@ -1237,6 +1249,26 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" +[[package]] +name = "rustls-ring" +version = "0.24.0-dev.2" +source = "git+https://github.com/rustls/rustls.git?branch=main#35a8f439137d45ed18962807d38684b0c6bc6e3f" +dependencies = [ + "ring", + "rustls 0.24.0-dev.2", + "rustls-pki-types", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-util" +version = "0.24.0-dev.2" +source = "git+https://github.com/rustls/rustls.git?branch=main#35a8f439137d45ed18962807d38684b0c6bc6e3f" +dependencies = [ + "rustls 0.24.0-dev.2", +] + [[package]] name = "rustls-webpki" version = "0.103.16" @@ -1249,6 +1281,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.104.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f05eb03bb89c8b88908947d1add6fbe564e58db7b686dbda59691aaec0e450" +dependencies = [ + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1578,7 +1620,7 @@ version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ - "rustls", + "rustls 0.23.46", "tokio", ] @@ -1801,7 +1843,7 @@ dependencies = [ "num-bigint 0.5.1", "reqwest", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.16", "serde", "serde_json", "url", diff --git a/README.md b/README.md index f77c9ba0..e8e6fa43 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,14 @@ CRL distribution point to fetch revocation information from, revocation checking [^2]: The fall-back webpki verifier configured for Linux/WASM does not support providing CRLs for revocation checking. If you require revocation checking on these platforms, prefer constructing your own -`WebPkiServerVerifier`, providing necessary CRLs. See the Rustls [`ServerCertVerifierBuilder`] docs for more +`WebPkiServerVerifier`, providing necessary CRLs. See the Rustls [`ServerVerifierBuilder`] docs for more information. [^3]: On Linux the [rustls-native-certs] and [openssl-probe] crates are used to try and discover the system CA bundle. Users may wish to augment these certificates with [webpki-roots] using [`Verifier::new_with_extra_roots`] in case a system CA bundle is unavailable. -[`ServerCertVerifierBuilder`]: https://docs.rs/rustls/latest/rustls/client/struct.ServerCertVerifierBuilder.html +[`ServerVerifierBuilder`]: https://docs.rs/rustls/latest/rustls/client/struct.ServerVerifierBuilder.html [`Verifier::new_with_extra_roots`]: https://docs.rs/rustls-platform-verifier/latest/rustls_platform_verifier/struct.Verifier.html#method.new_with_extra_roots [rustls-native-certs]: https://github.com/rustls/rustls-native-certs [openssl-probe]: https://github.com/alexcrichton/openssl-probe @@ -85,29 +85,20 @@ This means that it has received lots of exposure to edge cases and has real-worl ## Installation and setup On most platforms, no setup should be required beyond adding the dependency via `cargo`: ```toml -rustls-platform-verifier = "0.5" +rustls-platform-verifier = "0.8" ``` -To get a rustls `ClientConfig` configured to use the platform verifier use: - -```rust -use rustls::ClientConfig; -use rustls_platform_verifier::ConfigVerifierExt; -let config = ClientConfig::with_platform_verifier(); -``` - -This crate will use the [rustls process-default crypto provider](https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#using-the-per-process-default-cryptoprovider). To construct a `ClientConfig` with a different `CryptoProvider`, use: +To get a rustls `ClientConfig` configured to use the platform verifier, use: ```rust use rustls::ClientConfig; use rustls_platform_verifier::BuilderVerifierExt; -let arc_crypto_provider = std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider()); -let config = ClientConfig::builder_with_provider(arc_crypto_provider) - .with_safe_default_protocol_versions() - .unwrap() +let provider = std::sync::Arc::new(rustls_ring::DEFAULT_PROVIDER.clone()); +let config = ClientConfig::builder(provider) .with_platform_verifier() .unwrap() - .with_no_client_auth(); + .with_no_client_auth() + .unwrap(); ``` ### Android diff --git a/rustls-platform-verifier/Cargo.toml b/rustls-platform-verifier/Cargo.toml index 989cc2bc..9c01409f 100644 --- a/rustls-platform-verifier/Cargo.toml +++ b/rustls-platform-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-platform-verifier" -version = "0.7.1" +version = "0.8.0" description = "rustls-platform-verifier supports verifying TLS certificates in rustls with the operating system verifier" keywords = ["tls", "certificate", "verification", "os", "native"] repository = "https://github.com/rustls/rustls-platform-verifier" @@ -16,7 +16,7 @@ name = "rustls_platform_verifier" # Enables a C interface to use for testing where `cargo` can't be used. # This feature is not stable, nor is the interface exported when it is enabled. # Do not rely on this or use it in production. -ffi-testing = ["android_logger", "rustls/aws-lc-rs"] +ffi-testing = ["android_logger", "dep:rustls-ring"] # Enables APIs that expose lower-level verifier types for debugging purposes. dbg = [] # Enables `log::debug` base64-encoded logging of all end-entity certificates processed @@ -26,23 +26,21 @@ cert-logging = ["base64"] docsrs = ["jni"] [dependencies] -rustls = { version = "0.23.27", default-features = false, features = ["std"] } +rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", version = "0.24.0-dev.2", default-features = false, features = ["webpki"] } +rustls-ring = { git = "https://github.com/rustls/rustls.git", branch = "main", version = "0.24.0-dev.2", default-features = false, features = ["std"], optional = true } log = { version = "0.4" } base64 = { version = "0.22", optional = true } # Only used when the `cert-logging` feature is enabled. jni = { version = "0.22.4", default-features = false, optional = true } # Only used during doc generation [target.'cfg(all(unix, not(target_os = "android"), not(target_vendor = "apple"), not(target_arch = "wasm32")))'.dependencies] rustls-native-certs = "0.8" -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } [target.'cfg(target_os = "android")'.dependencies] rustls-platform-verifier-android = { path = "../android-release-support", version = "0.2.0" } jni = { version = "0.22", default-features = false } -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } android_logger = { version = "0.15", optional = true } # Only used during testing. [target.'cfg(target_arch = "wasm32")'.dependencies] -webpki = { package = "rustls-webpki", version = "0.103", default-features = false } webpki-root-certs = "1" [target.'cfg(any(target_vendor = "apple"))'.dependencies] @@ -55,7 +53,9 @@ security-framework-sys = "2.15" windows-sys = { version = ">=0.52.0, <0.62.0", default-features = false, features = ["Win32_Foundation", "Win32_Security_Cryptography"] } [dev-dependencies] -rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } +rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", version = "0.24.0-dev.2", default-features = false, features = ["webpki"] } +rustls-ring = { git = "https://github.com/rustls/rustls.git", branch = "main", version = "0.24.0-dev.2", default-features = false, features = ["std"] } +rustls-util = { git = "https://github.com/rustls/rustls.git", branch = "main", version = "0.24.0-dev.2", default-features = false } webpki-root-certs = "1" [package.metadata.docs.rs] diff --git a/rustls-platform-verifier/examples/update-certs.rs b/rustls-platform-verifier/examples/update-certs.rs index 1970f97d..d152aa08 100644 --- a/rustls-platform-verifier/examples/update-certs.rs +++ b/rustls-platform-verifier/examples/update-certs.rs @@ -1,6 +1,8 @@ -use std::{fs, io::Write, net::TcpStream, sync::Arc}; +use std::{fs, io::Write, iter, net::TcpStream, sync::Arc}; -use rustls::{pki_types::ServerName, ClientConfig, ClientConnection, RootCertStore, Stream}; +use rustls::crypto::Identity; +use rustls::{pki_types::ServerName, ClientConfig, RootCertStore, VecInput}; +use rustls_util::Stream; use webpki_root_certs::TLS_SERVER_ROOT_CERTS; fn main() -> Result<(), Box> { @@ -8,28 +10,43 @@ fn main() -> Result<(), Box> { let (_, ignored) = roots.add_parsable_certificates(TLS_SERVER_ROOT_CERTS.iter().cloned()); assert_eq!(ignored, 0, "{ignored} root certificates were ignored"); let config = Arc::new( - ClientConfig::builder() + ClientConfig::builder(Arc::new(rustls_ring::DEFAULT_PROVIDER.clone())) .with_root_certificates(roots) - .with_no_client_auth(), + .with_no_client_auth()?, ); for &host in HOSTS { - let server_name = ServerName::try_from(host)?; - let mut conn = ClientConnection::new(config.clone(), server_name)?; + let server_name = ServerName::try_from(host.to_owned())?; + let mut output = Vec::new(); + let mut conn = config.connect(server_name).build(&mut output)?; let mut sock = TcpStream::connect((host, 443))?; - let mut stream = Stream::new(&mut conn, &mut sock); + let mut input = VecInput::default(); + let mut received_plaintext = Vec::new(); + let mut stream = Stream::new( + &mut input, + &mut received_plaintext, + &mut output, + &mut conn, + &mut sock, + ); eprintln!("connecting to {host}..."); - if let Err(err) = stream.write_all(b"GET / HTTP/1.1\r\n\r\n") { - eprintln!("failed to write to {host}: {err}"); - } + stream.write_all(format!("GET / HTTP/1.1\r\nHost: {host}\r\n\r\n").as_bytes())?; + stream.flush()?; - let Some(certs) = conn.peer_certificates() else { + let Some(Identity::X509(certs)) = stream + .conn + .peer_identity() + .map(|identity| identity.identity()) + else { eprintln!("no certificates received for {host}"); continue; }; - for (i, der) in certs.iter().enumerate() { + for (i, der) in iter::once(&certs.end_entity) + .chain(certs.intermediates.iter()) + .enumerate() + { let host_name = host.replace('.', "_"); let fname = format!( "{}/src/tests/verification_real_world/{host_name}_valid_{}.crt", diff --git a/rustls-platform-verifier/src/lib.rs b/rustls-platform-verifier/src/lib.rs index 3c1da291..67f37731 100644 --- a/rustls-platform-verifier/src/lib.rs +++ b/rustls-platform-verifier/src/lib.rs @@ -39,7 +39,7 @@ pub use tests::ffi::*; pub fn verifier_for_dbg( root: CertificateDer<'static>, crypto_provider: Arc, -) -> Arc { +) -> Arc { Arc::new(Verifier::new_with_fake_root(root, crypto_provider)) } @@ -50,10 +50,12 @@ pub trait BuilderVerifierExt { /// ```rust /// use rustls::ClientConfig; /// use rustls_platform_verifier::BuilderVerifierExt; - /// let config = ClientConfig::builder() + /// let provider = std::sync::Arc::new(rustls_ring::DEFAULT_PROVIDER.clone()); + /// let config = ClientConfig::builder(provider) /// .with_platform_verifier() /// .unwrap() - /// .with_no_client_auth(); + /// .with_no_client_auth() + /// .unwrap(); /// ``` fn with_platform_verifier( self, @@ -64,7 +66,7 @@ impl BuilderVerifierExt for ConfigBuilder { fn with_platform_verifier( self, ) -> Result, rustls::Error> { - let verifier = Verifier::new(self.crypto_provider().clone())?; + let verifier = Verifier::new(self.provider().clone())?; Ok(self .dangerous() .with_custom_certificate_verifier(Arc::new(verifier))) @@ -73,20 +75,27 @@ impl BuilderVerifierExt for ConfigBuilder { /// Extension trait to help build a [`ClientConfig`] with the platform verifier. pub trait ConfigVerifierExt { - /// Build a [`ClientConfig`] with the platform verifier and the default `CryptoProvider`. + /// Configures an existing [`ClientConfig`] with the platform verifier. /// /// ```rust - /// use rustls::ClientConfig; + /// use rustls::{ClientConfig, RootCertStore}; /// use rustls_platform_verifier::ConfigVerifierExt; - /// let config = ClientConfig::with_platform_verifier(); + /// let provider = std::sync::Arc::new(rustls_ring::DEFAULT_PROVIDER.clone()); + /// let config = ClientConfig::builder(provider) + /// .with_root_certificates(RootCertStore::empty()) + /// .with_no_client_auth() + /// .unwrap() + /// .with_platform_verifier() + /// .unwrap(); /// ``` - fn with_platform_verifier() -> Result; + fn with_platform_verifier(self) -> Result; } impl ConfigVerifierExt for ClientConfig { - fn with_platform_verifier() -> Result { - Ok(ClientConfig::builder() - .with_platform_verifier()? - .with_no_client_auth()) + fn with_platform_verifier(mut self) -> Result { + let verifier = Verifier::new(self.provider().clone())?; + self.dangerous() + .set_certificate_verifier(Arc::new(verifier)); + Ok(self) } } diff --git a/rustls-platform-verifier/src/tests/mod.rs b/rustls-platform-verifier/src/tests/mod.rs index f464a75e..c005412d 100644 --- a/rustls-platform-verifier/src/tests/mod.rs +++ b/rustls-platform-verifier/src/tests/mod.rs @@ -1,15 +1,20 @@ #[cfg(feature = "ffi-testing")] pub mod ffi; +#[cfg(all(test, not(target_os = "android")))] +use std::hash::{DefaultHasher, Hasher}; use std::{error::Error as StdError, sync::Arc}; mod verification_real_world; mod verification_mock; +#[cfg(all(test, not(target_os = "android")))] +use rustls::client::danger::ServerVerifier; +use rustls::crypto::CryptoProvider; +use rustls::error::CertificateError; use rustls::{ - crypto::CryptoProvider, - pki_types, CertificateError, + pki_types, Error::{self as TlsError, InvalidCertificate}, }; @@ -43,11 +48,15 @@ pub fn assert_cert_error_eq( ) { // If the expected error is an "Other" CertificateError we can't directly assert equality, we rely // on the test caller to provide the correct value to compare. - if let Err(InvalidCertificate(CertificateError::Other(err))) = &expected { + if let ( + Err(InvalidCertificate(CertificateError::Other(err))), + Err(InvalidCertificate(CertificateError::Other(_))), + ) = (result, expected) + { let expected_err = expected_err.expect("error not provided for `Other` case handling"); let err: &E = err - .0 - .downcast_ref() + .source() + .and_then(|source| source.downcast_ref()) .expect("incorrect `Other` inner error kind"); assert_eq!(err, expected_err); } else { @@ -55,6 +64,92 @@ pub fn assert_cert_error_eq( } } +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn certificate_error_assertion_rejects_success() { + use crate::verification::EkuError; + let expected = Err(InvalidCertificate(CertificateError::Other( + rustls::error::OtherError::new(EkuError), + ))); + assert_cert_error_eq(&Ok(()), &expected, Some(&EkuError)); +} + +#[test] +#[should_panic(expected = "incorrect `Other` inner error kind")] +fn certificate_error_assertion_checks_actual_error() { + use crate::verification::EkuError; + let expected = Err(InvalidCertificate(CertificateError::Other( + rustls::error::OtherError::new(EkuError), + ))); + let actual = Err(InvalidCertificate(CertificateError::Other( + rustls::error::OtherError::new(std::io::Error::other("unexpected error")), + ))); + assert_cert_error_eq(&actual, &expected, Some(&EkuError)); +} + +#[cfg(not(target_os = "android"))] +#[test] +fn configuration_hash_distinguishes_extra_roots() { + let provider = test_provider(); + let first = crate::Verifier::new_with_extra_roots( + [pki_types::CertificateDer::from( + include_bytes!("verification_mock/root1.crt").as_slice(), + )], + provider.clone(), + ) + .unwrap(); + let second = crate::Verifier::new_with_extra_roots( + [pki_types::CertificateDer::from( + include_bytes!("verification_mock/root1-int1.crt").as_slice(), + )], + provider, + ) + .unwrap(); + assert_eq!(configuration_hash(&first), configuration_hash(&first)); + assert_ne!(configuration_hash(&first), configuration_hash(&second)); +} + +#[cfg(not(target_os = "android"))] +#[test] +fn configuration_hash_distinguishes_root_overrides() { + let provider = test_provider(); + let first = crate::Verifier::new_with_fake_root( + pki_types::CertificateDer::from(include_bytes!("verification_mock/root1.crt").as_slice()), + provider.clone(), + ); + let second = crate::Verifier::new_with_fake_root( + pki_types::CertificateDer::from( + include_bytes!("verification_mock/root1-int1.crt").as_slice(), + ), + provider, + ); + assert_ne!(configuration_hash(&first), configuration_hash(&second)); +} + +#[cfg(not(target_os = "android"))] +#[test] +fn configuration_hash_distinguishes_signature_algorithms() { + let provider = test_provider(); + let mut restricted = provider.as_ref().clone(); + restricted.signature_verification_algorithms = rustls::crypto::WebPkiSupportedAlgorithms::new( + &[], + &provider.signature_verification_algorithms.mapping()[1..], + ) + .unwrap(); + let root = + pki_types::CertificateDer::from(include_bytes!("verification_mock/root1.crt").as_slice()); + let first = crate::Verifier::new_with_fake_root(root.clone(), provider); + let second = crate::Verifier::new_with_fake_root(root, Arc::new(restricted)); + assert_ne!(configuration_hash(&first), configuration_hash(&second)); +} + fn test_provider() -> Arc { - Arc::new(rustls::crypto::aws_lc_rs::default_provider()) + Arc::new(rustls_ring::DEFAULT_PROVIDER.clone()) +} + +#[cfg(all(test, not(target_os = "android")))] +fn configuration_hash(verifier: &crate::Verifier) -> u64 { + let mut hasher = DefaultHasher::new(); + verifier.hash_config(&mut hasher); + hasher.finish() } diff --git a/rustls-platform-verifier/src/tests/verification_mock/ca.go b/rustls-platform-verifier/src/tests/verification_mock/ca.go index ec3b4de1..2043f1f7 100644 --- a/rustls-platform-verifier/src/tests/verification_mock/ca.go +++ b/rustls-platform-verifier/src/tests/verification_mock/ca.go @@ -191,7 +191,7 @@ func generateInt(intName string, serial int64, now time.Time, caKey crypto.Signe Organization: []string{intName}, }, NotBefore: now.Add(-OneDay), - NotAfter: now.Add(OneYear), + NotAfter: now.Add(2 * OneYear), IsCA: true, KeyUsage: x509.KeyUsageCertSign, BasicConstraintsValid: true, @@ -221,7 +221,7 @@ func generateRoot(name string, now time.Time) (crypto.Signer, error) { Organization: []string{name}, }, NotBefore: now.Add(-OneDay), - NotAfter: now.Add(OneYear), + NotAfter: now.Add(2 * OneYear), IsCA: true, KeyUsage: x509.KeyUsageCertSign, BasicConstraintsValid: true, @@ -257,7 +257,7 @@ func generateOCSPResponse(name string, status int, now time.Time, caKey crypto.S Status: status, SerialNumber: eeCert.SerialNumber, ThisUpdate: thisUpdate, - NextUpdate: thisUpdate.Add(1 * OneYear), + NextUpdate: thisUpdate.Add(2 * OneYear), } if status == ocsp.Revoked { diff --git a/rustls-platform-verifier/src/tests/verification_mock/mod.rs b/rustls-platform-verifier/src/tests/verification_mock/mod.rs index 83f272dc..90d3b0f0 100644 --- a/rustls-platform-verifier/src/tests/verification_mock/mod.rs +++ b/rustls-platform-verifier/src/tests/verification_mock/mod.rs @@ -22,21 +22,26 @@ ))] use core::time::Duration; +use rustls::client::danger::{ServerIdentity, ServerVerifier}; +use rustls::crypto::Identity; +use rustls::error::CertificateError; +#[cfg(not(windows))] +use rustls::error::OtherError; +use rustls::pki_types; +#[cfg(not(any(target_vendor = "apple", windows)))] +use rustls::pki_types::{DnsName, ServerName}; +use rustls::Error as TlsError; use std::convert::TryFrom; +use std::iter; use std::net::IpAddr; #[cfg(not(any(target_vendor = "apple", windows)))] use std::net::{Ipv4Addr, Ipv6Addr}; -use std::sync::Arc; - -use rustls::client::danger::ServerCertVerifier; -use rustls::pki_types; -#[cfg(not(any(target_vendor = "apple", windows)))] -use rustls::pki_types::{DnsName, ServerName}; -use rustls::{CertificateError, Error as TlsError, OtherError}; use super::TestCase; use crate::tests::{assert_cert_error_eq, test_provider}; -use crate::verification::{EkuError, Verifier}; +#[cfg(not(windows))] +use crate::verification::EkuError; +use crate::verification::Verifier; macro_rules! mock_root_test_cases { { $( $name:ident [ $target:meta ] => $test_case:expr ),+ , } => { @@ -119,13 +124,11 @@ pub(super) fn verification_without_mock_root() { // Similarly on UNIX platforms using the Webpki verifier, it can't fetch extra certificates through // AIA chasing or other mechanisms, and so we know this test will correctly verify an unknown // root in a chain fails validation. - let result = verifier.verify_server_cert( - &end_entity, - &intermediates, - &server_name, - &[], - verification_time(), - ); + let identity = + Identity::from_cert_chain(iter::once(end_entity).chain(intermediates).collect()).unwrap(); + let identity = ServerIdentity::new(&identity, &server_name, verification_time()); + + let result = verifier.verify_identity(&identity); assert_eq!( result.map(|_| ()), @@ -321,8 +324,14 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_example.com-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, verification_time: verification_time(), + #[cfg(windows)] + expected_result: Err(TlsError::InvalidCertificate(CertificateError::InvalidPurpose)), + #[cfg(not(windows))] expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), + CertificateError::Other(OtherError::new(EkuError)))), + #[cfg(windows)] + other_error: no_error!(), + #[cfg(not(windows))] other_error: Some(EkuError), }, wrong_eku_ipv4 [ any(windows, unix) ] => TestCase { @@ -330,8 +339,14 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_127.0.0.1-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, verification_time: verification_time(), + #[cfg(windows)] + expected_result: Err(TlsError::InvalidCertificate(CertificateError::InvalidPurpose)), + #[cfg(not(windows))] expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), + CertificateError::Other(OtherError::new(EkuError)))), + #[cfg(windows)] + other_error: no_error!(), + #[cfg(not(windows))] other_error: Some(EkuError), }, wrong_eku_ipv6 [ any(windows, unix) ] => TestCase { @@ -339,8 +354,14 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_1-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, verification_time: verification_time(), + #[cfg(windows)] + expected_result: Err(TlsError::InvalidCertificate(CertificateError::InvalidPurpose)), + #[cfg(not(windows))] expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::from(EkuError))))), + CertificateError::Other(OtherError::new(EkuError)))), + #[cfg(windows)] + other_error: no_error!(), + #[cfg(not(windows))] other_error: Some(EkuError), }, } @@ -373,13 +394,20 @@ fn test_with_mock_root( assert!(matches!(server_name, pki_types::ServerName::DnsName(_))); } - let result = verifier.verify_server_cert( - &end_entity, - &intermediates, + let presented_identity = + Identity::from_cert_chain(iter::once(end_entity).chain(intermediates).collect()).unwrap(); + let mut identity = ServerIdentity::new( + &presented_identity, &server_name, - test_case.stapled_ocsp.unwrap_or(&[]), test_case.verification_time, ); + identity.ocsp_response = test_case.stapled_ocsp.unwrap_or(&[]); + + let result = verifier.verify_identity(&identity); + + if let Ok(verified) = &result { + assert_eq!(verified.identity(), &presented_identity); + } assert_cert_error_eq( &result.map(|_| ()), diff --git a/rustls-platform-verifier/src/tests/verification_real_world/mod.rs b/rustls-platform-verifier/src/tests/verification_real_world/mod.rs index 55f749bb..0037b9bb 100644 --- a/rustls-platform-verifier/src/tests/verification_real_world/mod.rs +++ b/rustls-platform-verifier/src/tests/verification_real_world/mod.rs @@ -36,12 +36,15 @@ //! potentially poor performance. use core::time::Duration; +use std::iter; -use rustls::client::danger::ServerCertVerifier; +use rustls::client::danger::{ServerIdentity, ServerVerifier}; +use rustls::crypto::Identity; +use rustls::error::CertificateError; use rustls::pki_types; #[cfg(not(any(target_vendor = "apple", windows)))] use rustls::pki_types::{DnsName, ServerName}; -use rustls::{CertificateError, Error as TlsError}; +use rustls::Error as TlsError; use super::TestCase; use crate::tests::{assert_cert_error_eq, test_provider}; @@ -168,17 +171,13 @@ fn real_world_test(test_case: &TestCase) { let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap(); - let stapled_ocsp = test_case.stapled_ocsp.unwrap_or(&[]); + let identity = + Identity::from_cert_chain(iter::once(end_entity_cert).chain(intermediates).collect()) + .unwrap(); + let mut identity = ServerIdentity::new(&identity, &server_name, test_case.verification_time); + identity.ocsp_response = test_case.stapled_ocsp.unwrap_or(&[]); - let result = verifier - .verify_server_cert( - &end_entity_cert, - &intermediates, - &server_name, - stapled_ocsp, - test_case.verification_time, - ) - .map(|_| ()); + let result = verifier.verify_identity(&identity).map(|_| ()); assert_cert_error_eq( &result.map(|_| ()), diff --git a/rustls-platform-verifier/src/verification/android.rs b/rustls-platform-verifier/src/verification/android.rs index 3916a5fa..c8e79307 100644 --- a/rustls-platform-verifier/src/verification/android.rs +++ b/rustls-platform-verifier/src/verification/android.rs @@ -1,17 +1,27 @@ +use std::{ + hash::{Hash, Hasher}, + iter, + sync::Arc, +}; + use jni::{ jni_sig, jni_str, objects::{JByteArray, JObject, JObjectArray, JString, JValue}, signature::MethodSignature, Env, }; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; +use rustls::client::danger::{ + HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput, +}; +use rustls::crypto::{ + verify_tls12_signature, verify_tls13_signature, CertificateIdentity, CryptoProvider, Identity, + SignatureScheme, VerifiedIdentity, +}; +use rustls::error::{CertificateError, OtherError}; +#[cfg(any(test, feature = "ffi-testing"))] use rustls::pki_types; +use rustls::Error as TlsError; use rustls::Error::InvalidCertificate; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, -}; -use std::sync::Arc; use super::{log_server_cert, ALLOWED_EKUS}; use crate::android::{with_context, CachedClass}; @@ -89,21 +99,18 @@ impl Verifier { fn verify_certificate( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: Option<&[u8]>, - now: pki_types::UnixTime, + certificates: &CertificateIdentity<'_>, + identity: &ServerIdentity<'_, '_>, ) -> Result<(), TlsError> { - let certificate_chain = std::iter::once(end_entity) - .chain(intermediates) + let certificate_chain = iter::once(&certificates.end_entity) + .chain(&certificates.intermediates) .map(|cert| cert.as_ref()) .enumerate(); // Convert the unix timestamp into milliseconds, expressed as // an i64 to later be converted into a Java Long used for a Date // constructor. - let now: i64 = (now.as_secs() * 1000) + let now: i64 = (identity.now.as_secs() * 1000) .try_into() .map_err(|_| TlsError::FailedToGetCurrentTime)?; @@ -113,7 +120,7 @@ impl Verifier { let cert_list = { let array = JObjectArray::::new( cx.env, - intermediates.len() + 1, + certificates.intermediates.len() + 1, &JByteArray::null(), )?; @@ -139,9 +146,10 @@ impl Verifier { array }; - let ocsp_response = match ocsp_response { - Some(b) => cx.env.byte_array_from_slice(b)?, - None => JByteArray::null(), + let ocsp_response = if identity.ocsp_response.is_empty() { + JByteArray::null() + } else { + cx.env.byte_array_from_slice(identity.ocsp_response)? }; #[cfg(any(test, feature = "ffi-testing"))] @@ -172,7 +180,7 @@ impl Verifier { ) -> org.rustls.platformverifier.VerificationResult ); - let server_name = server_name.to_str(); + let server_name = identity.server_name.to_str(); // Android's verifier doesn't require this but trim trailing `.` labels for consistency across platforms. let server_name = server_name.strip_suffix('.').unwrap_or(&server_name); @@ -210,8 +218,8 @@ impl Verifier { VerifierStatus::Ok => { // If everything else was OK, check the hostname. rustls::client::verify_server_name( - &rustls::server::ParsedCertificate::try_from(end_entity)?, - server_name, + &rustls::server::ParsedCertificate::try_from(&certificates.end_entity)?, + identity.server_name, ) } VerifierStatus::Unavailable => Err(TlsError::General(String::from( @@ -230,7 +238,7 @@ impl Verifier { Err(InvalidCertificate(CertificateError::BadEncoding)) } VerifierStatus::InvalidExtension => Err(InvalidCertificate( - CertificateError::Other(OtherError(std::sync::Arc::new(super::EkuError))), + CertificateError::Other(OtherError::new(super::EkuError)), )), } } @@ -276,25 +284,23 @@ fn extract_result_info(env: &mut Env<'_>, result: JObject<'_>) -> (VerifierStatu } #[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( +impl ServerVerifier for Verifier { + fn verify_identity<'a>( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None + identity: &ServerIdentity<'a, '_>, + ) -> Result, TlsError> { + let Identity::X509(certificates) = identity.identity else { + return Err(InvalidCertificate(CertificateError::Other( + OtherError::new(std::io::Error::other( + "platform verifier only supports X.509 certificates", + )), + ))); }; - match self.verify_certificate(end_entity, intermediates, server_name, ocsp_data, now) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), + log_server_cert(&certificates.end_entity); + + match self.verify_certificate(certificates, identity) { + Ok(()) => Ok(VerifiedIdentity::assertion(identity.identity.clone())), Err(e) => { // This error only tells us what the system errored with, so it doesn't leak anything // sensitive. @@ -306,28 +312,20 @@ impl ServerCertVerifier for Verifier { fn verify_tls12_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls12_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls13_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } @@ -337,4 +335,17 @@ impl ServerCertVerifier for Verifier { .signature_verification_algorithms .supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + true + } + + fn hash_config(&self, mut h: &mut dyn Hasher) { + h.write(b"rustls-platform-verifier-android"); + #[cfg(any(test, feature = "ffi-testing"))] + self.test_only_root_ca_override.hash(&mut h); + self.crypto_provider + .signature_verification_algorithms + .hash(&mut h); + } } diff --git a/rustls-platform-verifier/src/verification/apple.rs b/rustls-platform-verifier/src/verification/apple.rs index b4e34e7d..67f12c87 100644 --- a/rustls-platform-verifier/src/verification/apple.rs +++ b/rustls-platform-verifier/src/verification/apple.rs @@ -1,13 +1,20 @@ -use std::sync::Arc; +use std::{ + hash::{Hash, Hasher}, + sync::Arc, +}; use core_foundation::date::CFDate; use core_foundation_sys::date::kCFAbsoluteTimeIntervalSince1970; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; -use rustls::pki_types; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, +use rustls::client::danger::{ + HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput, +}; +use rustls::crypto::{ + verify_tls12_signature, verify_tls13_signature, CertificateIdentity, CryptoProvider, Identity, + SignatureScheme, VerifiedIdentity, }; +use rustls::error::{CertificateError, OtherError}; +use rustls::pki_types; +use rustls::Error as TlsError; use security_framework::{ certificate::SecCertificate, policy::SecPolicy, secure_transport::SslProtocolSide, trust::SecTrust, @@ -104,20 +111,27 @@ impl Verifier { fn verify_certificate( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &str, - ocsp_response: Option<&[u8]>, - now: pki_types::UnixTime, + certificates: &CertificateIdentity<'_>, + identity: &ServerIdentity<'_, '_>, ) -> Result<(), TlsError> { - let certificates: Vec = std::iter::once(end_entity.as_ref()) - .chain(intermediates.iter().map(|cert| cert.as_ref())) + let certificates: Vec = std::iter::once(certificates.end_entity.as_ref()) + .chain(certificates.intermediates.iter().map(|cert| cert.as_ref())) .map(|cert| { SecCertificate::from_der(cert) .map_err(|_| TlsError::InvalidCertificate(CertificateError::BadEncoding)) }) .collect::, _>>()?; + let server_name = identity.server_name.to_str(); + // Apple's verifier doesn't require this but trim trailing `.` labels for consistency across platforms. + let server_name = server_name.strip_suffix('.').unwrap_or(&server_name); + + let ocsp_response = if identity.ocsp_response.is_empty() { + None + } else { + Some(identity.ocsp_response) + }; + // Create our verification policy suitable for verifying TLS chains. // This uses the "default" verification engine and parameters, the same as Windows. // @@ -140,7 +154,7 @@ impl Verifier { // Tell the system that we want to consider the certificates evaluation at the point // in time that `rustls` provided. - let now = system_time_to_cfdate(now)?; + let now = system_time_to_cfdate(identity.now)?; trust_evaluation .set_trust_verify_date(&now) .map_err(|e| invalid_certificate(e.to_string()))?; @@ -170,14 +184,14 @@ impl Verifier { if !extra_roots.is_empty() { trust_evaluation .set_anchor_certificates(extra_roots) - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?; + .map_err(|e| TlsError::Other(OtherError::new(e)))?; // We want to trust both the system-installed and the extra roots. This must be set // since calling `SecTrustSetAnchorCertificates` "disables the trusting of any // anchors other than the ones specified by this function call" by default. trust_evaluation .set_trust_anchor_certificates_only(false) - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?; + .map_err(|e| TlsError::Other(OtherError::new(e)))?; } // When testing, support using fake roots and ignoring default roots present on the system for @@ -226,7 +240,7 @@ impl Verifier { CertificateError::UnknownIssuer, )), errors::errSecInvalidExtendedKeyUsage => Ok(TlsError::InvalidCertificate( - CertificateError::Other(OtherError(Arc::new(super::EkuError))), + CertificateError::Other(OtherError::new(super::EkuError)), )), errors::errSecCertificateRevoked => { Ok(TlsError::InvalidCertificate(CertificateError::Revoked)) @@ -243,31 +257,21 @@ impl Verifier { } #[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( +impl ServerVerifier for Verifier { + fn verify_identity<'a>( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - // Convert IP addresses to name strings to ensure match check on leaf certificate. - // Ref: https://developer.apple.com/documentation/security/1392592-secpolicycreatessl - let server = server_name.to_str(); - // Apple's verifier doesn't require this but trim trailing `.` labels for consistency across platforms. - let server = server.strip_suffix('.').unwrap_or(&server); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None + identity: &ServerIdentity<'a, '_>, + ) -> Result, TlsError> { + let Identity::X509(certificates) = identity.identity else { + return Err(invalid_certificate( + "platform verifier only supports X.509 certificates", + )); }; - match self.verify_certificate(end_entity, intermediates, server, ocsp_data, now) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), + log_server_cert(&certificates.end_entity); + + match self.verify_certificate(certificates, identity) { + Ok(()) => Ok(VerifiedIdentity::assertion(identity.identity.clone())), Err(e) => { // This error only tells us what the system errored with, so it doesn't leak anything // sensitive. @@ -279,28 +283,20 @@ impl ServerCertVerifier for Verifier { fn verify_tls12_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls12_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls13_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } @@ -310,4 +306,24 @@ impl ServerCertVerifier for Verifier { .signature_verification_algorithms .supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + true + } + + fn hash_config(&self, mut h: &mut dyn Hasher) { + h.write(b"rustls-platform-verifier-apple"); + h.write_usize(self.extra_roots.len()); + for root in &self.extra_roots { + root.to_der().hash(&mut h); + } + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + self.test_only_root_ca_override + .as_ref() + .map(SecCertificate::to_der) + .hash(&mut h); + self.crypto_provider + .signature_verification_algorithms + .hash(&mut h); + } } diff --git a/rustls-platform-verifier/src/verification/mod.rs b/rustls-platform-verifier/src/verification/mod.rs index 9d947ff2..650b7fab 100644 --- a/rustls-platform-verifier/src/verification/mod.rs +++ b/rustls-platform-verifier/src/verification/mod.rs @@ -1,5 +1,5 @@ #[cfg(any(windows, target_vendor = "apple"))] -use std::sync::Arc; +use rustls::error::{CertificateError, OtherError}; #[cfg(all( any(unix, target_arch = "wasm32"), @@ -65,8 +65,8 @@ fn log_server_cert(_end_entity: &rustls::pki_types::CertificateDer<'_>) { // error with a platform specific error message. #[cfg(any(windows, target_vendor = "apple"))] fn invalid_certificate(reason: impl Into) -> rustls::Error { - rustls::Error::InvalidCertificate(rustls::CertificateError::Other(rustls::OtherError( - Arc::from(Box::from(reason.into())), + rustls::Error::InvalidCertificate(CertificateError::Other(OtherError::new( + std::io::Error::other(reason.into()), ))) } diff --git a/rustls-platform-verifier/src/verification/others.rs b/rustls-platform-verifier/src/verification/others.rs index 6e4796fb..6b89e006 100644 --- a/rustls-platform-verifier/src/verification/others.rs +++ b/rustls-platform-verifier/src/verification/others.rs @@ -1,13 +1,15 @@ use std::fmt::Debug; +use std::hash::Hasher; use std::sync::Arc; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::client::danger::{ + HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput, +}; use rustls::client::WebPkiServerVerifier; +use rustls::crypto::{CryptoProvider, SignatureScheme, VerifiedIdentity}; +use rustls::error::{CertificateError, OtherError}; use rustls::pki_types; -use rustls::{ - crypto::CryptoProvider, CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, - SignatureScheme, -}; +use rustls::Error as TlsError; use super::log_server_cert; @@ -66,12 +68,11 @@ impl Verifier { if let Some(test_root) = test_root { root_store.add(test_root)?; return Ok(Self { - inner: WebPkiServerVerifier::builder_with_provider( - root_store.into(), - crypto_provider.clone(), - ) - .build() - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?, + inner: Arc::new( + WebPkiServerVerifier::builder(Arc::new(root_store), &crypto_provider) + .build() + .map_err(|e| TlsError::Other(OtherError::new(e)))?, + ), }); } } @@ -118,27 +119,27 @@ impl Verifier { }; Ok(Self { - inner: WebPkiServerVerifier::builder_with_provider(root_store.into(), crypto_provider) - .build() - .map_err(|e| TlsError::Other(OtherError(Arc::new(e))))?, + inner: Arc::new( + WebPkiServerVerifier::builder(Arc::new(root_store), &crypto_provider) + .build() + .map_err(|e| TlsError::Other(OtherError::new(e)))?, + ), }) } } #[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( +impl ServerVerifier for Verifier { + fn verify_identity<'a>( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); + identity: &ServerIdentity<'a, '_>, + ) -> Result, TlsError> { + if let rustls::crypto::Identity::X509(certificates) = identity.identity { + log_server_cert(&certificates.end_entity); + } self.inner - .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) + .verify_identity(identity) .map_err(map_webpki_errors) // This only contains information from the system or other public // bits of the TLS handshake, so it can't leak anything. @@ -150,34 +151,37 @@ impl ServerCertVerifier for Verifier { fn verify_tls12_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - self.inner.verify_tls12_signature(message, cert, dss) + self.inner.verify_tls12_signature(input) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { - self.inner.verify_tls13_signature(message, cert, dss) + self.inner.verify_tls13_signature(input) } fn supported_verify_schemes(&self) -> Vec { self.inner.supported_verify_schemes() } + + fn request_ocsp_response(&self) -> bool { + self.inner.request_ocsp_response() + } + + fn hash_config(&self, h: &mut dyn Hasher) { + h.write(b"rustls-platform-verifier-others"); + self.inner.hash_config(h); + } } fn map_webpki_errors(err: TlsError) -> TlsError { match &err { TlsError::InvalidCertificate(CertificateError::InvalidPurpose) | TlsError::InvalidCertificate(CertificateError::InvalidPurposeContext { .. }) => { - TlsError::InvalidCertificate(CertificateError::Other(OtherError(Arc::new( - super::EkuError, - )))) + TlsError::InvalidCertificate(CertificateError::Other(OtherError::new(super::EkuError))) } _ => err, } diff --git a/rustls-platform-verifier/src/verification/windows.rs b/rustls-platform-verifier/src/verification/windows.rs index 24fadc9b..44193582 100644 --- a/rustls-platform-verifier/src/verification/windows.rs +++ b/rustls-platform-verifier/src/verification/windows.rs @@ -20,19 +20,23 @@ use std::{ convert::TryInto, + hash::{Hash, Hasher}, mem::{self, MaybeUninit}, os::raw::c_void, ptr::{self, NonNull}, sync::Arc, }; -use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; -use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; -use rustls::pki_types; -use rustls::{ - CertificateError, DigitallySignedStruct, Error as TlsError, Error::InvalidCertificate, - SignatureScheme, +use rustls::client::danger::{ + HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput, +}; +use rustls::crypto::{ + verify_tls12_signature, verify_tls13_signature, CertificateIdentity, CryptoProvider, Identity, + SignatureScheme, VerifiedIdentity, }; +use rustls::error::CertificateError; +use rustls::pki_types; +use rustls::{Error as TlsError, Error::InvalidCertificate}; use windows_sys::Win32::{ Foundation::{ CERT_E_CN_NO_MATCH, CERT_E_EXPIRED, CERT_E_INVALID_NAME, CERT_E_UNTRUSTEDROOT, @@ -240,15 +244,17 @@ impl Drop for Certificate { #[derive(Debug)] struct CertEngine { inner: NonNull, // HCERTENGINECONTEXT + roots: Vec>, } impl CertEngine { fn new_with_extra_roots( roots: impl IntoIterator>, ) -> Result { + let roots: Vec<_> = roots.into_iter().collect(); let mut exclusive_store = CertificateStore::new()?; - for root in roots { - exclusive_store.add_cert(&root)?; + for root in &roots { + exclusive_store.add_cert(root)?; } let mut config = CERT_CHAIN_ENGINE_CONFIG::zeroed_with_size(); @@ -268,7 +274,10 @@ impl CertEngine { Some(c) if res == TRUE => Some(c), _ => None, })?; - Ok(Self { inner: engine }) + Ok(Self { + inner: engine, + roots, + }) } #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] @@ -305,7 +314,10 @@ impl CertEngine { _ => None, })?; - Ok(Self { inner: engine }) + Ok(Self { + inner: engine, + roots: vec![root.to_vec().into()], + }) } } @@ -597,11 +609,8 @@ impl Verifier { /// Return `Ok(())` if the certificate was valid. fn verify_certificate( &self, - primary_cert: &[u8], - intermediate_certs: &[&[u8]], - server: &[u8], - ocsp_data: Option<&[u8]>, - now: pki_types::UnixTime, + certificates: &CertificateIdentity<'_>, + identity: &ServerIdentity<'_, '_>, ) -> Result<(), TlsError> { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] let mut store = match self.test_only_root_ca_override.as_ref() { @@ -614,19 +623,19 @@ impl Verifier { #[cfg(not(any(test, feature = "ffi-testing", feature = "dbg")))] let mut store = CertificateStore::new()?; - let mut primary_cert = store.add_cert(primary_cert)?; + let mut primary_cert = store.add_cert(certificates.end_entity.as_ref())?; - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; + for cert in &certificates.intermediates { + store.add_cert(cert.as_ref())?; } - if let Some(ocsp_data) = ocsp_data { + if !identity.ocsp_response.is_empty() { #[allow(clippy::as_conversions)] let data = CRYPT_INTEGER_BLOB { - cbData: ocsp_data.len().try_into().map_err(|_| { + cbData: identity.ocsp_response.len().try_into().map_err(|_| { invalid_certificate("Malformed OCSP response stapled to server certificate") })?, - pbData: ocsp_data.as_ptr() as *mut u8, + pbData: identity.ocsp_response.as_ptr() as *mut u8, }; // SAFETY: `data` is a valid pointer and matches the property ID. @@ -639,13 +648,22 @@ impl Verifier { } // Encode UTF-16, null-terminated - let server: Vec = server + let server_name = identity.server_name.to_str(); + // Trim trailing `.` labels to remove compatibility hazards with the Windows verifier. + // It performs exact equality comparisons in most cases (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-httpspolicycallbackdata), + // which causes problems with hostnames considered equivalent by other verifier and TLS implementations. + // + // Ref: https://github.com/rustls/rustls-platform-verifier/issues/240 + let server_name = server_name.strip_suffix('.').unwrap_or(&server_name); + let server: Vec = server_name + .as_bytes() .iter() .map(|c| u16::from(*c)) .chain(Some(0)) .collect(); - let mut cert_chain = store.new_chain_in(&primary_cert, now, store.engine.as_ref())?; + let mut cert_chain = + store.new_chain_in(&primary_cert, identity.now, store.engine.as_ref())?; // We only use `TrustStatus` here because it hasn't had verification performed on it. // SAFETY: The pointer is guaranteed to be non-null. @@ -661,11 +679,12 @@ impl Verifier { if extra_roots_may_needed && self.extra_roots.is_some() { let mut store = CertificateStore::new()?; - for cert in intermediate_certs.iter().copied() { - store.add_cert(cert)?; + for cert in &certificates.intermediates { + store.add_cert(cert.as_ref())?; } - cert_chain = store.new_chain_in(&primary_cert, now, self.extra_roots.as_ref())?; + cert_chain = + store.new_chain_in(&primary_cert, identity.now, self.extra_roots.as_ref())?; } let status = cert_chain.verify_chain_policy(server)?; @@ -695,41 +714,21 @@ impl Verifier { } #[cfg_attr(docsrs, doc(cfg(all())))] -impl ServerCertVerifier for Verifier { - fn verify_server_cert( +impl ServerVerifier for Verifier { + fn verify_identity<'a>( &self, - end_entity: &pki_types::CertificateDer<'_>, - intermediates: &[pki_types::CertificateDer<'_>], - server_name: &pki_types::ServerName, - ocsp_response: &[u8], - now: pki_types::UnixTime, - ) -> Result { - log_server_cert(end_entity); - - let name = server_name.to_str(); - // Trim trailing `.` labels to remove compatibility hazards with the Windows verifier. - // It performs exact quality comparisions in most cases (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-httpspolicycallbackdata) - // which causes problems with hostnames that are considered equivalent for security purposes in other verifier and TLS implementations. - // - // Ref: https://github.com/rustls/rustls-platform-verifier/issues/240 - let name = name.strip_suffix('.').unwrap_or(&name); - - let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.as_ref()).collect(); - - let ocsp_data = if !ocsp_response.is_empty() { - Some(ocsp_response) - } else { - None + identity: &ServerIdentity<'a, '_>, + ) -> Result, TlsError> { + let Identity::X509(certificates) = identity.identity else { + return Err(invalid_certificate( + "platform verifier only supports X.509 certificates", + )); }; - match self.verify_certificate( - end_entity.as_ref(), - &intermediate_certs, - name.as_bytes(), - ocsp_data, - now, - ) { - Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), + log_server_cert(&certificates.end_entity); + + match self.verify_certificate(certificates, identity) { + Ok(()) => Ok(VerifiedIdentity::assertion(identity.identity.clone())), Err(e) => { // SAFETY: // Errors are our own custom errors, WinAPI errors, or static strings. @@ -741,28 +740,20 @@ impl ServerCertVerifier for Verifier { fn verify_tls12_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls12_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &pki_types::CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &SignatureVerificationInput<'_>, ) -> Result { verify_tls13_signature( - message, - cert, - dss, + input, &self.crypto_provider.signature_verification_algorithms, ) } @@ -772,6 +763,23 @@ impl ServerCertVerifier for Verifier { .signature_verification_algorithms .supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + true + } + + fn hash_config(&self, mut h: &mut dyn Hasher) { + h.write(b"rustls-platform-verifier-windows"); + self.extra_roots + .as_ref() + .map(|engine| &engine.roots) + .hash(&mut h); + #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] + self.test_only_root_ca_override.hash(&mut h); + self.crypto_provider + .signature_verification_algorithms + .hash(&mut h); + } } /// A trait to represent an object that can be safely created with all zero values