//! Daemon-side wire-format crosscheck against the shared vector at //! `tests/crosscheck/vector.json` (at the repo root, two levels up //! from this crate). //! //! Each fixture in vector.json was generated by `reference_signer.py` //! using independent Python crypto, then is consumed by: //! - the TypeScript SDK (via tests/crosscheck/run_ts.mjs) //! - the Python SDK (via licensing-client-python/tests/test_crosscheck.py) //! - the Rust SDK (via its own unit tests against the same byte layouts) //! //! What was missing: the **daemon itself** never ran against the same //! vectors. Drift in the daemon's parser (which uses the same byte //! layouts but shares no code with the SDKs) could ship undetected //! until an SDK on the wire couldn't validate a daemon-issued key. //! //! This file closes the loop. For every fixture we call //! `crypto::parse_key` and assert that every parsed field matches //! the `expected` block byte-for-byte. If a future change to the //! daemon's parser drifts away from the wire format the SDKs //! enforce, this fails before the build even lands in a .s9pk. //! //! Signature verification is intentionally NOT included here — the //! crypto unit tests in `src/crypto/mod.rs` already prove the //! daemon's sign+verify roundtrip works, and the SDKs prove the //! same key verifies in three independent crypto implementations. //! What hadn't been pinned was the parser-to-fields contract, which //! is what this file enforces. use keysat::crypto; use serde_json::Value; use uuid::Uuid; const VECTOR_PATH: &str = concat!( env!("CARGO_MANIFEST_DIR"), "/../../tests/crosscheck/vector.json" ); fn load_vector() -> Value { let raw = std::fs::read_to_string(VECTOR_PATH).unwrap_or_else(|e| { panic!("crosscheck vector not at {VECTOR_PATH}: {e}\n\ If you cloned without the parent repo, make sure the \ tests/crosscheck/ directory is present.") }); serde_json::from_str(&raw).expect("vector.json should be valid JSON") } /// Run one fixture. Asserts every `expected` field matches the /// daemon's `parse_key` output. The vector's field names mirror /// what the SDKs use, which is camelCase; the daemon uses snake_case /// for the same things internally. fn check_fixture(name: &str, fixture: &Value) { let key = fixture["licenseKey"] .as_str() .unwrap_or_else(|| panic!("{name}: missing licenseKey")); let expected = &fixture["expected"]; let (payload, _signature, _signed_bytes) = crypto::parse_key(key) .unwrap_or_else(|e| panic!("{name}: parse_key failed: {e}")); // Version let want_version = expected["version"].as_u64().unwrap(); assert_eq!( payload.version as u64, want_version, "{name}: version mismatch" ); // UUIDs let want_product = Uuid::parse_str(expected["productUuid"].as_str().unwrap()).unwrap(); assert_eq!(payload.product_id, want_product, "{name}: product_id"); let want_license = Uuid::parse_str(expected["licenseUuid"].as_str().unwrap()).unwrap(); assert_eq!(payload.license_id, want_license, "{name}: license_id"); // Times (Unix seconds) let want_issued = expected["issuedAt"].as_i64().unwrap(); assert_eq!(payload.issued_at, want_issued, "{name}: issued_at"); let want_expires = expected["expiresAt"].as_i64().unwrap(); assert_eq!(payload.expires_at, want_expires, "{name}: expires_at"); // Flags + derived bits let want_flags = expected["flags"].as_u64().unwrap(); assert_eq!(payload.flags as u64, want_flags, "{name}: flags"); let want_fp_bound = expected["isFingerprintBound"].as_bool().unwrap(); assert_eq!( payload.is_fingerprint_bound(), want_fp_bound, "{name}: is_fingerprint_bound" ); let want_trial = expected["isTrial"].as_bool().unwrap(); assert_eq!(payload.is_trial(), want_trial, "{name}: is_trial"); // Entitlements (order-sensitive — wire format preserves order) let want_ents: Vec = expected["entitlements"] .as_array() .unwrap() .iter() .map(|v| v.as_str().unwrap().to_string()) .collect(); assert_eq!(payload.entitlements, want_ents, "{name}: entitlements"); // Fingerprint hash. The `expected.fingerprintHashHex` is what the // wire format must contain; we compare against payload's bytes. let want_fp_hex = expected["fingerprintHashHex"].as_str().unwrap(); let got_fp_hex = hex::encode(payload.fingerprint_hash); assert_eq!(got_fp_hex, want_fp_hex, "{name}: fingerprint_hash"); // Cross-check: if `fingerprintRaw` is provided and binding is // active, hashing the raw fingerprint with the daemon's helper // should produce the same hex. This locks in the // `hash_fingerprint` SHA-256 contract that SDKs rely on. if want_fp_bound { if let Some(raw) = expected["fingerprintRaw"].as_str() { let computed = crypto::hash_fingerprint(raw); let computed_hex = hex::encode(computed); assert_eq!( computed_hex, want_fp_hex, "{name}: hash_fingerprint(raw) does not match wire bytes" ); } } } #[test] fn vector_v1_legacy_roundtrip() { let vector = load_vector(); check_fixture("v1", &vector["v1"]); } #[test] fn vector_v2_trial_with_entitlements() { let vector = load_vector(); check_fixture("v2", &vector["v2"]); } #[test] fn vector_v2_perpetual_unbound() { let vector = load_vector(); check_fixture("v2_perpetual_unbound", &vector["v2_perpetual_unbound"]); } /// The vector file's publicKeyPem is the master crosscheck key — /// every fixture's signature was made against that PEM. This test /// just locks in that the file has it (so a future regeneration of /// the vectors keeps the public-key surface accessible to all /// tooling that reads vector.json). #[test] fn vector_has_public_key_pem() { let vector = load_vector(); let pem = vector["publicKeyPem"] .as_str() .expect("vector.json must include publicKeyPem"); assert!( pem.contains("BEGIN PUBLIC KEY"), "publicKeyPem should be a PEM block: {pem}" ); }