chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDeTCCAmGgAwIBAgIJAMnA8BB8xT6wMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMQ8wDQYDVQQKDAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTAeFw0y
MTEwMTEyMDAzNTRaFw0yMzEwMTEyMDAzNTRaMGIxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQK
DAZCYWRTU0wxFTATBgNVBAMMDCouYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMIE7PiM7gTCs9hQ1XBYzJMY61yoaEmwIrX5lZ6xKyx2
PmzAS2BMTOqytMAPgLaw+XLJhgL5XEFdEyt/ccRLvOmULlA3pmccYYz2QULFRtMW
hyefdOsKnRFSJiFzbIRMeVXk0WvoBj1IFVKtsyjbqv9u/2CVSndrOfEk0TG23U3A
xPxTuW1CrbV8/q71FdIzSOciccfCFHpsKOo3St/qbLVytH5aohbcabFXRNsKEqve
ww9HdFxBIuGa+RuT5q0iBikusbpJHAwnnqP7i/dAcgCskgjZjFeEU4EFy+b+a1SY
QCeFxxC7c3DvaRhBB0VVfPlkPz0sw6l865MaTIbRyoUCAwEAAaMyMDAwCQYDVR0T
BAIwADAjBgNVHREEHDAaggwqLmJhZHNzbC5jb22CCmJhZHNzbC5jb20wDQYJKoZI
hvcNAQELBQADggEBAC4DensZ5tCTeCNJbHABYPwwqLUFOMITKOOgF3t8EqOan0CH
ST1NNi4jPslWrVhQ4Y3UbAhRBdqXl5N/NFfMzDosPpOjFgtifh8Z2s3w8vdlEZzf
A4mYTC8APgdpWyNgMsp8cdXQF7QOfdnqOfdnY+pfc8a8joObR7HEaeVxhJs+XL4E
CLByw5FR+svkYgCbQGWIgrM1cRpmXemt6Gf/XgFNP2PdubxqDEcnWlTMk8FCBVb1
nVDSiPjYShwnWsOOshshCRCAiIBPCKPX0QwKDComQlRrgMIvddaSzFFTKPoNZjC+
CUspSNnL7V9IIHvqKlRSmu+zIpm2VJCp1xLulk8=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,17 @@
use std::env;
/// Cargo, at least sometimes, sets SSL_CERT_FILE and SSL_CERT_DIR internally, as
/// it uses OpenSSL. So, always unset both at the beginning of a test even if
/// the test doesn't use either.
///
/// # Safety
///
/// This is only safe if used together with `#[serial]` because calling
/// `[env::remove_var()]` is unsafe if another thread is running.
///
/// Note that `env::remove_var()` is scheduled to become unsafe in Rust
/// Edition 2024.
pub(crate) unsafe fn clear_env() {
env::remove_var("SSL_CERT_FILE");
env::remove_var("SSL_CERT_DIR");
}

View File

