#![allow(clippy::indexing_slicing)]
use prikk_crypto::Ed25519KeyPair;
use crate::{
GatedOperation, RepositoryLayout, acquire_container_locks, add_trusted_maintainer,
load_maintainer_trust_policy, remove_trusted_maintainer, verify_signer_trusted,
};
use crate::fsutil::{TestFailPoint, fail_after_for_test, fail_once_for_test};
use crate::layout::LockableContainer;
use crate::maintainer_signing::Ed25519MaintainerSigner;
use crate::test_support::unique_temp_dir;
fn public_key_hex(seed: &[u8; 32]) -> String {
let key_pair = Ed25519KeyPair::from_seed(seed);
key_pair
.public_key_bytes()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
#[test]
fn add_and_load_single_maintainer_policy() {
let root = unique_temp_dir("trust-add-load");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let seed = [7_u8; 32];
let public_key = public_key_hex(&seed);
let added = add_trusted_maintainer(&layout, "maintainer_1", &public_key);
assert!(added.is_ok());
if let Ok((adopted, newly_added)) = added {
assert!(newly_added);
assert_eq!(adopted.key_id, "maintainer_1");
}
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(loaded.keys.len(), 1);
assert_eq!(loaded.keys[0].key_id, "maintainer_1");
assert_eq!(
loaded.keys[0].public_key,
Ed25519KeyPair::from_seed(&seed).public_key_bytes()
);
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn trust_add_and_remove_refuse_while_trust_policy_lock_is_externally_held()
-> prikk_error::Result<()> {
let root = unique_temp_dir("trust-container-lock-conflict");
let layout = RepositoryLayout::init(root.clone())?;
let key = public_key_hex(&[7_u8; 32]);
let other_key = public_key_hex(&[8_u8; 32]);
let held = acquire_container_locks(&layout, &[LockableContainer::TrustPolicy])?;
assert!(add_trusted_maintainer(&layout, "blocked", &key).is_err());
drop(held);
assert!(add_trusted_maintainer(&layout, "blocked", &key).is_ok());
assert!(add_trusted_maintainer(&layout, "kept", &other_key).is_ok());
let held = acquire_container_locks(&layout, &[LockableContainer::TrustPolicy])?;
assert!(remove_trusted_maintainer(&layout, "blocked").is_err());
drop(held);
assert!(remove_trusted_maintainer(&layout, "blocked")?);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn adopting_a_second_key_id_keeps_the_first_trusted() {
let root = unique_temp_dir("trust-add-second");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let first_key = public_key_hex(&[1_u8; 32]);
let second_key = public_key_hex(&[2_u8; 32]);
assert!(add_trusted_maintainer(&layout, "alice", &first_key).is_ok());
assert!(add_trusted_maintainer(&layout, "bob", &second_key).is_ok());
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(loaded.keys.len(), 2);
assert_eq!(loaded.keys[0].key_id, "alice");
assert_eq!(loaded.keys[1].key_id, "bob");
assert_eq!(
loaded.keys[0].public_key,
Ed25519KeyPair::from_seed(&[1_u8; 32]).public_key_bytes()
);
assert_eq!(
loaded.keys[1].public_key,
Ed25519KeyPair::from_seed(&[2_u8; 32]).public_key_bytes()
);
}
if let Ok(alice) = Ed25519MaintainerSigner::from_seed("alice", &[1_u8; 32]) {
assert!(verify_signer_trusted(&layout, &alice, GatedOperation::Seal).is_ok());
}
if let Ok(bob) = Ed25519MaintainerSigner::from_seed("bob", &[2_u8; 32]) {
assert!(verify_signer_trusted(&layout, &bob, GatedOperation::Seal).is_ok());
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn readopting_the_same_key_id_and_key_is_idempotent() {
let root = unique_temp_dir("trust-readopt-same");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let key = public_key_hex(&[3_u8; 32]);
assert!(add_trusted_maintainer(&layout, "maintainer", &key).is_ok());
let second_call = add_trusted_maintainer(&layout, "maintainer", &key);
assert!(second_call.is_ok());
if let Ok((_, newly_added)) = second_call {
assert!(!newly_added);
}
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(
loaded.keys.len(),
1,
"idempotent re-adoption must not duplicate"
);
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn readopting_an_existing_key_id_with_a_different_key_is_refused() {
let root = unique_temp_dir("trust-readopt-conflict");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let original = public_key_hex(&[4_u8; 32]);
let conflicting = public_key_hex(&[5_u8; 32]);
assert!(add_trusted_maintainer(&layout, "maintainer", &original).is_ok());
assert!(add_trusted_maintainer(&layout, "maintainer", &conflicting).is_err());
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(loaded.keys.len(), 1);
assert_eq!(
loaded.keys[0].public_key,
Ed25519KeyPair::from_seed(&[4_u8; 32]).public_key_bytes()
);
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn policy_rejects_unsafe_key_id() {
let root = unique_temp_dir("trust-unsafe-key");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let public_key = public_key_hex(&[7_u8; 32]);
assert!(add_trusted_maintainer(&layout, "../bad", &public_key).is_err());
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn a_corrupt_policy_container_fails_closed_rather_than_resolving_stale_or_empty() {
let root = unique_temp_dir("trust-corrupt-policy");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let key = public_key_hex(&[1_u8; 32]);
assert!(add_trusted_maintainer(&layout, "alice", &key).is_ok());
assert!(
std::fs::write(
layout.trust_policy_container_slot_path(crate::layout::ContainerSlot::A),
b"not a valid trust policy container at all",
)
.is_ok()
);
assert!(
load_maintainer_trust_policy(&layout).is_err(),
"a corrupt policy container must fail closed, not resolve to the prior snapshot or empty"
);
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn signer_trust_binding_rejects_seed_public_key_mismatch() {
let root = unique_temp_dir("trust-seed-mismatch");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let trusted_seed = [7_u8; 32];
let signing_seed = [8_u8; 32];
let public_key = public_key_hex(&trusted_seed);
assert!(add_trusted_maintainer(&layout, "maintainer", &public_key).is_ok());
let signer = match Ed25519MaintainerSigner::from_seed("maintainer", &signing_seed) {
Ok(signer) => signer,
Err(error) => panic!("test maintainer signer should be constructible: {error}"),
};
assert!(verify_signer_trusted(&layout, &signer, GatedOperation::Seal).is_err());
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn failed_key_append_keeps_previous_effective_policy() {
let root = unique_temp_dir("trust-key-append-failure");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let old_key = public_key_hex(&[7_u8; 32]);
let new_key = public_key_hex(&[8_u8; 32]);
assert!(add_trusted_maintainer(&layout, "old", &old_key).is_ok());
fail_once_for_test(TestFailPoint::AppendWrite);
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_err());
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(loaded.keys.len(), 1);
assert_eq!(loaded.keys[0].key_id, "old");
}
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_ok());
let retried = load_maintainer_trust_policy(&layout);
assert!(retried.is_ok());
if let Ok(retried) = retried {
assert_eq!(retried.keys.len(), 2);
assert_eq!(retried.keys[0].key_id, "old");
assert_eq!(retried.keys[1].key_id, "new");
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn failed_policy_append_exposes_retryable_new_effective_policy() {
let root = unique_temp_dir("trust-policy-append-failure");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let old_key = public_key_hex(&[7_u8; 32]);
let new_key = public_key_hex(&[8_u8; 32]);
assert!(add_trusted_maintainer(&layout, "old", &old_key).is_ok());
fail_after_for_test(TestFailPoint::RequiredDirectorySync, 3);
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_err());
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert_eq!(
loaded.keys.len(),
2,
"the policy append already landed before the directory sync failed"
);
assert_eq!(loaded.keys[1].key_id, "new");
}
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_ok());
let retried = load_maintainer_trust_policy(&layout);
assert!(retried.is_ok());
if let Ok(retried) = retried {
assert_eq!(retried.keys.len(), 2);
assert_eq!(retried.keys[1].key_id, "new");
}
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn trust_reads_and_updates_remain_on_retained_repository_root() -> prikk_error::Result<()> {
let root = unique_temp_dir("trust-root-replacement");
let layout = RepositoryLayout::init(root.clone())?;
let first = public_key_hex(&[7_u8; 32]);
let second = public_key_hex(&[8_u8; 32]);
assert!(add_trusted_maintainer(&layout, "maintainer", &first).is_ok());
let displaced = root.join(".prikk-displaced");
std::fs::rename(layout.prikk_dir(), &displaced)?;
std::fs::create_dir_all(root.join(".prikk/trust"))?;
std::fs::copy(
displaced.join("trust/keys.container"),
root.join(".prikk/trust/keys.container"),
)?;
std::fs::copy(
displaced.join("trust/policy-a.container"),
root.join(".prikk/trust/policy-a.container"),
)?;
std::fs::copy(
displaced.join("trust/policy-b.container"),
root.join(".prikk/trust/policy-b.container"),
)?;
std::fs::copy(
displaced.join("trust/policy-generation.log"),
root.join(".prikk/trust/policy-generation.log"),
)?;
assert_eq!(
load_maintainer_trust_policy(&layout)?.keys[0].key_id,
"maintainer"
);
assert!(add_trusted_maintainer(&layout, "second", &second).is_ok());
let loaded = load_maintainer_trust_policy(&layout)?;
assert_eq!(loaded.keys.len(), 2);
assert_eq!(loaded.keys[1].key_id, "second");
assert_eq!(
loaded.keys[1].public_key,
Ed25519KeyPair::from_seed(&[8_u8; 32]).public_key_bytes()
);
assert!(root.join(".prikk/trust/policy-a.container").is_file());
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn trust_append_failures_pin_effective_state_and_retry() -> prikk_error::Result<()> {
for (boundary, skip) in [("key", 0), ("policy", 1)] {
let root = unique_temp_dir(&format!("trust-append-failure-{boundary}"));
let layout = RepositoryLayout::init(root.clone())?;
let old_key = public_key_hex(&[7_u8; 32]);
let new_key = public_key_hex(&[8_u8; 32]);
assert!(add_trusted_maintainer(&layout, "old", &old_key).is_ok());
fail_after_for_test(TestFailPoint::AppendWrite, skip);
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_err());
let retained = load_maintainer_trust_policy(&layout)?;
assert_eq!(retained.keys.len(), 1, "boundary {boundary}");
assert_eq!(retained.keys[0].key_id, "old");
assert_eq!(
retained.keys[0].public_key,
Ed25519KeyPair::from_seed(&[7_u8; 32]).public_key_bytes()
);
assert!(add_trusted_maintainer(&layout, "new", &new_key).is_ok());
let retried = load_maintainer_trust_policy(&layout)?;
assert_eq!(retried.keys.len(), 2, "boundary {boundary}");
assert_eq!(retried.keys[1].key_id, "new");
let _ = std::fs::remove_dir_all(root);
}
Ok(())
}
#[test]
fn remove_revokes_a_key_without_disturbing_others() -> prikk_error::Result<()> {
let root = unique_temp_dir("trust-remove-basic");
let layout = RepositoryLayout::init(root.clone())?;
let alice_key = public_key_hex(&[1_u8; 32]);
let bob_key = public_key_hex(&[2_u8; 32]);
add_trusted_maintainer(&layout, "alice", &alice_key)?;
add_trusted_maintainer(&layout, "bob", &bob_key)?;
assert!(remove_trusted_maintainer(&layout, "alice")?);
let loaded = load_maintainer_trust_policy(&layout)?;
assert_eq!(loaded.keys.len(), 1);
assert_eq!(loaded.keys[0].key_id, "bob");
let alice_signer = Ed25519MaintainerSigner::from_seed("alice", &[1_u8; 32])?;
assert!(verify_signer_trusted(&layout, &alice_signer, GatedOperation::Seal).is_err());
let bob_signer = Ed25519MaintainerSigner::from_seed("bob", &[2_u8; 32])?;
assert!(verify_signer_trusted(&layout, &bob_signer, GatedOperation::Seal).is_ok());
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn remove_of_an_unadopted_key_id_is_a_reported_no_op() -> prikk_error::Result<()> {
let root = unique_temp_dir("trust-remove-noop");
let layout = RepositoryLayout::init(root.clone())?;
add_trusted_maintainer(&layout, "alice", &public_key_hex(&[1_u8; 32]))?;
assert!(!remove_trusted_maintainer(&layout, "never-adopted")?);
let loaded = load_maintainer_trust_policy(&layout)?;
assert_eq!(loaded.keys.len(), 1);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn remove_of_the_last_adopted_key_is_refused() -> prikk_error::Result<()> {
let root = unique_temp_dir("trust-remove-last");
let layout = RepositoryLayout::init(root.clone())?;
add_trusted_maintainer(&layout, "alice", &public_key_hex(&[1_u8; 32]))?;
assert!(remove_trusted_maintainer(&layout, "alice").is_err());
let loaded = load_maintainer_trust_policy(&layout)?;
assert_eq!(
loaded.keys.len(),
1,
"the refused removal must not have changed anything"
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn a_removed_keys_case_variant_becomes_addable() {
let root = unique_temp_dir("trust-remove-case-variant");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let original_key = public_key_hex(&[1_u8; 32]);
let other_key = public_key_hex(&[2_u8; 32]);
assert!(add_trusted_maintainer(&layout, "Dev-Maintainer", &original_key).is_ok());
assert!(add_trusted_maintainer(&layout, "someone-else", &other_key).is_ok());
assert!(remove_trusted_maintainer(&layout, "Dev-Maintainer").is_ok());
assert!(add_trusted_maintainer(&layout, "dev-maintainer", &other_key).is_ok());
}
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn a_changed_key_under_a_removed_and_readded_id_is_still_refused() {
let root = unique_temp_dir("trust-remove-tofu-persists");
let layout = RepositoryLayout::init(root.clone());
assert!(layout.is_ok());
if let Ok(layout) = layout {
let original_key = public_key_hex(&[1_u8; 32]);
let other_key = public_key_hex(&[2_u8; 32]);
assert!(add_trusted_maintainer(&layout, "maintainer", &original_key).is_ok());
assert!(add_trusted_maintainer(&layout, "someone-else", &other_key).is_ok());
assert!(remove_trusted_maintainer(&layout, "maintainer").is_ok());
let changed_key = public_key_hex(&[3_u8; 32]);
assert!(add_trusted_maintainer(&layout, "maintainer", &changed_key).is_err());
assert!(add_trusted_maintainer(&layout, "maintainer", &original_key).is_ok());
let loaded = load_maintainer_trust_policy(&layout);
assert!(loaded.is_ok());
if let Ok(loaded) = loaded {
assert!(loaded.keys.iter().any(|key| key.key_id == "maintainer"));
}
}
let _ = std::fs::remove_dir_all(root);
}