chore: merge rename commit into Plan 1B branch

Resolves conflicts from merging origin/main (idfoto→relicario rename):
- Kept Plan 1A's typed-item vault.rs, lib.rs, integration.rs over main's
  old entry-based versions
- Took main's relicario_dir() fix in CLI main.rs (sed had missed idfoto_dir)
- Kept Plan 1A's UnsupportedFormatVersion error variant in crypto.rs
- Kept Plan 1A's opaque Decrypt message (audit M4) in error.rs
- Deleted entry.rs (replaced by item.rs + typed modules in Plan 1A)
- Resolved Cargo.toml description to main's "relicario password manager"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-19 20:58:35 -04:00
51 changed files with 1230 additions and 1419 deletions

View File

@@ -2,7 +2,7 @@
name = "relicario-cli"
version = "0.1.0"
edition = "2021"
description = "CLI for idfoto password manager"
description = "CLI for relicario password manager"
[[bin]]
name = "relicario"

View File

@@ -1,4 +1,4 @@
//! idfoto CLI -- the platform layer for the idfoto password manager.
//! relicario CLI -- the platform layer for the relicario password manager.
//!
//! This binary provides the filesystem, git, and terminal I/O that
//! [`relicario_core`] intentionally excludes. It is the "glue" between the
@@ -69,7 +69,7 @@ struct Cli {
/// All available CLI subcommands.
#[derive(Subcommand)]
enum Commands {
/// Initialize a new idfoto vault in the current directory.
/// Initialize a new relicario vault in the current directory.
/// Creates the directory structure, generates a random image secret,
/// embeds it in the carrier image, and sets up git.
Init {
@@ -150,14 +150,14 @@ struct DeviceEntry {
// ─── Helper functions ───────────────────────────────────────────────────────
/// Returns the vault root directory (the current working directory).
/// The vault is always rooted at the directory where `idfoto` is invoked.
/// The vault is always rooted at the directory where `relicario` is invoked.
fn vault_dir() -> PathBuf {
std::env::current_dir().expect("failed to get current directory")
}
/// Returns the path to the `.relicario/` configuration directory within the vault.
fn idfoto_dir() -> PathBuf {
vault_dir().join(".idfoto")
fn relicario_dir() -> PathBuf {
vault_dir().join(".relicario")
}
/// Read the 32-byte vault salt from `.relicario/salt`.
@@ -166,7 +166,7 @@ fn idfoto_dir() -> PathBuf {
/// not secret (stored in plaintext) -- its purpose is to prevent precomputed
/// rainbow table attacks against the Argon2id KDF.
fn read_salt() -> Result<[u8; 32]> {
let data = fs::read(idfoto_dir().join("salt")).context("failed to read salt")?;
let data = fs::read(relicario_dir().join("salt")).context("failed to read salt")?;
let mut salt = [0u8; 32];
if data.len() != 32 {
bail!("invalid salt file: expected 32 bytes, got {}", data.len());
@@ -177,7 +177,7 @@ fn read_salt() -> Result<[u8; 32]> {
/// Read the KDF parameters from `.relicario/params.json`.
fn read_params() -> Result<KdfParams> {
let data = fs::read_to_string(idfoto_dir().join("params.json"))
let data = fs::read_to_string(relicario_dir().join("params.json"))
.context("failed to read params.json")?;
let params: KdfParams = serde_json::from_str(&data).context("failed to parse params.json")?;
Ok(params)
@@ -319,7 +319,7 @@ fn generate_password(length: usize) -> String {
// ─── Command implementations ────────────────────────────────────────────────
/// Initialize a new idfoto vault in the current directory.
/// Initialize a new relicario vault in the current directory.
///
/// Full sequence:
/// 1. Read the carrier JPEG provided by the user.
@@ -375,18 +375,18 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
.context("failed to derive master key")?;
// 8. Create directory structure
let idfoto = idfoto_dir();
fs::create_dir_all(&idfoto).context("failed to create .idfoto directory")?;
let relicario = relicario_dir();
fs::create_dir_all(&relicario).context("failed to create .relicario directory")?;
fs::create_dir_all(vault_dir().join("entries")).context("failed to create entries directory")?;
// 9. Write config files
fs::write(idfoto.join("salt"), &salt).context("failed to write salt")?;
fs::write(relicario.join("salt"), &salt).context("failed to write salt")?;
fs::write(
idfoto.join("params.json"),
relicario.join("params.json"),
serde_json::to_string_pretty(&params)?,
)
.context("failed to write params.json")?;
fs::write(idfoto.join("devices.json"), "[]").context("failed to write devices.json")?;
fs::write(relicario.join("devices.json"), "[]").context("failed to write devices.json")?;
// 10. Encrypt empty manifest
let manifest = Manifest::new();
@@ -404,7 +404,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
if !status.success() {
bail!("git init failed");
}
git_commit("feat: initialize idfoto vault")?;
git_commit("feat: initialize relicario vault")?;
// 13. Success
eprintln!("Vault initialized successfully.");
@@ -760,7 +760,7 @@ fn cmd_sync() -> Result<()> {
/// Read the device registry from `.relicario/devices.json`.
fn read_devices() -> Result<Vec<DeviceEntry>> {
let path = idfoto_dir().join("devices.json");
let path = relicario_dir().join("devices.json");
let data = fs::read_to_string(&path).context("failed to read devices.json")?;
let devices: Vec<DeviceEntry> = serde_json::from_str(&data).context("failed to parse devices.json")?;
Ok(devices)
@@ -769,13 +769,13 @@ fn read_devices() -> Result<Vec<DeviceEntry>> {
/// Write the device registry to `.relicario/devices.json`.
fn write_devices(devices: &[DeviceEntry]) -> Result<()> {
let data = serde_json::to_string_pretty(devices)?;
fs::write(idfoto_dir().join("devices.json"), data).context("failed to write devices.json")?;
fs::write(relicario_dir().join("devices.json"), data).context("failed to write devices.json")?;
Ok(())
}
/// Register a new device by generating an ed25519 keypair.
///
/// The private key is saved to `~/.config/idfoto/<name>.key` with
/// The private key is saved to `~/.config/relicario/<name>.key` with
/// restrictive permissions (0600 on Unix). The public key is added to
/// the vault's devices.json and committed to git.
///

View File

@@ -2,7 +2,7 @@
name = "relicario-core"
version = "0.1.0"
edition = "2021"
description = "Core library for idfoto password manager"
description = "Core library for relicario password manager"
[dependencies]
thiserror = "2"
@@ -25,3 +25,4 @@ hex = "0.4"
url = { version = "2", features = ["serde"] }
getrandom = "0.2"
[dev-dependencies]

View File

@@ -298,7 +298,7 @@ mod tests {
#[test]
fn encrypt_decrypt_round_trip() {
let key = [0xABu8; 32];
let plaintext = b"hello, idfoto!";
let plaintext = b"hello, relicario!";
let ciphertext = encrypt(&key, plaintext).unwrap();
let decrypted = decrypt(&key, &ciphertext).unwrap();

View File

@@ -14,9 +14,14 @@ use thiserror::Error;
/// steganography -> serialization -> device keys.
#[derive(Debug, Error)]
pub enum RelicarioError {
/// The Argon2id key derivation failed. This typically means invalid KDF
/// parameters were supplied (e.g., memory cost below Argon2's minimum).
#[error("key derivation failed: {0}")]
Kdf(String),
/// XChaCha20-Poly1305 encryption failed. In practice this is extremely rare
/// -- the only realistic cause is an internal library error, since the cipher
/// accepts arbitrary-length plaintext.
#[error("encryption failed: {0}")]
Encrypt(String),
@@ -24,6 +29,10 @@ pub enum RelicarioError {
#[error("decryption failed")]
Decrypt,
/// The binary ciphertext blob does not match the expected format (e.g.,
/// too short to contain the version byte + nonce + tag, or an unrecognized
/// version byte). This usually indicates file corruption or a version
/// mismatch between the writer and reader.
#[error("invalid vault format: {0}")]
Format(String),
@@ -42,9 +51,15 @@ pub enum RelicarioError {
#[error("attachment too large: {size} bytes > {max} bytes max")]
AttachmentTooLarge { size: u64, max: u64 },
/// A general error from the image steganography subsystem (imgsecret).
/// Covers issues like failing to decode the carrier JPEG or failing to
/// encode the output JPEG after modification.
#[error("imgsecret: {0}")]
ImgSecret(String),
/// The carrier image is too small to hold the embedded secret with
/// sufficient redundancy. The embed region (central 70% of the image)
/// must contain at least `BLOCKS_PER_COPY * MIN_COPIES` 8x8 blocks.
#[error("image too small: need at least {min_width}x{min_height}, got {actual_width}x{actual_height}")]
ImageTooSmall {
min_width: u32,
@@ -53,12 +68,22 @@ pub enum RelicarioError {
actual_height: u32,
},
/// Secret extraction from a JPEG failed. This can mean:
/// - The image never had a secret embedded in it.
/// - The image was recompressed below Q85, destroying the QIM watermarks.
/// - The image was cropped beyond the 15% crumple zone.
/// - Majority-vote confidence fell below the 60% threshold on one or more bits.
#[error("extraction failed: no valid secret found in image")]
ExtractionFailed,
/// JSON serialization or deserialization of an entry or manifest failed.
/// Wraps [`serde_json::Error`] transparently via `#[from]`.
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
/// An error related to device ed25519 key operations. Device keys are
/// separate from the vault KDF -- revoking a device does not require
/// rotating the passphrase or reference image.
#[error("device key error: {0}")]
DeviceKey(String),
}

View File

@@ -1,6 +1,6 @@
//! DCT-based steganographic embedding of a 256-bit secret in JPEG images.
//!
//! This is the novel component of idfoto. It hides a 32-byte secret inside a
//! This is the novel component of relicario. It hides a 32-byte secret inside a
//! JPEG image's luminance channel using Quantization Index Modulation (QIM) on
//! mid-frequency DCT coefficients, with majority voting across multiple redundant
//! copies for robustness.

View File

@@ -1,6 +1,6 @@
//! # relicario-core
//!
//! Platform-agnostic core library for the idfoto password manager.
//! Platform-agnostic core library for the relicario password manager.
//!
//! This crate is intentionally **bytes-in/bytes-out** -- it performs no filesystem
//! access, no network I/O, and no git operations. All inputs arrive as byte slices

View File

@@ -2,7 +2,7 @@
name = "relicario-wasm"
version = "0.1.0"
edition = "2021"
description = "WASM bindings for idfoto password manager"
description = "WASM bindings for relicario password manager"
[lib]
crate-type = ["cdylib", "rlib"]

View File

@@ -1,4 +1,4 @@
//! WASM bindings for the idfoto password manager.
//! WASM bindings for the relicario password manager.
//!
//! This crate wraps [`relicario_core`] for use in a Chrome MV3 browser extension via
//! `wasm-bindgen`. Every function marked `#[wasm_bindgen]` is callable from