From 3811b07014f775f3b80e9be6c17083fcdc8d4ae1 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Wed, 6 May 2026 19:41:04 -0400 Subject: [PATCH] refactor(cli): move cmd_recovery_qr family into commands/recovery_qr.rs --- crates/relicario-cli/src/commands/mod.rs | 1 + .../relicario-cli/src/commands/recovery_qr.rs | 69 +++++++++++++++++++ crates/relicario-cli/src/main.rs | 65 +---------------- 3 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 crates/relicario-cli/src/commands/recovery_qr.rs diff --git a/crates/relicario-cli/src/commands/mod.rs b/crates/relicario-cli/src/commands/mod.rs index 0db23df..dcbdc68 100644 --- a/crates/relicario-cli/src/commands/mod.rs +++ b/crates/relicario-cli/src/commands/mod.rs @@ -14,6 +14,7 @@ pub mod import; pub mod init; pub mod list; pub mod rate; +pub mod recovery_qr; pub mod status; pub mod sync; pub mod trash; diff --git a/crates/relicario-cli/src/commands/recovery_qr.rs b/crates/relicario-cli/src/commands/recovery_qr.rs new file mode 100644 index 0000000..7aaf08d --- /dev/null +++ b/crates/relicario-cli/src/commands/recovery_qr.rs @@ -0,0 +1,69 @@ +//! `relicario recovery-qr {generate,unwrap}` — last-resort vault-key escape hatch. + +use anyhow::{Context, Result}; + +use crate::RecoveryQrCmd; + +pub fn cmd_recovery_qr(cmd: RecoveryQrCmd) -> Result<()> { + match cmd { + RecoveryQrCmd::Generate => cmd_recovery_qr_generate(), + RecoveryQrCmd::Unwrap => cmd_recovery_qr_unwrap(), + } +} + +fn cmd_recovery_qr_generate() -> Result<()> { + use relicario_core::{generate_recovery_qr, imgsecret}; + use zeroize::Zeroizing; + + let image_path = crate::session::get_image_path()?; + let image_bytes = std::fs::read(&image_path) + .with_context(|| format!("read reference image {}", image_path.display()))?; + let image_secret = imgsecret::extract(&image_bytes) + .context("extract image secret")?; + + let passphrase = Zeroizing::new( + rpassword::prompt_password("Enter vault passphrase: ") + .context("read passphrase")? + ); + + let payload = generate_recovery_qr(passphrase.as_str(), &image_secret) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + use qrcode::{EcLevel, QrCode, render::unicode}; + let code = QrCode::with_error_correction_level(payload.as_bytes(), EcLevel::M) + .expect("valid payload"); + let image = code + .render::() + .dark_color(unicode::Dense1x2::Dark) + .light_color(unicode::Dense1x2::Light) + .build(); + println!("{image}"); + println!("Recovery QR generated. Print or photograph this code and store it securely."); + println!("The QR has NOT been saved to disk."); + Ok(()) +} + +fn cmd_recovery_qr_unwrap() -> Result<()> { + use relicario_core::unwrap_recovery_qr; + use std::io::BufRead; + use zeroize::Zeroizing; + + println!("Paste the base64 recovery QR payload and press Enter:"); + let stdin = std::io::stdin(); + let payload_b64 = stdin.lock().lines().next() + .context("no input")??; + let payload_b64 = payload_b64.trim().to_owned(); + + let bytes = data_encoding::BASE64.decode(payload_b64.as_bytes()) + .map_err(|e| anyhow::anyhow!("base64 decode: {e}"))?; + + let passphrase = Zeroizing::new( + rpassword::prompt_password("Enter passphrase: ") + .context("read passphrase")? + ); + + let secret = unwrap_recovery_qr(&bytes, passphrase.as_str()) + .map_err(|e| anyhow::anyhow!("{e}"))?; + println!("image_secret: {}", hex::encode(secret.as_ref())); + Ok(()) +} diff --git a/crates/relicario-cli/src/main.rs b/crates/relicario-cli/src/main.rs index 2b4c004..6b5634a 100644 --- a/crates/relicario-cli/src/main.rs +++ b/crates/relicario-cli/src/main.rs @@ -457,7 +457,7 @@ fn main() -> Result<()> { } Commands::Rate { passphrase } => commands::rate::cmd_rate(passphrase), Commands::Device { action } => cmd_device(action), - Commands::RecoveryQr { cmd } => cmd_recovery_qr(cmd), + Commands::RecoveryQr { cmd } => commands::recovery_qr::cmd_recovery_qr(cmd), } } @@ -1322,66 +1322,3 @@ fn cmd_device(action: DeviceAction) -> Result<()> { } } -fn cmd_recovery_qr(cmd: RecoveryQrCmd) -> Result<()> { - match cmd { - RecoveryQrCmd::Generate => cmd_recovery_qr_generate(), - RecoveryQrCmd::Unwrap => cmd_recovery_qr_unwrap(), - } -} - -fn cmd_recovery_qr_generate() -> Result<()> { - use relicario_core::{generate_recovery_qr, imgsecret}; - use zeroize::Zeroizing; - - let image_path = crate::session::get_image_path()?; - let image_bytes = std::fs::read(&image_path) - .with_context(|| format!("read reference image {}", image_path.display()))?; - let image_secret = imgsecret::extract(&image_bytes) - .context("extract image secret")?; - - let passphrase = Zeroizing::new( - rpassword::prompt_password("Enter vault passphrase: ") - .context("read passphrase")? - ); - - let payload = generate_recovery_qr(passphrase.as_str(), &image_secret) - .map_err(|e| anyhow::anyhow!("{e}"))?; - - use qrcode::{EcLevel, QrCode, render::unicode}; - let code = QrCode::with_error_correction_level(payload.as_bytes(), EcLevel::M) - .expect("valid payload"); - let image = code - .render::() - .dark_color(unicode::Dense1x2::Dark) - .light_color(unicode::Dense1x2::Light) - .build(); - println!("{image}"); - println!("Recovery QR generated. Print or photograph this code and store it securely."); - println!("The QR has NOT been saved to disk."); - Ok(()) -} - -fn cmd_recovery_qr_unwrap() -> Result<()> { - use relicario_core::unwrap_recovery_qr; - use std::io::BufRead; - use zeroize::Zeroizing; - - println!("Paste the base64 recovery QR payload and press Enter:"); - let stdin = std::io::stdin(); - let payload_b64 = stdin.lock().lines().next() - .context("no input")??; - let payload_b64 = payload_b64.trim().to_owned(); - - let bytes = data_encoding::BASE64.decode(payload_b64.as_bytes()) - .map_err(|e| anyhow::anyhow!("base64 decode: {e}"))?; - - let passphrase = Zeroizing::new( - rpassword::prompt_password("Enter passphrase: ") - .context("read passphrase")? - ); - - let secret = unwrap_recovery_qr(&bytes, passphrase.as_str()) - .map_err(|e| anyhow::anyhow!("{e}"))?; - println!("image_secret: {}", hex::encode(secret.as_ref())); - Ok(()) -}