#![cfg(all(
any(windows, unix, target_os = "android"),
// These OSes require a simulator runtime and bundle.
not(target_os = "tvos"),
not(target_os = "watchos"),
not(target_os = "visionos")
))]
use std::convert::TryFrom;
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, verification_time};
use crate::verification::{EkuError, Verifier};
macro_rules! mock_root_test_cases {
{ $( $name:ident [ $target:meta ] => $test_case:expr ),+ , } => {
mock_root_test_cases!(@ $($name [ $target ] => $test_case),+,);
#[cfg(test)]
mod tests {
$(
#[cfg($target)]
#[test]
pub fn $name() {
super::$name()
}
)+
}
#[cfg(feature = "ffi-testing")]
pub static ALL_TEST_CASES: &'static [fn()] = &[
$(
#[cfg($target)]
$name,
)+
];
};
{@ $( $name:ident [ $target:meta ] => $test_case:expr ),+ , } => {
$(
#[cfg($target)]
pub(super) fn $name() {
test_with_mock_root(&$test_case, Roots::OnlyExtra);
#[cfg(all($target, not(target_os = "android")))]
test_with_mock_root(&$test_case, Roots::ExtraAndPlatform);
}
)+
};
}
macro_rules! no_error {
() => {
None::<std::convert::Infallible>
};
}
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");
const ROOT1_INT1_LOCALHOST_IPV6_GOOD: &[u8] = include_bytes!("root1-int1-ee_1-good.crt");
const EXAMPLE_COM: &str = "example.com";
const LOCALHOST_IPV4: &str = "127.0.0.1";
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() {
let crypto_provider = test_provider();
#[cfg(target_os = "freebsd")]
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(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);
let intermediates = [pki_types::CertificateDer::from(ROOT1_INT1)];
let result = verifier.verify_server_cert(
&end_entity,
&intermediates,
&server_name,
&[],
verification_time(),
);
assert_eq!(
result.map(|_| ()),
Err(TlsError::InvalidCertificate(
CertificateError::UnknownIssuer
))
);
}
#[test]
fn test_verification_without_mock_root() {
verification_without_mock_root()
}
mock_root_test_cases! {
valid_no_stapling_dns [ any(windows, unix) ] => TestCase {
reference_id: EXAMPLE_COM,
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
valid_no_stapling_ipv4 [ any(windows, unix) ] => 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, unix) ] => 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, unix) ] => 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, unix) ] => 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, unix) ] => 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!(),
},
revoked_dns [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase {
reference_id: EXAMPLE_COM,
chain: &[include_bytes!("root1-int1-ee_example.com-revoked.crt"), ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
expected_result: Ok(()),
other_error: no_error!(),
},
stapled_revoked_dns [ any(windows, target_os = "android", target_vendor = "apple") ] => TestCase {
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_vendor = "apple") ] => 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_vendor = "apple") ] => 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!(),
},
ee_only_dns [ any(windows, unix) ] => TestCase {
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, unix) ] => 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, unix) ] => 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!(),
},
domain_mismatch_dns [ any(windows, unix) ] => TestCase {
reference_id: "example.org",
chain: &[ROOT1_INT1_EXAMPLE_COM_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
#[cfg(not(any(target_vendor = "apple", windows)))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext {
expected: ServerName::DnsName(DnsName::try_from("example.org").unwrap()),
presented: vec!["DnsName(\"example.com\")".to_owned()]
})),
#[cfg(any(target_vendor = "apple", windows))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
domain_mismatch_ipv4 [ any(windows, unix) ] => TestCase {
reference_id: "198.168.0.1",
chain: &[ROOT1_INT1_LOCALHOST_IPV4_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
#[cfg(not(any(target_vendor = "apple", windows)))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext {
expected: ServerName::IpAddress(pki_types::IpAddr::V4(Ipv4Addr::from([198, 168, 0, 1]).into())),
presented: vec!["IpAddress(127.0.0.1)".to_owned()],
})),
#[cfg(any(target_vendor = "apple", windows))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
domain_mismatch_ipv6 [ any(windows, unix) ] => TestCase {
reference_id: "::ffff:c6a8:1",
chain: &[ROOT1_INT1_LOCALHOST_IPV6_GOOD, ROOT1_INT1],
stapled_ocsp: None,
verification_time: verification_time(),
#[cfg(not(any(target_vendor = "apple", windows)))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForNameContext {
expected: ServerName::IpAddress(pki_types::IpAddr::V6(Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 198, 168, 0, 1]).into())),
presented: vec!["IpAddress(0::1)".to_owned()],
})),
#[cfg(any(target_vendor = "apple", windows))]
expected_result: Err(TlsError::InvalidCertificate(CertificateError::NotValidForName)),
other_error: no_error!(),
},
wrong_eku_dns [ any(windows, unix) ] => 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(OtherError(Arc::from(EkuError))))),
other_error: Some(EkuError),
},
wrong_eku_ipv4 [ any(windows, unix) ] => TestCase {
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(OtherError(Arc::from(EkuError))))),
other_error: Some(EkuError),
},
wrong_eku_ipv6 [ any(windows, unix) ] => TestCase {
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(OtherError(Arc::from(EkuError))))),
other_error: Some(EkuError),
},
}
fn test_with_mock_root<E: std::error::Error + PartialEq + 'static>(
test_case: &TestCase<E>,
root_src: Roots,
) {
log::info!("verifying {:?}", test_case.expected_result);
let provider = test_provider();
let verifier = match root_src {
Roots::OnlyExtra => Verifier::new_with_fake_root(ROOT1, provider), #[cfg(not(target_os = "android"))]
Roots::ExtraAndPlatform => Verifier::new_with_extra_roots([ROOT1], provider).unwrap(),
};
let mut chain = test_case
.chain
.iter()
.map(|bytes| pki_types::CertificateDer::from(*bytes));
let end_entity = chain.next().unwrap();
let intermediates: Vec<pki_types::CertificateDer<'_>> = chain.collect();
let server_name = pki_types::ServerName::try_from(test_case.reference_id).unwrap();
if test_case.reference_id.parse::<IpAddr>().is_ok() {
assert!(matches!(server_name, pki_types::ServerName::IpAddress(_)));
} else {
assert!(matches!(server_name, pki_types::ServerName::DnsName(_)));
}
let result = verifier.verify_server_cert(
&end_entity,
&intermediates,
&server_name,
test_case.stapled_ocsp.unwrap_or(&[]),
test_case.verification_time,
);
assert_cert_error_eq(
&result.map(|_| ()),
&test_case.expected_result,
test_case.other_error.as_ref(),
);
}
enum Roots {
OnlyExtra,
#[cfg(not(target_os = "android"))]
ExtraAndPlatform,
}