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//! - APIs are available for passphrase-based encryption and decryption. These should
12//! only be used with passphrases that were provided by (or generated for) a human.
13//! - For compatibility with existing SSH keys, enable the `ssh` feature flag, and use
14//! [`ssh::Recipient`] and [`ssh::Identity`].
15//!
16//! Age-encrypted files are binary and non-malleable. To encode them as text, use the
17//! wrapping readers and writers in the [`armor`] module, behind the `armor` feature flag.
18//!
19//! *Caution*: all crate versions prior to 1.0 are beta releases for **testing purposes
20//! only**.
21//!
22//! [age-encryption.org/v1]: https://age-encryption.org/v1
23//! [rage]: https://crates.io/crates/rage
24//! [Go]: https://filippo.io/age
25//!
26//! # Examples
27//!
28//! ## Recipient-based encryption
29//!
30//! ```
31//! use std::io::{Read, Write};
32//! use std::iter;
33//!
34//! # fn run_main() -> Result<(), ()> {
35//! let key = age::x25519::Identity::generate();
36//! let pubkey = key.to_public();
37//!
38//! let plaintext = b"Hello world!";
39//!
40//! // Encrypt the plaintext to a ciphertext...
41//! # fn encrypt(pubkey: age::x25519::Recipient, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
42//! let encrypted = {
43//! let encryptor = age::Encryptor::with_recipients(vec![Box::new(pubkey)])
44//! .expect("we provided a recipient");
45//!
46//! let mut encrypted = vec![];
47//! let mut writer = encryptor.wrap_output(&mut encrypted)?;
48//! writer.write_all(plaintext)?;
49//! writer.finish()?;
50//!
51//! encrypted
52//! };
53//! # Ok(encrypted)
54//! # }
55//!
56//! // ... and decrypt the obtained ciphertext to the plaintext again.
57//! # fn decrypt(key: age::x25519::Identity, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
58//! let decrypted = {
59//! let decryptor = match age::Decryptor::new(&encrypted[..])? {
60//! age::Decryptor::Recipients(d) => d,
61//! _ => unreachable!(),
62//! };
63//!
64//! let mut decrypted = vec![];
65//! let mut reader = decryptor.decrypt(iter::once(&key as &dyn age::Identity))?;
66//! reader.read_to_end(&mut decrypted);
67//!
68//! decrypted
69//! };
70//! # Ok(decrypted)
71//! # }
72//! # let decrypted = decrypt(
73//! # key,
74//! # encrypt(pubkey, &plaintext[..]).map_err(|_| ())?
75//! # ).map_err(|_| ())?;
76//!
77//! assert_eq!(decrypted, plaintext);
78//! # Ok(())
79//! # }
80//!
81//! # run_main().unwrap();
82//! ```
83//!
84//! ## Passphrase-based encryption
85//!
86//! ```
87//! use age::secrecy::Secret;
88//! use std::io::{Read, Write};
89//!
90//! # fn run_main() -> Result<(), ()> {
91//! let plaintext = b"Hello world!";
92//! let passphrase = "this is not a good passphrase";
93//!
94//! // Encrypt the plaintext to a ciphertext using the passphrase...
95//! # fn encrypt(passphrase: &str, plaintext: &[u8]) -> Result<Vec<u8>, age::EncryptError> {
96//! let encrypted = {
97//! let encryptor = age::Encryptor::with_user_passphrase(Secret::new(passphrase.to_owned()));
98//!
99//! let mut encrypted = vec![];
100//! let mut writer = encryptor.wrap_output(&mut encrypted)?;
101//! writer.write_all(plaintext)?;
102//! writer.finish()?;
103//!
104//! encrypted
105//! };
106//! # Ok(encrypted)
107//! # }
108//!
109//! // ... and decrypt the ciphertext to the plaintext again using the same passphrase.
110//! # fn decrypt(passphrase: &str, encrypted: Vec<u8>) -> Result<Vec<u8>, age::DecryptError> {
111//! let decrypted = {
112//! let decryptor = match age::Decryptor::new(&encrypted[..])? {
113//! age::Decryptor::Passphrase(d) => d,
114//! _ => unreachable!(),
115//! };
116//!
117//! let mut decrypted = vec![];
118//! let mut reader = decryptor.decrypt(&Secret::new(passphrase.to_owned()), None)?;
119//! reader.read_to_end(&mut decrypted);
120//!
121//! decrypted
122//! };
123//! # Ok(decrypted)
124//! # }
125//! # let decrypted = decrypt(
126//! # passphrase,
127//! # encrypt(passphrase, &plaintext[..]).map_err(|_| ())?
128//! # ).map_err(|_| ())?;
129//!
130//! assert_eq!(decrypted, plaintext);
131//! # Ok(())
132//! # }
133//! # run_main().unwrap();
134//! ```
135
136#![cfg_attr(docsrs, feature(doc_cfg))]
137#![forbid(unsafe_code)]
138// Catch documentation errors caused by code changes.
139#![deny(rustdoc::broken_intra_doc_links)]
140#![deny(missing_docs)]
141
142// Re-export crates that are used in our public API.
143pub use age_core::secrecy;
144
145mod error;
146mod format;
147mod identity;
148mod keys;
149mod primitives;
150mod protocol;
151mod util;
152
153pub use error::{DecryptError, EncryptError};
154pub use identity::{IdentityFile, IdentityFileEntry};
155pub use primitives::stream;
156pub use protocol::{decryptor, Decryptor, Encryptor};
157
158#[cfg(feature = "armor")]
159pub use primitives::armor;
160
161#[cfg(feature = "cli-common")]
162#[cfg_attr(docsrs, doc(cfg(feature = "cli-common")))]
163pub mod cli_common;
164
165mod i18n;
166pub use i18n::localizer;
167
168//
169// Identity types
170//
171
172pub mod encrypted;
173mod scrypt;
174pub mod x25519;
175
176#[cfg(feature = "plugin")]
177#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
178pub mod plugin;
179
180#[cfg(feature = "ssh")]
181#[cfg_attr(docsrs, doc(cfg(feature = "ssh")))]
182pub mod ssh;
183
184use age_core::{
185 format::{FileKey, Stanza},
186 secrecy::SecretString,
187};
188
189/// A private key or other value that can unwrap an opaque file key from a recipient
190/// stanza.
191pub trait Identity {
192 /// Attempts to unwrap the given stanza with this identity.
193 ///
194 /// This method is part of the `Identity` trait to expose age's [one joint] for
195 /// external implementations. You should not need to call this directly; instead, pass
196 /// identities to [`RecipientsDecryptor::decrypt`].
197 ///
198 /// Returns:
199 /// - `Some(Ok(file_key))` on success.
200 /// - `Some(Err(e))` if a decryption error occurs.
201 /// - `None` if the recipient stanza does not match this key.
202 ///
203 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
204 /// [`RecipientsDecryptor::decrypt`]: protocol::decryptor::RecipientsDecryptor::decrypt
205 fn unwrap_stanza(&self, stanza: &Stanza) -> Option<Result<FileKey, DecryptError>>;
206
207 /// Attempts to unwrap any of the given stanzas, which are assumed to come from the
208 /// same age file header, and therefore contain the same file key.
209 ///
210 /// This method is part of the `Identity` trait to expose age's [one joint] for
211 /// external implementations. You should not need to call this directly; instead, pass
212 /// identities to [`RecipientsDecryptor::decrypt`].
213 ///
214 /// Returns:
215 /// - `Some(Ok(file_key))` on success.
216 /// - `Some(Err(e))` if a decryption error occurs.
217 /// - `None` if none of the recipient stanzas match this identity.
218 ///
219 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
220 /// [`RecipientsDecryptor::decrypt`]: protocol::decryptor::RecipientsDecryptor::decrypt
221 fn unwrap_stanzas(&self, stanzas: &[Stanza]) -> Option<Result<FileKey, DecryptError>> {
222 stanzas.iter().find_map(|stanza| self.unwrap_stanza(stanza))
223 }
224}
225
226/// A public key or other value that can wrap an opaque file key to a recipient stanza.
227///
228/// Implementations of this trait might represent more than one recipient.
229pub trait Recipient {
230 /// Wraps the given file key, returning stanzas to be placed in an age file header.
231 ///
232 /// Implementations MUST NOT return more than one stanza per "actual recipient".
233 ///
234 /// This method is part of the `Recipient` trait to expose age's [one joint] for
235 /// external implementations. You should not need to call this directly; instead, pass
236 /// recipients to [`Encryptor::with_recipients`].
237 ///
238 /// [one joint]: https://www.imperialviolet.org/2016/05/16/agility.html
239 fn wrap_file_key(&self, file_key: &FileKey) -> Result<Vec<Stanza>, EncryptError>;
240}
241
242/// Callbacks that might be triggered during encryption or decryption.
243///
244/// Structs that implement this trait should be given directly to the individual
245/// `Recipient` or `Identity` implementations that require them.
246pub trait Callbacks: Clone + Send + Sync + 'static {
247 /// Shows a message to the user.
248 ///
249 /// This can be used to prompt the user to take some physical action, such as
250 /// inserting a hardware key.
251 fn display_message(&self, message: &str);
252
253 /// Requests that the user provides confirmation for some action.
254 ///
255 /// This can be used to, for example, request that a hardware key the plugin wants to
256 /// try either be plugged in, or skipped.
257 ///
258 /// - `message` is the request or call-to-action to be displayed to the user.
259 /// - `yes_string` and (optionally) `no_string` will be displayed on buttons or next
260 /// to selection options in the user's UI.
261 ///
262 /// Returns:
263 /// - `Some(true)` if the user selected the option marked with `yes_string`.
264 /// - `Some(false)` if the user selected the option marked with `no_string` (or the
265 /// default negative confirmation label).
266 /// - `None` if the confirmation request could not be given to the user (for example,
267 /// if there is no UI for displaying messages).
268 fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option<bool>;
269
270 /// Requests non-private input from the user.
271 ///
272 /// To request private inputs, use [`Callbacks::request_passphrase`].
273 fn request_public_string(&self, description: &str) -> Option<String>;
274
275 /// Requests a passphrase to decrypt a key.
276 fn request_passphrase(&self, description: &str) -> Option<SecretString>;
277}
278
279/// Helper for fuzzing the Header parser and serializer.
280#[cfg(fuzzing)]
281pub fn fuzz_header(data: &[u8]) {
282 if let Ok(header) = format::Header::read(data) {
283 let mut buf = Vec::with_capacity(data.len());
284 header.write(&mut buf).expect("can write header");
285 assert_eq!(&buf[..], &data[..buf.len()]);
286 }
287}