feat(cli): init --key-file generates a .relkey second factor

Add `--key-file <path>` to `relicario init` as a mutually exclusive
alternative to `--image`/`--output`. When given, a 32-byte secret is
generated with OsRng, armored via `keyfile_encode`, written to the
supplied path (gitignored, never committed), and `params.json` records
`"second_factor": "keyfile"`. The `--image` arg is now Option<PathBuf>;
existing image-vault callers are unchanged. `ParamsFile::for_new_vault`
now takes the second-factor variant as an explicit argument.

Tests: un-ignore init_keyfile_then_unlock_keyfile_round_trips (full
round-trip), strengthen its assertion to check for distinctive username
"octocat", and add init_keyfile_writes_relkey_and_keyfile_params
(armor, params parse, gitignore). Full workspace green (0 failures),
clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McMF5pUJbCMricmFEnf2sG
This commit is contained in:
adlee-was-taken
2026-06-25 23:32:41 -04:00
parent e8f6635188
commit 2daa3502e9
4 changed files with 112 additions and 30 deletions

View File

@@ -148,7 +148,7 @@ pub(crate) struct ParamsKdf {
}
impl ParamsFile {
pub fn for_new_vault(params: &KdfParams) -> Self {
pub fn for_new_vault(params: &KdfParams, second_factor: SecondFactor) -> Self {
Self {
format_version: 2,
kdf: ParamsKdf {
@@ -159,7 +159,7 @@ impl ParamsFile {
},
aead: "xchacha20poly1305".into(),
salt_path: ".relicario/salt".into(),
second_factor: SecondFactor::Image,
second_factor,
}
}
@@ -277,7 +277,7 @@ mod tests {
#[test]
fn for_new_vault_produces_expected_shape() {
let params = KdfParams { argon2_m: 65536, argon2_t: 3, argon2_p: 4 };
let pf = ParamsFile::for_new_vault(&params);
let pf = ParamsFile::for_new_vault(&params, relicario_core::SecondFactor::Image);
let v = serde_json::to_value(&pf).expect("to_value");
assert_eq!(v["format_version"], 2);
assert_eq!(v["kdf"]["algorithm"], "argon2id-v0x13");