server/lib/tests/the_license_scanner_signs_its_evidence.rs
//! **S6 — the licence scanner.** A package goes through the scanner framework
//! with the licence scanner in the licence slot: a denied licence blocks, an
//! unknown one warns and holds, and the licence evidence is signed and verifies.
use std::sync::Arc;
use holger_server_lib::attest::{
ArtifactDigest, AttestSigner, AttestStore, AttestTrust, HOLGER_SCAN_EVIDENCE_V1,
};
use holger_server_lib::audit::MemoryAuditLog;
use holger_server_lib::scanner::attestation::verify_evidence;
use holger_server_lib::scanner::license::{
LicensePolicy, LicenseScanner, UnknownLicense, FINDING_DENIED, FINDING_UNKNOWN,
};
use holger_server_lib::scanner::{
EvidenceStore, ScanPipeline, ScanSubject, ScanVerdict, ScannerKind, ScannerSet,
};
use znippy_common::sign::{dev, ArchiveSigner, DsseEnvelope};
fn wheel(license_header: &str) -> Vec<u8> {
let meta = format!("Metadata-Version: 2.4\nName: demo_pkg\nVersion: 1.2.0\n{license_header}\nA demo package.\n");
zip_entry::fixture::zip(&[
("demo_pkg/__init__.py", b"VALUE = 1\n".as_slice()),
("demo_pkg-1.2.0.dist-info/METADATA", meta.as_bytes()),
])
}
fn signing(root: &std::path::Path) -> (Arc<AttestStore>, Arc<AttestSigner>, Vec<u8>) {
let (ca_key, ca_der) = dev::mint_ca("holger-license-ca").unwrap();
let leaf = dev::new_p256_signer(&ca_key, &ca_der, "holger-license").unwrap();
let cert = leaf.certificate_der().to_vec();
let mut trust = AttestTrust::new();
trust.add_root_der(ca_der);
trust.enrol_signer_der(&cert);
let store = Arc::new(AttestStore::new(root).with_trust(trust));
(
store,
Arc::new(AttestSigner::from_signer(Box::new(leaf), cert.clone())),
cert,
)
}
fn pipeline(root: &std::path::Path) -> (ScanPipeline, Arc<AttestStore>, Vec<u8>) {
let (attest, signer, cert) = signing(&root.join("attest"));
let policy = LicensePolicy::new(
vec!["MIT".into()],
vec!["GPL-3.0".into()],
UnknownLicense::default(),
);
let set = ScannerSet::new()
.with(Arc::new(LicenseScanner::new(policy)))
.require(ScannerKind::License);
let p = ScanPipeline::new(
set,
EvidenceStore::new(root.join("evidence"), Arc::new(MemoryAuditLog::new())),
)
.with_attestation(attest.clone(), signer);
(p, attest, cert)
}
#[test]
fn the_license_evidence_is_signed_and_verifies() {
let tmp = tempfile::tempdir().unwrap();
let (pipeline, attest, cert) = pipeline(tmp.path());
let bytes = wheel("License-Expression: MIT AND GPL-3.0");
let file = "demo_pkg-1.2.0-py3-none-any.whl";
let ev = pipeline
.scan(
"importer",
"pypi-incoming",
&ScanSubject::new(file, "pip", &bytes),
)
.unwrap();
let rec = ev.records_of(ScannerKind::License).next().unwrap();
assert_eq!(rec.scanner_id, LicenseScanner::ID);
assert_eq!(rec.verdict, ScanVerdict::Block, "{}", rec.detail);
assert!(
rec.findings
.iter()
.any(|f| f.id.starts_with(FINDING_DENIED) && f.id.contains("GPL-3.0")),
"{:?}",
rec.findings
);
assert_eq!(ev.verdict, ScanVerdict::Block);
// Signed, and it verifies back to exactly the evidence.
let sha = ArtifactDigest::of_sha256(&bytes);
let stored = attest.query(&sha, HOLGER_SCAN_EVIDENCE_V1).unwrap();
assert_eq!(stored.len(), 1);
let envelope = stored[0].envelope_json.clone();
assert_eq!(
verify_evidence(&attest, &envelope, std::slice::from_ref(&cert), &sha).unwrap(),
ev
);
// Turning the licence block into a pass inside the envelope breaks the signature.
let mut env = DsseEnvelope::from_json(std::str::from_utf8(&envelope).unwrap()).unwrap();
let text = String::from_utf8(env.payload.clone()).unwrap();
assert!(text.contains("\"verdict\":\"block\""));
env.payload = text
.replace("\"verdict\":\"block\"", "\"verdict\":\"pass\"")
.into_bytes();
let err =
verify_evidence(&attest, env.to_json().unwrap().as_bytes(), &[cert], &sha).unwrap_err();
assert_eq!(err.code().code, "holger-attest-5", "{err}");
}
#[test]
fn an_unknown_license_is_held_and_the_hold_is_in_the_signed_evidence() {
let tmp = tempfile::tempdir().unwrap();
let (pipeline, attest, cert) = pipeline(tmp.path());
let bytes = wheel("License: All rights reserved, see LICENSE");
let ev = pipeline
.scan(
"importer",
"pypi-incoming",
&ScanSubject::new("demo_pkg-1.2.0-py3-none-any.whl", "pip", &bytes),
)
.unwrap();
let rec = ev.records_of(ScannerKind::License).next().unwrap();
assert_eq!(
(rec.verdict, rec.hold),
(ScanVerdict::Warn, true),
"{}",
rec.detail
);
assert!(rec.findings.iter().any(|f| f.id == FINDING_UNKNOWN));
assert!(ev.held());
assert!(
ev.reasons
.iter()
.any(|r| r.contains("holds the artifact for review")),
"{:?}",
ev.reasons
);
let sha = ArtifactDigest::of_sha256(&bytes);
let stored = attest.query(&sha, HOLGER_SCAN_EVIDENCE_V1).unwrap();
let back = verify_evidence(&attest, &stored[0].envelope_json, &[cert], &sha).unwrap();
assert!(back.held(), "the hold survives the signature round trip");
assert_eq!(back, ev);
}
#[test]
fn an_allowed_or_branch_passes() {
let tmp = tempfile::tempdir().unwrap();
let (pipeline, _, _) = pipeline(tmp.path());
let bytes = wheel("License-Expression: MIT OR GPL-3.0");
let ev = pipeline
.scan(
"importer",
"pypi-incoming",
&ScanSubject::new("demo_pkg-1.2.0-py3-none-any.whl", "pip", &bytes),
)
.unwrap();
let rec = ev.records_of(ScannerKind::License).next().unwrap();
assert_eq!(rec.verdict, ScanVerdict::Pass, "{}", rec.detail);
assert!(!ev.held());
}