Skip to main content

age/
lib.rs

1//! *Library for encrypting and decrypting age files*
2//!
3//! This crate implements file encryption according to the [age-encryption.org/v1]
4//! specification. It generates and consumes encrypted files that are compatible with the
5//! [rage] CLI tool, as well as the reference [Go] implementation.
6//!
7//! The encryption and decryption APIs are provided by [`Encryptor`] and [`Decryptor`].
8//! There are several ways to use these:
9//! - For most cases (including programmatic usage), use [`Encryptor::with_recipients`]
10//!   with [`x25519::Recipient`], and [`Decryptor`] with [`x25519::Identity`].
11//! - For passphrase-based encryption and decryption, use [`scrypt::Recipient`] and
12//!   [`scrypt::Identity`], or the helper method [`Encryptor::with_user_passphrase`].
13//!   These should only be used with passphrases that were provided by (or generated for)
14//!   a human.
15//! - For compatibility with existing SSH keys, enable the `ssh` feature flag, and use
16//!   [`ssh::Recipient`] and [`ssh::Identity`].
17//!
18//! Age-encrypted files are binary and non-malleable. To encode them as text, use the
19//! wrapping readers and writers in the [`armor`] module, behind the `armor` feature flag.
20//!
21//! *Caution*: all crate versions prior to 1.0 are beta releases for **testing purposes
22//! only**.
23//!
24//! [age-encryption.org/v1]: https://age-encryption.org/v1
25//! [rage]: https://crates.io/crates/rage
26//! [Go]: https://filippo.io/age
27//!
28//! # Examples
29//!
30//! ## Streamlined APIs
31//!
32//! These are useful when you only need to encrypt to a single recipient, and the data is
33//! small enough to fit in memory.
34//!
35//! ### Recipient-based encryption
36//!
37//! ```
38//! # fn run_main() -> Result<(), ()> {
39//! let key = age::x25519::Identity::generate();
40//! let pubkey = key.to_public();
41//!
42//! let plaintext = b"Hello world!";
43//!
44//! # fn encrypt(pubkey: age::x25519::Recipient, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
45//! let encrypted = age::encrypt(&pubkey, plaintext)?;
46//! # Ok(encrypted)
47//! # }
48//! # fn decrypt(key: age::x25519::Identity, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
49//! let decrypted = age::decrypt(&key, &encrypted)?;
50//! # Ok(decrypted)
51//! # }
52//! # let decrypted = decrypt(
53//! #     key,
54//! #     encrypt(pubkey, &plaintext[..]).map_err(|_| ())?
55//! # ).map_err(|_| ())?;
56//!
57//! assert_eq!(decrypted, plaintext);
58//! # Ok(())
59//! # }
60//! # run_main().unwrap();
61//! ```
62//!
63//! ## Passphrase-based encryption
64//!
65//! ```
66//! use age::secrecy::SecretString;
67//!
68//! # fn run_main() -> Result<(), ()> {
69//! let passphrase = SecretString::from("this is not a good passphrase".to_owned());
70//! let recipient = age::scrypt::Recipient::new(passphrase.clone());
71//! let identity = age::scrypt::Identity::new(passphrase);
72//!
73//! let plaintext = b"Hello world!";
74//!
75//! # fn encrypt(recipient: age::scrypt::Recipient, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
76//! let encrypted = age::encrypt(&recipient, plaintext)?;
77//! # Ok(encrypted)
78//! # }
79//! # fn decrypt(identity: age::scrypt::Identity, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
80//! let decrypted = age::decrypt(&identity, &encrypted)?;
81//! # Ok(decrypted)
82//! # }
83//! # let decrypted = decrypt(
84//! #     identity,
85//! #     encrypt(recipient, &plaintext[..]).map_err(|_| ())?
86//! # ).map_err(|_| ())?;
87//!
88//! assert_eq!(decrypted, plaintext);
89//! # Ok(())
90//! # }
91//! # run_main().unwrap();
92//! ```
93//!
94//! ## Full APIs
95//!
96//! The full APIs support encrypting to multiple recipients, streaming the data, and have
97//! async I/O options.
98//!
99//! ### Recipient-based encryption
100//!
101//! ```
102//! use std::io::{Read, Write};
103//! use std::iter;
104//!
105//! # fn run_main() -> Result<(), ()> {
106//! let key = age::x25519::Identity::generate();
107//! let pubkey = key.to_public();
108//!
109//! let plaintext = b"Hello world!";
110//!
111//! // Encrypt the plaintext to a ciphertext...
112//! # fn encrypt(pubkey: age::x25519::Recipient, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
113//! let encrypted = {
114//!     let encryptor = age::Encryptor::with_recipients(iter::once(&pubkey as _))
115//!         .expect("we provided a recipient");
116//!
117//!     let mut encrypted = vec![];
118//!     let mut writer = encryptor.wrap_output(&mut encrypted)?;
119//!     writer.write_all(plaintext)?;
120//!     writer.finish()?;
121//!
122//!     encrypted
123//! };
124//! # Ok(encrypted)
125//! # }
126//!
127//! // ... and decrypt the obtained ciphertext to the plaintext again.
128//! # fn decrypt(key: age::x25519::Identity, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
129//! let decrypted = {
130//!     let decryptor = age::Decryptor::new(&encrypted[..])?;
131//!
132//!     let mut decrypted = vec![];
133//!     let mut reader = decryptor.decrypt(iter::once(&key as &dyn age::Identity))?;
134//!     reader.read_to_end(&mut decrypted);
135//!
136//!     decrypted
137//! };
138//! # Ok(decrypted)
139//! # }
140//! # let decrypted = decrypt(
141//! #     key,
142//! #     encrypt(pubkey, &plaintext[..]).map_err(|_| ())?
143//! # ).map_err(|_| ())?;
144//!
145//! assert_eq!(decrypted, plaintext);
146//! # Ok(())
147//! # }
148//!
149//! # run_main().unwrap();
150//! ```
151//!
152//! ## Passphrase-based encryption
153//!
154//! ```
155//! use age::secrecy::SecretString;
156//! use std::io::{Read, Write};
157//! use std::iter;
158//!
159//! # fn run_main() -> Result<(), ()> {
160//! let plaintext = b"Hello world!";
161//! let passphrase = SecretString::from("this is not a good passphrase".to_owned());
162//!
163//! // Encrypt the plaintext to a ciphertext using the passphrase...
164//! # fn encrypt(passphrase: SecretString, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
165//! let encrypted = {
166//!     let encryptor = age::Encryptor::with_user_passphrase(passphrase.clone());
167//!
168//!     let mut encrypted = vec![];
169//!     let mut writer = encryptor.wrap_output(&mut encrypted)?;
170//!     writer.write_all(plaintext)?;
171//!     writer.finish()?;
172//!
173//!     encrypted
174//! };
175//! # Ok(encrypted)
176//! # }
177//!
178//! // ... and decrypt the ciphertext to the plaintext again using the same passphrase.
179//! # fn decrypt(passphrase: SecretString, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
180//! let decrypted = {
181//!     let decryptor = age::Decryptor::new(&encrypted[..])?;
182//!
183//!     let mut decrypted = vec![];
184//!     let mut reader = decryptor.decrypt(iter::once(&age::scrypt::Identity::new(passphrase) as _))?;
185//!     reader.read_to_end(&mut decrypted);
186//!
187//!     decrypted
188//! };
189//! # Ok(decrypted)
190//! # }
191//! # let decrypted = decrypt(
192//! #     passphrase.clone(),
193//! #     encrypt(passphrase, &plaintext[..]).map_err(|_| ())?
194//! # ).map_err(|_| ())?;
195//!
196//! assert_eq!(decrypted, plaintext);
197//! # Ok(())
198//! # }
199//! # run_main().unwrap();
200//! ```
201
202#![cfg_attr(docsrs, feature(doc_cfg))]
203#![forbid(unsafe_code)]
204// Catch documentation errors caused by code changes.
205#![deny(rustdoc::broken_intra_doc_links)]
206#![deny(missing_docs)]
207
208use std::collections::HashSet;
209
210// Re-export crates that are used in our public API.
211pub use age_core::secrecy;
212
213mod error;
214mod format;
215mod identity;
216mod keys;
217mod primitives;
218mod protocol;
219mod util;
220
221pub use error::{DecryptError, EncryptError, IdentityFileConvertError};
222pub use identity::IdentityFile;
223pub use primitives::stream;
224pub use protocol::{Decryptor, Encryptor};
225
226#[cfg(feature = "armor")]
227#[cfg_attr(docsrs, doc(cfg(feature = "armor")))]
228pub use primitives::armor;
229
230#[cfg(feature = "cli-common")]
231#[cfg_attr(docsrs, doc(cfg(feature = "cli-common")))]
232pub mod cli_common;
233
234mod i18n;
235pub use i18n::localizer;
236
237//
238// Simple interface
239//
240
241mod simple;
242pub use simple::{decrypt, encrypt};
243
244#[cfg(feature = "armor")]
245#[cfg_attr(docsrs, doc(cfg(feature = "armor")))]
246pub use simple::encrypt_and_armor;
247
248//
249// Identity types
250//
251
252pub mod encrypted;
253pub mod scrypt;
254pub mod x25519;
255
256#[cfg(feature = "plugin")]
257#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
258pub mod plugin;
259
260#[cfg(feature = "ssh")]
261#[cfg_attr(docsrs, doc(cfg(feature = "ssh")))]
262pub mod ssh;
263
264//
265// Core traits
266//
267
268use age_core::{
269    format::{FileKey, Stanza},
270    secrecy::SecretString,
271};
272
273/// A private key or other value that can unwrap an opaque file key from a recipient
274/// stanza.
275///
276/// # Implementation notes
277///
278/// The canonical entry point for this trait is [`Identity::unwrap_stanzas`]. The default
279/// implementation of that method is:
280/// ```ignore
281/// stanzas.iter().find_map(|stanza| self.unwrap_stanza(stanza))
282/// ```
283///
284/// The `age` crate otherwise does not call [`Identity::unwrap_stanza`] directly. As such,
285/// if you want to add file-level stanza checks, override [`Identity::unwrap_stanzas`].
286pub trait Identity {
287    /// Attempts to unwrap the given stanza with this identity.
288    ///
289    /// This method is part of the `Identity` trait to expose age's [one joint] for
290    /// external implementations. You should not need to call this directly; instead, pass
291    /// identities to [`Decryptor::decrypt`].
292    ///
293    /// The `age` crate only calls this method via [`Identity::unwrap_stanzas`].
294    ///
295    /// Returns:
296    /// - `Some(Ok(file_key))` on success.
297    /// - `Some(Err(e))` if a decryption error occurs.
298    /// - `None` if the recipient stanza does not match this key.
299    ///
300    /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
301    fn unwrap_stanza(&self, stanza: &Stanza) -> Option<Result<FileKey, DecryptError>>;
302
303    /// Attempts to unwrap any of the given stanzas, which are assumed to come from the
304    /// same age file header, and therefore contain the same file key.
305    ///
306    /// This method is part of the `Identity` trait to expose age's [one joint] for
307    /// external implementations. You should not need to call this directly; instead, pass
308    /// identities to [`Decryptor::decrypt`].
309    ///
310    /// Returns:
311    /// - `Some(Ok(file_key))` on success.
312    /// - `Some(Err(e))` if a decryption error occurs.
313    /// - `None` if none of the recipient stanzas match this identity.
314    ///
315    /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
316    fn unwrap_stanzas(&self, stanzas: &[Stanza]) -> Option<Result<FileKey, DecryptError>> {
317        stanzas.iter().find_map(|stanza| self.unwrap_stanza(stanza))
318    }
319}
320
321/// A public key or other value that can wrap an opaque file key to a recipient stanza.
322///
323/// Implementations of this trait might represent more than one recipient.
324pub trait Recipient {
325    /// Wraps the given file key, returning stanzas to be placed in an age file header,
326    /// and labels that constrain how the stanzas may be combined with those from other
327    /// recipients.
328    ///
329    /// Implementations may return more than one stanza per "actual recipient", e.g. to
330    /// support multiple formats, to build group aliases, or to act as a proxy.
331    ///
332    /// This method is part of the `Recipient` trait to expose age's [one joint] for
333    /// external implementations. You should not need to call this directly; instead, pass
334    /// recipients to [`Encryptor::with_recipients`].
335    ///
336    /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
337    ///
338    /// # Labels
339    ///
340    /// [`Encryptor`] will succeed at encrypting only if every recipient returns the same
341    /// set of labels. Subsets or partial overlapping sets are not allowed; all sets must
342    /// be identical. Labels are compared exactly, and are case-sensitive.
343    ///
344    /// Label sets can be used to ensure a recipient is only encrypted to alongside other
345    /// recipients with equivalent properties, or to ensure a recipient is always used
346    /// alone. A recipient with no particular properties to enforce should return an empty
347    /// label set.
348    ///
349    /// Labels can have any value that is a valid arbitrary string (`1*VCHAR` in ABNF),
350    /// but usually take one of several forms:
351    ///   - *Common public label* - used by multiple recipients to permit their stanzas to
352    ///     be used only together. Examples include:
353    ///     - `postquantum` - indicates that the recipient stanzas being generated are
354    ///       postquantum-secure, and that they can only be combined with other stanzas
355    ///       that are also postquantum-secure.
356    ///   - *Common private label* - used by recipients created by the same private entity
357    ///     to permit their recipient stanzas to be used only together. For example,
358    ///     private recipients used in a corporate environment could all send the same
359    ///     private label in order to prevent compliant age clients from simultaneously
360    ///     wrapping file keys with other recipients.
361    ///   - *Random label* - used by recipients that want to ensure their stanzas are not
362    ///     used with any other recipient stanzas. This can be used to produce a file key
363    ///     that is only encrypted to a single recipient stanza, for example to preserve
364    ///     its authentication properties.
365    fn wrap_file_key(
366        &self,
367        file_key: &FileKey,
368    ) -> Result<(Vec<Stanza>, HashSet<String>), EncryptError>;
369}
370
371/// Callbacks that might be triggered during encryption or decryption.
372///
373/// Structs that implement this trait should be given directly to the individual
374/// `Recipient` or `Identity` implementations that require them.
375pub trait Callbacks: Clone + Send + Sync + 'static {
376    /// Shows a message to the user.
377    ///
378    /// This can be used to prompt the user to take some physical action, such as
379    /// inserting a hardware key.
380    ///
381    /// No guarantee is provided that the user sees this message (for example, if there is
382    /// no UI for displaying messages).
383    fn display_message(&self, message: &str);
384
385    /// Requests that the user provides confirmation for some action.
386    ///
387    /// This can be used to, for example, request that a hardware key the plugin wants to
388    /// try either be plugged in, or skipped.
389    ///
390    /// - `message` is the request or call-to-action to be displayed to the user.
391    /// - `yes_string` and (optionally) `no_string` will be displayed on buttons or next
392    ///   to selection options in the user's UI.
393    ///
394    /// Returns:
395    /// - `Some(true)` if the user selected the option marked with `yes_string`.
396    /// - `Some(false)` if the user selected the option marked with `no_string` (or the
397    ///   default negative confirmation label).
398    /// - `None` if the confirmation request could not be given to the user (for example,
399    ///   if there is no UI for displaying messages).
400    fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option<bool>;
401
402    /// Requests non-private input from the user.
403    ///
404    /// To request private inputs, use [`Callbacks::request_passphrase`].
405    ///
406    /// Returns:
407    /// - `Some(input)` with the user-provided input.
408    /// - `None` if no input could be requested from the user (for example, if there is no
409    ///   UI for displaying messages or typing inputs).
410    fn request_public_string(&self, description: &str) -> Option<String>;
411
412    /// Requests a passphrase to decrypt a key.
413    ///
414    /// Returns:
415    /// - `Some(passphrase)` with the user-provided passphrase.
416    /// - `None` if no passphrase could be requested from the user (for example, if there
417    ///   is no UI for displaying messages or typing inputs).
418    fn request_passphrase(&self, description: &str) -> Option<SecretString>;
419}
420
421/// An implementation of [`Callbacks`] that does not allow callbacks.
422///
423/// No user interaction will occur; [`Recipient`] or [`Identity`] implementations will
424/// receive `None` from the callbacks that return responses, and will act accordingly.
425#[derive(Clone, Copy, Debug)]
426pub struct NoCallbacks;
427
428impl Callbacks for NoCallbacks {
429    fn display_message(&self, _: &str) {}
430
431    fn confirm(&self, _: &str, _: &str, _: Option<&str>) -> Option<bool> {
432        None
433    }
434
435    fn request_public_string(&self, _: &str) -> Option<String> {
436        None
437    }
438
439    fn request_passphrase(&self, _: &str) -> Option<SecretString> {
440        None
441    }
442}
443
444//
445// Fuzzing APIs
446//
447
448/// Helper for fuzzing the Header parser and serializer.
449#[cfg(fuzzing)]
450pub fn fuzz_header(data: &[u8]) {
451    if let Ok(header) = format::Header::read(data) {
452        let mut buf = Vec::with_capacity(data.len());
453        header.write(&mut buf).expect("can write header");
454        assert_eq!(&buf[..], &data[..buf.len()]);
455    }
456}