The `w1-test-doubles` task: the in-memory seams every later wave's contract tests run against, behind their production seams. - tests/common/mod.rs — the doubles, test-only (nothing reachable from lib.rs, no feature): MemIdentityProvider (registered fingerprints -> alkcall Identity resolutions over the IdentityProvider trait; token path resolves none per ADR-004), TestVault (a wrapper over alkvault 0.1's shipped in-memory construction — resolving the flagged vault-double question: wrap, not re-impl), MemHostKeyPolicy (exact (host, fingerprint) rules + explicit default verdict, fail-closed by default, inspectable decision log), and the fixture table. Module docs record the location/vault/fixture decisions and the row/stamp discipline (greppable Contract stamps). - tests/vectors/ — one unified fixture set: four ssh-keygen-minted ed25519 keypairs (committed .key + .pub + fingerprints.txt recorded from ssh-keygen -lf, OpenSSH_9.6p1). The previous inline PEM constants in src/key_source.rs were rejected by OpenSSH itself (russh-only decodable); all keys were re-minted and the overlapping vector set retired — one fixture set now serves the parity tests, the custody round-trip, and every later auth/mapping test (src-unit tests share it via include_str!). - tests/doubles.rs — the end-to-end rows: register -> resolve, seal -> source -> decode (+ locked/absent/tampered postures), host-key set in order, policy verdicts honored-and-logged. - tests/fingerprint_parity.rs and src/key_source.rs tests moved onto the shared fixture files; src changes are #cfg(test) only. Verification: cargo test (33 + 9 + 5 green; --all-features also green), cargo clippy --all-targets -- -D warnings clean, cargo fmt --check clean, taskgraph validate ok (12 tasks).
147 lines
5.7 KiB
Rust
147 lines
5.7 KiB
Rust
//! Fingerprint-format parity — ADR-004 decision 1's contract
|
|
//! invariant proven against independent-issuer vectors (POC-1 §1).
|
|
//!
|
|
//! The vectors are the wave-1 test-key fixture set
|
|
//! (`tests/common/mod.rs`'s [`FIXTURE_KEYS`]; the committed files
|
|
//! under `tests/vectors/`), each key's fingerprint recorded
|
|
//! byte-verbatim from `ssh-keygen -lf` (OpenSSH 9.6p1, at
|
|
//! vector-generation time; see `tests/vectors/fingerprints.txt`). The
|
|
//! crate's single fingerprint path must reproduce those strings
|
|
//! exactly from every issuer shape — the committed public blob, the
|
|
//! private key's derived public half, and the committed `.pub` file —
|
|
//! so registration values from any standard issuer compare equal,
|
|
//! without cross-issuer drift.
|
|
|
|
mod common;
|
|
|
|
use std::fs;
|
|
|
|
use common::FIXTURE_KEYS;
|
|
|
|
const VECTOR_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/vectors");
|
|
|
|
/// The crate's single fingerprint path reproduces the recorded value
|
|
/// from the committed public blob alone — the public-key-only issuer
|
|
/// shape (a registry that only ever sees public material).
|
|
///
|
|
/// - *Contract stamp:* ADR-004 decision 1 (fingerprint parity with
|
|
/// `ssh-keygen -lf`; POC-1 §1's parity checkpoint shape).
|
|
#[test]
|
|
fn computed_fingerprints_match_recorded_values_for_public_blobs() {
|
|
for key in FIXTURE_KEYS {
|
|
let parsed = russh::keys::parse_public_key_base64(key.blob)
|
|
.expect("committed fixture blob parses as OpenSSH key material");
|
|
assert_eq!(
|
|
alkssh::fingerprint(&parsed),
|
|
key.fingerprint,
|
|
"parity drift on {name}",
|
|
name = key.name
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The private-key path — the shape every auth/mapping test uses
|
|
/// (`decode_secret_key` → sign/serve) — yields a public half whose
|
|
/// fingerprint is the same recorded value.
|
|
///
|
|
/// - *Contract stamp:* ADR-004 decision 1 (the fingerprint string
|
|
/// format is a contract invariant across issuers).
|
|
#[test]
|
|
fn private_derived_fingerprints_match_recorded_values() {
|
|
for key in FIXTURE_KEYS {
|
|
let decoded = key.private_key();
|
|
assert_eq!(
|
|
alkssh::fingerprint(decoded.public_key()),
|
|
key.fingerprint,
|
|
"private-derived parity drift on {name}",
|
|
name = key.name
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The committed fixture files are the recorded keys: each `.pub`
|
|
/// starts with the recorded blob line, and each `.key` is an
|
|
/// unencrypted OpenSSH private-key PEM (the ADR-005 decision 1 inner
|
|
/// format) whose decoded public half fingerprints to the recorded
|
|
/// value.
|
|
///
|
|
/// - *Contract stamp:* ADR-004 decision 1 (independent-issuer vectors
|
|
/// are the parity proof — the commitment must be the recorded key).
|
|
/// - *Contract stamp:* ADR-005 decision 1 (the fixtures' inner format
|
|
/// is exactly the at-rest contract: unencrypted OpenSSH PEM).
|
|
#[test]
|
|
fn committed_fixture_files_match_the_recorded_keys() {
|
|
for key in FIXTURE_KEYS {
|
|
let pub_path = format!("{VECTOR_DIR}/{}.pub", key.name);
|
|
let pub_file = fs::read_to_string(&pub_path).expect("committed .pub readable");
|
|
let expected_line = format!("ssh-ed25519 {blob} ", blob = key.blob);
|
|
assert!(
|
|
pub_file.starts_with(&expected_line),
|
|
"fixture file {name} does not start with the recorded blob",
|
|
name = key.name
|
|
);
|
|
|
|
let key_path = format!("{VECTOR_DIR}/{}.key", key.name);
|
|
let key_file = fs::read_to_string(&key_path).expect("committed .key readable");
|
|
assert!(
|
|
key_file.starts_with("-----BEGIN OPENSSH PRIVATE KEY-----"),
|
|
"fixture {name} is not an OpenSSH private-key PEM",
|
|
name = key.name
|
|
);
|
|
assert_eq!(key_file, key.pem, "committed .key is the fixture PEM");
|
|
assert!(
|
|
!key_file.contains("ENCRYPTED"),
|
|
"fixture {name} is unencrypted (ADR-005 decision 1)",
|
|
name = key.name
|
|
);
|
|
let decoded = key.private_key();
|
|
let from_file = decoded.public_key();
|
|
assert_eq!(
|
|
alkssh::fingerprint(from_file),
|
|
key.fingerprint,
|
|
"committed .key public half drifts on {name}",
|
|
name = key.name
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The recorded manifest (`tests/vectors/fingerprints.txt`) records
|
|
/// every fixture — no fixture ships without its independent issuer's
|
|
/// fingerprint.
|
|
///
|
|
/// - *Contract stamp:* wave-1 test-double convention (recorded
|
|
/// `ssh-keygen -lf` fingerprints, the independent-issuer record).
|
|
#[test]
|
|
fn recorded_manifest_covers_every_fixture() {
|
|
let manifest =
|
|
fs::read_to_string(format!("{VECTOR_DIR}/fingerprints.txt")).expect("manifest readable");
|
|
for key in FIXTURE_KEYS {
|
|
assert!(
|
|
manifest.contains(&format!("{}.pub {fp}", key.name, fp = key.fingerprint)),
|
|
"manifest does not record {name} at {fp}",
|
|
name = key.name,
|
|
fp = key.fingerprint
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The registered-string comparison is byte-identical: the computed
|
|
/// value meets a registered `ssh-keygen` string with no padding or
|
|
/// url-safe-alphabet drift (`+`/`/`, no `=`).
|
|
///
|
|
/// - *Contract stamp:* ADR-004 decision 1 (`SHA256:` + unpadded
|
|
/// base64, standard alphabet — byte-identical comparison by
|
|
/// contract).
|
|
#[test]
|
|
fn registered_string_compare_is_byte_identical() {
|
|
let alpha = FIXTURE_KEYS
|
|
.iter()
|
|
.find(|k| k.name == "alpha")
|
|
.expect("alpha fixture present");
|
|
let computed = alkssh::fingerprint(&alpha.public_key());
|
|
let registered: &str = alpha.fingerprint;
|
|
assert_eq!(computed, registered);
|
|
assert_eq!(registered.len(), 50, "SHA256: + 43 unpadded base64 chars");
|
|
assert!(!registered.contains('=') && !registered.contains('!'));
|
|
}
|