Skip to content

Commit

Permalink
transformer: reproducibly derive serial number
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nikstur committed May 8, 2024
1 parent 5937318 commit 655aaed
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 6 deletions.
3 changes: 2 additions & 1 deletion nix/build-bom.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
''

73 changes: 73 additions & 0 deletions rust/transformer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rust/transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions rust/transformer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -28,6 +31,7 @@ impl Cli {
&self.target,
&self.buildtime_input,
&self.runtime_input,
&self.output,
)
}
}
25 changes: 23 additions & 2 deletions rust/transformer/src/cyclonedx.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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,
};
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};

Expand All @@ -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 {
Expand Down
10 changes: 7 additions & 3 deletions rust/transformer/src/transform.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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(|| {
Expand Down Expand Up @@ -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(())
}

0 comments on commit 655aaed

Please sign in to comment.