crates/gunnar-crypt/examples/sk_v1_fixture.rs
//! **A TEST fixture for the console's browser matrix** (CRYPT-PQ-SK): the v1
//! (X25519) wrap of a person's encryption key to a security key, as a console
//! from before the wrap v2 made it, so the matrix can check that the console
//! re-wraps such a method to v2 on the next unlock.
//!
//! ```text
//! sk_v1_fixture <public key> <recovery-wrap file> <credential id, base64url> <PRF output, base64url>
//! (the recovery code on stdin) → the v1 wrap (an armored age file) on stdout
//! ```
//!
//! The person's key is opened with their printed recovery code (made in the
//! console a moment before), checked against the public half, and wrapped to
//! the credential's v1 recipient. Nothing secret is printed: the output is
//! ciphertext. Run only against a throwaway rig.
use std::io::Read as _;
fn b64url(text: &str) -> Vec<u8> {
use base64::Engine as _;
base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(text.trim().trim_end_matches('='))
.unwrap_or_else(|e| fail(&format!("not base64url: {e}")))
}
fn fail(why: &str) -> ! {
eprintln!("sk_v1_fixture: {why}");
std::process::exit(2)
}
fn main() {
use gunnar_crypt::userkey::{self, security_key};
let args: Vec<String> = std::env::args().skip(1).collect();
let [public, wrap_file, credential, prf] = args.as_slice() else {
fail("usage: sk_v1_fixture <public key> <recovery-wrap file> <credential id> <PRF output>; the recovery code on stdin");
};
let mut code = String::new();
std::io::stdin()
.read_to_string(&mut code)
.unwrap_or_else(|e| fail(&format!("stdin: {e}")));
let wrap = std::fs::read(wrap_file).unwrap_or_else(|e| fail(&format!("{wrap_file}: {e}")));
let key = userkey::open_with_recovery_code(&wrap, code.trim(), public)
.unwrap_or_else(|e| fail(&e.to_string()));
let prf: [u8; 32] = b64url(prf)
.try_into()
.unwrap_or_else(|_| fail("the PRF output is not 32 bytes"));
let v1 = key
.wrap_to_security_key(&security_key::recipient(&prf, &b64url(credential)))
.unwrap_or_else(|e| fail(&e.to_string()));
print!("{v1}");
}