#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use gunnar_crypt::members::Level;
use gunnar_crypt::skwrap;
use gunnar_crypt::userkey::{self, security_key, SecurityKeyWrap, UserKey, UserKeyError};
const CRED: &[u8] = b"credential v2";
fn prf(fill: u8) -> [u8; 32] {
[fill; 32]
}
#[test]
fn a_v2_wrap_round_trips_and_names_no_x25519() {
for level in [Level::QuantumSafe, Level::Classic] {
let user = UserKey::generate(level).unwrap();
let wrap = user.wrap_to_security_key_v2(&prf(7), CRED).unwrap();
assert!(wrap.starts_with(skwrap::TAG), "{wrap}");
assert!(skwrap::is_v2(&wrap));
let parts = skwrap::parse(&wrap).expect("a well-formed v2 wrap");
assert_eq!((parts.salt.len(), parts.nonce.len()), (32, 24));
assert!(
gunnar_crypt::policy::age_stanzas(wrap.as_bytes()).is_none(),
"not an age file: no X25519 stanza"
);
let (opened, version) =
userkey::open_with_security_key(wrap.as_bytes(), &prf(7), CRED, &user.public())
.unwrap();
assert_eq!(
(opened.public(), version),
(user.public(), SecurityKeyWrap::V2)
);
let again = user.wrap_to_security_key_v2(&prf(7), CRED).unwrap();
assert_ne!(again, wrap, "a fresh salt and nonce per wrap");
assert!(userkey::wrap_is_quantum_safe("security-key", &wrap));
}
assert_eq!(skwrap::INFO, b"gunnar-crypt user-key wrap v2");
}
#[test]
fn a_wrong_prf_output_is_refused() {
let user = UserKey::generate(Level::NEW).unwrap();
let wrap = user.wrap_to_security_key_v2(&prf(7), CRED).unwrap();
let wrong = userkey::open_with_security_key(wrap.as_bytes(), &prf(8), CRED, &user.public())
.unwrap_err();
assert_eq!(wrong, UserKeyError::WrongKey);
let other = userkey::open_with_security_key(
wrap.as_bytes(),
&prf(7),
b"another credential",
&user.public(),
)
.unwrap_err();
assert_eq!(
other,
UserKeyError::WrongKey,
"the credential id is bound in"
);
let mut parts = skwrap::parse(&wrap).unwrap();
parts.sealed[3] ^= 1;
let flipped = skwrap::format(&parts);
assert_eq!(
userkey::open_with_security_key(flipped.as_bytes(), &prf(7), CRED, &user.public())
.unwrap_err(),
UserKeyError::WrongKey
);
let someone = UserKey::generate(Level::NEW).unwrap();
let swapped = someone.wrap_to_security_key_v2(&prf(7), CRED).unwrap();
assert_eq!(
userkey::open_with_security_key(swapped.as_bytes(), &prf(7), CRED, &user.public())
.unwrap_err(),
UserKeyError::NotThisAccountsKey,
"a substituted wrap is refused"
);
for bad in [
"",
"gunnar-crypt user-key wrap v2",
"gunnar-crypt user-key wrap v2 AAAA AAAA AAAA",
"gunnar-crypt user-key wrap v3 x y z",
] {
assert!(skwrap::parse(bad).is_none(), "{bad:?}");
}
}
#[test]
fn a_v1_wrap_opens_and_is_rewrapped_to_v2() {
let user = UserKey::generate(Level::NEW).unwrap();
let v1 = user
.wrap_to_security_key(&security_key::recipient(&prf(5), CRED))
.unwrap();
assert!(!skwrap::is_v2(&v1));
assert!(!userkey::wrap_is_quantum_safe("security-key", &v1));
let (opened, version) =
userkey::open_with_security_key(v1.as_bytes(), &prf(5), CRED, &user.public()).unwrap();
assert_eq!(version, SecurityKeyWrap::V1);
let v2 = opened.wrap_to_security_key_v2(&prf(5), CRED).unwrap();
let (again, version) =
userkey::open_with_security_key(v2.as_bytes(), &prf(5), CRED, &user.public()).unwrap();
assert_eq!(
(again.public(), version),
(user.public(), SecurityKeyWrap::V2)
);
assert!(userkey::wrap_is_quantum_safe("security-key", &v2));
assert!(
!userkey::wrap_is_quantum_safe("ssh", &v2),
"the SSH method stays not quantum-safe"
);
assert!(userkey::wrap_is_quantum_safe("recovery-code", ""));
}