Skip to main content

sidedns_core/certs/trust/
java.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use super::TrustStore;
5
6const ALIAS: &str = "sidedns-local-ca";
7const STORE_PASS: &str = "changeit";
8
9pub struct JavaStore;
10
11impl TrustStore for JavaStore {
12    fn name(&self) -> &str {
13        "java"
14    }
15
16    fn is_available(&self) -> bool {
17        keytool_path().is_some() && cacerts_path().is_some()
18    }
19
20    fn is_installed(&self, _cert_path: &Path) -> bool {
21        let (Some(keytool), Some(cacerts)) = (keytool_path(), cacerts_path()) else {
22            return false;
23        };
24        let output = Command::new(keytool)
25            .args([
26                "-list",
27                "-keystore",
28                cacerts.to_str().unwrap(),
29                "-storepass",
30                STORE_PASS,
31                "-alias",
32                ALIAS,
33            ])
34            .output();
35        match output {
36            Ok(o) => o.status.success(),
37            Err(_) => false,
38        }
39    }
40
41    fn install(&self, cert_path: &Path) -> anyhow::Result<()> {
42        let keytool = keytool_path()
43            .ok_or_else(|| anyhow::anyhow!("keytool not found — is a JDK installed?"))?;
44        let cacerts =
45            cacerts_path().ok_or_else(|| anyhow::anyhow!("Java cacerts file not found"))?;
46
47        let output = Command::new(keytool)
48            .args([
49                "-importcert",
50                "-noprompt",
51                "-trustcacerts",
52                "-alias",
53                ALIAS,
54                "-keystore",
55                cacerts.to_str().unwrap(),
56                "-storepass",
57                STORE_PASS,
58                "-file",
59                cert_path.to_str().unwrap(),
60            ])
61            .output()?;
62
63        anyhow::ensure!(
64            output.status.success(),
65            "keytool -importcert failed: {}",
66            String::from_utf8_lossy(&output.stderr)
67        );
68        Ok(())
69    }
70
71    fn uninstall(&self, _cert_path: &Path) -> anyhow::Result<()> {
72        let (Some(keytool), Some(cacerts)) = (keytool_path(), cacerts_path()) else {
73            return Ok(());
74        };
75
76        if !self.is_installed(_cert_path) {
77            return Ok(());
78        }
79
80        let output = Command::new(keytool)
81            .args([
82                "-delete",
83                "-alias",
84                ALIAS,
85                "-keystore",
86                cacerts.to_str().unwrap(),
87                "-storepass",
88                STORE_PASS,
89            ])
90            .output()?;
91
92        anyhow::ensure!(
93            output.status.success(),
94            "keytool -delete failed: {}",
95            String::from_utf8_lossy(&output.stderr)
96        );
97        Ok(())
98    }
99}
100
101fn keytool_path() -> Option<PathBuf> {
102    // Check JAVA_HOME first, then PATH
103    if let Ok(java_home) = std::env::var("JAVA_HOME") {
104        let candidate = PathBuf::from(java_home).join("bin").join("keytool");
105        if candidate.exists() {
106            return Some(candidate);
107        }
108    }
109
110    std::env::var_os("PATH").as_ref().and_then(|path| {
111        std::env::split_paths(path).find_map(|dir| {
112            let c = dir.join("keytool");
113            if c.exists() {
114                return Some(c);
115            }
116            #[cfg(windows)]
117            {
118                let c = dir.join("keytool.exe");
119                if c.exists() {
120                    return Some(c);
121                }
122            }
123            None
124        })
125    })
126}
127
128fn cacerts_path() -> Option<PathBuf> {
129    // Try JAVA_HOME
130    if let Ok(java_home) = std::env::var("JAVA_HOME") {
131        for relative in &["lib/security/cacerts", "jre/lib/security/cacerts"] {
132            let p = PathBuf::from(&java_home).join(relative);
133            if p.exists() {
134                return Some(p);
135            }
136        }
137    }
138
139    None
140}