@@ -0,0 +1,152 @@
//! This test attempts to verify that the set of 'native'
//! certificates produced by this crate is roughly similar
//! to the set of certificates in the mozilla root program
//! as expressed by the `webpki-roots` crate.
//!
//! This is, obviously, quite a heuristic test.
mod common;
use std::collections::HashMap;
use pki_types::Der;
use ring::io::der;
use serial_test::serial;
use webpki::anchor_from_trusted_cert;
fn stringify_x500name(subject: &Der<'_>) -> String {
let mut parts = vec![];
let mut reader = untrusted::Reader::new(subject.as_ref().into());
while !reader.at_end() {
let (tag, contents) = der::read_tag_and_get_value(&mut reader).unwrap();
assert!(tag == 0x31); // sequence, constructed, context=1
let mut inner = untrusted::Reader::new(contents);
let pair = der::expect_tag_and_get_value(&mut inner, der::Tag::Sequence).unwrap();
let mut pair = untrusted::Reader::new(pair);
let oid = der::expect_tag_and_get_value(&mut pair, der::Tag::OID).unwrap();
let (value_ty, value) = der::read_tag_and_get_value(&mut pair).unwrap();
let name = match oid.as_slice_less_safe() {
[0x55, 0x04, 0x03] => "CN",
[0x55, 0x04, 0x05] => "serialNumber",
[0x55, 0x04, 0x06] => "C",
[0x55, 0x04, 0x07] => "L",
[0x55, 0x04, 0x08] => "ST",
[0x55, 0x04, 0x09] => "STREET",
[0x55, 0x04, 0x0a] => "O",
[0x55, 0x04, 0x0b] => "OU",
[0x55, 0x04, 0x11] => "postalCode",
[0x55, 0x04, 0x61] => "organizationIdentifier",
[0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19] => "domainComponent",
[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01] => "emailAddress",
_ => panic!("unhandled x500 attr {oid:?}"),
};
let str_value = match value_ty {
// PrintableString, UTF8String, TeletexString or IA5String
0x0c | 0x13 | 0x14 | 0x16 => std::str::from_utf8(value.as_slice_less_safe()).unwrap(),
_ => panic!("unhandled x500 value type {value_ty:?}"),
};
parts.push(format!("{name}={str_value}"));
}
parts.join(", ")
}
#[test]
fn test_does_not_have_many_roots_unknown_by_mozilla() {
let native = rustls_native_certs::load_native_certs();
let mozilla = webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|ta| (ta.subject_public_key_info.as_ref(), ta))
.collect::<HashMap<_, _>>();
let mut missing_in_moz_roots = 0;
for cert in &native.certs {
let cert = anchor_from_trusted_cert(cert).unwrap();
if let Some(moz) = mozilla.get(cert.subject_public_key_info.as_ref()) {
assert_eq!(cert.subject, moz.subject, "subjects differ for public key");
} else {
println!(
"Native anchor {:?} is missing from mozilla set",
stringify_x500name(&cert.subject)
);
missing_in_moz_roots += 1;
}
}
#[cfg(windows)]
let threshold = 2.0; // no more than 160% extra roots; windows CI vm has lots of extra roots
#[cfg(target_os = "macos")]
let threshold = 0.6; // macOS has a bunch of extra roots, too
#[cfg(not(any(windows, target_os = "macos")))]
let threshold = 0.5; // no more than 50% extra roots
let diff = (missing_in_moz_roots as f64) / (mozilla.len() as f64);
println!("mozilla: {:?}", mozilla.len());
println!("native: {:?}", native.certs.len());
println!(
"{missing_in_moz_roots:?} anchors present in native set but not mozilla ({}%)",
diff * 100.
);
assert!(diff < threshold, "too many unknown roots");
}
#[test]
fn test_contains_most_roots_known_by_mozilla() {
let native = rustls_native_certs::load_native_certs();
let mut native_map = HashMap::new();
for anchor in &native.certs {
let cert = anchor_from_trusted_cert(anchor).unwrap();
let spki = cert.subject_public_key_info.as_ref();
native_map.insert(spki.to_owned(), anchor);
}
let mut missing_in_native_roots = 0;
let mozilla = webpki_roots::TLS_SERVER_ROOTS;
for cert in mozilla {
if !native_map.contains_key(cert.subject_public_key_info.as_ref()) {
println!(
"Mozilla anchor {:?} is missing from native set",
stringify_x500name(&cert.subject)
);
missing_in_native_roots += 1;
}
}
#[cfg(windows)]
let threshold = 0.95; // no more than 95% extra roots; windows misses *many* roots
#[cfg(target_os = "macos")]
let threshold = 0.6; // no more than 60% extra roots; macOS has a bunch of extra roots, too
#[cfg(not(any(windows, target_os = "macos")))]
let threshold = 0.5; // no more than 50% extra roots
let diff = (missing_in_native_roots as f64) / (mozilla.len() as f64);
println!("mozilla: {:?}", mozilla.len());
println!("native: {:?}", native.certs.len());
println!(
"{missing_in_native_roots:?} anchors present in mozilla set but not native ({}%)",
diff * 100.
);
assert!(diff < threshold, "too many missing roots");
}
#[test]
#[serial]
fn util_list_certs() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
let native = rustls_native_certs::load_native_certs();
for (i, cert) in native.certs.iter().enumerate() {
let cert = anchor_from_trusted_cert(cert).unwrap();
println!("cert[{i}] = {}", stringify_x500name(&cert.subject));
}
}

