refactor(cli): single canonical ParamsFile in session.rs (Plan B Phase 5)
Promotes ParamsFile to a module-level pub(crate) struct with both Serialize and Deserialize derives. for_new_vault() constructor + into_kdf_params() inversion replace the two-definition split between commands/init.rs (write) and session.rs read_params (read). On-disk JSON format unchanged — fixture test asserts round-trip with the current params.json layout.
This commit is contained in:
@@ -65,17 +65,7 @@ pub fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
|
|||||||
fs::write(relicario_dir.join("salt"), salt)?;
|
fs::write(relicario_dir.join("salt"), salt)?;
|
||||||
fs::write(
|
fs::write(
|
||||||
relicario_dir.join("params.json"),
|
relicario_dir.join("params.json"),
|
||||||
serde_json::to_string_pretty(&ParamsFile {
|
serde_json::to_string_pretty(&crate::session::ParamsFile::for_new_vault(¶ms))?,
|
||||||
format_version: 2,
|
|
||||||
kdf: ParamsKdf {
|
|
||||||
algorithm: "argon2id-v0x13".into(),
|
|
||||||
argon2_m: params.argon2_m,
|
|
||||||
argon2_t: params.argon2_t,
|
|
||||||
argon2_p: params.argon2_p,
|
|
||||||
},
|
|
||||||
aead: "xchacha20poly1305".into(),
|
|
||||||
salt_path: ".relicario/salt".into(),
|
|
||||||
})?,
|
|
||||||
)?;
|
)?;
|
||||||
let manifest = Manifest::new();
|
let manifest = Manifest::new();
|
||||||
fs::write(root.join("manifest.enc"), encrypt_manifest(&manifest, &master_key)?)?;
|
fs::write(root.join("manifest.enc"), encrypt_manifest(&manifest, &master_key)?)?;
|
||||||
@@ -106,20 +96,3 @@ pub fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
|
|||||||
eprintln!(" \u{2192} back this file up somewhere safe; it is your second factor.");
|
eprintln!(" \u{2192} back this file up somewhere safe; it is your second factor.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct ParamsFile {
|
|
||||||
format_version: u32,
|
|
||||||
kdf: ParamsKdf,
|
|
||||||
aead: String,
|
|
||||||
salt_path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
struct ParamsKdf {
|
|
||||||
algorithm: String,
|
|
||||||
argon2_m: u32,
|
|
||||||
argon2_t: u32,
|
|
||||||
argon2_p: u32,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -107,17 +107,52 @@ fn read_salt(root: &Path) -> Result<[u8; 32]> {
|
|||||||
Ok(salt)
|
Ok(salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_params(root: &Path) -> Result<KdfParams> {
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
// params.json layout: { "format_version": 2, "kdf": { "argon2_m": ..., ... }, ... }
|
pub(crate) struct ParamsFile {
|
||||||
// We extract only the "kdf" sub-object and deserialize it as KdfParams.
|
pub format_version: u32,
|
||||||
#[derive(serde::Deserialize)]
|
pub kdf: ParamsKdf,
|
||||||
struct ParamsFile {
|
pub aead: String,
|
||||||
kdf: KdfParams,
|
pub salt_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub(crate) struct ParamsKdf {
|
||||||
|
pub algorithm: String,
|
||||||
|
pub argon2_m: u32,
|
||||||
|
pub argon2_t: u32,
|
||||||
|
pub argon2_p: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParamsFile {
|
||||||
|
pub fn for_new_vault(params: &KdfParams) -> Self {
|
||||||
|
Self {
|
||||||
|
format_version: 2,
|
||||||
|
kdf: ParamsKdf {
|
||||||
|
algorithm: "argon2id-v0x13".into(),
|
||||||
|
argon2_m: params.argon2_m,
|
||||||
|
argon2_t: params.argon2_t,
|
||||||
|
argon2_p: params.argon2_p,
|
||||||
|
},
|
||||||
|
aead: "xchacha20poly1305".into(),
|
||||||
|
salt_path: ".relicario/salt".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_kdf_params(self) -> KdfParams {
|
||||||
|
KdfParams {
|
||||||
|
argon2_m: self.kdf.argon2_m,
|
||||||
|
argon2_t: self.kdf.argon2_t,
|
||||||
|
argon2_p: self.kdf.argon2_p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_params(root: &Path) -> Result<KdfParams> {
|
||||||
let s = fs::read_to_string(root.join(".relicario").join("params.json"))
|
let s = fs::read_to_string(root.join(".relicario").join("params.json"))
|
||||||
.context("failed to read .relicario/params.json")?;
|
.context("failed to read .relicario/params.json")?;
|
||||||
let pf: ParamsFile = serde_json::from_str(&s).context("failed to parse params.json")?;
|
let pf: ParamsFile = serde_json::from_str(&s).context("failed to parse params.json")?;
|
||||||
Ok(pf.kdf)
|
Ok(pf.into_kdf_params())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate the reference image path via `RELICARIO_IMAGE` env var or interactive prompt.
|
/// Locate the reference image path via `RELICARIO_IMAGE` env var or interactive prompt.
|
||||||
@@ -149,3 +184,72 @@ fn atomic_write(path: &Path, data: &[u8]) -> Result<()> {
|
|||||||
fs::rename(&tmp, path).with_context(|| format!("failed to rename {}", path.display()))?;
|
fs::rename(&tmp, path).with_context(|| format!("failed to rename {}", path.display()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const FIXTURE: &str = r#"{
|
||||||
|
"format_version": 2,
|
||||||
|
"kdf": {
|
||||||
|
"algorithm": "argon2id-v0x13",
|
||||||
|
"argon2_m": 65536,
|
||||||
|
"argon2_t": 3,
|
||||||
|
"argon2_p": 4
|
||||||
|
},
|
||||||
|
"aead": "xchacha20poly1305",
|
||||||
|
"salt_path": ".relicario/salt"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn params_file_round_trips_current_layout() {
|
||||||
|
let pf: ParamsFile = serde_json::from_str(FIXTURE).expect("parse fixture");
|
||||||
|
assert_eq!(pf.format_version, 2);
|
||||||
|
assert_eq!(pf.kdf.algorithm, "argon2id-v0x13");
|
||||||
|
assert_eq!(pf.kdf.argon2_m, 65536);
|
||||||
|
assert_eq!(pf.kdf.argon2_t, 3);
|
||||||
|
assert_eq!(pf.kdf.argon2_p, 4);
|
||||||
|
assert_eq!(pf.aead, "xchacha20poly1305");
|
||||||
|
assert_eq!(pf.salt_path, ".relicario/salt");
|
||||||
|
|
||||||
|
let kdf = ParamsFile {
|
||||||
|
format_version: pf.format_version,
|
||||||
|
kdf: ParamsKdf {
|
||||||
|
algorithm: pf.kdf.algorithm.clone(),
|
||||||
|
argon2_m: pf.kdf.argon2_m,
|
||||||
|
argon2_t: pf.kdf.argon2_t,
|
||||||
|
argon2_p: pf.kdf.argon2_p,
|
||||||
|
},
|
||||||
|
aead: pf.aead.clone(),
|
||||||
|
salt_path: pf.salt_path.clone(),
|
||||||
|
}
|
||||||
|
.into_kdf_params();
|
||||||
|
assert_eq!(kdf.argon2_m, 65536);
|
||||||
|
assert_eq!(kdf.argon2_t, 3);
|
||||||
|
assert_eq!(kdf.argon2_p, 4);
|
||||||
|
|
||||||
|
let serialized = serde_json::to_string(&pf).expect("re-serialize");
|
||||||
|
let pf2: ParamsFile = serde_json::from_str(&serialized).expect("parse re-serialized");
|
||||||
|
assert_eq!(pf2.format_version, 2);
|
||||||
|
assert_eq!(pf2.kdf.algorithm, "argon2id-v0x13");
|
||||||
|
assert_eq!(pf2.kdf.argon2_m, 65536);
|
||||||
|
assert_eq!(pf2.kdf.argon2_t, 3);
|
||||||
|
assert_eq!(pf2.kdf.argon2_p, 4);
|
||||||
|
assert_eq!(pf2.aead, "xchacha20poly1305");
|
||||||
|
assert_eq!(pf2.salt_path, ".relicario/salt");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(¶ms);
|
||||||
|
let v = serde_json::to_value(&pf).expect("to_value");
|
||||||
|
assert_eq!(v["format_version"], 2);
|
||||||
|
assert_eq!(v["kdf"]["algorithm"], "argon2id-v0x13");
|
||||||
|
assert_eq!(v["kdf"]["argon2_m"], 65536);
|
||||||
|
assert_eq!(v["kdf"]["argon2_t"], 3);
|
||||||
|
assert_eq!(v["kdf"]["argon2_p"], 4);
|
||||||
|
assert_eq!(v["aead"], "xchacha20poly1305");
|
||||||
|
assert_eq!(v["salt_path"], ".relicario/salt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user