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

nordisk/holger

update-bundle/src/tests/roundtrip.rs

//! Real znippy container round-trip: pack → load → verify → extract, plus a
//! tampered-archive check.

use std::fs;

use super::testkit::*;
use crate::api::{extract_bundle_file, verify_bundle_file};
use crate::container::{load_bundle, pack_bundle};
use crate::verify::TargetSpec;

#[test]
fn pack_load_verify_roundtrip() {
    let b = valid_bundle();
    let dir = tempfile::tempdir().unwrap();
    let out = dir
        .path()
        .join("ExampleCorp-ExampleDrive-1.2.3.holger.znippy");
    pack_bundle(&b.members, &out).unwrap();
    assert!(out.exists());

    let loaded = load_bundle(&out).unwrap();
    assert_eq!(loaded.len(), b.members.len());
    for (k, v) in &b.members {
        assert_eq!(
            loaded.get(k).map(|x| x.as_slice()),
            Some(v.as_slice()),
            "member {k}"
        );
    }
    let rep = verify_bundle_file(&out, &b.policy, &TargetSpec::default(), None).unwrap();
    assert_eq!(rep.approvals.len(), 2);
    assert_eq!(rep.bundle_id, b.bundle_id);
}

#[test]
fn extract_writes_verified_tree_into_empty_dir() {
    let b = valid_bundle();
    let dir = tempfile::tempdir().unwrap();
    let out = dir.path().join("bundle.holger.znippy");
    pack_bundle(&b.members, &out).unwrap();

    let dest = dir.path().join("extracted");
    extract_bundle_file(&out, &b.policy, &TargetSpec::default(), &dest).unwrap();
    assert_eq!(
        fs::read(dest.join("payload/firmware.bin")).unwrap(),
        b"firmware-v1"
    );
    assert!(dest.join("core.json").exists());
    assert!(dest.join("signatures/release-manifest.json").exists());
}

#[test]
fn extract_refuses_non_empty_destination() {
    let b = valid_bundle();
    let dir = tempfile::tempdir().unwrap();
    let out = dir.path().join("bundle.holger.znippy");
    pack_bundle(&b.members, &out).unwrap();
    let dest = dir.path().join("dest");
    fs::create_dir_all(&dest).unwrap();
    fs::write(dest.join("occupied"), b"x").unwrap();
    assert!(extract_bundle_file(&out, &b.policy, &TargetSpec::default(), &dest).is_err());
}

#[test]
fn tampered_archive_fails_verification() {
    let b = valid_bundle();
    let dir = tempfile::tempdir().unwrap();
    let out = dir.path().join("bundle.holger.znippy");
    pack_bundle(&b.members, &out).unwrap();

    let mut bytes = fs::read(&out).unwrap();
    // Prefer to tamper a known payload's bytes (proves the manifest-digest layer
    // catches a member change end-to-end through the real container). Small members
    // are stored with little/no compression, so the literal is usually present. If
    // not, corrupt the archive header instead (proves structural rejection). Either
    // way a tampered archive must not verify.
    if let Some(pos) = bytes.windows(11).position(|w| w == b"firmware-v1") {
        bytes[pos + 3] ^= 0x01;
    } else {
        for b in bytes.iter_mut().take(64) {
            *b ^= 0xff;
        }
    }
    fs::write(&out, &bytes).unwrap();

    assert!(
        verify_bundle_file(&out, &b.policy, &TargetSpec::default(), None).is_err(),
        "a tampered archive must not verify"
    );
}