Skip to main content

simoom_sys/
certstore.rs

1//! The operating system's trusted root certificates, for TLS clients.
2//!
3//! - Windows: the `ROOT` system store of the current user (which includes the machine roots),
4//!   read with `CertOpenSystemStoreW` and `CertEnumCertificatesInStore` from crypt32.
5//! - Elsewhere: nothing here; Unix systems keep their roots in PEM bundles (`/etc/ssl/...`,
6//!   `SSL_CERT_FILE`), which simoom-tls reads itself. macOS ships `/etc/ssl/cert.pem` with the
7//!   system roots, so the bundle path covers it too.
8
9/// DER-encoded root certificates from the OS store (empty where the OS has no such store or it
10/// cannot be opened).
11pub fn system_roots() -> Vec<Vec<u8>> {
12    imp::roots()
13}
14
15#[cfg(windows)]
16mod imp {
17    use core::ffi::c_void;
18
19    /// The fields of `CERT_CONTEXT` that are read.
20    #[repr(C)]
21    struct CertContext {
22        encoding_type: u32,
23        cert_encoded: *const u8,
24        cert_encoded_len: u32,
25        cert_info: *mut c_void,
26        store: *mut c_void,
27    }
28
29    #[link(name = "crypt32")]
30    extern "system" {
31        fn CertOpenSystemStoreW(prov: usize, subsystem: *const u16) -> *mut c_void;
32        fn CertEnumCertificatesInStore(store: *mut c_void, prev: *const CertContext) -> *const CertContext;
33        fn CertCloseStore(store: *mut c_void, flags: u32) -> i32;
34    }
35
36    const X509_ASN_ENCODING: u32 = 1;
37
38    pub(super) fn roots() -> Vec<Vec<u8>> {
39        let name: Vec<u16> = "ROOT".encode_utf16().chain(Some(0)).collect();
40        // SAFETY: `name` is a NUL-terminated UTF-16 string that outlives the call; a zero
41        // provider handle asks for the default provider.
42        let store = unsafe { CertOpenSystemStoreW(0, name.as_ptr()) };
43        if store.is_null() {
44            return Vec::new();
45        }
46        let mut out = Vec::new();
47        let mut ctx: *const CertContext = std::ptr::null();
48        loop {
49            // SAFETY: `store` is an open store handle; `ctx` is null or the context returned by
50            // the previous call, which this call frees (the documented enumeration pattern).
51            ctx = unsafe { CertEnumCertificatesInStore(store, ctx) };
52            if ctx.is_null() {
53                break;
54            }
55            // SAFETY: a non-null context returned by the enumeration is valid until the next
56            // call; its encoded certificate is `cert_encoded_len` readable bytes.
57            let der = unsafe {
58                let c = &*ctx;
59                if c.encoding_type & X509_ASN_ENCODING == 0 || c.cert_encoded.is_null() {
60                    None
61                } else {
62                    Some(std::slice::from_raw_parts(c.cert_encoded, c.cert_encoded_len as usize).to_vec())
63                }
64            };
65            if let Some(d) = der {
66                out.push(d);
67            }
68        }
69        // SAFETY: `store` is open and no context from it is still held (the enumeration ended
70        // with null, which freed the last one).
71        unsafe { CertCloseStore(store, 0) };
72        out
73    }
74}
75
76#[cfg(not(windows))]
77mod imp {
78    pub(super) fn roots() -> Vec<Vec<u8>> {
79        Vec::new()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    #[cfg(windows)]
86    #[test]
87    fn windows_root_store_has_certificates() {
88        let roots = super::system_roots();
89        assert!(roots.len() > 5, "{} roots", roots.len());
90        assert!(roots.iter().all(|d| d.first() == Some(&0x30)), "DER SEQUENCE");
91    }
92
93    #[cfg(not(windows))]
94    #[test]
95    fn other_systems_use_bundles() {
96        assert!(super::system_roots().is_empty());
97    }
98}