View File

@@ -0,0 +1,329 @@
mod common;
use std::io::{ErrorKind, Read, Write};
use std::net::TcpStream;
#[cfg(unix)]
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::{env, panic};
// #[serial] is used on all these tests to run them sequentially. If they're run in parallel,
// the global env var configuration in the env var test interferes with the others.
use serial_test::serial;
/// Check if connection to site works using native roots.
///
/// Yields an Err if and only if there is an issue connecting that
/// appears to be due to a certificate problem.
///
/// # Panics
///
/// Panics on errors unrelated to the TLS connection like errors during
/// certificate loading, or connecting via TCP.
fn check_site(domain: &str) -> Result<(), ()> {
check_site_with_roots(domain, rustls_native_certs::load_native_certs().unwrap())
}
/// Check if connection to site works using the given roots.
///
/// Yields an Err if and only if there is an issue connecting that
/// appears to be due to a certificate problem.
///
/// # Panics
///
/// Panics on errors unrelated to the TLS connection like connecting via TCP.
fn check_site_with_roots(
domain: &str,
root_certs: Vec<pki_types::CertificateDer<'static>>,
) -> Result<(), ()> {
let mut roots = rustls::RootCertStore::empty();
roots.add_parsable_certificates(root_certs);
let config = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth();
let mut conn = rustls::ClientConnection::new(
Arc::new(config),
pki_types::ServerName::try_from(domain)
.unwrap()
.to_owned(),
)
.unwrap();
let mut sock = TcpStream::connect(format!("{domain}:443")).unwrap();
let mut tls = rustls::Stream::new(&mut conn, &mut sock);
let result = tls.write_all(
format!(
"GET / HTTP/1.1\r\n\
Host: {domain}\r\n\
Connection: close\r\n\
Accept-Encoding: identity\r\n\
\r\n"
)
.as_bytes(),
);
match result {
Ok(()) => (),
Err(e) if e.kind() == ErrorKind::InvalidData => return Err(()), // TLS error
Err(e) => panic!("{e}"),
}
let mut plaintext = [0u8; 1024];
let len = tls.read(&mut plaintext).unwrap();
assert!(plaintext[..len].starts_with(b"HTTP/1.1 ")); // or whatever
Ok(())
}
#[test]
#[serial]
#[ignore]
fn google() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("google.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn amazon() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("amazon.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn facebook() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("facebook.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn netflix() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("netflix.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn ebay() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("ebay.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn apple() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
check_site("apple.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn badssl_with_env() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
// Self-signed certs should never be trusted by default:
assert!(check_site("self-signed.badssl.com").is_err());
// But they should be trusted if SSL_CERT_FILE is set:
env::set_var(
"SSL_CERT_FILE",
// The CA cert, downloaded directly from the site itself:
PathBuf::from("./tests/badssl-com-chain.pem"),
);
check_site("self-signed.badssl.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn badssl_with_dir_from_env() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
let temp_dir = tempfile::TempDir::new().unwrap();
let original = Path::new("tests/badssl-com-chain.pem")
.canonicalize()
.unwrap();
let link1 = temp_dir.path().join("5d30f3c5.3");
#[cfg(unix)]
let link2 = temp_dir.path().join("fd3003c5.0");
env::set_var(
"SSL_CERT_DIR",
// The CA cert, downloaded directly from the site itself:
temp_dir.path(),
);
assert!(check_site("self-signed.badssl.com").is_err());
// OpenSSL uses symlinks too. So, use one for testing too, if possible.
#[cfg(unix)]
symlink(original, link1).unwrap();
#[cfg(not(unix))]
std::fs::copy(original, link1).unwrap();
// Dangling symlink
#[cfg(unix)]
symlink("/a/path/which/does/not/exist/hopefully", link2).unwrap();
check_site("self-signed.badssl.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn ssl_cert_dir_multiple_paths_are_respected() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
// Create 2 temporary directories
let temp_dir1 = tempfile::TempDir::new().unwrap();
let temp_dir2 = tempfile::TempDir::new().unwrap();
// Copy the certificate to the 2nd dir, leaving the 1st one
// empty.
let original = Path::new("tests/badssl-com-chain.pem")
.canonicalize()
.unwrap();
let cert = temp_dir2.path().join("5d30f3c5.3");
std::fs::copy(original, cert).unwrap();
let list_sep = if cfg!(windows) { ';' } else { ':' };
let value = format!(
"{}{}{}",
temp_dir1.path().display(),
list_sep,
temp_dir2.path().display()
);
env::set_var("SSL_CERT_DIR", value);
check_site("self-signed.badssl.com").unwrap();
}
#[test]
#[serial]
#[ignore]
fn ssl_cert_dir_non_hash_based_name() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
// Create temporary directory
let temp_dir = tempfile::TempDir::new().unwrap();
// Copy the certificate to the dir
let original = Path::new("tests/badssl-com-chain.pem")
.canonicalize()
.unwrap();
let cert = temp_dir.path().join("test.pem");
std::fs::copy(original, cert).unwrap();
env::set_var(
"SSL_CERT_DIR",
// The CA cert, downloaded directly from the site itself:
temp_dir.path(),
);
check_site("self-signed.badssl.com").unwrap();
}
#[test]
#[serial]
#[ignore]
#[cfg(target_os = "linux")]
fn google_with_dir_but_broken_file() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
env::set_var("SSL_CERT_DIR", "/etc/ssl/certs");
env::set_var("SSL_CERT_FILE", "not-exist");
let res = rustls_native_certs::load_native_certs();
let first_err = res.errors.first().unwrap().to_string();
dbg!(&first_err);
assert!(first_err.contains("from file"));
assert!(first_err.contains("not-exist"));
check_site_with_roots("google.com", res.certs).unwrap();
}
#[test]
#[serial]
#[ignore]
#[cfg(target_os = "linux")]
fn google_with_file_but_broken_dir() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
env::set_var("SSL_CERT_DIR", "/not-exist");
env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt");
let res = rustls_native_certs::load_native_certs();
let first_err = res.errors.first().unwrap().to_string();
dbg!(&first_err);
assert!(first_err.contains("opening directory"));
assert!(first_err.contains("/not-exist"));
check_site_with_roots("google.com", res.certs).unwrap();
}
#[test]
#[serial]
#[ignore]
#[cfg(target_os = "linux")]
fn nothing_works_with_broken_file_and_dir() {
unsafe {
// SAFETY: safe because of #[serial]
common::clear_env();
}
env::set_var("SSL_CERT_DIR", "/not-exist");
env::set_var("SSL_CERT_FILE", "not-exist");
let res = rustls_native_certs::load_native_certs();
assert_eq!(res.errors.len(), 2);
let first_err = res.errors.first().unwrap().to_string();
dbg!(&first_err);
assert!(first_err.contains("from file"));
assert!(first_err.contains("not-exist"));
let second_err = res.errors.get(1).unwrap().to_string();
dbg!(&second_err);
assert!(second_err.contains("opening directory"));
assert!(second_err.contains("/not-exist"));
}