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:
@@ -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"
|
||||
|
||||
@@ -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(¶ms)?,
|
||||
)
|
||||
.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.
|
||||
///
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user