use super::TrustStore;
use crate::{Error, Result};
use sha1::Sha1;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
pub struct JavaTrustStore {
cert_path: PathBuf,
unique_name: String,
}
impl JavaTrustStore {
pub fn new(cert_path: &Path, unique_name: String) -> Self {
Self {
cert_path: cert_path.to_path_buf(),
unique_name,
}
}
fn detect_java() -> Option<JavaConfig> {
let java_home = env::var("JAVA_HOME").ok()?;
let java_home_path = PathBuf::from(&java_home);
if !java_home_path.exists() {
return None;
}
#[cfg(target_os = "windows")]
let keytool_name = "keytool.exe";
#[cfg(not(target_os = "windows"))]
let keytool_name = "keytool";
let keytool_path = java_home_path.join("bin").join(keytool_name);
if !keytool_path.exists() {
return None;
}
let mut cacerts_path = java_home_path.join("lib/security/cacerts");
if !cacerts_path.exists() {
cacerts_path = java_home_path.join("jre/lib/security/cacerts");
if !cacerts_path.exists() {
return None;
}
}
Some(JavaConfig {
java_home: java_home_path,
keytool_path,
cacerts_path,
})
}
pub fn is_available() -> bool {
Self::detect_java().is_some()
}
pub fn has_keytool() -> bool {
Self::detect_java()
.map(|cfg| cfg.keytool_path.exists())
.unwrap_or(false)
}
fn exec_keytool(args: &[&str]) -> Result<std::process::Output> {
let config = Self::detect_java()
.ok_or_else(|| Error::TrustStore("Java not found. Please set JAVA_HOME".to_string()))?;
let output = Command::new(&config.keytool_path)
.args(args)
.output()
.map_err(|e| Error::CommandFailed(format!("Failed to execute keytool: {}", e)))?;
#[cfg(unix)]
{
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("java.io.FileNotFoundException") {
let output = Command::new("sudo")
.arg(&config.keytool_path)
.args(args)
.env("JAVA_HOME", &config.java_home)
.output()
.map_err(|e| {
Error::CommandFailed(format!(
"Failed to execute keytool with sudo: {}",
e
))
})?;
return Ok(output);
}
}
}
Ok(output)
}
}
#[derive(Debug)]
struct JavaConfig {
java_home: PathBuf,
keytool_path: PathBuf,
cacerts_path: PathBuf,
}
impl TrustStore for JavaTrustStore {
fn check(&self) -> Result<bool> {
if !Self::has_keytool() {
return Ok(false);
}
let config =
Self::detect_java().ok_or_else(|| Error::TrustStore("Java not found".to_string()))?;
let cacerts_str = config
.cacerts_path
.to_str()
.ok_or_else(|| Error::TrustStore("Invalid cacerts path".to_string()))?;
let args = vec!["-list", "-keystore", cacerts_str, "-storepass", "changeit"];
let output = Self::exec_keytool(&args)?;
if !output.status.success() {
return Ok(false);
}
let cert_pem = fs::read_to_string(&self.cert_path)?;
let pem_data = pem::parse(&cert_pem)
.map_err(|e| Error::Certificate(format!("Failed to parse PEM: {}", e)))?;
let cert_der = pem_data.contents();
let mut sha1_hasher = Sha1::new();
sha1_hasher.update(cert_der);
let sha1_result = sha1_hasher.finalize();
let sha1_hex = hex::encode_upper(sha1_result);
let mut sha256_hasher = Sha256::new();
sha256_hasher.update(cert_der);
let sha256_result = sha256_hasher.finalize();
let sha256_hex = hex::encode_upper(sha256_result);
let stdout = String::from_utf8_lossy(&output.stdout);
let stdout_no_colons = stdout.replace(":", "");
Ok(stdout_no_colons.contains(&sha1_hex) || stdout_no_colons.contains(&sha256_hex))
}
fn install(&self) -> Result<()> {
if !Self::has_keytool() {
return Err(Error::TrustStore(
"keytool not found. Please set JAVA_HOME".to_string(),
));
}
let config =
Self::detect_java().ok_or_else(|| Error::TrustStore("Java not found".to_string()))?;
let cacerts_str = config
.cacerts_path
.to_str()
.ok_or_else(|| Error::TrustStore("Invalid cacerts path".to_string()))?;
let cert_path_str = self
.cert_path
.to_str()
.ok_or_else(|| Error::TrustStore("Invalid certificate path".to_string()))?;
let args = vec![
"-importcert",
"-noprompt",
"-keystore",
cacerts_str,
"-storepass",
"changeit",
"-file",
cert_path_str,
"-alias",
&self.unique_name,
];
let output = Self::exec_keytool(&args)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(Error::TrustStore(format!(
"Failed to install certificate in Java keystore: {}",
stderr
)));
}
Ok(())
}
fn uninstall(&self) -> Result<()> {
if !Self::has_keytool() {
return Ok(());
}
let config = match Self::detect_java() {
Some(cfg) => cfg,
None => return Ok(()),
};
let cacerts_str = match config.cacerts_path.to_str() {
Some(s) => s,
None => return Ok(()),
};
let args = vec![
"-delete",
"-alias",
&self.unique_name,
"-keystore",
cacerts_str,
"-storepass",
"changeit",
];
let output = Self::exec_keytool(&args)?;
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("does not exist") {
return Ok(());
}
if !output.status.success() {
eprintln!(
"Warning: Failed to remove certificate from Java keystore: {}",
stderr
);
}
Ok(())
}
}