Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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());
}