use super::error::AlgorithmKind;
use super::policy::AlgorithmId;
fn primitive(alg: AlgorithmId) -> &'static str {
match alg {
AlgorithmId::CipherRc4 => "stream-cipher",
_ => match alg.kind() {
AlgorithmKind::Hash => "hash",
AlgorithmKind::SymmetricCipher => "block-cipher",
AlgorithmKind::SignatureSign | AlgorithmKind::SignatureVerify => "signature",
AlgorithmKind::KeyDerivation => "kdf",
AlgorithmKind::RandomBytes => "drbg",
},
}
}
fn component(alg: AlgorithmId) -> serde_json::Value {
let token = alg.token();
let mut cert = Vec::new();
if alg.is_fips_approved() {
cert.push("fips140-3");
} else {
cert.push("none");
}
serde_json::json!({
"type": "cryptographic-asset",
"name": token,
"bom-ref": format!("crypto/algorithm/{token}"),
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": primitive(alg),
"classicalSecurityLevel": alg.min_security_bits(),
"nistQuantumSecurityLevel": 0,
"certificationLevel": cert,
}
}
})
}
pub fn cbom_json() -> String {
let components: Vec<serde_json::Value> =
super::inventory().into_iter().map(component).collect();
let doc = serde_json::json!({
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"metadata": {
"timestamp": chrono::Utc::now().to_rfc3339(),
"tools": {
"components": [{
"type": "application",
"name": "pdf_oxide",
"version": env!("CARGO_PKG_VERSION"),
}]
}
},
"components": components,
});
serde_json::to_string(&doc).unwrap_or_else(|_| "{}".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::record_algorithm_use;
#[test]
fn cbom_is_well_formed_cyclonedx_listing_exercised_algorithms() {
record_algorithm_use(AlgorithmId::HashSha256);
record_algorithm_use(AlgorithmId::HashMd5);
let json = cbom_json();
let v: serde_json::Value = serde_json::from_str(&json).expect("valid JSON");
assert_eq!(v["bomFormat"], "CycloneDX");
assert_eq!(v["specVersion"], "1.6");
assert_eq!(v["metadata"]["tools"]["components"][0]["name"], "pdf_oxide");
let comps = v["components"].as_array().expect("components array");
let by_name = |n: &str| {
comps
.iter()
.find(|c| c["name"] == n)
.unwrap_or_else(|| panic!("{n} present"))
.clone()
};
let sha = by_name("sha256");
assert_eq!(sha["type"], "cryptographic-asset");
assert_eq!(sha["cryptoProperties"]["algorithmProperties"]["primitive"], "hash");
assert_eq!(
sha["cryptoProperties"]["algorithmProperties"]["certificationLevel"][0],
"fips140-3"
);
let md5 = by_name("md5");
assert_eq!(md5["cryptoProperties"]["algorithmProperties"]["certificationLevel"][0], "none");
for c in comps {
assert_eq!(c["cryptoProperties"]["assetType"], "algorithm");
assert!(c["bom-ref"]
.as_str()
.unwrap()
.starts_with("crypto/algorithm/"));
}
}
}