Skip to content

Eagerly build root store on miscellaneous Unix platforms #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
contents: read

env:
RUSTFLAGS: -D warnings -F unused_must_use
RUSTFLAGS: -D warnings

jobs:
clippy-build-std:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ let config = ClientConfig::builder_with_provider(arc_crypto_provider)
.with_safe_default_protocol_versions()
.unwrap()
.with_platform_verifier()
.unwrap()
.with_no_client_auth();
```

Expand Down
4 changes: 2 additions & 2 deletions rustls-platform-verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rustls-platform-verifier"
version = "0.5.3"
version = "0.6.0"
authors = ["ComplexSpaces <[email protected]>", "1Password"]
description = "rustls-platform-verifier supports verifying TLS certificates in rustls with the operating system verifier"
keywords = ["tls", "certificate", "verification", "os", "native"]
Expand Down Expand Up @@ -33,13 +33,13 @@ rustls = { version = "0.23.27", default-features = false, features = ["std"] }
log = { version = "0.4" }
base64 = { version = "0.22", optional = true } # Only used when the `cert-logging` feature is enabled.
jni = { version = "0.21", default-features = false, optional = true } # Only used during doc generation
once_cell = "1.9"

[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]
once_cell = "1.9"
rustls-platform-verifier-android = { path = "../android-release-support", version = "0.1.0" }
jni = { version = "0.21", default-features = false }
webpki = { package = "rustls-webpki", version = "0.103", default-features = false }
Expand Down
97 changes: 29 additions & 68 deletions rustls-platform-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]

use rustls::{client::WantsClientCert, ClientConfig, ConfigBuilder, WantsVerifier};
use std::sync::Arc;

#[cfg(feature = "dbg")]
use rustls::crypto::CryptoProvider;
#[cfg(feature = "dbg")]
use rustls::pki_types::CertificateDer;
use rustls::{client::WantsClientCert, ClientConfig, ConfigBuilder, WantsVerifier};

mod verification;
pub use verification::Verifier;

Expand All @@ -26,66 +31,16 @@ mod tests;
#[cfg_attr(feature = "ffi-testing", allow(unused_imports))]
pub use tests::ffi::*;

/// Creates and returns a `rustls` configuration that verifies TLS
/// certificates in the best way for the underlying OS platform, using
/// safe defaults for the `rustls` configuration.
///
/// # Example
///
/// This example shows how to use the custom verifier with the `reqwest` crate:
/// ```ignore
/// # use reqwest::ClientBuilder;
/// #[tokio::main]
/// use rustls_platform_verifier::ConfigVerifierExt;
///
/// async fn main() {
/// let client = ClientBuilder::new()
/// .use_preconfigured_tls(ClientConfig::with_platform_verifier())
/// .build()
/// .expect("nothing should fail");
///
/// let _response = client.get("https://example.com").send().await;
/// }
/// ```
///
/// **Important:** You must ensure that your `reqwest` version is using the same Rustls
/// version as this crate or it will panic when downcasting the `&dyn Any` verifier.
///
/// If you require more control over the rustls [`ClientConfig`], you can import the
/// [`BuilderVerifierExt`] trait and call `.with_platform_verifier()` on the [`ConfigBuilder`].
///
/// Refer to the crate level documentation to see what platforms
/// are currently supported.
#[deprecated(since = "0.4.0", note = "use the `ConfigVerifierExt` instead")]
pub fn tls_config() -> ClientConfig {
ClientConfig::with_platform_verifier()
}

/// Attempts to construct a `rustls` configuration that verifies TLS certificates in the best way
/// for the underlying OS platform, using the provided
/// [`CryptoProvider`][rustls::crypto::CryptoProvider].
///
/// See [`tls_config`] for further documentation.
///
/// # Errors
///
/// Propagates any error returned by [`rustls::ConfigBuilder::with_safe_default_protocol_versions`].
#[deprecated(since = "0.4.0", note = "use the `BuilderVerifierExt` instead")]
pub fn tls_config_with_provider(
provider: Arc<rustls::crypto::CryptoProvider>,
) -> Result<ClientConfig, rustls::Error> {
Ok(ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()?
.with_platform_verifier()
.with_no_client_auth())
}

