From 62c9ae353b3a6b504383723a4ec83d050c1b7b74 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 4 Dec 2023 18:09:19 -0500 Subject: [PATCH] Cargo: update to rustls 0.22, associated updates For the time being, this branch continues to unconditionally use *ring* as the crypto provider. Follow-up work to expose this as a choice (e.g allowing aws-lc-rs as a provider) may be interesting. Deps: * updated rustls 0.21 -> 0.22 * added pki-types 1.0 Linux deps: * rustls-native-certs 0.6 -> 0.7 * webpki 0.101 -> 0.102 Android deps: * webpki 0.101 -> 0.102 WASM32 deps: * webpki-roots 0.25 -> 0.26 Summary of breaking change updates: * `ServerName`, `Certificate`, and `OwnedTrustAnchor` types are now sourced from `pki_types`, with an associated generic lifetime. The `OwnedTrustAnchor` type is now just `TrustAnchor`. * The 'dangerous' rustls crate feature was removed, and associated items moved into new locations with the import path emphasizing danger. * "Other error" types changed to use a specific `rustls::OtherError` inner variant. * `SystemTime` for verifiers replaced with `pki_types::UnixTime`. * Default fns on `ServerCertVerifier` trait were removed, must be reconstituted with `rustls::verify_tls12_signature`, `rustls::verify_tls13_signature` and `WebPkiSupportedAlgorithms.supported_schemes` using a `CryptoProvider`. * `ServerName` now supports a `to_str` operation, avoiding the need to `match` and handle unsupported name types. * `WebPkiVerifier` was renamed to `WebPkiServerVerifier`, handled as an `Arc` and constructed with a builder. --- Cargo.lock | 137 +++++++++++++++++++---- Cargo.toml | 11 +- src/lib.rs | 10 +- src/tests/mod.rs | 1 + src/tests/verification_mock/mod.rs | 37 +++--- src/tests/verification_real_world/mod.rs | 9 +- src/verification/android.rs | 106 ++++++++++-------- src/verification/apple.rs | 97 ++++++++++------ src/verification/mod.rs | 15 +-- src/verification/others.rs | 104 +++++++++++------ src/verification/windows.rs | 88 ++++++++++----- 11 files changed, 397 insertions(+), 218 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d85db0d..a8a07e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,6 +222,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.0" @@ -326,7 +337,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls", + "rustls 0.21.7", "tokio", "tokio-rustls", ] @@ -394,9 +405,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "log" @@ -585,8 +596,8 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.7", + "rustls-pemfile 1.0.3", "serde", "serde_json", "serde_urlencoded", @@ -609,12 +620,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -628,19 +653,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", - "ring", - "rustls-webpki", + "ring 0.16.20", + "rustls-webpki 0.101.4", "sct", ] +[[package]] +name = "rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc238b76c51bbc449c55ffbc39d03772a057cc8cf783c49d4af4c2537b74a8b" +dependencies = [ + "log", + "ring 0.17.6", + "rustls-pki-types", + "rustls-webpki 0.102.0", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.0.0", + "rustls-pki-types", "schannel", "security-framework", ] @@ -654,6 +694,22 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb0a1f9b9efec70d32e6d6aa3e58ebd88c3754ec98dfe9145c63cf54cc829b83" + [[package]] name = "rustls-platform-verifier" version = "0.1.0" @@ -666,9 +722,10 @@ dependencies = [ "log", "once_cell", "reqwest", - "rustls", + "rustls 0.22.0", "rustls-native-certs", - "rustls-webpki", + "rustls-pki-types", + "rustls-webpki 0.102.0", "security-framework", "security-framework-sys", "tokio", @@ -682,8 +739,19 @@ version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" +dependencies = [ + "ring 0.17.6", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] @@ -716,8 +784,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -822,6 +890,18 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.29" @@ -902,7 +982,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.7", "tokio", ] @@ -979,6 +1059,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -1093,9 +1179,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "0de2cfda980f21be5a7ed2eadb3e6fe074d56022bea2cdeb1a62eb220fc04188" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "winapi" @@ -1203,3 +1292,9 @@ dependencies = [ "cfg-if", "windows-sys", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 09d2378e..7ca315aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,26 +37,27 @@ cert-logging = ["base64"] docsrs = ["jni", "once_cell"] [dependencies] -rustls = { version = "0.21", features = ["dangerous_configuration", "tls12", "logging"] } +rustls = { version = "0.22", features = ["tls12", "logging"] } +pki-types = { package = "rustls-pki-types", version = "1" } log = { version = "0.4" } base64 = { version = "0.21", optional = true } # Only used when the `cert-logging` feature is enabled. jni = { version = "0.19", default-features = false, optional = true } # Only used during doc generation once_cell = { version = "1.9", optional = true } # Only used during doc generation. [target.'cfg(target_os = "linux")'.dependencies] -rustls-native-certs = "0.6" +rustls-native-certs = "0.7" once_cell = "1.9" -webpki = { package = "rustls-webpki", version = "0.101", features = ["alloc", "std"] } +webpki = { package = "rustls-webpki", version = "0.102", features = ["ring", "alloc", "std"] } [target.'cfg(target_os = "android")'.dependencies] jni = { version = "0.19", default-features = false } -webpki = { package = "rustls-webpki", version = "0.101", features = ["alloc", "std"] } +webpki = { package = "rustls-webpki", version = "0.102", features = ["ring", "alloc", "std"] } once_cell = "1.9" android_logger = { version = "0.13", optional = true } # Only used during testing. [target.'cfg(target_arch = "wasm32")'.dependencies] once_cell = "1.9" -webpki-roots = "0.25" +webpki-roots = "0.26" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] core-foundation = "0.9" diff --git a/src/lib.rs b/src/lib.rs index aeba0637..04f4d23c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,13 +49,13 @@ pub use tests::ffi::*; /// /// If you require more control over the rustls `ClientConfig`, you can /// instantiate a [Verifier] with [Verifier::default] and then use it -/// with [rustls::ConfigBuilder::with_custom_certificate_verifier]. +/// with [rustls::ConfigBuilder::dangerous::with_custom_certificate_verifier]. /// /// Refer to the crate level documentation to see what platforms /// are currently supported. pub fn tls_config() -> ClientConfig { - rustls::ClientConfig::builder() - .with_safe_defaults() + ClientConfig::builder() + .dangerous() .with_custom_certificate_verifier(verifier_for_testing()) .with_no_client_auth() } @@ -63,12 +63,12 @@ pub fn tls_config() -> ClientConfig { /// Exposed for test usage. Don't use this, use [tls_config] instead. /// /// This verifier must be exactly equivalent to the verifier used in the `ClientConfig` returned by [tls_config]. -pub(crate) fn verifier_for_testing() -> Arc { +pub(crate) fn verifier_for_testing() -> Arc { Arc::new(Verifier::new()) } /// Exposed for debugging customer certificate issues. Don't use this, use [tls_config] instead. #[cfg(feature = "dbg")] -pub fn verifier_for_dbg(root: &[u8]) -> Arc { +pub fn verifier_for_dbg(root: &[u8]) -> Arc { Arc::new(Verifier::new_with_fake_root(root)) } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f42f7f8f..8988f16d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -39,6 +39,7 @@ pub fn assert_cert_error_eq( if let Err(InvalidCertificate(CertificateError::Other(err))) = &expected { let expected_err = expected_err.expect("error not provided for `Other` case handling"); let err: &E = err + .0 .downcast_ref() .expect("incorrect `Other` inner error kind"); assert_eq!(err, expected_err); diff --git a/src/tests/verification_mock/mod.rs b/src/tests/verification_mock/mod.rs index b2aff1f2..3dfce158 100644 --- a/src/tests/verification_mock/mod.rs +++ b/src/tests/verification_mock/mod.rs @@ -23,7 +23,8 @@ use super::TestCase; use crate::tests::assert_cert_error_eq; use crate::verification::{EkuError, Verifier}; -use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError}; +use rustls::client::danger::ServerCertVerifier; +use rustls::{CertificateError, Error as TlsError, OtherError}; use std::convert::TryFrom; use std::net::IpAddr; use std::sync::Arc; @@ -84,18 +85,17 @@ const LOCALHOST_IPV6: &str = "::1"; pub(super) fn verification_without_mock_root() { let verifier = crate::verifier_for_testing(); - let server_name = rustls::client::ServerName::try_from(EXAMPLE_COM).unwrap(); - let end_entity = rustls::Certificate(ROOT1_INT1_EXAMPLE_COM_GOOD.to_vec()); - let intermediates = [rustls::Certificate(ROOT1_INT1.to_vec())]; + let server_name = pki_types::ServerName::try_from(EXAMPLE_COM).unwrap(); + let end_entity = pki_types::CertificateDer::from(ROOT1_INT1_EXAMPLE_COM_GOOD.to_vec()); + let intermediates = [pki_types::CertificateDer::from(ROOT1_INT1.to_vec())]; // Fails because the server cert has no trust root in Windows, and can't since it uses a self-signed CA. let result = verifier.verify_server_cert( &end_entity, &intermediates, &server_name, - &mut std::iter::empty(), &[], - std::time::SystemTime::now(), + pki_types::UnixTime::now(), ); assert_eq!( @@ -236,7 +236,7 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_example.com-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(Arc::from(EkuError)))), + CertificateError::Other(OtherError(Arc::from(EkuError))))), other_error: Some(EkuError), }, wrong_eku_ipv4 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase { @@ -244,7 +244,7 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_127.0.0.1-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(Arc::from(EkuError)))), + CertificateError::Other(OtherError(Arc::from(EkuError))))), other_error: Some(EkuError), }, wrong_eku_ipv6 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase { @@ -252,7 +252,7 @@ mock_root_test_cases! { chain: &[include_bytes!("root1-int1-ee_1-wrong_eku.crt"), ROOT1_INT1], stapled_ocsp: None, expected_result: Err(TlsError::InvalidCertificate( - CertificateError::Other(Arc::from(EkuError)))), + CertificateError::Other(OtherError(Arc::from(EkuError))))), other_error: Some(EkuError), }, } @@ -264,32 +264,25 @@ fn test_with_mock_root(test_case: &T let mut chain = test_case .chain .iter() - .map(|bytes| rustls::Certificate(bytes.to_vec())); + .map(|bytes| pki_types::CertificateDer::from(bytes.to_vec())); let end_entity = chain.next().unwrap(); - let intermediates: Vec = chain.collect(); + let intermediates: Vec> = chain.collect(); - let server_name = rustls::client::ServerName::try_from(test_case.reference_id).unwrap(); + let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap(); if test_case.reference_id.parse::().is_ok() { - assert!(matches!( - server_name, - rustls::client::ServerName::IpAddress(_) - )); + assert!(matches!(server_name, pki_types::ServerName::IpAddress(_))); } else { - assert!(matches!( - server_name, - rustls::client::ServerName::DnsName(_) - )); + assert!(matches!(server_name, pki_types::ServerName::DnsName(_))); } let result = verifier.verify_server_cert( &end_entity, &intermediates, &server_name, - &mut std::iter::empty(), test_case.stapled_ocsp.unwrap_or(&[]), - std::time::SystemTime::now(), + pki_types::UnixTime::now(), ); assert_cert_error_eq( diff --git a/src/tests/verification_real_world/mod.rs b/src/tests/verification_real_world/mod.rs index 6af3fc74..564e75c9 100644 --- a/src/tests/verification_real_world/mod.rs +++ b/src/tests/verification_real_world/mod.rs @@ -129,12 +129,12 @@ fn real_world_test(test_case: &TestCase) { let mut chain = test_case .chain .iter() - .map(|bytes| rustls::Certificate(bytes.to_vec())); + .map(|bytes| pki_types::CertificateDer::from(bytes.to_vec())); let end_entity_cert = chain.next().unwrap(); - let intermediates: Vec = chain.collect(); + let intermediates: Vec> = chain.collect(); - let server_name = rustls::client::ServerName::try_from(test_case.reference_id).unwrap(); + let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap(); let stapled_ocsp = test_case.stapled_ocsp.unwrap_or(&[]); @@ -143,9 +143,8 @@ fn real_world_test(test_case: &TestCase) { &end_entity_cert, &intermediates, &server_name, - &mut std::iter::empty(), stapled_ocsp, - std::time::SystemTime::now(), + pki_types::UnixTime::now(), ) .map(|_| ()); diff --git a/src/verification/android.rs b/src/verification/android.rs index 90239095..389a8aa9 100644 --- a/src/verification/android.rs +++ b/src/verification/android.rs @@ -3,11 +3,14 @@ use jni::{ strings::JavaStr, JNIEnv, }; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature}; use rustls::Error::InvalidCertificate; -use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError, ServerName}; -use std::time::SystemTime; +use rustls::{ + CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, +}; -use super::{log_server_cert, unsupported_server_name, ALLOWED_EKUS}; +use super::{log_server_cert, ALLOWED_EKUS}; use crate::android::{with_context, CachedClass}; static CERT_VERIFIER_CLASS: CachedClass = @@ -35,7 +38,7 @@ enum VerifierStatus { const AUTH_TYPE: &str = "RSA"; /// A TLS certificate verifier that utilizes the Android platform verifier. -#[derive(Default)] +#[derive(Default, Debug)] pub struct Verifier { /// Testing only: The root CA certificate to trust. #[cfg(any(test, feature = "ffi-testing"))] @@ -75,25 +78,23 @@ impl Verifier { fn verify_certificate( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &rustls::ServerName, - server_name_str: &str, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName<'_>, ocsp_response: Option<&[u8]>, - now: SystemTime, + now: pki_types::UnixTime, ) -> Result<(), TlsError> { let certificate_chain = std::iter::once(end_entity) .chain(intermediates) .map(|cert| cert.as_ref()) .enumerate(); - #[allow(clippy::as_conversions)] - let now: i64 = now - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() + // 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) .try_into() - .unwrap(); + .map_err(|_| TlsError::FailedToGetCurrentTime)?; let verification_result = with_context(|cx| { let env = cx.env(); @@ -172,7 +173,7 @@ impl Verifier { VERIFIER_CALL, &[ JValue::from(*cx.application_context()), - JValue::from(env.new_string(server_name_str)?), + JValue::from(env.new_string(&server_name.to_str())?), JValue::from(env.new_string(AUTH_TYPE)?), JValue::from(JObject::from(allowed_ekus)), JValue::from(ocsp_response), @@ -215,7 +216,7 @@ impl Verifier { Err(InvalidCertificate(CertificateError::BadEncoding)) } VerifierStatus::InvalidExtension => Err(InvalidCertificate( - CertificateError::Other(std::sync::Arc::new(super::EkuError)), + CertificateError::Other(OtherError(std::sync::Arc::new(super::EkuError))), )), } } @@ -258,45 +259,22 @@ fn extract_result_info(env: &JNIEnv<'_>, result: JObject<'_>) -> (VerifierStatus impl ServerCertVerifier for Verifier { fn verify_server_cert( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &rustls::ServerName, - // Android has no support for providing SCTs to the verifier, - // but it does consider them internally if the hostname matches a - // system-specified list. - _scts: &mut dyn Iterator, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName, ocsp_response: &[u8], - now: SystemTime, - ) -> Result { + now: pki_types::UnixTime, + ) -> Result { log_server_cert(end_entity); - // Verify the server name is one that we support and extract a string to use - // for the platform verifier call. - let ip_name; - let server_name_str = match server_name { - ServerName::DnsName(dns_name) => dns_name.as_ref(), - ServerName::IpAddress(ip_addr) => { - ip_name = ip_addr.to_string(); - &ip_name - } - _ => return Err(unsupported_server_name()), - }; - let ocsp_data = if !ocsp_response.is_empty() { Some(ocsp_response) } else { None }; - match self.verify_certificate( - end_entity, - intermediates, - server_name, - server_name_str, - ocsp_data, - now, - ) { - Ok(()) => Ok(rustls::client::ServerCertVerified::assertion()), + match self.verify_certificate(end_entity, intermediates, server_name, ocsp_data, now) { + Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), Err(e) => { // This error only tells us what the system errored with, so it doesn't leak anything // sensitive. @@ -305,4 +283,38 @@ impl ServerCertVerifier for Verifier { } } } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } } diff --git a/src/verification/apple.rs b/src/verification/apple.rs index 05e8f5b2..41337e5a 100644 --- a/src/verification/apple.rs +++ b/src/verification/apple.rs @@ -1,13 +1,16 @@ -use super::{log_server_cert, unsupported_server_name}; +use super::log_server_cert; use crate::verification::invalid_certificate; use core_foundation::date::CFDate; use core_foundation_sys::date::kCFAbsoluteTimeIntervalSince1970; -use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError}; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature}; +use rustls::{ + CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, +}; use security_framework::{ certificate::SecCertificate, policy::SecPolicy, secure_transport::SslProtocolSide, trust::SecTrust, }; -use std::time::SystemTime; mod errors { pub(super) use security_framework_sys::base::{ @@ -16,29 +19,25 @@ mod errors { }; } -fn system_time_to_cfdate(time: SystemTime) -> Result { +#[allow(clippy::as_conversions)] +fn system_time_to_cfdate(time: pki_types::UnixTime) -> Result { // SAFETY: The interval is defined by macOS externally, but is always present and never modified at runtime // since its a global variable. // // See https://developer.apple.com/documentation/corefoundation/kcfabsolutetimeintervalsince1970. - let unix_adjustment = unsafe { - #[allow(clippy::as_conversions)] - std::time::Duration::from_secs(kCFAbsoluteTimeIntervalSince1970 as u64) - }; + let unix_adjustment = unsafe { kCFAbsoluteTimeIntervalSince1970 as u64 }; // Convert a system timestamp based off the UNIX epoch into the // Apple epoch used by all `CFAbsoluteTime` values. // Subtracting Durations with sub() will panic on overflow - #[allow(clippy::as_conversions)] - time.duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_| TlsError::FailedToGetCurrentTime)? + time.as_secs() .checked_sub(unix_adjustment) .ok_or(TlsError::FailedToGetCurrentTime) - .map(|epoch| CFDate::new(epoch.as_secs() as f64)) + .map(|epoch| CFDate::new(epoch as f64)) } /// A TLS certificate verifier that utilizes the Apple platform certificate facilities. -#[derive(Default)] +#[derive(Default, Debug)] pub struct Verifier { /// Testing only: The root CA certificate to trust. #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] @@ -65,14 +64,14 @@ impl Verifier { fn verify_certificate( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], server_name: &str, ocsp_response: Option<&[u8]>, - now: SystemTime, + now: pki_types::UnixTime, ) -> Result<(), TlsError> { - let certificates: Vec = std::iter::once(end_entity.0.as_slice()) - .chain(intermediates.iter().map(|cert| cert.0.as_slice())) + let certificates: Vec = std::iter::once(end_entity.as_ref()) + .chain(intermediates.iter().map(|cert| cert.as_ref())) .map(|cert| { SecCertificate::from_der(cert) .map_err(|_| TlsError::InvalidCertificate(CertificateError::BadEncoding)) @@ -164,7 +163,7 @@ impl Verifier { CertificateError::UnknownIssuer, )), errors::errSecInvalidExtendedKeyUsage => Ok(TlsError::InvalidCertificate( - CertificateError::Other(std::sync::Arc::new(super::EkuError)), + CertificateError::Other(OtherError(std::sync::Arc::new(super::EkuError))), )), errors::errSecCertificateRevoked => { Ok(TlsError::InvalidCertificate(CertificateError::Revoked)) @@ -183,27 +182,17 @@ impl Verifier { impl ServerCertVerifier for Verifier { fn verify_server_cert( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName, ocsp_response: &[u8], - now: SystemTime, - ) -> Result { + 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 ip_name; - - let server = match server_name { - rustls::ServerName::DnsName(name) => name.as_ref(), - rustls::ServerName::IpAddress(addr) => { - ip_name = addr.to_string(); - &ip_name - } - _ => return Err(unsupported_server_name()), - }; + let server = server_name.to_str(); let ocsp_data = if !ocsp_response.is_empty() { Some(ocsp_response) @@ -211,8 +200,8 @@ impl ServerCertVerifier for Verifier { None }; - match self.verify_certificate(end_entity, intermediates, server, ocsp_data, now) { - Ok(()) => Ok(rustls::client::ServerCertVerified::assertion()), + match self.verify_certificate(end_entity, intermediates, &server, ocsp_data, now) { + Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), Err(e) => { // This error only tells us what the system errored with, so it doesn't leak anything // sensitive. @@ -221,4 +210,38 @@ impl ServerCertVerifier for Verifier { } } } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } } diff --git a/src/verification/mod.rs b/src/verification/mod.rs index 068def88..5d05b3ed 100644 --- a/src/verification/mod.rs +++ b/src/verification/mod.rs @@ -38,29 +38,23 @@ impl std::error::Error for EkuError {} // Log the certificate we are verifying so that we can try and find what may be wrong with it // if we need to debug a user's situation. -fn log_server_cert(_end_entity: &rustls::Certificate) { +fn log_server_cert(_end_entity: &pki_types::CertificateDer<'_>) { #[cfg(feature = "cert-logging")] { use base64::Engine; log::debug!( "verifying certificate: {}", - base64::engine::general_purpose::STANDARD.encode(&_end_entity.0) + base64::engine::general_purpose::STANDARD.encode(_end_entity.as_ref()) ); } } -#[cfg(any(windows, target_os = "android", target_os = "macos", target_os = "ios"))] -fn unsupported_server_name() -> rustls::Error { - log::error!("TLS error: unsupported name type"); - rustls::Error::UnsupportedNameType -} - // Unknown certificate error shorthand. Used when we need to construct an "Other" certificate // error with a platform specific error message. #[cfg(any(windows, target_os = "macos", target_os = "ios"))] fn invalid_certificate(reason: impl Into) -> rustls::Error { - rustls::Error::InvalidCertificate(rustls::CertificateError::Other(std::sync::Arc::from( - Box::from(reason.into()), + rustls::Error::InvalidCertificate(rustls::CertificateError::Other(rustls::OtherError( + std::sync::Arc::from(Box::from(reason.into())), ))) } @@ -79,6 +73,7 @@ mod tests { use crate::tls_config; use reqwest::ClientBuilder; + #[ignore] // TODO(@cpu): Re-enable once there is a Reqwest branch w/ Rustls 0.22 support. #[tokio::test] async fn can_verify_server_cert() { let builder = ClientBuilder::new().use_preconfigured_tls(tls_config()); diff --git a/src/verification/others.rs b/src/verification/others.rs index beefab2e..957e3557 100644 --- a/src/verification/others.rs +++ b/src/verification/others.rs @@ -1,13 +1,16 @@ use super::log_server_cert; use once_cell::sync::OnceCell; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::client::WebPkiServerVerifier; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature}; use rustls::{ - client::{ServerCertVerifier, WebPkiVerifier}, - CertificateError, Error as TlsError, + CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, }; -use std::sync::Mutex; +use std::fmt::Debug; +use std::sync::{Arc, Mutex}; /// A TLS certificate verifier that uses the system's root store and WebPKI. -#[derive(Default)] +#[derive(Default, Debug)] pub struct Verifier { // We use a `OnceCell` so we only need // to try loading native root certs once per verifier. @@ -16,11 +19,11 @@ pub struct Verifier { // locking and unlocking the application will pull fresh root // certificates from disk, picking up on any changes // that might have been made since. - inner: OnceCell, + inner: OnceCell>, // Extra trust anchors to add to the verifier above and beyond those provided by the // platform via rustls-native-certs. - extra_roots: Mutex>, + extra_roots: Mutex>>, /// Testing only: an additional root CA certificate to trust. #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] @@ -42,7 +45,9 @@ impl Verifier { /// Creates a new verifier whose certificate validation is provided by /// WebPKI, using root certificates provided by the platform and augmented by /// the provided extra root certificates. - pub fn new_with_extra_roots(roots: impl IntoIterator) -> Self { + pub fn new_with_extra_roots( + roots: impl IntoIterator>, + ) -> Self { Self { inner: OnceCell::new(), extra_roots: roots.into_iter().collect::>().into(), @@ -62,18 +67,23 @@ impl Verifier { } // Attempt to load CA root certificates present on system, fallback to WebPKI roots if error - fn init_verifier(&self) -> Result { + fn init_verifier(&self) -> Result, TlsError> { let mut root_store = rustls::RootCertStore::empty(); // For testing only: load fake root cert, instead of native/WebPKI roots #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] { if let Some(test_root) = &self.test_only_root_ca_override { - let (added, ignored) = root_store.add_parsable_certificates(&[test_root.clone()]); + let (added, ignored) = + root_store.add_parsable_certificates([pki_types::CertificateDer::from( + test_root.clone(), + )]); if (added != 1) || (ignored != 0) { panic!("Failed to insert fake, test-only root trust anchor"); } - return Ok(WebPkiVerifier::new(root_store, None)); + return WebPkiServerVerifier::builder(root_store.into()) + .build() + .map_err(|e| TlsError::Other(OtherError(Arc::new(e)))); } } @@ -82,7 +92,7 @@ impl Verifier { let mut extra_roots = self.extra_roots.try_lock().unwrap(); if !extra_roots.is_empty() { let count = extra_roots.len(); - root_store.add_trust_anchors(&mut extra_roots.drain(..)); + root_store.extend(extra_roots.drain(..)); log::debug!( "Loaded {count} extra CA certificates in addition to possible system roots", ); @@ -91,8 +101,7 @@ impl Verifier { #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))] match rustls_native_certs::load_native_certs() { Ok(certs) => { - let certs: Vec> = certs.into_iter().map(|c| c.0).collect(); - let (added, ignored) = root_store.add_parsable_certificates(&certs); + let (added, ignored) = root_store.add_parsable_certificates(certs); if ignored != 0 { log::warn!("Some CA root certificates were ignored due to errors"); @@ -130,37 +139,27 @@ impl Verifier { })); }; - Ok(WebPkiVerifier::new(root_store, None)) + WebPkiServerVerifier::builder(root_store.into()) + .build() + .map_err(|e| TlsError::Other(OtherError(Arc::new(e)))) } } impl ServerCertVerifier for Verifier { fn verify_server_cert( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName, ocsp_response: &[u8], - now: std::time::SystemTime, - ) -> Result { + now: pki_types::UnixTime, + ) -> Result { log_server_cert(end_entity); let verifier = self.inner.get_or_try_init(|| self.init_verifier())?; verifier - .verify_server_cert( - end_entity, - intermediates, - server_name, - // We currently ignore certificate transparency data so that - // WebPKI doesn't verify it. Since none of the other platforms currently - // don't want possibly-bad CT data to cause problems on one platform but not - // others. On top of that, rustls's verification of it is currently "best effort." - &mut std::iter::empty(), - ocsp_response, - now, - ) + .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) .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. @@ -169,15 +168,50 @@ impl ServerCertVerifier for Verifier { e }) } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } } fn map_webpki_errors(err: TlsError) -> TlsError { if let TlsError::InvalidCertificate(CertificateError::Other(other_err)) = &err { - if let Some(webpki::Error::RequiredEkuNotFound) = other_err.downcast_ref::() + if let Some(webpki::Error::RequiredEkuNotFound) = + other_err.0.downcast_ref::() { - return TlsError::InvalidCertificate(CertificateError::Other(std::sync::Arc::new( + return TlsError::InvalidCertificate(CertificateError::Other(OtherError(Arc::new( super::EkuError, - ))); + )))); } } diff --git a/src/verification/windows.rs b/src/verification/windows.rs index 23952bfc..4168fe3f 100644 --- a/src/verification/windows.rs +++ b/src/verification/windows.rs @@ -18,11 +18,15 @@ //! [Microsoft's Documentation]: //! [Microsoft's Example]: -use super::{log_server_cert, unsupported_server_name, ALLOWED_EKUS}; +use super::{log_server_cert, ALLOWED_EKUS}; use crate::windows::{ c_void_from_ref, c_void_from_ref_mut, nonnull_from_const_ptr, ZeroedWithSize, }; -use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError}; +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature}; +use rustls::{ + CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, +}; use winapi::{ shared::{ minwindef::{DWORD, FILETIME, TRUE}, @@ -49,7 +53,6 @@ use std::{ convert::TryInto, mem::{self, MaybeUninit}, ptr::{self, NonNull}, - time::SystemTime, }; use crate::verification::invalid_certificate; @@ -325,7 +328,7 @@ impl CertificateStore { fn new_chain_in( &self, certificate: &Certificate, - now: SystemTime, + now: pki_types::UnixTime, ) -> Result { let mut cert_chain = ptr::null(); @@ -348,15 +351,13 @@ impl CertificateStore { const UNIX_ADJUSTMENT: std::time::Duration = std::time::Duration::from_secs(11_644_473_600); - let since_unix_epoch = now - .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_| TlsError::FailedToGetCurrentTime)?; + let since_unix_epoch = now.as_secs(); // Convert the duration from the UNIX epoch to the Window one, and then convert // the result into a `FILETIME` structure. - let since_windows_epoch = since_unix_epoch + UNIX_ADJUSTMENT; - let intervals = (since_windows_epoch.as_nanos() / 100) as u64; + let since_windows_epoch = since_unix_epoch + UNIX_ADJUSTMENT.as_secs(); + let intervals = (since_windows_epoch * 1_000_000_000) / 100; FILETIME { dwLowDateTime: (intervals & u32::MAX as u64) as u32, @@ -455,9 +456,9 @@ fn map_trust_error_status(unfiltered_status: DWORD) -> Result<(), TlsError> { wincrypt::CERT_TRUST_IS_NOT_VALID_FOR_USAGE | wincrypt::CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE, ) { - return Err(InvalidCertificate(CertificateError::Other( + return Err(InvalidCertificate(CertificateError::Other(OtherError( std::sync::Arc::new(super::EkuError), - ))); + )))); } // Otherwise, if there is only one class of error, map that class to @@ -488,7 +489,7 @@ fn map_trust_error_status(unfiltered_status: DWORD) -> Result<(), TlsError> { } /// A TLS certificate verifier that utilizes the Windows certificate facilities. -#[derive(Default)] +#[derive(Default, Debug)] pub struct Verifier { /// Testing only: The root CA certificate to trust. #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] @@ -522,7 +523,7 @@ impl Verifier { intermediate_certs: &[&[u8]], server: &[u8], ocsp_data: Option<&[u8]>, - now: SystemTime, + now: pki_types::UnixTime, ) -> Result<(), TlsError> { #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] let mut store = match self.test_only_root_ca_override.as_ref() { @@ -597,26 +598,17 @@ fn size_of_struct(val: &T) -> u32 { impl ServerCertVerifier for Verifier { fn verify_server_cert( &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &rustls::client::ServerName, - _scts: &mut dyn Iterator, + end_entity: &pki_types::CertificateDer<'_>, + intermediates: &[pki_types::CertificateDer<'_>], + server_name: &pki_types::ServerName, ocsp_response: &[u8], - now: SystemTime, - ) -> Result { + now: pki_types::UnixTime, + ) -> Result { log_server_cert(end_entity); - let ip_name; - let name = match server_name { - rustls::ServerName::DnsName(name) => name.as_ref(), - rustls::ServerName::IpAddress(addr) => { - ip_name = addr.to_string(); - &ip_name - } - _ => return Err(unsupported_server_name()), - }; + let name = server_name.to_str(); - let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.0.as_slice()).collect(); + let intermediate_certs: Vec<&[u8]> = intermediates.iter().map(|c| c.as_ref()).collect(); let ocsp_data = if !ocsp_response.is_empty() { Some(ocsp_response) @@ -625,13 +617,13 @@ impl ServerCertVerifier for Verifier { }; match self.verify_certificate( - &end_entity.0, + end_entity.as_ref(), &intermediate_certs, name.as_bytes(), ocsp_data, now, ) { - Ok(()) => Ok(rustls::client::ServerCertVerified::assertion()), + Ok(()) => Ok(rustls::client::danger::ServerCertVerified::assertion()), Err(e) => { // SAFETY: // Errors are our own custom errors, WinAPI errors, or static strings. @@ -640,4 +632,38 @@ impl ServerCertVerifier for Verifier { } } } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &pki_types::CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } }