refactor(cli): move cmd_recovery_qr family into commands/recovery_qr.rs
This commit is contained in:
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(())
|
||||
}
|
||||
Reference in New Issue
Block a user