From 655aaed4e1d07d77f433bcca28f4bc4c47563c6c Mon Sep 17 00:00:00 2001 From: nikstur Date: Wed, 8 May 2024 18:40:53 +0200 Subject: [PATCH] transformer: reproducibly derive serial number Derive the serial number from the outPath of the SBOM derivation. This usese the first 16 bytes of the SHA256 hash of the entire outPath. --- nix/build-bom.nix | 3 +- rust/transformer/Cargo.lock | 73 +++++++++++++++++++++++++++++++ rust/transformer/Cargo.toml | 2 + rust/transformer/src/cli.rs | 4 ++ rust/transformer/src/cyclonedx.rs | 25 ++++++++++- rust/transformer/src/transform.rs | 10 +++-- 6 files changed, 111 insertions(+), 6 deletions(-) diff --git a/nix/build-bom.nix b/nix/build-bom.nix index 08813b4..fa26eaa 100644 --- a/nix/build-bom.nix +++ b/nix/build-bom.nix @@ -18,6 +18,7 @@ runCommand "${drv.name}.cdx.json" { buildInputs = [ transformer ]; } '' bombon-transformer ${drv} \ ${toString flags} \ ${buildtimeDependencies drv extraPaths} \ - ${runtimeDependencies drv extraPaths} > $out + ${runtimeDependencies drv extraPaths} \ + $out '' diff --git a/rust/transformer/Cargo.lock b/rust/transformer/Cargo.lock index 747b9d5..0891569 100644 --- a/rust/transformer/Cargo.lock +++ b/rust/transformer/Cargo.lock @@ -84,6 +84,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bombon-transformer" version = "0.1.0" @@ -94,6 +103,8 @@ dependencies = [ "itertools", "serde", "serde_json", + "sha2", + "uuid", ] [[package]] @@ -148,6 +159,25 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cyclonedx-bom" version = "0.5.0" @@ -178,6 +208,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "either" version = "1.11.0" @@ -193,6 +233,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.14" @@ -379,6 +429,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -462,6 +523,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -483,6 +550,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/rust/transformer/Cargo.toml b/rust/transformer/Cargo.toml index de12bf4..25e0385 100644 --- a/rust/transformer/Cargo.toml +++ b/rust/transformer/Cargo.toml @@ -12,6 +12,8 @@ cyclonedx-bom = "0.5" itertools = "0.12" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" +sha2 = "0.10" +uuid = "1.8" [lints.rust] unsafe_code = "forbid" diff --git a/rust/transformer/src/cli.rs b/rust/transformer/src/cli.rs index 0ba631e..c7feb16 100644 --- a/rust/transformer/src/cli.rs +++ b/rust/transformer/src/cli.rs @@ -19,6 +19,9 @@ pub struct Cli { /// Path to a newline separated .txt file containing the runtime input runtime_input: PathBuf, + + /// Path to write the SBOM to + output: PathBuf, } impl Cli { @@ -28,6 +31,7 @@ impl Cli { &self.target, &self.buildtime_input, &self.runtime_input, + &self.output, ) } } diff --git a/rust/transformer/src/cyclonedx.rs b/rust/transformer/src/cyclonedx.rs index 18f0cfd..1d10011 100644 --- a/rust/transformer/src/cyclonedx.rs +++ b/rust/transformer/src/cyclonedx.rs @@ -1,6 +1,8 @@ +use std::path::Path; + use anyhow::Result; use cyclonedx_bom::external_models::uri::Purl; -use cyclonedx_bom::models::bom::Bom; +use cyclonedx_bom::models::bom::{Bom, UrnUuid}; use cyclonedx_bom::models::component::{Classification, Component, Components}; use cyclonedx_bom::models::external_reference::{ ExternalReference, ExternalReferenceType, ExternalReferences, @@ -8,6 +10,7 @@ use cyclonedx_bom::models::external_reference::{ use cyclonedx_bom::models::license::{License, LicenseChoice, Licenses}; use cyclonedx_bom::models::metadata::Metadata; use cyclonedx_bom::models::tool::{Tool, Tools}; +use sha2::{Digest, Sha256}; use crate::derivation::{self, Derivation, Meta}; @@ -22,15 +25,33 @@ impl CycloneDXBom { Ok(output) } - pub fn build(target: Derivation, components: CycloneDXComponents) -> Self { + pub fn build(target: Derivation, components: CycloneDXComponents, output: &Path) -> Self { Self(Bom { components: Some(components.into()), metadata: Some(metadata_from_derivation(target)), + // Derive a reproducible serial number from the output path. This works because the Nix + // outPath of the derivation is input addressed and thus reproducible. + serial_number: Some(derive_serial_number(output.as_os_str().as_encoded_bytes())), ..Bom::default() }) } } +/// Derive a serial number from some arbitrary data. +/// +/// This data is hashed with SHA256 and the first 16 bytes are used to create a UUID to serve as a +/// serial number. +fn derive_serial_number(data: &[u8]) -> UrnUuid { + let hash = Sha256::digest(data); + let array: [u8; 32] = hash.into(); + #[allow(clippy::expect_used)] + let bytes = array[..16] + .try_into() + .expect("Failed to extract 16 bytes from SHA256 hash"); + let uuid = uuid::Builder::from_bytes(bytes).into_uuid(); + UrnUuid::from(uuid) +} + pub struct CycloneDXComponents(Components); impl CycloneDXComponents { diff --git a/rust/transformer/src/transform.rs b/rust/transformer/src/transform.rs index d10e65f..02c84c1 100644 --- a/rust/transformer/src/transform.rs +++ b/rust/transformer/src/transform.rs @@ -1,4 +1,5 @@ -use std::io::{self, Write}; +use std::fs::File; +use std::io::Write; use std::path::Path; use anyhow::{Context, Result}; @@ -14,6 +15,7 @@ pub fn transform( target_path: &str, buildtime_input_path: &Path, runtime_input_path: &Path, + output: &Path, ) -> Result<()> { let mut buildtime_input = BuildtimeInput::from_file(buildtime_input_path)?; let target_derivation = buildtime_input.0.remove(target_path).with_context(|| { @@ -48,8 +50,10 @@ pub fn transform( CycloneDXComponents::new(runtime_derivations) }; - let bom = CycloneDXBom::build(target_derivation, components); - io::stdout().write_all(&bom.serialize()?)?; + let bom = CycloneDXBom::build(target_derivation, components, &output); + let mut file = + File::create(output).with_context(|| format!("Failed to create file {output:?}"))?; + file.write_all(&bom.serialize()?)?; Ok(()) }