feat(core+cli): SecondFactor hint on ParamsFile (default image, back-compat)

Add SecondFactor enum (Image | Keyfile, serde lowercase) to relicario-core::crypto,
re-export from lib.rs alongside KdfParams.  Add #[serde(default)] second_factor field
to ParamsFile in relicario-cli::session so existing params.json files (no field) still
parse as Image.  for_new_vault() initialises the field to Image.  No unlock logic
changed; that is deferred to Task 4.  TDD: tests written RED first, then GREEN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McMF5pUJbCMricmFEnf2sG
This commit is contained in:
adlee-was-taken
2026-06-25 21:02:13 -04:00
parent 79284979c1
commit b08b388d5d
3 changed files with 68 additions and 2 deletions

View File

@@ -12,7 +12,7 @@ use zeroize::Zeroizing;
use relicario_core::{
decrypt_item, decrypt_manifest, decrypt_settings,
derive_master_key, encrypt_item, encrypt_manifest, encrypt_settings,
imgsecret, Item, ItemId, KdfParams, Manifest, VaultSettings,
imgsecret, Item, ItemId, KdfParams, Manifest, SecondFactor, VaultSettings,
};
use crate::helpers::vault_dir;
@@ -119,6 +119,11 @@ pub(crate) struct ParamsFile {
pub kdf: ParamsKdf,
pub aead: String,
pub salt_path: String,
/// Which second-factor container this vault uses to supply its 256-bit secret.
/// Defaults to `Image` (reference JPEG via DCT stego) for back-compat with
/// params.json files written before keyfile support was introduced.
#[serde(default)]
pub second_factor: SecondFactor,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -142,6 +147,7 @@ impl ParamsFile {
},
aead: "xchacha20poly1305".into(),
salt_path: ".relicario/salt".into(),
second_factor: SecondFactor::Image,
}
}
@@ -248,6 +254,32 @@ mod tests {
assert_eq!(v["salt_path"], ".relicario/salt");
}
#[test]
fn params_file_second_factor_backcompat() {
// An old params.json without second_factor must deserialize with Image (back-compat).
let pf: ParamsFile = serde_json::from_str(FIXTURE).expect("parse old fixture");
assert_eq!(pf.second_factor, relicario_core::SecondFactor::Image);
}
#[test]
fn params_file_second_factor_keyfile() {
// A params.json WITH "second_factor":"keyfile" must deserialize as Keyfile.
let with_keyfile = r#"{
"format_version": 2,
"kdf": {
"algorithm": "argon2id-v0x13",
"argon2_m": 65536,
"argon2_t": 3,
"argon2_p": 4
},
"aead": "xchacha20poly1305",
"salt_path": ".relicario/salt",
"second_factor": "keyfile"
}"#;
let pf: ParamsFile = serde_json::from_str(with_keyfile).expect("parse keyfile fixture");
assert_eq!(pf.second_factor, relicario_core::SecondFactor::Keyfile);
}
#[test]
fn after_manifest_change_writes_manifest_and_groups_cache() {
let dir = tempfile::TempDir::new().unwrap();