Skip to content

Commit 321eba7

Browse files
committed
feat(sbom): include source identity evidence
1 parent 285c3b8 commit 321eba7

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

‎src/package.rs‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ use crate::domain::{Candidate, Toolchain};
55
use crate::version::matches_req;
66
use cyclonedx_bom::models::component::{Classification, Component, Components};
77
use cyclonedx_bom::models::dependency::{Dependencies, Dependency};
8+
use cyclonedx_bom::models::external_reference::{
9+
ExternalReference, ExternalReferenceType, ExternalReferences,
10+
};
11+
use cyclonedx_bom::models::hash::{Hash, HashAlgorithm, HashValue, Hashes};
812
use cyclonedx_bom::models::lifecycle::{Lifecycle, Lifecycles, Phase};
913
use cyclonedx_bom::models::metadata::Metadata;
1014
use cyclonedx_bom::models::property::{Properties, Property};
@@ -608,6 +612,57 @@ pub fn package_plan_to_bom(plan: &PackagePlan) -> Result<Bom, PackageError> {
608612
.description
609613
.as_deref()
610614
.map(NormalizedString::new);
615+
root.hashes = plan
616+
.sources
617+
.first()
618+
.and_then(|source| source.sha256.as_deref())
619+
.map(|checksum| Hashes(vec![sha256_hash(checksum)]));
620+
let mut source_references = Vec::new();
621+
let mut seen_references = BTreeSet::new();
622+
for source in &plan.sources {
623+
if let Some(git) = source.git.as_deref() {
624+
let key = (ExternalReferenceType::Vcs.to_string(), git.to_string());
625+
if seen_references.insert(key) {
626+
let mut reference =
627+
ExternalReference::new(ExternalReferenceType::Vcs, Uri::new(git));
628+
let identity = [
629+
source.tag.as_deref().map(|tag| format!("tag={tag}")),
630+
source
631+
.commit
632+
.as_deref()
633+
.map(|commit| format!("commit={commit}")),
634+
]
635+
.into_iter()
636+
.flatten()
637+
.collect::<Vec<_>>()
638+
.join(" ");
639+
if !identity.is_empty() {
640+
reference.comment = Some(identity);
641+
}
642+
source_references.push(reference);
643+
}
644+
}
645+
if let Some(url) = source_archive_url(source) {
646+
let key = (ExternalReferenceType::Distribution.to_string(), url.clone());
647+
if seen_references.insert(key) {
648+
let mut reference = ExternalReference::new(
649+
ExternalReferenceType::Distribution,
650+
Uri::new(&url),
651+
);
652+
reference.hashes = source
653+
.sha256
654+
.as_deref()
655+
.map(|checksum| Hashes(vec![sha256_hash(checksum)]));
656+
reference.comment = source.target_directory.as_deref().map(|directory| {
657+
format!("staged in package source directory {directory}")
658+
});
659+
source_references.push(reference);
660+
}
661+
}
662+
}
663+
if !source_references.is_empty() {
664+
root.external_references = Some(ExternalReferences(source_references));
665+
}
611666
root.properties = Some(Properties(vec![
612667
Property::new("eb-stack:origin", origin_name(&plan.origin)),
613668
Property::new("eb-stack:lifecycle", "pre-build-plan"),
@@ -690,6 +745,27 @@ pub fn package_plan_to_bom(plan: &PackagePlan) -> Result<Bom, PackageError> {
690745
})
691746
}
692747

748+
fn sha256_hash(checksum: &str) -> Hash {
749+
Hash {
750+
alg: HashAlgorithm::SHA_256,
751+
content: HashValue(checksum.to_string()),
752+
}
753+
}
754+
755+
fn source_archive_url(source: &SourceArtifact) -> Option<String> {
756+
source.url.clone().or_else(|| {
757+
let git = source.git.as_deref()?;
758+
let base = git.trim_end_matches(".git");
759+
if let Some(tag) = source.tag.as_deref() {
760+
Some(format!("{base}/archive/refs/tags/{tag}.tar.gz"))
761+
} else if let Some(commit) = source.commit.as_deref() {
762+
Some(format!("{base}/archive/{commit}.tar.gz"))
763+
} else {
764+
None
765+
}
766+
})
767+
}
768+
693769
pub fn package_plan_to_cyclonedx(plan: &PackagePlan) -> Result<Value, PackageError> {
694770
let bom = package_plan_to_bom(plan)?;
695771
let mut output = Vec::new();

0 commit comments

Comments
 (0)