Skip to content
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

tests: use a fixed SystemTime for certificate validation #50

Merged
merged 2 commits into from
Jan 4, 2024
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
14 changes: 14 additions & 0 deletions rustls-platform-verifier/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod ffi;

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

mod verification_real_world;

Expand All @@ -19,6 +20,9 @@ struct TestCase<'a, E: StdError> {
/// The stapled OCSP response given to us by Rustls, if any.
pub stapled_ocsp: Option<&'a [u8]>,

/// The time to use as the current time for verification.
pub verification_time: SystemTime,

pub expected_result: Result<(), TlsError>,

/// An error that should be present inside an expected `CertificateError::Other` variant.
Expand Down Expand Up @@ -46,3 +50,13 @@ pub fn assert_cert_error_eq<E: StdError + PartialEq + 'static>(
assert_eq!(result, expected);
}
}

/// Return a fixed [SystemTime] for certificate validation purposes.
///
/// We fix the "now" value used for certificate validation to a fixed point in time at which
/// we know the test certificates are valid. This must be updated if the mock certificates
/// are regenerated.
pub(crate) fn verification_time() -> SystemTime {
// Wednesday, January 3, 2024 6:03:08 PM UTC
SystemTime::UNIX_EPOCH + Duration::from_secs(1_704_304_988)
}
4 changes: 4 additions & 0 deletions rustls-platform-verifier/src/tests/verification_mock/ca.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// Generates the test data files used in the tests in verification_mock.rs.
//
// After re-generating mock certificates be sure to also update the fixed
// verification timestamp in `mod.rs`'s `verification_time` fn to match
// the current time.
//
// The primary point of this program is to fully automate the creation of the
// test data, with minimal tool dependencies (e.g. no OpenSSL), with low effort.
//
Expand Down
24 changes: 21 additions & 3 deletions rustls-platform-verifier/src/tests/verification_mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
))]

use super::TestCase;
use crate::tests::assert_cert_error_eq;
use crate::tests::{assert_cert_error_eq, verification_time};
use crate::verification::{EkuError, Verifier};
use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError};
use std::convert::TryFrom;
Expand Down Expand Up @@ -95,7 +95,7 @@ pub(super) fn verification_without_mock_root() {
&server_name,
&mut std::iter::empty(),
&[],
std::time::SystemTime::now(),
verification_time(),
);

