crates/veilvoice-crypto/examples/seal_and_open.rs
what this file is for · veilvoice-crypto · 80 lines · the same file on GitHub
The file as it is in the tree, in the colours you chose. A line number is a link, and so is every box in this file’s diagram: it opens here with the function it names marked.
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Sealing a recording into a `.veil` container and opening it again, plus
// handling a typed passphrase so that it does not linger any longer than it
// must.
//
// cargo run -p veilvoice-crypto --example seal_and_open
//
// Compiled on every commit by `cargo clippy --workspace --all-targets`, so the
// copy of this in `docs/USING_THE_CRATES.md` cannot quietly stop being true.
//
// In plain words
// --------------
//
// A short example of encrypting a recording and opening it again, for somebody
// who wants to use VeilVoice's encryption in their own program.
//
// It also shows the care taken with a typed passphrase: it is wiped as soon as
// it has been used rather than left sitting in memory, which is the part most
// examples of this kind quietly skip.
use veilvoice_crypto::{container, kdf::KdfParams, Secret};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Stand in for a WAV. The container does not care what the bytes are.
let recording: Vec<u8> = b"RIFF....WAVEfmt ....pretend this is audio".to_vec();
// A passphrase arrives from a prompt or a text field as an ordinary
// `String`: swappable heap memory that nothing wipes. Copy it into a
// `Secret`, which is page-locked where the OS allows and zeroized on drop,
// and zero the copy you made on the way.
//
// What this does NOT do is wipe the `String`'s own buffer -- that needs
// `unsafe`, and every crate here carries `#![forbid(unsafe_code)]`. The
// residue is audit item A-5, recorded rather than papered over: for as
// long as something is receiving keystrokes, the bytes are ordinary
// memory. Shrinking that window from "until the program exits" to "while
// the user was typing" is the part that was worth doing.
let typed = String::from("correct horse battery staple");
let mut buffer = typed.into_bytes();
let secret = Secret::new(&mut buffer);
debug_assert!(
buffer.iter().all(|b| *b == 0),
"Secret::new must zero what it was given"
);
if !secret.is_locked() {
// Reported rather than assumed: page locking genuinely fails on some
// systems, and a library that pretends otherwise is worse than one
// that does not try.
eprintln!("note: this passphrase could not be page-locked out of swap");
}
// The KDF cost travels with the file, which is what lets an old container
// still open after the defaults rise. Coming back *out* of a file those
// values are attacker-controlled, which is why they are bounded on parse
// rather than trusted (findings F-2, F-3, F-20).
let sealed = container::seal_with_password(secret.expose(), &recording, KdfParams::default())?;
println!(
"sealed {} bytes into {} bytes",
recording.len(),
sealed.len()
);
let opened = container::open_with_password(secret.expose(), &sealed)?;
assert_eq!(opened, recording);
println!("opened, and the bytes match");
// A wrong passphrase is an error, never a partial result.
match container::open_with_password(b"not the passphrase", &sealed) {
Ok(_) => panic!("a wrong passphrase must never open a container"),
Err(why) => println!("wrong passphrase refused: {why}"),
}
println!(
"a sealed recording is written to {}",
container::veil_path(std::path::Path::new("clean.wav")).display()
);
Ok(())
}