fix(core): NFC normalize backup passphrase (audit B2)

Backup KDF was passing raw passphrase bytes to Argon2id without NFC
normalization, causing cross-platform restore failures for non-ASCII
passphrases (macOS NFD vs Linux NFC).

Now matches derive_master_key behavior from crypto.rs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-05-02 01:29:08 -04:00
parent 27c4ac69cb
commit bbdbcca87b
2 changed files with 36 additions and 1 deletions

View File

@@ -186,3 +186,30 @@ fn tampered_ciphertext_rejected_as_decrypt_error() {
other => panic!("expected Decrypt for tampered tag, got {other:?}"),
}
}
#[test]
fn backup_roundtrip_with_nfd_passphrase() {
// "café" in NFD (decomposed: e + combining acute accent)
let nfd_passphrase = "caf\u{0065}\u{0301}";
// "café" in NFC (precomposed é)
let nfc_passphrase = "caf\u{00E9}";
let input = BackupInput {
salt: &[0u8; 32],
params_json: r#"{"format_version":2,"kdf":{"argon2_m":256,"argon2_t":1,"argon2_p":1},"aead":"xchacha20poly1305","salt_path":".relicario/salt"}"#,
devices_json: "[]",
manifest_enc: &[1, 2, 3],
settings_enc: &[4, 5, 6],
items: vec![],
attachments: vec![],
reference_jpg: None,
git_archive: None,
};
// Pack with NFD passphrase
let packed = pack_backup(input, nfd_passphrase).unwrap();
// Unpack with NFC passphrase — should work after fix
let unpacked = unpack_backup(&packed, nfc_passphrase).unwrap();
assert_eq!(unpacked.manifest_enc, vec![1, 2, 3]);
}