/// Exposed for debugging certificate issues with standalone tools.
///
/// This is not intended for production use, you should use [tls_config] instead.
/// This is not intended for production use, you should use [`BuilderVerifierExt`] or
/// [`ConfigVerifierExt`] instead.
#[cfg(feature = "dbg")]
pub fn verifier_for_dbg(root: &[u8]) -> Arc<dyn rustls::client::danger::ServerCertVerifier> {
Arc::new(Verifier::new_with_fake_root(root))
pub fn verifier_for_dbg(
root: CertificateDer<'static>,
crypto_provider: Arc<CryptoProvider>,
) -> Arc<dyn rustls::client::danger::ServerCertVerifier> {
Arc::new(Verifier::new_with_fake_root(root, crypto_provider))
}

/// Extension trait to help configure [`ClientConfig`]s with the platform verifier.
Expand All @@ -97,16 +52,22 @@ pub trait BuilderVerifierExt {
/// use rustls_platform_verifier::BuilderVerifierExt;
/// let config = ClientConfig::builder()
/// .with_platform_verifier()
/// .unwrap()
/// .with_no_client_auth();
/// ```
fn with_platform_verifier(self) -> ConfigBuilder<ClientConfig, WantsClientCert>;
fn with_platform_verifier(
self,
) -> Result<ConfigBuilder<ClientConfig, WantsClientCert>, rustls::Error>;
}

impl BuilderVerifierExt for ConfigBuilder<ClientConfig, WantsVerifier> {
fn with_platform_verifier(self) -> ConfigBuilder<ClientConfig, WantsClientCert> {
let provider = self.crypto_provider().clone();
self.dangerous()
.with_custom_certificate_verifier(Arc::new(Verifier::new().with_provider(provider)))
fn with_platform_verifier(
self,
) -> Result<ConfigBuilder<ClientConfig, WantsClientCert>, rustls::Error> {
let verifier = Verifier::new(self.crypto_provider().clone())?;
Ok(self
.dangerous()
.with_custom_certificate_verifier(Arc::new(verifier)))
}
}

Expand All @@ -119,13 +80,13 @@ pub trait ConfigVerifierExt {
/// use rustls_platform_verifier::ConfigVerifierExt;
/// let config = ClientConfig::with_platform_verifier();
/// ```
fn with_platform_verifier() -> ClientConfig;
fn with_platform_verifier() -> Result<ClientConfig, rustls::Error>;
}

impl ConfigVerifierExt for ClientConfig {
fn with_platform_verifier() -> ClientConfig {
ClientConfig::builder()
.with_platform_verifier()
.with_no_client_auth()
fn with_platform_verifier() -> Result<ClientConfig, rustls::Error> {
Ok(ClientConfig::builder()
.with_platform_verifier()?
.with_no_client_auth())
}
}
1 change: 0 additions & 1 deletion rustls-platform-verifier/src/tests/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ mod android {
.with_filter(log_filter),
);
crate::android::init_with_env(env, cx).unwrap();
crate::tests::ensure_global_state();
std::panic::set_hook(Box::new(|info| {
let msg = if let Some(msg) = info.payload().downcast_ref::<&'static str>() {
msg
Expand Down
12 changes: 8 additions & 4 deletions rustls-platform-verifier/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#[cfg(feature = "ffi-testing")]
pub mod ffi;

use std::error::Error as StdError;
use std::time::Duration;
use std::{error::Error as StdError, sync::Arc};

mod verification_real_world;

mod verification_mock;

use rustls::{pki_types, CertificateError, Error as TlsError, Error::InvalidCertificate};
use rustls::{
crypto::CryptoProvider,
pki_types, CertificateError,
Error::{self as TlsError, InvalidCertificate},
};

struct TestCase<'a, E: StdError> {
/// The name of the server we're connecting to.
Expand Down Expand Up @@ -62,6 +66,6 @@ pub(crate) fn verification_time() -> pki_types::UnixTime {
pki_types::UnixTime::since_unix_epoch(Duration::from_secs(1_748_633_220))
}

fn ensure_global_state() {
let _ = rustls::crypto::ring::default_provider().install_default();
fn test_provider() -> Arc<CryptoProvider> {
Arc::new(rustls::crypto::ring::default_provider())
}
24 changes: 14 additions & 10 deletions rustls-platform-verifier/src/tests/verification_mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use rustls::pki_types::{DnsName, ServerName};
use rustls::{CertificateError, Error as TlsError, OtherError};

use super::TestCase;
use crate::tests::{assert_cert_error_eq, ensure_global_state, verification_time};
use crate::tests::{assert_cert_error_eq, test_provider, verification_time};
use crate::verification::{EkuError, Verifier};

macro_rules! mock_root_test_cases {
Expand Down Expand Up @@ -80,7 +80,8 @@ macro_rules! no_error {
};
}

const ROOT1: &[u8] = include_bytes!("root1.crt");
const ROOT1: pki_types::CertificateDer<'static> =
pki_types::CertificateDer::from_slice(include_bytes!("root1.crt"));
const ROOT1_INT1: &[u8] = include_bytes!("root1-int1.crt");
const ROOT1_INT1_EXAMPLE_COM_GOOD: &[u8] = include_bytes!("root1-int1-ee_example.com-good.crt");
const ROOT1_INT1_LOCALHOST_IPV4_GOOD: &[u8] = include_bytes!("root1-int1-ee_127.0.0.1-good.crt");
Expand All @@ -93,18 +94,21 @@ const LOCALHOST_IPV6: &str = "::1";
#[cfg(any(test, feature = "ffi-testing"))]
#[cfg_attr(feature = "ffi-testing", allow(dead_code))]
pub(super) fn verification_without_mock_root() {
ensure_global_state();
let crypto_provider = test_provider();

// Since Rustls 0.22 constructing a webpki verifier (like the one backing Verifier on unix
// systems) without any roots produces `OtherError(NoRootAnchors)` - since our FreeBSD CI
// runner fails to find any roots with openssl-probe we need to provide webpki-root-certs here
// or the test will fail with the `OtherError` instead of the expected `CertificateError`.
#[cfg(target_os = "freebsd")]
let verifier =
Verifier::new_with_extra_roots(webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned())
.unwrap();
let verifier = Verifier::new_with_extra_roots(
webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned(),
crypto_provider,
)
.unwrap();

#[cfg(not(target_os = "freebsd"))]
let verifier = Verifier::new();
let verifier = Verifier::new(crypto_provider).unwrap();

let server_name = pki_types::ServerName::try_from(EXAMPLE_COM).unwrap();
let end_entity = pki_types::CertificateDer::from(ROOT1_INT1_EXAMPLE_COM_GOOD);
Expand Down Expand Up @@ -334,13 +338,13 @@ fn test_with_mock_root<E: std::error::Error + PartialEq + 'static>(
test_case: &TestCase<E>,
root_src: Roots,
) {
ensure_global_state();
log::info!("verifying {:?}", test_case.expected_result);

let provider = test_provider();
let verifier = match root_src {
Roots::OnlyExtra => Verifier::new_with_fake_root(ROOT1), // TODO: time
Roots::OnlyExtra => Verifier::new_with_fake_root(ROOT1, provider), // TODO: time
#[cfg(not(target_os = "android"))]
Roots::ExtraAndPlatform => Verifier::new_with_extra_roots([ROOT1.into()]).unwrap(),
Roots::ExtraAndPlatform => Verifier::new_with_extra_roots([ROOT1], provider).unwrap(),
};
let mut chain = test_case
.chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use rustls::pki_types::{DnsName, ServerName};
use rustls::{CertificateError, Error as TlsError};

use super::TestCase;
use crate::tests::{assert_cert_error_eq, ensure_global_state, verification_time};
use crate::tests::{assert_cert_error_eq, test_provider, verification_time};
use crate::Verifier;

// This is the certificate chain presented by one server for
Expand Down Expand Up @@ -120,22 +120,25 @@ macro_rules! no_error {
}

fn real_world_test<E: std::error::Error>(test_case: &TestCase<E>) {
ensure_global_state();
log::info!(
"verifying ref ID {:?} expected {:?}",
test_case.reference_id,
test_case.expected_result
);

let crypto_provider = test_provider();

// On BSD systems openssl-probe fails to find the system CA bundle,
// so we must provide extra roots from webpki-root-cert.
#[cfg(target_os = "freebsd")]
let verifier =
Verifier::new_with_extra_roots(webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned())
.unwrap();
let verifier = Verifier::new_with_extra_roots(
webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned(),
crypto_provider,
)
.unwrap();

#[cfg(not(target_os = "freebsd"))]
let verifier = Verifier::new();
let verifier = Verifier::new(crypto_provider).unwrap();

let mut chain = test_case
.chain
Expand Down
Loading