use std::fs;
use std::path::{Path, PathBuf};
use rustls_pki_types::CertificateDer;
pub(crate) type LoadedCerts = Vec<CertificateDer<'static>>;
#[derive(Debug)]
pub(crate) struct KeystoreError {
pub path: PathBuf,
pub kind: KeystoreErrorKind,
}
#[derive(Debug)]
pub(crate) enum KeystoreErrorKind {
Io(std::io::Error),
UnknownFormat,
JksParse(String),
Pkcs12Parse(String),
NoTrustedCerts,
}
impl std::fmt::Display for KeystoreError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
KeystoreErrorKind::Io(e) => {
write!(f, "Could not open tls_roots {:?}: {}", self.path, e)
}
KeystoreErrorKind::UnknownFormat => write!(
f,
"tls_roots {:?} is not a recognised keystore (expected JKS magic 0xFEEDFEED \
or PKCS#12 ASN.1 SEQUENCE prefix 0x30)",
self.path
),
KeystoreErrorKind::JksParse(msg) => write!(
f,
"Failed to parse JKS tls_roots {:?}: {} \
(wrong password, truncated, or unsupported version?)",
self.path, msg
),
KeystoreErrorKind::Pkcs12Parse(msg) => write!(
f,
"Failed to parse PKCS#12 tls_roots {:?}: {} \
(wrong password, truncated, or unsupported algorithm?)",
self.path, msg
),
KeystoreErrorKind::NoTrustedCerts => write!(
f,
"tls_roots {:?} contains no trusted-certificate entries — \
a trust store with only private keys is not usable",
self.path
),
}
}
}
pub(crate) fn load_truststore_certs(
path: &Path,
password: &str,
) -> std::result::Result<LoadedCerts, KeystoreError> {
let buf = fs::read(path).map_err(|e| KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::Io(e),
})?;
if buf.len() < 4 {
return Err(KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::UnknownFormat,
});
}
let magic = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
let certs = if magic == 0xFEED_FEED {
load_jks(path, &buf, password)?
} else if buf[0] == 0x30 {
load_pkcs12(path, &buf, password)?
} else {
return Err(KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::UnknownFormat,
});
};
if certs.is_empty() {
return Err(KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::NoTrustedCerts,
});
}
Ok(certs)
}
fn load_jks(
path: &Path,
data: &[u8],
password: &str,
) -> std::result::Result<LoadedCerts, KeystoreError> {
let mut ks = jks::KeyStore::new();
ks.load(data, password.as_bytes())
.map_err(|e| KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::JksParse(e.to_string()),
})?;
let mut out = Vec::new();
for alias in ks.aliases() {
if !ks.is_trusted_certificate_entry(&alias) {
continue;
}
let entry = ks
.get_trusted_certificate_entry(&alias)
.map_err(|e| KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::JksParse(e.to_string()),
})?;
out.push(CertificateDer::from(entry.certificate.content));
}
Ok(out)
}
fn load_pkcs12(
path: &Path,
data: &[u8],
password: &str,
) -> std::result::Result<LoadedCerts, KeystoreError> {
let ks = p12_keystore::KeyStore::from_pkcs12(data, password).map_err(|e| KeystoreError {
path: path.to_path_buf(),
kind: KeystoreErrorKind::Pkcs12Parse(e.to_string()),
})?;
let mut out = Vec::new();
for (_alias, entry) in ks.entries() {
if let p12_keystore::KeyStoreEntry::Certificate(cert) = entry {
out.push(CertificateDer::from(cert.as_der().to_vec()));
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn missing_file() {
let err = load_truststore_certs(Path::new("/no/such/file"), "x").unwrap_err();
assert!(matches!(err.kind, KeystoreErrorKind::Io(_)));
}
#[test]
fn empty_file_is_unknown_format() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("empty.bin");
std::fs::File::create(&path).unwrap();
let err = load_truststore_certs(&path, "x").unwrap_err();
assert!(matches!(err.kind, KeystoreErrorKind::UnknownFormat));
}
#[test]
fn random_bytes_are_unknown_format() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("garbage.bin");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(&[0x12, 0x34, 0x56, 0x78, 0x9a]).unwrap();
let err = load_truststore_certs(&path, "x").unwrap_err();
assert!(matches!(err.kind, KeystoreErrorKind::UnknownFormat));
}
#[test]
fn jks_magic_but_garbage_body_is_jks_parse_error() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bad.jks");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(&[
0xFE, 0xED, 0xFE, 0xED, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
])
.unwrap();
let err = load_truststore_certs(&path, "wrong").unwrap_err();
assert!(
matches!(err.kind, KeystoreErrorKind::JksParse(_)),
"got: {:?}",
err.kind
);
}
#[test]
fn jks_truststore_round_trip() {
use rustls_pki_types::CertificateDer;
use rustls_pki_types::pem::PemObject;
let mut ca_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
ca_path.pop();
ca_path.push("tls_certs");
ca_path.push("server_rootCA.pem");
let pem_bytes = std::fs::read(&ca_path).unwrap();
let mut der_iter = CertificateDer::pem_slice_iter(&pem_bytes);
let ca_der = der_iter.next().unwrap().unwrap();
let ca_der_bytes: Vec<u8> = ca_der.as_ref().to_vec();
let mut ks = jks::KeyStore::new();
ks.set_trusted_certificate_entry(
"ca",
jks::TrustedCertificateEntry {
creation_time: std::time::SystemTime::now(),
certificate: jks::Certificate {
cert_type: "X.509".to_string(),
content: ca_der_bytes.clone(),
},
},
)
.unwrap();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("trust.jks");
let mut out = std::fs::File::create(&path).unwrap();
ks.store(&mut out, b"changeit").unwrap();
drop(out);
let err = load_truststore_certs(&path, "wrong").unwrap_err();
assert!(
matches!(err.kind, KeystoreErrorKind::JksParse(_)),
"got: {:?}",
err.kind
);
let certs = load_truststore_certs(&path, "changeit").unwrap();
assert_eq!(certs.len(), 1);
assert_eq!(certs[0].as_ref(), ca_der_bytes.as_slice());
}
}