Skip to content

Commit 5ab4443

Browse files
Address CodeRabbit review round 6: key-wrap primitive, ecosystem-scoped libs, algorithmFamily validation
- Add KeyWrap variant to Primitive enum per CycloneDX 1.7 schema - Scope library identity by (ecosystem, library_name) to prevent cross-ecosystem collisions - Validate algorithmFamily against CycloneDX 1.7 registry, emit None for unknown values Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8483e43 commit 5ab4443

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

extlib/cryptoscan/src/crypto_algorithm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub enum Primitive {
4242
Pke,
4343
Kem,
4444
KeyAgree,
45+
KeyWrap,
4546
Kdf,
4647
Xof,
4748
Drbg,
@@ -62,6 +63,7 @@ impl Primitive {
6263
Primitive::Pke => "pke",
6364
Primitive::Kem => "kem",
6465
Primitive::KeyAgree => "key-agree",
66+
Primitive::KeyWrap => "key-wrap",
6567
Primitive::Kdf => "kdf",
6668
Primitive::Xof => "xof",
6769
Primitive::Drbg => "drbg",

extlib/cryptoscan/src/cyclonedx.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ pub struct BomDependency {
107107
pub fn to_cyclonedx_bom(findings: &[CryptoFinding]) -> CycloneDxBom {
108108
let mut components = Vec::new();
109109
let mut dependencies = Vec::new();
110-
let mut library_algorithms: HashMap<String, Vec<String>> = HashMap::new();
110+
// Key by (ecosystem, library_name) to avoid collapsing libraries across ecosystems
111+
let mut library_algorithms: HashMap<(String, String), Vec<String>> = HashMap::new();
111112

112113
// Group findings by bom_ref to aggregate detection contexts
113114
let mut algo_findings: HashMap<String, Vec<&CryptoFinding>> = HashMap::new();
@@ -117,7 +118,7 @@ pub fn to_cyclonedx_bom(findings: &[CryptoFinding]) -> CycloneDxBom {
117118
// Track library -> algorithm for `provides` relationships
118119
if let Some(lib) = &finding.providing_library {
119120
library_algorithms
120-
.entry(lib.clone())
121+
.entry((finding.ecosystem.clone(), lib.clone()))
121122
.or_default()
122123
.push(bom_ref.clone());
123124
}
@@ -135,7 +136,7 @@ pub fn to_cyclonedx_bom(findings: &[CryptoFinding]) -> CycloneDxBom {
135136

136137
let algo_props = AlgorithmProperties {
137138
primitive: primitive_str,
138-
algorithm_family: Some(first.algorithm.algorithm_family.clone()),
139+
algorithm_family: valid_algorithm_family(&first.algorithm.algorithm_family),
139140
parameter_set_identifier: first.algorithm.parameter_set.clone(),
140141
elliptic_curve: first.algorithm.elliptic_curve.clone(),
141142
mode: first.algorithm.mode.clone(),
@@ -209,14 +210,8 @@ pub fn to_cyclonedx_bom(findings: &[CryptoFinding]) -> CycloneDxBom {
209210
}
210211

211212
// Create library components with `provides` relationships
212-
let mut seen_libs: HashSet<String> = HashSet::new();
213-
for (lib_name, algo_refs) in &library_algorithms {
214-
if seen_libs.contains(lib_name) {
215-
continue;
216-
}
217-
seen_libs.insert(lib_name.clone());
218-
219-
let lib_ref = format!("lib/{}", lib_name);
213+
for ((ecosystem, lib_name), algo_refs) in &library_algorithms {
214+
let lib_ref = format!("lib/{}/{}", ecosystem, lib_name);
220215

221216
components.push(BomComponent {
222217
component_type: "library".to_string(),
@@ -270,6 +265,27 @@ fn make_bom_ref(name: &str, oid: &Option<String>) -> String {
270265
}
271266
}
272267

268+
/// Validate an algorithm family string against the CycloneDX 1.7 registry.
269+
/// Returns `Some(family)` if it's a known value, `None` otherwise.
270+
fn valid_algorithm_family(family: &str) -> Option<String> {
271+
const KNOWN_FAMILIES: &[&str] = &[
272+
"AES", "RSA", "EC", "SHA-1", "SHA-2", "SHA-3", "SHAKE",
273+
"3DES", "DES", "Blowfish", "RC4", "RC2", "CAST5", "IDEA",
274+
"Camellia", "SEED", "ARIA", "Serpent", "Twofish", "Threefish",
275+
"ChaCha20-Poly1305", "Salsa20", "HMAC", "CMAC", "GMAC", "KMAC",
276+
"Poly1305", "SipHash", "ECDSA", "EdDSA", "DSA",
277+
"ECDH", "DH", "X25519", "X448", "HKDF", "PBKDF2",
278+
"scrypt", "Argon2", "bcrypt", "BLAKE2", "BLAKE3",
279+
"MD5", "MD4", "RIPEMD", "Whirlpool",
280+
"ML-KEM", "ML-DSA", "SLH-DSA", "FN-DSA",
281+
];
282+
if KNOWN_FAMILIES.iter().any(|&known| known.eq_ignore_ascii_case(family)) {
283+
Some(family.to_string())
284+
} else {
285+
None
286+
}
287+
}
288+
273289
fn chrono_timestamp() -> String {
274290
// Simple UTC timestamp without chrono dependency
275291
let now = std::time::SystemTime::now()

0 commit comments

Comments
 (0)