anubis_age/lib.rs
1//! # Anubis Rage - Post-Quantum Secure File Encryption
2//!
3//! **Quantum-resistant file encryption using ML-KEM-1024 (NIST FIPS 203)**
4//!
5//! Anubis Rage is a modern, simple, and secure file encryption tool and library that implements
6//! **post-quantum cryptography** to protect your files against both current and future threats,
7//! including attacks from quantum computers.
8//!
9//! ## Table of Contents
10//!
11//! - [Quick Start](#quick-start)
12//! - [What is Anubis Rage?](#what-is-anubis-rage)
13//! - [Security Guarantees](#security-guarantees)
14//! - [Installation](#installation)
15//! - [Library Usage](#library-usage)
16//! - [Command-Line Usage](#command-line-usage)
17//! - [File Format](#file-format)
18//! - [Cryptographic Stack](#cryptographic-stack)
19//! - [Performance](#performance)
20//! - [Examples](#examples)
21//!
22//! ## Quick Start
23//!
24//! ### Library Usage
25//!
26//! ```rust
27//! use std::io::{Read, Write};
28//!
29//! # fn run_main() -> Result<(), Box<dyn std::error::Error>> {
30//! // Generate Hybrid keypair (X25519 + ML-KEM-1024) - RECOMMENDED
31//! let identity = age::pqc::hybrid::Identity::generate();
32//! let recipient = identity.to_public();
33//!
34//! // Encrypt (uses both X25519 and ML-KEM-1024)
35//! let plaintext = b"Secret message with defense-in-depth security!";
36//! let encryptor = age::Encryptor::with_recipients(vec![&recipient as _])?;
37//! let mut ciphertext = vec![];
38//! let mut writer = encryptor.wrap_output(&mut ciphertext)?;
39//! writer.write_all(plaintext)?;
40//! writer.finish()?;
41//!
42//! // Decrypt (both X25519 and ML-KEM-1024 must succeed)
43//! let decryptor = age::Decryptor::new(&ciphertext[..])?;
44//! let mut decrypted = vec![];
45//! let mut reader = decryptor.decrypt(vec![&identity as _])?;
46//! reader.read_to_end(&mut decrypted)?;
47//!
48//! assert_eq!(decrypted, plaintext);
49//! # Ok(())
50//! # }
51//! # run_main().unwrap();
52//! ```
53//!
54//! ### CLI Tool
55//!
56//! ```bash
57//! # Install
58//! cargo install anubis-rage
59//!
60//! # Generate a key
61//! anubis-rage-keygen -o key.txt
62//!
63//! # Encrypt a file
64//! anubis-rage -r $(grep -o 'anubis1[^"]*' key.txt) -o secret.txt.anubis secret.txt
65//!
66//! # Decrypt a file
67//! anubis-rage -d -i key.txt -o decrypted.txt secret.txt.anubis
68//! ```
69//!
70//! ## What is Anubis Rage?
71//!
72//! Anubis Rage is a **post-quantum secure** file encryption tool based on **ML-KEM-1024**
73//! (Module-Lattice-Based Key-Encapsulation Mechanism), standardized as **NIST FIPS 203**.
74//!
75//! ### Key Features
76//!
77//! - ✅ **Post-Quantum Security**: NIST Category 5 (maximum security level)
78//! - ✅ **Simple & Modern**: Small explicit keys, no config files, UNIX-style composability
79//! - ✅ **Streaming Encryption**: Handles files of any size with constant memory usage
80//! - ✅ **Authenticated Encryption**: AES-256-GCM-SIV or ChaCha20-Poly1305 AEAD
81//! - ✅ **Forward Secrecy**: Ephemeral key encapsulation per recipient
82//! - ✅ **Standards Compliant**: NIST FIPS 203, FIPS 198-1, SP 800-56C
83//!
84//! ### Why Post-Quantum Cryptography?
85//!
86//! Quantum computers (when built at sufficient scale) will break current public-key cryptography:
87//!
88//! - **Shor's Algorithm**: Breaks RSA, ECDSA, ECDH in polynomial time
89//! - **Grover's Algorithm**: Halves symmetric key security (256-bit → 128-bit effective)
90//!
91//! Anubis Rage uses **lattice-based cryptography** (ML-KEM-1024) which resists both classical
92//! and quantum attacks, ensuring your encrypted files remain secure for decades to come.
93//!
94//! ## Security Guarantees
95//!
96//! ### Cryptographic Security
97//!
98//! | Property | Status |
99//! |----------|--------|
100//! | **Confidentiality** | ✅ IND-CCA2 secure (ML-KEM-1024) |
101//! | **Integrity** | ✅ Authenticated encryption (AEAD) |
102//! | **Forward Secrecy** | ✅ Ephemeral key wrapping |
103//! | **Post-Quantum** | ✅ NIST Category 5 (256-bit quantum security) |
104//! | **Classical Security** | ✅ 256-bit equivalent (AES-256) |
105//!
106//! ### NIST Category 5 Security Level
107//!
108//! Anubis Rage achieves **NIST Category 5** - the highest security classification:
109//!
110//! - **Classical Attack Cost**: 2^256 operations (equivalent to AES-256)
111//! - **Quantum Attack Cost**: > 2^170 quantum gates (exceeds NIST requirement)
112//! - **Public Key Size**: 2592 bytes
113//! - **Secret Key Size**: 4736 bytes
114//! - **Ciphertext Overhead**: 1568 bytes + 64-byte SHA-512 MAC
115//!
116//! ## Installation
117//!
118//! ### As a Library
119//!
120//! Add to your `Cargo.toml`:
121//!
122//! ```toml
123//! [dependencies]
124//! anubis-rage = "1.0"
125//! ```
126//!
127//! ### As a CLI Tool
128//!
129//! ```bash
130//! cargo install anubis-rage
131//! ```
132//!
133//! This installs three binaries:
134//! - **`anubis-rage`**: Encryption/decryption tool
135//! - **`anubis-rage-keygen`**: Key generation utility
136//! - **`anubis-rage-sign`**: Digital signature tool (ML-DSA-87)
137//!
138//! ## Library Usage
139//!
140//! ### Basic Encryption/Decryption
141//!
142//! ```rust
143//! use anubis_rage::{pqc::mlkem, Encryptor, Decryptor};
144//! use std::io::{Read, Write};
145//!
146//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
147//! // Generate keypair
148//! let identity = mlkem::Identity::generate();
149//! let recipient = identity.to_public();
150//!
151//! // Encrypt data
152//! let plaintext = b"Top secret quantum-safe data";
153//! let encryptor = Encryptor::with_recipients(vec![&recipient as _])?;
154//! let mut ciphertext = vec![];
155//! let mut writer = encryptor.wrap_output(&mut ciphertext)?;
156//! writer.write_all(plaintext)?;
157//! writer.finish()?;
158//!
159//! // Decrypt data
160//! let decryptor = Decryptor::new(&ciphertext[..])?;
161//! let mut decrypted = vec![];
162//! let mut reader = decryptor.decrypt(vec![&identity as _])?;
163//! reader.read_to_end(&mut decrypted)?;
164//!
165//! assert_eq!(decrypted, plaintext);
166//! # Ok(())
167//! # }
168//! ```
169//!
170//! ### Multi-Recipient Encryption
171//!
172//! ```rust,no_run
173//! use anubis_rage::{pqc::mlkem, Encryptor};
174//!
175//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
176//! // Generate keys for multiple recipients
177//! let alice = mlkem::Identity::generate();
178//! let bob = mlkem::Identity::generate();
179//! let carol = mlkem::Identity::generate();
180//!
181//! let recipients = vec![
182//! &alice.to_public() as &dyn anubis_rage::Recipient,
183//! &bob.to_public() as &dyn anubis_rage::Recipient,
184//! &carol.to_public() as &dyn anubis_rage::Recipient,
185//! ];
186//!
187//! // Encrypt to all recipients (any can decrypt)
188//! let encryptor = Encryptor::with_recipients(recipients)?;
189//! // ... encrypt data ...
190//! # Ok(())
191//! # }
192//! ```
193//!
194//! ### Async I/O Support
195//!
196//! ```rust,no_run
197//! use anubis_rage::{pqc::mlkem, Encryptor};
198//! use futures::io::AsyncWriteExt;
199//!
200//! # #[tokio::main]
201//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
202//! let identity = mlkem::Identity::generate();
203//! let recipient = identity.to_public();
204//!
205//! let encryptor = Encryptor::with_recipients(vec![&recipient as _])?;
206//! let mut encrypted = vec![];
207//! let mut writer = encryptor.wrap_async_output(&mut encrypted).await?;
208//! writer.write_all(b"Async quantum-safe encryption!").await?;
209//! writer.close().await?;
210//! # Ok(())
211//! # }
212//! ```
213//!
214//! ## Command-Line Usage
215//!
216//! ### Key Generation
217//!
218//! ```bash
219//! # Generate new ML-KEM-1024 keypair
220//! anubis-rage-keygen -o identity.txt
221//! chmod 600 identity.txt
222//!
223//! # Extract public key
224//! grep "public key:" identity.txt
225//! ```
226//!
227//! ### Encryption
228//!
229//! ```bash
230//! # Encrypt to a recipient
231//! anubis-rage -r RECIPIENT_PUBLIC_KEY -o file.anubis file.txt
232//!
233//! # Encrypt to multiple recipients
234//! anubis-rage -r alice.pub -r bob.pub -o file.anubis file.txt
235//!
236//! # Encrypt with ASCII armor
237//! anubis-rage -r RECIPIENT --armor -o file.anubis file.txt
238//!
239//! # Pipe encryption
240//! cat file.txt | anubis-rage -r RECIPIENT > file.anubis
241//! ```
242//!
243//! ### Decryption
244//!
245//! ```bash
246//! # Decrypt with identity file
247//! anubis-rage -d -i identity.txt -o output.txt file.anubis
248//!
249//! # Pipe decryption
250//! cat file.anubis | anubis-rage -d -i identity.txt > output.txt
251//! ```
252//!
253//! ## File Format
254//!
255//! Anubis Rage uses the `anubis-encryption.org/v1` file format:
256//!
257//! ```text
258//! anubis-encryption.org/v1
259//! -> MLKEM-1024
260//! [2144-char base64: ML-KEM-1024 encapsulated key (1568 bytes)]
261//! [76-char base64: wrapped file key with ChaCha20-Poly1305 (44 bytes)]
262//! --- [86-char base64: SHA-512 HMAC header MAC (64 bytes)]
263//! [encrypted payload with AES-256-GCM-SIV or ChaCha20-Poly1305]
264//! ```
265//!
266//! ### Size Overhead
267//!
268//! - **Fixed header**: ~2.4 KB
269//! - **Per recipient**: ~2.1 KB
270//! - **Total**: ~2.4 KB + (num_recipients × 2.1 KB)
271//!
272//! For files >100 KB, overhead is <2%. For files >1 MB, overhead is <0.2%.
273//!
274//! ## Cryptographic Stack
275//!
276//! Anubis Rage uses **NIST Category 5** (maximum strength) cryptography throughout:
277//!
278//! | Component | Algorithm | Security Level | Standard |
279//! |-----------|-----------|----------------|----------|
280//! | **Key Encapsulation** | ML-KEM-1024 | Cat. 5 (256-bit) | NIST FIPS 203 |
281//! | **Key Derivation** | HKDF-SHA512 | 256-bit | SP 800-56C Rev. 2 |
282//! | **Message Auth** | HMAC-SHA512 | 256-bit | FIPS 198-1 |
283//! | **AEAD (Default)** | AES-256-GCM-SIV | 256-bit | RFC 8452 |
284//! | **AEAD (Alt)** | ChaCha20-Poly1305 | 256-bit | RFC 8439 |
285//! | **Random Generation** | OS CSPRNG | System | `/dev/urandom` |
286//!
287//! ### Key Derivation Process
288//!
289//! ```text
290//! 1. ML-KEM-1024.Encapsulate(recipient_pk) → (ciphertext, shared_secret)
291//! 2. wrap_key = HKDF-SHA512-Expand(
292//! HKDF-SHA512-Extract(
293//! salt: recipient_pk || ciphertext,
294//! IKM: shared_secret
295//! ),
296//! info: "anubis-encryption.org/v1/MLKEM-1024",
297//! L: 32 bytes
298//! )
299//! 3. encrypted_file_key = ChaCha20-Poly1305.Encrypt(wrap_key, file_key)
300//! 4. payload = AES-256-GCM-SIV.Encrypt(file_key, plaintext)
301//! ```
302//!
303//! ## Performance
304//!
305//! Benchmarks on Apple M1 with 2.0 GB video file:
306//!
307//! | Operation | Throughput | Time |
308//! |-----------|------------|------|
309//! | **Encryption** | ~187 MB/s | 10.97s |
310//! | **Decryption** | ~159 MB/s | 12.89s |
311//! | **Key Generation** | N/A | ~2ms |
312//!
313//! ### Cryptographic Operation Timing
314//!
315//! - **ML-KEM-1024 KeyGen**: ~2ms
316//! - **ML-KEM-1024 Encapsulate**: ~0.5ms
317//! - **ML-KEM-1024 Decapsulate**: ~0.6ms
318//! - **HKDF-SHA512 Derive**: <0.1ms
319//! - **File Encryption**: I/O-bound (~170 MB/s)
320//!
321//! ### Memory Usage
322//!
323//! - **Encryption**: ~64 KB constant (streaming)
324//! - **Decryption**: ~64 KB constant (streaming)
325//! - **Key Storage**: ~5 KB per identity
326//!
327//! Files of any size can be encrypted with constant memory usage.
328//!
329//! ## Examples
330//!
331//! ### File Encryption
332//!
333//! ```rust,no_run
334//! use anubis_rage::{pqc::mlkem, Encryptor};
335//! use std::fs::File;
336//! use std::io::{copy, Write};
337//!
338//! fn encrypt_file(
339//! input_path: &str,
340//! output_path: &str,
341//! recipient: &mlkem::Recipient
342//! ) -> Result<(), Box<dyn std::error::Error>> {
343//! let encryptor = Encryptor::with_recipients(vec![recipient as _])?;
344//!
345//! let mut input = File::open(input_path)?;
346//! let output = File::create(output_path)?;
347//! let mut writer = encryptor.wrap_output(output)?;
348//!
349//! copy(&mut input, &mut writer)?;
350//! writer.finish()?;
351//!
352//! Ok(())
353//! }
354//! ```
355//!
356//! ### Streaming Large Files
357//!
358//! ```rust,no_run
359//! use anubis_rage::{pqc::mlkem, Encryptor, Decryptor};
360//! use std::io::{Read, Write, copy};
361//!
362//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
363//! let identity = mlkem::Identity::generate();
364//! let recipient = identity.to_public();
365//!
366//! // Encrypt a large file with constant memory
367//! let encryptor = Encryptor::with_recipients(vec![&recipient as _])?;
368//! let input = std::fs::File::open("large-file.bin")?;
369//! let output = std::fs::File::create("large-file.bin.anubis")?;
370//! let mut writer = encryptor.wrap_output(output)?;
371//! std::io::copy(&mut input.take(u64::MAX), &mut writer)?;
372//! writer.finish()?;
373//!
374//! // Decrypt with constant memory
375//! let encrypted = std::fs::File::open("large-file.bin.anubis")?;
376//! let decryptor = Decryptor::new(encrypted)?;
377//! let mut reader = decryptor.decrypt(vec![&identity as _])?;
378//! let mut output = std::fs::File::create("large-file-decrypted.bin")?;
379//! std::io::copy(&mut reader, &mut output)?;
380//! # Ok(())
381//! # }
382//! ```
383//!
384//! ### Identity File Management
385//!
386//! ```rust,no_run
387//! use anubis_rage::{IdentityFile, pqc::mlkem};
388//! use std::fs::File;
389//! use std::io::Write;
390//!
391//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
392//! // Generate and save identity
393//! let identity = mlkem::Identity::generate();
394//! let recipient = identity.to_public();
395//!
396//! let identity_file = format!(
397//! "# Anubis Rage ML-KEM-1024 identity\n\
398//! # public key: {}\n\
399//! {}",
400//! recipient.to_string(),
401//! identity.to_string()
402//! );
403//!
404//! let mut file = File::create("identity.txt")?;
405//! file.write_all(identity_file.as_bytes())?;
406//! # Ok(())
407//! # }
408//! ```
409//!
410//! ## Module Organization
411//!
412//! - [`pqc`]: Post-quantum cryptography (ML-KEM-1024, ML-DSA-87)
413//! - [`Encryptor`]: Encryption API
414//! - [`Decryptor`]: Decryption API
415//! - [`armor`]: ASCII armoring support
416//! - [`stream`]: Low-level streaming encryption primitives
417//! - [`fips`]: FIPS 140-3 compliance and self-tests
418//!
419//! ## Platform Support
420//!
421//! Anubis Rage runs on all platforms supported by Rust and liboqs:
422//!
423//! - ✅ Linux (x86_64, aarch64)
424//! - ✅ macOS (Intel, Apple Silicon)
425//! - ✅ Windows (x86_64)
426//! - ✅ BSD (FreeBSD, OpenBSD, NetBSD)
427//! - ✅ Android / iOS (via FFI)
428//!
429//! ## Standards Compliance
430//!
431//! - **NIST FIPS 203**: ML-KEM (Module-Lattice-Based KEM)
432//! - **NIST FIPS 204**: ML-DSA (Module-Lattice-Based Digital Signature Algorithm)
433//! - **NIST FIPS 198-1**: HMAC (Keyed-Hash Message Authentication Code)
434//! - **NIST SP 800-56C Rev. 2**: Key Derivation (HKDF)
435//! - **RFC 8452**: AES-GCM-SIV (Authenticated Encryption)
436//! - **RFC 8439**: ChaCha20-Poly1305 (Authenticated Encryption)
437//! - **RFC 4648**: Base64 Encoding
438//!
439//! ## License
440//!
441//! Licensed under either of:
442//!
443//! - Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE))
444//! - MIT License ([LICENSE-MIT](../LICENSE-MIT))
445//!
446//! at your option.
447//!
448//! ## Contributing
449//!
450//! Contributions are welcome! Please see [CONTRIBUTING.md](../CONTRIBUTING.md).
451//!
452//! For security issues, see [SECURITY.md](../SECURITY.md) or email security@anubis-rage.org.
453//!
454
455#![cfg_attr(docsrs, feature(doc_cfg))]
456#![forbid(unsafe_code)]
457// Catch documentation errors caused by code changes.
458#![deny(rustdoc::broken_intra_doc_links)]
459#![deny(missing_docs)]
460
461use std::collections::HashSet;
462
463// Re-export crates that are used in our public API.
464pub use anubis_core::secrecy;
465
466mod error;
467mod format;
468mod identity;
469mod keys;
470mod primitives;
471mod protocol;
472mod util;
473
474pub use error::{DecryptError, EncryptError, IdentityFileConvertError};
475pub use identity::IdentityFile;
476pub use primitives::stream;
477pub use protocol::{Decryptor, Encryptor};
478
479#[cfg(feature = "armor")]
480#[cfg_attr(docsrs, doc(cfg(feature = "armor")))]
481pub use primitives::armor;
482
483#[cfg(feature = "cli-common")]
484#[cfg_attr(docsrs, doc(cfg(feature = "cli-common")))]
485pub mod cli_common;
486
487mod i18n;
488pub use i18n::localizer;
489
490//
491// Simple interface
492//
493
494mod simple;
495pub use simple::{decrypt, encrypt};
496
497#[cfg(feature = "armor")]
498#[cfg_attr(docsrs, doc(cfg(feature = "armor")))]
499pub use simple::encrypt_and_armor;
500
501//
502// Identity types
503//
504
505/// Post-quantum cryptography (PQC) using ML-KEM (formerly Kyber).
506///
507/// This module provides Level-5 post-quantum secure encryption using ML-KEM-1024.
508#[cfg(feature = "pqc-mlkem")]
509#[cfg_attr(docsrs, doc(cfg(feature = "pqc-mlkem")))]
510pub mod pqc;
511
512/// FIPS 140-3 compliance module
513///
514/// This module implements:
515/// - Module integrity verification
516/// - Power-up self-tests (POST)
517/// - Conditional self-tests
518///
519/// Required for FIPS 140-3 Level 1 certification.
520pub mod fips;
521
522//
523// Core traits
524//
525
526use anubis_core::{
527 format::{FileKey, Stanza},
528 secrecy::SecretString,
529};
530
531/// A private key or other value that can unwrap an opaque file key from a recipient
532/// stanza.
533///
534/// # Implementation notes
535///
536/// The canonical entry point for this trait is [`Identity::unwrap_stanzas`]. The default
537/// implementation of that method is:
538/// ```ignore
539/// stanzas.iter().find_map(|stanza| self.unwrap_stanza(stanza))
540/// ```
541///
542/// The `age` crate otherwise does not call [`Identity::unwrap_stanza`] directly. As such,
543/// if you want to add file-level stanza checks, override [`Identity::unwrap_stanzas`].
544pub trait Identity {
545 /// Attempts to unwrap the given stanza with this identity.
546 ///
547 /// This method is part of the `Identity` trait to expose age's [one joint] for
548 /// external implementations. You should not need to call this directly; instead, pass
549 /// identities to [`Decryptor::decrypt`].
550 ///
551 /// The `age` crate only calls this method via [`Identity::unwrap_stanzas`].
552 ///
553 /// Returns:
554 /// - `Some(Ok(file_key))` on success.
555 /// - `Some(Err(e))` if a decryption error occurs.
556 /// - `None` if the recipient stanza does not match this key.
557 ///
558 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
559 fn unwrap_stanza(&self, stanza: &Stanza) -> Option<Result<FileKey, DecryptError>>;
560
561 /// Attempts to unwrap any of the given stanzas, which are assumed to come from the
562 /// same age file header, and therefore contain the same file key.
563 ///
564 /// This method is part of the `Identity` trait to expose age's [one joint] for
565 /// external implementations. You should not need to call this directly; instead, pass
566 /// identities to [`Decryptor::decrypt`].
567 ///
568 /// Returns:
569 /// - `Some(Ok(file_key))` on success.
570 /// - `Some(Err(e))` if a decryption error occurs.
571 /// - `None` if none of the recipient stanzas match this identity.
572 ///
573 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
574 fn unwrap_stanzas(&self, stanzas: &[Stanza]) -> Option<Result<FileKey, DecryptError>> {
575 stanzas.iter().find_map(|stanza| self.unwrap_stanza(stanza))
576 }
577}
578
579/// A public key or other value that can wrap an opaque file key to a recipient stanza.
580///
581/// Implementations of this trait might represent more than one recipient.
582pub trait Recipient {
583 /// Wraps the given file key, returning stanzas to be placed in an age file header,
584 /// and labels that constrain how the stanzas may be combined with those from other
585 /// recipients.
586 ///
587 /// Implementations may return more than one stanza per "actual recipient", e.g. to
588 /// support multiple formats, to build group aliases, or to act as a proxy.
589 ///
590 /// This method is part of the `Recipient` trait to expose age's [one joint] for
591 /// external implementations. You should not need to call this directly; instead, pass
592 /// recipients to [`Encryptor::with_recipients`].
593 ///
594 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
595 ///
596 /// # Labels
597 ///
598 /// [`Encryptor`] will succeed at encrypting only if every recipient returns the same
599 /// set of labels. Subsets or partial overlapping sets are not allowed; all sets must
600 /// be identical. Labels are compared exactly, and are case-sensitive.
601 ///
602 /// Label sets can be used to ensure a recipient is only encrypted to alongside other
603 /// recipients with equivalent properties, or to ensure a recipient is always used
604 /// alone. A recipient with no particular properties to enforce should return an empty
605 /// label set.
606 ///
607 /// Labels can have any value that is a valid arbitrary string (`1*VCHAR` in ABNF),
608 /// but usually take one of several forms:
609 /// - *Common public label* - used by multiple recipients to permit their stanzas to
610 /// be used only together. Examples include:
611 /// - `postquantum` - indicates that the recipient stanzas being generated are
612 /// postquantum-secure, and that they can only be combined with other stanzas
613 /// that are also postquantum-secure.
614 /// - *Common private label* - used by recipients created by the same private entity
615 /// to permit their recipient stanzas to be used only together. For example,
616 /// private recipients used in a corporate environment could all send the same
617 /// private label in order to prevent compliant age clients from simultaneously
618 /// wrapping file keys with other recipients.
619 /// - *Random label* - used by recipients that want to ensure their stanzas are not
620 /// used with any other recipient stanzas. This can be used to produce a file key
621 /// that is only encrypted to a single recipient stanza, for example to preserve
622 /// its authentication properties.
623 fn wrap_file_key(
624 &self,
625 file_key: &FileKey,
626 ) -> Result<(Vec<Stanza>, HashSet<String>), EncryptError>;
627}
628
629/// Callbacks that might be triggered during encryption or decryption.
630///
631/// Structs that implement this trait should be given directly to the individual
632/// `Recipient` or `Identity` implementations that require them.
633pub trait Callbacks: Clone + Send + Sync + 'static {
634 /// Shows a message to the user.
635 ///
636 /// This can be used to prompt the user to take some physical action, such as
637 /// inserting a hardware key.
638 ///
639 /// No guarantee is provided that the user sees this message (for example, if there is
640 /// no UI for displaying messages).
641 fn display_message(&self, message: &str);
642
643 /// Requests that the user provides confirmation for some action.
644 ///
645 /// This can be used to, for example, request that a hardware key the plugin wants to
646 /// try either be plugged in, or skipped.
647 ///
648 /// - `message` is the request or call-to-action to be displayed to the user.
649 /// - `yes_string` and (optionally) `no_string` will be displayed on buttons or next
650 /// to selection options in the user's UI.
651 ///
652 /// Returns:
653 /// - `Some(true)` if the user selected the option marked with `yes_string`.
654 /// - `Some(false)` if the user selected the option marked with `no_string` (or the
655 /// default negative confirmation label).
656 /// - `None` if the confirmation request could not be given to the user (for example,
657 /// if there is no UI for displaying messages).
658 fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option<bool>;
659
660 /// Requests non-private input from the user.
661 ///
662 /// To request private inputs, use [`Callbacks::request_passphrase`].
663 ///
664 /// Returns:
665 /// - `Some(input)` with the user-provided input.
666 /// - `None` if no input could be requested from the user (for example, if there is no
667 /// UI for displaying messages or typing inputs).
668 fn request_public_string(&self, description: &str) -> Option<String>;
669
670 /// Requests a passphrase to decrypt a key.
671 ///
672 /// Returns:
673 /// - `Some(passphrase)` with the user-provided passphrase.
674 /// - `None` if no passphrase could be requested from the user (for example, if there
675 /// is no UI for displaying messages or typing inputs).
676 fn request_passphrase(&self, description: &str) -> Option<SecretString>;
677}
678
679/// An implementation of [`Callbacks`] that does not allow callbacks.
680///
681/// No user interaction will occur; [`Recipient`] or [`Identity`] implementations will
682/// receive `None` from the callbacks that return responses, and will act accordingly.
683#[derive(Clone, Copy, Debug)]
684pub struct NoCallbacks;
685
686impl Callbacks for NoCallbacks {
687 fn display_message(&self, _: &str) {}
688
689 fn confirm(&self, _: &str, _: &str, _: Option<&str>) -> Option<bool> {
690 None
691 }
692
693 fn request_public_string(&self, _: &str) -> Option<String> {
694 None
695 }
696
697 fn request_passphrase(&self, _: &str) -> Option<SecretString> {
698 None
699 }
700}
701
702//
703// Fuzzing APIs
704//
705
706/// Helper for fuzzing the Header parser and serializer.
707#[cfg(fuzzing)]
708pub fn fuzz_header(data: &[u8]) {
709 if let Ok(header) = format::Header::read(data) {
710 let mut buf = Vec::with_capacity(data.len());
711 header.write(&mut buf).expect("can write header");
712 assert_eq!(&buf[..], &data[..buf.len()]);
713 }
714}