refactor(cli): move cmd_recovery_qr family into commands/recovery_qr.rs
This commit is contained in:
@@ -14,6 +14,7 @@ pub mod import;
|
|||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod rate;
|
pub mod rate;
|
||||||
|
pub mod recovery_qr;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod trash;
|
pub mod trash;
|
||||||
|
|||||||
69
crates/relicario-cli/src/commands/recovery_qr.rs
Normal file
69
crates/relicario-cli/src/commands/recovery_qr.rs
Normal file
@@ -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::<unicode::Dense1x2>()
|
||||||
|
.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(())
|
||||||
|
}
|
||||||
@@ -457,7 +457,7 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Commands::Rate { passphrase } => commands::rate::cmd_rate(passphrase),
|
Commands::Rate { passphrase } => commands::rate::cmd_rate(passphrase),
|
||||||
Commands::Device { action } => cmd_device(action),
|
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::<unicode::Dense1x2>()
|
|
||||||
.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(())
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user