From cb5b62ca30bb0cd69cc87889dff9b41da1d09244 Mon Sep 17 00:00:00 2001 From: Dan Seminara Date: Sun, 21 Dec 2025 00:20:18 -0500 Subject: [PATCH] Use `aws-lc-rs` instead of `chacha20poly1305` crate --- Cargo.lock | 95 +++++++++++++++++++++--------------- Cargo.toml | 2 +- age-core/Cargo.toml | 2 +- age-core/src/primitives.rs | 40 ++++++++++----- age/Cargo.toml | 2 +- age/src/error.rs | 4 +- age/src/primitives/stream.rs | 44 ++++++++++------- 7 files changed, 115 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37a3e255..bd46990f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,11 +65,11 @@ dependencies = [ "aes", "aes-gcm", "age-core", + "aws-lc-rs", "base64", "bcrypt-pbkdf", "bech32", "cbc", - "chacha20poly1305", "cipher", "console", "cookie-factory", @@ -112,8 +112,8 @@ dependencies = [ name = "age-core" version = "0.11.0" dependencies = [ + "aws-lc-rs", "base64", - "chacha20poly1305", "cookie-factory", "hkdf", "io_tee", @@ -245,6 +245,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +dependencies = [ + "aws-lc-sys", + "untrusted", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -408,10 +431,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.33" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -423,30 +447,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.39" @@ -496,7 +496,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -559,6 +558,15 @@ dependencies = [ "roff", ] +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.2" @@ -895,6 +903,12 @@ dependencies = [ "toml", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "findshlibs" version = "0.10.2" @@ -968,6 +982,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fuse_mt" version = "0.6.1" @@ -1845,17 +1865,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.2" @@ -2815,6 +2824,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index fde3ada2..5fcb93dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ age-core = { version = "0.11.0", path = "age-core" } base64 = "0.21" # - ChaCha20-Poly1305 from RFC 7539 -chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] } +aws-lc-rs = "1.13.1" # - X25519 from RFC 7748 x25519-dalek = { version = "2", features = ["static_secrets"] } diff --git a/age-core/Cargo.toml b/age-core/Cargo.toml index 131a54dd..5ce08fdc 100644 --- a/age-core/Cargo.toml +++ b/age-core/Cargo.toml @@ -19,7 +19,7 @@ maintenance = { status = "experimental" } [dependencies] # Dependencies exposed in a public API: # (Breaking upgrades to these require a breaking upgrade to this crate.) -chacha20poly1305.workspace = true +aws-lc-rs.workspace = true cookie-factory.workspace = true io_tee = "0.1.1" nom.workspace = true diff --git a/age-core/src/primitives.rs b/age-core/src/primitives.rs index 75571873..535d31b2 100644 --- a/age-core/src/primitives.rs +++ b/age-core/src/primitives.rs @@ -1,8 +1,8 @@ //! Primitive cryptographic operations used across various `age` components. -use chacha20poly1305::{ - aead::{self, generic_array::typenum::Unsigned, Aead, AeadCore, KeyInit}, - ChaCha20Poly1305, +use aws_lc_rs::{ + aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305}, + error, }; use hkdf::Hkdf; use sha2::Sha256; @@ -13,9 +13,18 @@ use sha2::Sha256; /// /// [RFC 7539]: https://tools.ietf.org/html/rfc7539 pub fn aead_encrypt(key: &[u8; 32], plaintext: &[u8]) -> Vec { - let c = ChaCha20Poly1305::new(key.into()); - c.encrypt(&[0; 12].into(), plaintext) - .expect("we won't overflow the ChaCha20 block counter") + let k = LessSafeKey::new( + UnboundKey::new(&CHACHA20_POLY1305, key).expect("byte length of key will match expected"), + ); + let mut buffer = Vec::with_capacity(plaintext.len() + CHACHA20_POLY1305.tag_len()); + buffer.extend_from_slice(plaintext); + k.seal_in_place_append_tag( + Nonce::assume_unique_for_key([0; 12]), + Aad::empty(), + &mut buffer, + ) + .expect("encryption won't fail"); + buffer } /// `decrypt[key](ciphertext)` - decrypts a message of an expected fixed size. @@ -31,13 +40,22 @@ pub fn aead_decrypt( key: &[u8; 32], size: usize, ciphertext: &[u8], -) -> Result, aead::Error> { - if ciphertext.len() != size + ::TagSize::to_usize() { - return Err(aead::Error); +) -> Result, error::Unspecified> { + if ciphertext.len() != size + CHACHA20_POLY1305.tag_len() { + return Err(error::Unspecified); } - let c = ChaCha20Poly1305::new(key.into()); - c.decrypt(&[0; 12].into(), ciphertext) + let k = LessSafeKey::new( + UnboundKey::new(&CHACHA20_POLY1305, key).expect("byte length of key will match expected"), + ); + let mut buffer = Vec::from(ciphertext); + k.open_in_place( + Nonce::assume_unique_for_key([0; 12]), + Aad::empty(), + &mut buffer, + )?; + buffer.truncate(buffer.len() - CHACHA20_POLY1305.tag_len()); + Ok(buffer) } /// `HKDF[salt, label](key, 32)` diff --git a/age/Cargo.toml b/age/Cargo.toml index 994ca3f5..224cbc46 100644 --- a/age/Cargo.toml +++ b/age/Cargo.toml @@ -20,7 +20,7 @@ age-core.workspace = true # Dependencies exposed in a public API: # (Breaking upgrades to these require a breaking upgrade to this crate.) base64.workspace = true -chacha20poly1305.workspace = true +aws-lc-rs.workspace = true hmac.workspace = true i18n-embed.workspace = true rand.workspace = true diff --git a/age/src/error.rs b/age/src/error.rs index 5505d4dd..c6cdf62a 100644 --- a/age/src/error.rs +++ b/age/src/error.rs @@ -413,8 +413,8 @@ impl fmt::Display for DecryptError { } } -impl From for DecryptError { - fn from(_: chacha20poly1305::aead::Error) -> Self { +impl From for DecryptError { + fn from(_: aws_lc_rs::error::Unspecified) -> Self { DecryptError::DecryptionFailed } } diff --git a/age/src/primitives/stream.rs b/age/src/primitives/stream.rs index 652e79ce..9f65c88c 100644 --- a/age/src/primitives/stream.rs +++ b/age/src/primitives/stream.rs @@ -1,10 +1,7 @@ //! I/O helper structs for age file encryption and decryption. use age_core::secrecy::{ExposeSecret, SecretSlice}; -use chacha20poly1305::{ - aead::{generic_array::GenericArray, Aead, KeyInit, KeySizeUser}, - ChaCha20Poly1305, -}; +use aws_lc_rs::aead::{self, Aad, LessSafeKey, UnboundKey, CHACHA20_POLY1305}; use pin_project::pin_project; use std::cmp; use std::io::{self, Read, Seek, SeekFrom, Write}; @@ -20,12 +17,11 @@ use futures::{ use std::pin::Pin; const CHUNK_SIZE: usize = 64 * 1024; +const KEY_SIZE: usize = 32; const TAG_SIZE: usize = 16; const ENCRYPTED_CHUNK_SIZE: usize = CHUNK_SIZE + TAG_SIZE; -pub(crate) struct PayloadKey( - pub(crate) GenericArray::KeySize>, -); +pub(crate) struct PayloadKey(pub(crate) [u8; KEY_SIZE]); impl Drop for PayloadKey { fn drop(&mut self) { @@ -89,14 +85,17 @@ struct EncryptedChunk { /// /// [STREAM]: https://eprint.iacr.org/2015/189.pdf pub(crate) struct Stream { - aead: ChaCha20Poly1305, + key: LessSafeKey, nonce: Nonce, } impl Stream { fn new(key: PayloadKey) -> Self { Stream { - aead: ChaCha20Poly1305::new(&key.0), + key: LessSafeKey::new( + UnboundKey::new(&CHACHA20_POLY1305, &key.0) + .expect("byte length of key will match expected"), + ), nonce: Nonce::default(), } } @@ -185,10 +184,15 @@ impl Stream { io::Error::new(io::ErrorKind::WriteZero, "last chunk has been processed") })?; - let encrypted = self - .aead - .encrypt(&self.nonce.to_bytes().into(), chunk) - .expect("we will never hit chacha20::MAX_BLOCKS because of the chunk size"); + let mut encrypted = Vec::with_capacity(chunk.len() + CHACHA20_POLY1305.tag_len()); + encrypted.extend_from_slice(chunk); + self.key + .seal_in_place_append_tag( + aead::Nonce::assume_unique_for_key(self.nonce.to_bytes()), + Aad::empty(), + &mut encrypted, + ) + .expect("encryption won't fail"); self.nonce.increment_counter(); Ok(encrypted) @@ -201,14 +205,18 @@ impl Stream { io::Error::new(io::ErrorKind::InvalidData, "last chunk has been processed") })?; - let decrypted = self - .aead - .decrypt(&self.nonce.to_bytes().into(), chunk) - .map(SecretSlice::from) + let mut decrypted = Vec::from(chunk); + self.key + .open_in_place( + aead::Nonce::assume_unique_for_key(self.nonce.to_bytes()), + Aad::empty(), + &mut decrypted, + ) .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decryption error"))?; + decrypted.truncate(decrypted.len() - CHACHA20_POLY1305.tag_len()); self.nonce.increment_counter(); - Ok(decrypted) + Ok(SecretSlice::from(decrypted)) } fn is_complete(&self) -> bool {