70 lines
2.4 KiB
Rust
70 lines
2.4 KiB
Rust
//! `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(())
|
|
}
|