use std::path::{Path, PathBuf};
use anyhow::Context;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use scion_proto::address::IsdAsn;
use serde::{Deserialize, Serialize};
use super::{CertifiedKeyPair, StoreCertificateDer, StoreKeyDer};
use crate::network::scion::trust_store::TrustStore;
pub fn trust_store_from_directory(path: impl AsRef<Path>) -> anyhow::Result<TrustStore> {
let path = path.as_ref();
let trcs = collect_raw_trcs(path)?;
let as_dir_readers = collect_as_dir_readers(path)?;
let mut trust_store = TrustStore::new();
for (raw, trc_path) in trcs {
trust_store.add_isd_trust_store(raw).context(format!(
"failed to add TRC from file {}",
trc_path.display()
))?;
}
for as_reader in as_dir_readers {
let identity = as_reader.load_as_identity()?;
trust_store.add_as_key_pair(as_reader.isd_asn, identity)?;
let ca_identities = as_reader.load_ca_identities()?;
if let Some((ca_identity, root_identity)) = ca_identities {
trust_store.add_as_ca(as_reader.isd_asn, root_identity, ca_identity)?;
}
}
Ok(trust_store)
}
fn collect_raw_trcs(base_path: &Path) -> anyhow::Result<Vec<(Vec<u8>, PathBuf)>> {
let trc_path = base_path.join("trcs");
let mut trc_paths = Vec::new();
collect_files_with_extension(&trc_path, "trc", &mut trc_paths)?;
let mut trcs = Vec::new();
for trc_path in trc_paths {
let raw = std::fs::read(&trc_path)
.with_context(|| format!("failed to read TRC file {}", trc_path.display()))?;
trcs.push((raw, trc_path));
}
Ok(trcs)
}
fn collect_as_dir_readers(base_path: &Path) -> anyhow::Result<Vec<AsDirReader>> {
let mut parsers = Vec::new();
for entry in std::fs::read_dir(base_path)
.with_context(|| format!("failed to read directory {}", base_path.display()))?
{
let entry = entry.with_context(|| {
format!("failed to read entry in directory {}", base_path.display())
})?;
let entry_path = entry.path();
if entry_path.is_dir()
&& let Some(name) = entry_path.file_name().and_then(|n| n.to_str())
&& name.starts_with("AS")
{
let reader = AsDirReader::new(entry_path.clone()).with_context(|| {
format!(
"failed to create AS directory parser for {}",
entry_path.display()
)
})?;
parsers.push(reader);
}
}
Ok(parsers)
}
#[derive(Debug, Serialize, Deserialize)]
struct CpAsTemplate {
isd_as: IsdAsn,
}
struct AsDirReader {
pub isd_asn: IsdAsn,
pub path: PathBuf,
}
impl AsDirReader {
fn new(path: PathBuf) -> anyhow::Result<Self> {
let isd_asn = {
let tmpl_path = path.join("./crypto/as/cp-as.tmpl");
let tmpl = std::fs::read_to_string(&tmpl_path).with_context(|| {
format!("failed to read AS template file {}", tmpl_path.display())
})?;
let template: CpAsTemplate = serde_json::from_str(&tmpl).with_context(|| {
format!("failed to parse AS template file {}", tmpl_path.display())
})?;
template.isd_as
};
Ok(AsDirReader { isd_asn, path })
}
fn load_as_identity(&self) -> anyhow::Result<CertifiedKeyPair> {
let as_crypto_dir = self.path.join("crypto").join("as");
let key_path = as_crypto_dir.join("cp-as.key");
let mut certs = Vec::new();
collect_files_with_extension(&as_crypto_dir, "pem", &mut certs)?;
if certs.len() != 1 {
anyhow::bail!(
"expected exactly one PEM file in {}, found {}",
as_crypto_dir.display(),
certs.len()
);
}
let key_der = PrivateKeyDer::from_pem_file(&key_path).with_context(|| {
format!("failed to parse AS private key PEM {}", key_path.display())
})?;
let cert_der = CertificateDer::from_pem_file(&certs[0]).with_context(|| {
format!("failed to parse AS certificate PEM {}", certs[0].display())
})?;
Ok(CertifiedKeyPair {
key: StoreKeyDer(key_der),
cert: StoreCertificateDer(cert_der),
})
}
fn load_ca_identities(&self) -> anyhow::Result<Option<(CertifiedKeyPair, CertifiedKeyPair)>> {
let ca_crypto_dir = self.path.join("crypto").join("ca");
if !ca_crypto_dir.exists() {
return Ok(None);
}
let certs = {
let mut certs = Vec::new();
collect_files_with_extension(&ca_crypto_dir, "crt", &mut certs)?;
certs
};
if certs.len() != 2 {
anyhow::bail!(
"expected exactly two CRT files in {}, found {}",
ca_crypto_dir.display(),
certs.len()
);
}
let key_path = ca_crypto_dir.join("cp-ca.key");
let root_key_path = ca_crypto_dir.join("cp-root.key");
let key_der = PrivateKeyDer::from_pem_file(&key_path).with_context(|| {
format!("failed to parse CA private key PEM {}", key_path.display())
})?;
let root_key_der = PrivateKeyDer::from_pem_file(&root_key_path).with_context(|| {
format!(
"failed to parse CA root private key PEM {}",
root_key_path.display()
)
})?;
let (idx, _path) = certs
.iter()
.enumerate()
.find(|(_, p)| {
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.ends_with(".root.crt"))
})
.ok_or_else(|| {
anyhow::anyhow!(
"failed to find root certificate in CA crypto directory {}",
ca_crypto_dir.display()
)
})?;
let (root_crt_path, ca_cert_path) = match idx {
0 => (&certs[0], &certs[1]),
1 => (&certs[1], &certs[0]),
_ => unreachable!(),
};
let cert_der = CertificateDer::from_pem_file(ca_cert_path).with_context(|| {
format!(
"failed to parse CA certificate PEM {}",
ca_cert_path.display()
)
})?;
let root_cert_der = CertificateDer::from_pem_file(root_crt_path).with_context(|| {
format!(
"failed to parse CA root certificate PEM {}",
root_crt_path.display()
)
})?;
Ok(Some((
CertifiedKeyPair {
key: StoreKeyDer(key_der),
cert: StoreCertificateDer(cert_der),
},
CertifiedKeyPair {
key: StoreKeyDer(root_key_der),
cert: StoreCertificateDer(root_cert_der),
},
)))
}
}
fn collect_files_with_extension(
path: &Path,
extension: &str,
acc: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
for entry in std::fs::read_dir(path)
.with_context(|| format!("failed to read directory {}", path.display()))?
{
let entry = entry
.with_context(|| format!("failed to read entry in directory {}", path.display()))?;
let entry_path = entry.path();
if entry_path.is_dir() {
collect_files_with_extension(&entry_path, extension, acc)?;
} else if entry_path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext == extension)
{
acc.push(entry_path);
}
}
Ok(())
}