assert_eq!(
Expand All @@ -120,41 +120,47 @@ mock_root_test_cases! {
reference_id: EXAMPLE_COM,
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
complexspaces marked this conversation as resolved.
Show resolved Hide resolved
expected_result: Ok(()),
other_error: no_error!(),
},
valid_no_stapling_ipv4 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV4,
chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
valid_no_stapling_ipv6 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV6,
chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
valid_stapled_good_dns [ any(windows, target_os = "android", target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: EXAMPLE_COM,
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_example.com-good.ocsp")),
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
valid_stapled_good_ipv4 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV4,
chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_127.0.0.1-good.ocsp")),
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
valid_stapled_good_ipv6 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV6,
chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_1-good.ocsp")),
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand All @@ -166,20 +172,23 @@ mock_root_test_cases! {
reference_id: EXAMPLE_COM,
chain: &[include_bytes!("root1-int1-ee_example.com-revoked.crt"), ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_example.com-revoked.ocsp")),
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)),
other_error: no_error!(),
},
stapled_revoked_ipv4 [ any(windows, target_os = "android", target_os = "macos") ] => TestCase {
reference_id: LOCALHOST_IPV4,
chain: &[include_bytes!("root1-int1-ee_127.0.0.1-revoked.crt"), ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_127.0.0.1-revoked.ocsp")),
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)),
other_error: no_error!(),
},
stapled_revoked_ipv6 [ any(windows, target_os = "android", target_os = "macos") ] => TestCase {
reference_id: LOCALHOST_IPV6,
chain: &[include_bytes!("root1-int1-ee_1-revoked.crt"), ROOT1_INT1],
stapled_ocsp: Some(include_bytes!("root1-int1-ee_1-revoked.ocsp")),
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::Revoked)),
other_error: no_error!(),
},
Expand All @@ -192,20 +201,23 @@ mock_root_test_cases! {
reference_id: EXAMPLE_COM,
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)),
other_error: no_error!(),
},
ee_only_ipv4 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV4,
chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)),
other_error: no_error!(),
},
ee_only_ipv6 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: LOCALHOST_IPV6,
chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::UnknownIssuer)),
other_error: no_error!(),
},
Expand All @@ -214,27 +226,31 @@ mock_root_test_cases! {
reference_id: "example.org",
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
domain_mismatch_ipv4 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: "198.168.0.1",
chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
domain_mismatch_ipv6 [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: "::ffff:c6a8:1",
chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
wrong_eku_dns [ any(windows, target_os = "android", target_os = "macos", target_os = "linux") ] => TestCase {
reference_id: EXAMPLE_COM,
chain: &[include_bytes!("root1-int1-ee_example.com-wrong_eku.crt"), ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(
CertificateError::Other(Arc::from(EkuError)))),
other_error: Some(EkuError),
Expand All @@ -243,6 +259,7 @@ mock_root_test_cases! {
reference_id: LOCALHOST_IPV4,
chain: &[include_bytes!("root1-int1-ee_127.0.0.1-wrong_eku.crt"), ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(
CertificateError::Other(Arc::from(EkuError)))),
other_error: Some(EkuError),
Expand All @@ -251,6 +268,7 @@ mock_root_test_cases! {
reference_id: LOCALHOST_IPV6,
chain: &[include_bytes!("root1-int1-ee_1-wrong_eku.crt"), ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(
CertificateError::Other(Arc::from(EkuError)))),
other_error: Some(EkuError),
Expand Down Expand Up @@ -289,7 +307,7 @@ fn test_with_mock_root<E: std::error::Error + PartialEq + 'static>(test_case: &T
&server_name,
&mut std::iter::empty(),
test_case.stapled_ocsp.unwrap_or(&[]),
std::time::SystemTime::now(),
test_case.verification_time,
);

assert_cert_error_eq(
Expand Down
25 changes: 13 additions & 12 deletions rustls-platform-verifier/src/tests/verification_real_world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@
//! fetching then the trust anchors for these certificates might not be
//! trusted by the operating system's root store.
//!
//! XXX: Currently these tests are a time-bomb because they validate the
//! certificates as of the current system time, because the version of
//! Rustls we use does not support passing in a different time. The newest
//! version of Rustls does have that capability. We need to upgrade to that
//! version of Rustls, and/or otherwise change these tests, before these
//! certificates expire in Fall/Winter 2022.
//!
//! XXX: These tests should be using a stapled OCSP responses so that the
//! (operating-system-based) verifier doesn't try to fetch an OCSP
//! response or CRL certificate. However, until we can fix the validation
Expand All @@ -42,21 +35,22 @@
//! Thus we don't expect these tests to be flaky w.r.t. that, except for
//! potentially poor performance.
use super::TestCase;
use crate::{tests::assert_cert_error_eq, Verifier};
use crate::tests::{assert_cert_error_eq, verification_time};
use crate::Verifier;
use rustls::{client::ServerCertVerifier, CertificateError, Error as TlsError};
use std::convert::TryFrom;

// This is the certificate chain presented by one server for
// my.1password.com when this test was updated 2022-09-22. It is
// my.1password.com when this test was updated 2023-08-01. It is
// valid for *.1password.com and 1password.com from
// "Jul 24 00:00:00 2022 GMT" through "Aug 22 23:59:59 2023 GMT".
// "Jun 24 00:00:00 2023 GMT" through "Jul 22 23:59:59 2024 GMT".
//
// Use this to template view the certificate using OpenSSL:
// ```sh
// openssl x509 -inform der -text -in 1password_com_valid_1.crt | less
// ```
//
// You can update the cert file with `update_valid_1_cert.bash`
// You can update the cert file with `update_valid_ee_certs.rs`
const VALID_1PASSWORD_COM_CHAIN: &[&[u8]] = &[
include_bytes!("1password_com_valid_1.crt"),
include_bytes!("1password_com_valid_2.crt"),
Expand Down Expand Up @@ -145,7 +139,7 @@ fn real_world_test<E: std::error::Error>(test_case: &TestCase<E>) {
&server_name,
&mut std::iter::empty(),
stapled_ocsp,
std::time::SystemTime::now(),
test_case.verification_time,
)
.map(|_| ());

Expand All @@ -165,6 +159,7 @@ real_world_test_cases! {
reference_id: MY_1PASSWORD_COM,
chain: VALID_1PASSWORD_COM_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand All @@ -173,6 +168,7 @@ real_world_test_cases! {
reference_id: MY_1PASSWORD_COM,
chain: VALID_1PASSWORD_COM_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand All @@ -181,6 +177,7 @@ real_world_test_cases! {
reference_id: "1password.com",
chain: VALID_1PASSWORD_COM_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand All @@ -189,6 +186,7 @@ real_world_test_cases! {
reference_id: VALID_UNRELATED_DOMAIN,
chain: VALID_1PASSWORD_COM_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
Expand All @@ -198,6 +196,7 @@ real_world_test_cases! {
reference_id: VALID_UNRELATED_DOMAIN,
chain: VALID_UNRELATED_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand All @@ -207,13 +206,15 @@ real_world_test_cases! {
reference_id: MY_1PASSWORD_COM,
chain: VALID_UNRELATED_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
letsencrypt => TestCase {
reference_id: LETSENCRYPT_ORG,
chain: VALID_LETSENCRYPT_ORG_CHAIN,
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
Expand Down
Loading