Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 55 additions & 40 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
2 changes: 1 addition & 1 deletion age-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 29 additions & 11 deletions age-core/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<u8> {
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.
Expand All @@ -31,13 +40,22 @@ pub fn aead_decrypt(
key: &[u8; 32],
size: usize,
ciphertext: &[u8],
) -> Result<Vec<u8>, aead::Error> {
if ciphertext.len() != size + <ChaCha20Poly1305 as AeadCore>::TagSize::to_usize() {
return Err(aead::Error);
) -> Result<Vec<u8>, 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)`
Expand Down
2 changes: 1 addition & 1 deletion age/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions age/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,8 @@ impl fmt::Display for DecryptError {
}
}

impl From<chacha20poly1305::aead::Error> for DecryptError {
fn from(_: chacha20poly1305::aead::Error) -> Self {
impl From<aws_lc_rs::error::Unspecified> for DecryptError {
fn from(_: aws_lc_rs::error::Unspecified) -> Self {
DecryptError::DecryptionFailed
}
}
Expand Down
44 changes: 26 additions & 18 deletions age/src/primitives/stream.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<u8, <ChaCha20Poly1305 as KeySizeUser>::KeySize>,
);
pub(crate) struct PayloadKey(pub(crate) [u8; KEY_SIZE]);

impl Drop for PayloadKey {
fn drop(&mut self) {
Expand Down Expand Up @@ -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(),
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down