|
| 1 | +use crate::ResultType; |
| 2 | +#[cfg(any(target_os = "android", target_os = "ios"))] |
| 3 | +use rustls_pki_types::{ServerName, UnixTime}; |
| 4 | +use std::sync::Arc; |
| 5 | +use tokio_rustls::rustls::{self, client::WebPkiServerVerifier, ClientConfig}; |
| 6 | +#[cfg(any(target_os = "android", target_os = "ios"))] |
| 7 | +use tokio_rustls::rustls::{ |
| 8 | + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, |
| 9 | + DigitallySignedStruct, Error as TLSError, SignatureScheme, |
| 10 | +}; |
| 11 | + |
| 12 | +/// A certificate verifier that tries a primary verifier first, |
| 13 | +/// and falls back to a platform verifier if the primary fails. |
| 14 | +#[cfg(any(target_os = "android", target_os = "ios"))] |
| 15 | +#[derive(Debug)] |
| 16 | +struct FallbackPlatformVerifier { |
| 17 | + primary: Arc<dyn ServerCertVerifier>, |
| 18 | + fallback: Arc<dyn ServerCertVerifier>, |
| 19 | +} |
| 20 | + |
| 21 | +#[cfg(any(target_os = "android", target_os = "ios"))] |
| 22 | +impl FallbackPlatformVerifier { |
| 23 | + fn with_platform_fallback( |
| 24 | + primary: Arc<dyn ServerCertVerifier>, |
| 25 | + provider: Arc<rustls::crypto::CryptoProvider>, |
| 26 | + ) -> Result<Self, TLSError> { |
| 27 | + #[cfg(target_os = "android")] |
| 28 | + if !crate::config::ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED |
| 29 | + .load(std::sync::atomic::Ordering::Relaxed) |
| 30 | + { |
| 31 | + return Err(TLSError::General( |
| 32 | + "rustls-platform-verifier not initialized".to_string(), |
| 33 | + )); |
| 34 | + } |
| 35 | + let fallback = Arc::new(rustls_platform_verifier::Verifier::new(provider)?); |
| 36 | + Ok(Self { primary, fallback }) |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +#[cfg(any(target_os = "android", target_os = "ios"))] |
| 41 | +impl ServerCertVerifier for FallbackPlatformVerifier { |
| 42 | + fn verify_server_cert( |
| 43 | + &self, |
| 44 | + end_entity: &rustls_pki_types::CertificateDer<'_>, |
| 45 | + intermediates: &[rustls_pki_types::CertificateDer<'_>], |
| 46 | + server_name: &ServerName<'_>, |
| 47 | + ocsp_response: &[u8], |
| 48 | + now: UnixTime, |
| 49 | + ) -> Result<ServerCertVerified, TLSError> { |
| 50 | + match self.primary.verify_server_cert( |
| 51 | + end_entity, |
| 52 | + intermediates, |
| 53 | + server_name, |
| 54 | + ocsp_response, |
| 55 | + now, |
| 56 | + ) { |
| 57 | + Ok(verified) => Ok(verified), |
| 58 | + Err(primary_err) => { |
| 59 | + match self.fallback.verify_server_cert( |
| 60 | + end_entity, |
| 61 | + intermediates, |
| 62 | + server_name, |
| 63 | + ocsp_response, |
| 64 | + now, |
| 65 | + ) { |
| 66 | + Ok(verified) => Ok(verified), |
| 67 | + Err(fallback_err) => { |
| 68 | + log::error!( |
| 69 | + "Both primary and fallback verifiers failed to verify server certificate, primary error: {:?}, fallback error: {:?}", |
| 70 | + primary_err, |
| 71 | + fallback_err |
| 72 | + ); |
| 73 | + Err(primary_err) |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + fn verify_tls12_signature( |
| 81 | + &self, |
| 82 | + message: &[u8], |
| 83 | + cert: &rustls_pki_types::CertificateDer<'_>, |
| 84 | + dss: &DigitallySignedStruct, |
| 85 | + ) -> Result<HandshakeSignatureValid, TLSError> { |
| 86 | + // Both WebPkiServerVerifier and rustls_platform_verifier use the same signature verification implementation. |
| 87 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L278 |
| 88 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/crypto/mod.rs#L17 |
| 89 | + // https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L9 |
| 90 | + // https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L6 |
| 91 | + self.primary.verify_tls12_signature(message, cert, dss) |
| 92 | + } |
| 93 | + |
| 94 | + fn verify_tls13_signature( |
| 95 | + &self, |
| 96 | + message: &[u8], |
| 97 | + cert: &rustls_pki_types::CertificateDer<'_>, |
| 98 | + dss: &DigitallySignedStruct, |
| 99 | + ) -> Result<HandshakeSignatureValid, TLSError> { |
| 100 | + // Same implementation as verify_tls12_signature. |
| 101 | + self.primary.verify_tls13_signature(message, cert, dss) |
| 102 | + } |
| 103 | + |
| 104 | + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { |
| 105 | + // Both WebPkiServerVerifier and rustls_platform_verifier use the same crypto provider, |
| 106 | + // so their supported signature schemes are identical. |
| 107 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L172C52-L172C85 |
| 108 | + // https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L327 |
| 109 | + // https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L304 |
| 110 | + self.primary.supported_verify_schemes() |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +fn webpki_server_verifier( |
| 115 | + provider: Arc<rustls::crypto::CryptoProvider>, |
| 116 | +) -> ResultType<Arc<WebPkiServerVerifier>> { |
| 117 | + // Load root certificates from both bundled webpki_roots and system-native certificate stores. |
| 118 | + // This approach is consistent with how reqwest and tokio-tungstenite handle root certificates. |
| 119 | + // https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L95 |
| 120 | + // https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L643 |
| 121 | + let mut root_cert_store = rustls::RootCertStore::empty(); |
| 122 | + root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); |
| 123 | + let rustls_native_certs::CertificateResult { certs, errors, .. } = |
| 124 | + rustls_native_certs::load_native_certs(); |
| 125 | + if !errors.is_empty() { |
| 126 | + log::warn!("native root CA certificate loading errors: {errors:?}"); |
| 127 | + } |
| 128 | + root_cert_store.add_parsable_certificates(certs); |
| 129 | + |
| 130 | + // Build verifier using with_root_certificates behavior (WebPkiServerVerifier without CRLs). |
| 131 | + // Both reqwest and tokio-tungstenite use this approach. |
| 132 | + // https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L749 |
| 133 | + // https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L127 |
| 134 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/client/builder.rs#L47 |
| 135 | + // with_root_certificates creates a WebPkiServerVerifier without revocation checking: |
| 136 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L177 |
| 137 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L168 |
| 138 | + // Since no CRL is provided (as is the case here), we must explicitly set allow_unknown_revocation_status() |
| 139 | + // to match the behavior of with_root_certificates, which allows unknown revocation status by default. |
| 140 | + // https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L37 |
| 141 | + // Note: build() only returns an error if the root certificate store is empty, which won't happen here. |
| 142 | + let verifier = rustls::client::WebPkiServerVerifier::builder_with_provider( |
| 143 | + Arc::new(root_cert_store), |
| 144 | + provider.clone(), |
| 145 | + ) |
| 146 | + .allow_unknown_revocation_status() |
| 147 | + .build() |
| 148 | + .map_err(|e| anyhow::anyhow!(e))?; |
| 149 | + Ok(verifier) |
| 150 | +} |
| 151 | + |
| 152 | +pub fn client_config() -> ResultType<ClientConfig> { |
| 153 | + // Use the default builder which uses the default protocol versions and crypto provider. |
| 154 | + // The with_protocol_versions API has been removed in rustls master branch: |
| 155 | + // https://github.com/rustls/rustls/pull/2599 |
| 156 | + // This approach is consistent with tokio-tungstenite's usage: |
| 157 | + // https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L126 |
| 158 | + let config_builder = rustls::ClientConfig::builder(); |
| 159 | + let provider = config_builder.crypto_provider().clone(); |
| 160 | + let webpki_verifier = webpki_server_verifier(provider.clone())?; |
| 161 | + #[cfg(any(target_os = "android", target_os = "ios"))] |
| 162 | + { |
| 163 | + match FallbackPlatformVerifier::with_platform_fallback(webpki_verifier.clone(), provider) { |
| 164 | + Ok(fallback_verifier) => { |
| 165 | + let config = config_builder |
| 166 | + .dangerous() |
| 167 | + .with_custom_certificate_verifier(Arc::new(fallback_verifier)) |
| 168 | + .with_no_client_auth(); |
| 169 | + Ok(config) |
| 170 | + } |
| 171 | + Err(e) => { |
| 172 | + log::error!( |
| 173 | + "Failed to create fallback verifier: {:?}, use webpki verifier instead", |
| 174 | + e |
| 175 | + ); |
| 176 | + let config = config_builder |
| 177 | + .with_webpki_verifier(webpki_verifier) |
| 178 | + .with_no_client_auth(); |
| 179 | + Ok(config) |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + #[cfg(not(any(target_os = "android", target_os = "ios")))] |
| 184 | + { |
| 185 | + let config = config_builder |
| 186 | + .with_webpki_verifier(webpki_verifier) |
| 187 | + .with_no_client_auth(); |
| 188 | + Ok(config) |
| 189 | + } |
| 190 | +} |
0 commit comments