Skip to main content

xianyun_pki/truststore/
install.rs

1//! System trust store installation/removal.
2//!
3//! Ported from `internal/truststore/install.go`.
4
5use std::path::Path;
6
7use crate::ca;
8use crate::truststore::command_runner::CommandRunner;
9use crate::truststore::nss;
10
11#[derive(Debug, thiserror::Error)]
12pub enum TrustError {
13    #[error("locating CA certificate: {0}")]
14    Locating(#[from] ca::PkiError),
15    #[error("installing CA in system trust store: {0}")]
16    InstallingSystem(String),
17    #[error("removing CA from system trust store: {0}")]
18    RemovingSystem(String),
19    /// Ported from `ErrEarlierSigningsTrusted`: the CA was installed, but
20    /// earlier signings of its key could not be removed and are still
21    /// trusted.
22    #[error("earlier signings of the CA's key are still trusted: {0}")]
23    EarlierSigningsTrusted(String),
24}
25
26/// Ported from `Install`.
27///
28/// Adaptation from Go: `removeSupersededSystem` only has a real (non-trivial)
29/// implementation on `//go:build darwin`, which is out of scope for this
30/// port (see `PORT_NOTES.md`). On Linux, `installSystem` overwrites the
31/// one caproni file, so `remove_superseded_system` is trivially a no-op —
32/// wired here for completeness, but it never has anything to remove on
33/// this platform.
34///
35/// # Errors
36///
37/// Returns [`TrustError`] if the CA certificate can't be located, or if
38/// installing it into the system (or, best-effort, NSS) trust store fails.
39pub fn install(pki_dir: &Path, runner: &dyn CommandRunner) -> Result<(), TrustError> {
40    let cert_path = ca::cert_path(pki_dir)?;
41
42    println!("Installing Caproni Dev CA...");
43    println!("  CA: {}", cert_path.display());
44
45    system::install_system(runner, &cert_path)
46        .map_err(|e| TrustError::InstallingSystem(e.to_string()))?;
47    println!("  Done: installed in system trust store");
48
49    match nss::install_nss(&cert_path) {
50        Ok(()) => println!("  Done: installed in Firefox NSS store"),
51        Err(e) => println!("  Note: Firefox NSS: {e}"),
52    }
53
54    let removed = system::remove_superseded_system(runner, &cert_path)
55        .map_err(|e| TrustError::EarlierSigningsTrusted(e.to_string()))?;
56    announce_removed(removed);
57
58    Ok(())
59}
60
61/// Ported from `Uninstall`.
62///
63/// # Errors
64///
65/// Returns [`TrustError`] if the CA certificate can't be located, or if
66/// removing it from the system trust store fails.
67pub fn uninstall(pki_dir: &Path, runner: &dyn CommandRunner) -> Result<(), TrustError> {
68    let cert_path = ca::cert_path(pki_dir)?;
69
70    println!("Removing Caproni Dev CA...");
71
72    system::uninstall_system(runner, &cert_path)
73        .map_err(|e| TrustError::RemovingSystem(e.to_string()))?;
74    println!("  Done: removed from system trust store");
75
76    report_superseded(&cert_path, runner);
77
78    match nss::uninstall_nss() {
79        Ok(()) => println!("  Done: removed from Firefox NSS store"),
80        Err(e) => println!("  Note: Firefox NSS: {e}"),
81    }
82
83    Ok(())
84}
85
86/// Ported from the package-private `reportSuperseded`.
87pub(crate) fn report_superseded(cert_path: &Path, runner: &dyn CommandRunner) {
88    match system::remove_superseded_system(runner, cert_path) {
89        Ok(removed) => announce_removed(removed),
90        Err(e) => println!("  Note: earlier Caproni Dev CA certificates: {e}"),
91    }
92}
93
94fn announce_removed(removed: usize) {
95    if removed > 0 {
96        println!("  Done: removed {removed} earlier certificate(s) for this CA's key");
97    }
98}
99
100#[cfg(target_os = "macos")]
101use crate::truststore::system_darwin as system;
102#[cfg(target_os = "linux")]
103use crate::truststore::system_linux as system;
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::truststore::helpers::new_mock;
109
110    /// Ported from `TestInstall_CertNotFound`.
111    #[test]
112    fn install_fails_when_cert_not_found() {
113        let dir = tempfile::tempdir().unwrap();
114        let runner = new_mock();
115
116        let err = install(dir.path(), &runner).unwrap_err();
117        assert!(err.to_string().contains("CA certificate not found"));
118    }
119
120    /// Ported from `TestUninstall_CertNotFound`.
121    #[test]
122    fn uninstall_fails_when_cert_not_found() {
123        let dir = tempfile::tempdir().unwrap();
124        let runner = new_mock();
125
126        let err = uninstall(dir.path(), &runner).unwrap_err();
127        assert!(err.to_string().contains("CA certificate not found"));
128    }
129}