prikk-store 0.25.0

Prikk storage crate scaffold.
Documentation
//! Publication-trust verification regressions.

use prikk_object::{ObjectEnvelope, ObjectType};

use super::super::trust::PublicationTrustVerifier;
use crate::test_support::unique_temp_dir;
use crate::{
    Ed25519MaintainerSigner, MaintainerSigner, RepositoryLayout, add_trusted_maintainer,
    maintainer_signature,
};

#[test]
fn missing_policy_is_reported_once_while_count_advances() -> prikk_error::Result<()> {
    assert_invalid_policy_sequence("verify-trust-missing-policy", None)
}

#[test]
fn malformed_policy_is_reported_once_while_count_advances() -> prikk_error::Result<()> {
    assert_invalid_policy_sequence(
        "verify-trust-malformed-policy",
        Some(b"not a valid trust policy container at all"),
    )
}

#[test]
fn trusted_and_untrusted_envelopes_preserve_counts_and_order() -> prikk_error::Result<()> {
    let root = unique_temp_dir("verify-trust-envelope-order");
    let layout = RepositoryLayout::init(root.clone())?;
    let signer = Ed25519MaintainerSigner::from_seed("trusted-maintainer", &[0x46; 32])?;
    add_trusted_maintainer(&layout, signer.key_id(), &public_key_hex(&signer))?;

    let trusted = signed_envelope(b"trusted", &signer)?;
    let untrusted_first =
        ObjectEnvelope::unsigned(ObjectType::Blob, 1, b"untrusted-first".to_vec());
    let untrusted_second =
        ObjectEnvelope::unsigned(ObjectType::Blob, 1, b"untrusted-second".to_vec());
    let first_id = untrusted_first.object_id().to_string();
    let second_id = untrusted_second.object_id().to_string();

    let mut verifier = PublicationTrustVerifier::new(&layout);
    verifier.verify(&trusted)?;
    assert_eq!(verifier.checked_records, 1);
    assert!(verifier.issues.is_empty());

    verifier.verify(&untrusted_first)?;
    verifier.verify(&trusted)?;
    verifier.verify(&untrusted_second)?;

    assert_eq!(verifier.checked_records, 4);
    assert_eq!(verifier.issues.len(), 2);
    assert!(
        verifier
            .issues
            .iter()
            .all(|issue| { issue.code == "PRIKK-TRUST-PUBLICATION-UNTRUSTED" })
    );
    let mut issues = verifier.issues.iter();
    assert!(
        issues
            .next()
            .is_some_and(|issue| issue.message.contains(&first_id))
    );
    assert!(
        issues
            .next()
            .is_some_and(|issue| issue.message.contains(&second_id))
    );
    assert!(issues.next().is_none());

    let _ = std::fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn verify_returns_the_matched_key_id_on_success() -> prikk_error::Result<()> {
    let root = unique_temp_dir("verify-trust-matched-key-id");
    let layout = RepositoryLayout::init(root.clone())?;
    let signer = Ed25519MaintainerSigner::from_seed("matched-maintainer", &[0x21; 32])?;
    add_trusted_maintainer(&layout, signer.key_id(), &public_key_hex(&signer))?;

    let envelope = signed_envelope(b"matched", &signer)?;
    let mut verifier = PublicationTrustVerifier::new(&layout);
    let matched = verifier.verify(&envelope)?;
    assert_eq!(matched, Some(signer.key_id().to_string()));
    assert!(verifier.issues.is_empty());

    let _ = std::fs::remove_dir_all(root);
    Ok(())
}

fn assert_invalid_policy_sequence(
    fixture_name: &str,
    malformed_policy: Option<&[u8]>,
) -> prikk_error::Result<()> {
    let root = unique_temp_dir(fixture_name);
    let layout = RepositoryLayout::init(root.clone())?;
    if let Some(policy) = malformed_policy {
        std::fs::write(
            layout.trust_policy_container_slot_path(crate::layout::ContainerSlot::A),
            policy,
        )?;
    }

    let first = ObjectEnvelope::unsigned(ObjectType::Blob, 1, b"first".to_vec());
    let second = ObjectEnvelope::unsigned(ObjectType::Blob, 1, b"second".to_vec());
    let mut verifier = PublicationTrustVerifier::new(&layout);

    verifier.verify(&first)?;
    assert_eq!(verifier.checked_records, 1);
    assert_eq!(verifier.issues.len(), 1);
    assert_eq!(
        verifier.issues.first().map(|issue| issue.code),
        Some("PRIKK-TRUST-POLICY-INVALID")
    );

    verifier.verify(&second)?;
    assert_eq!(verifier.checked_records, 2);
    assert_eq!(verifier.issues.len(), 1);
    assert_eq!(
        verifier.issues.first().map(|issue| issue.code),
        Some("PRIKK-TRUST-POLICY-INVALID")
    );

    let _ = std::fs::remove_dir_all(root);
    Ok(())
}

fn signed_envelope(
    payload: &[u8],
    signer: &impl MaintainerSigner,
) -> prikk_error::Result<ObjectEnvelope> {
    let mut envelope = ObjectEnvelope::unsigned(ObjectType::Blob, 1, payload.to_vec());
    envelope.add_signature(maintainer_signature(
        signer,
        envelope.object_type,
        envelope.object_id(),
    )?)?;
    Ok(envelope)
}

fn public_key_hex(signer: &impl MaintainerSigner) -> String {
    signer
        .public_key_bytes()
        .iter()
        .map(|byte| format!("{byte:02x}"))
        .collect()
}