Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ChaCha20 functions to no_std #32

Merged
merged 1 commit into from
Apr 18, 2024
Merged
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
132 changes: 63 additions & 69 deletions protocol/src/chacha20poly1305.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ChaCha20 is used directly by the length cipher.
pub(crate) mod chacha20;
mod poly1305;

Expand All @@ -9,19 +10,16 @@ use alloc::fmt;
/// Zero array for padding slices.
const ZEROES: [u8; 16] = [0u8; 16];

/// Errors encrypting and decrypting messages with ChaCha20 and Poly1305 authentication tags.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
UnauthenticatedAdditionalData,
CiphertextTooShort,
IncorrectBuffer,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::UnauthenticatedAdditionalData => write!(f, "Unauthenticated aad."),
Error::CiphertextTooShort => write!(f, "Ciphertext must be at least 16 bytes."),
Error::IncorrectBuffer => write!(f, "The buffer provided was incorrect. Ensure the buffer is 16 bytes longer than the message."),
}
}
}
Expand All @@ -31,12 +29,11 @@ impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::UnauthenticatedAdditionalData => None,
Error::CiphertextTooShort => None,
Error::IncorrectBuffer => None,
}
}
}

/// Encrypt and decrypt content along with a authentication tag.
#[derive(Debug)]
pub struct ChaCha20Poly1305 {
key: [u8; 32],
Expand All @@ -48,17 +45,19 @@ impl ChaCha20Poly1305 {
ChaCha20Poly1305 { key, nonce }
}

pub fn encrypt<'a>(
self,
plaintext: &'a mut [u8],
aad: Option<&'a [u8]>,
buffer: &'a mut [u8],
) -> Result<&'a [u8], Error> {
if plaintext.len() + 16 != buffer.len() {
return Err(Error::IncorrectBuffer);
}
/// Encrypt content in place and return the poly1305 16-byte authentication tag.
///
/// # Arguments
///
/// - `content` - Plaintext to be encrypted in place.
/// - `aad` - Optional metadata covered by the authentication tag.
///
/// # Returns
///
/// The 16-byte authentication tag.
pub fn encrypt(self, content: &mut [u8], aad: Option<&[u8]>) -> Result<[u8; 16], Error> {
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
chacha.apply_keystream(plaintext);
chacha.apply_keystream(content);
let keystream = chacha.get_keystream(0);
let mut poly = Poly1305::new(
keystream[..32]
Expand All @@ -73,39 +72,38 @@ impl ChaCha20Poly1305 {
poly.add(&ZEROES[0..(16 - aad_overflow)]);
}

poly.add(plaintext);
let text_overflow = plaintext.len() % 16;
poly.add(content);
let text_overflow = content.len() % 16;
if text_overflow > 0 {
poly.add(&ZEROES[0..(16 - text_overflow)]);
}

let aad_len = aad.len().to_le_bytes();
let msg_len = plaintext.len().to_le_bytes();
let msg_len = content.len().to_le_bytes();
let mut len_buffer = [0u8; 16];
len_buffer[..aad_len.len()].copy_from_slice(&aad_len[..]);
for i in 0..msg_len.len() {
len_buffer[i + aad_len.len()] = msg_len[i]
}
poly.add(&len_buffer);
let tag = poly.tag();
for i in 0..plaintext.len() {
if i < plaintext.len() {
buffer[i] = plaintext[i]
}
}
for i in 0..tag.len() {
if i < tag.len() {
buffer[plaintext.len() + i] = tag[i]
}
}
Ok(&buffer[..plaintext.len() + tag.len()])

Ok(tag)
}

pub fn decrypt<'a>(
/// Decrypt the ciphertext in place if authentication tag is correct.
///
/// # Arguments
///
/// - `content` - Ciphertext to be decrypted in place.
/// - `tag` - 16-byte authentication tag.
/// - `aad` - Optional metadata covered by the authentication tag.
pub fn decrypt(
self,
ciphertext: &'a mut [u8],
aad: Option<&'a [u8]>,
) -> Result<&'a [u8], Error> {
content: &mut [u8],
tag: [u8; 16],
aad: Option<&[u8]>,
) -> Result<(), Error> {
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 0);
let keystream = chacha.get_keystream(0);
let mut poly = Poly1305::new(
Expand All @@ -114,38 +112,33 @@ impl ChaCha20Poly1305 {
.expect("32 is a valid subset of 64."),
);
let aad = aad.unwrap_or(&[]);
if ciphertext.len() >= 16 {
let (received_msg, received_tag) = ciphertext.split_at_mut(ciphertext.len() - 16);
poly.add(aad);
// AAD and ciphertext are padded if not 16-byte aligned.
let aad_overflow = aad.len() % 16;
if aad_overflow > 0 {
poly.add(&ZEROES[0..(16 - aad_overflow)]);
}
poly.add(received_msg);
let msg_overflow = received_msg.len() % 16;
if msg_overflow > 0 {
poly.add(&ZEROES[0..(16 - msg_overflow)]);
}

let aad_len = aad.len().to_le_bytes();
let msg_len = received_msg.len().to_le_bytes();
let mut len_buffer = [0u8; 16];
len_buffer[..aad_len.len()].copy_from_slice(&aad_len[..]);
for i in 0..msg_len.len() {
len_buffer[i + aad_len.len()] = msg_len[i]
}
poly.add(&len_buffer);
let tag = poly.tag();
if tag.eq(received_tag) {
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
chacha.apply_keystream(received_msg);
Ok(received_msg)
} else {
Err(Error::UnauthenticatedAdditionalData)
}
poly.add(aad);
// AAD and ciphertext are padded if not 16-byte aligned.
let aad_overflow = aad.len() % 16;
if aad_overflow > 0 {
poly.add(&ZEROES[0..(16 - aad_overflow)]);
}
poly.add(content);
let msg_overflow = content.len() % 16;
if msg_overflow > 0 {
poly.add(&ZEROES[0..(16 - msg_overflow)]);
}

let aad_len = aad.len().to_le_bytes();
let msg_len = content.len().to_le_bytes();
let mut len_buffer = [0u8; 16];
len_buffer[..aad_len.len()].copy_from_slice(&aad_len[..]);
for i in 0..msg_len.len() {
len_buffer[i + aad_len.len()] = msg_len[i]
}
poly.add(&len_buffer);
let derived_tag = poly.tag();
if derived_tag.eq(&tag) {
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
chacha.apply_keystream(content);
Ok(())
} else {
Err(Error::CiphertextTooShort)
Err(Error::UnauthenticatedAdditionalData)
}
}
}
Expand All @@ -171,11 +164,12 @@ mod tests {
.as_slice()
.try_into()
.unwrap();
let mut buffer = [0u8; 130];
let cipher = ChaCha20Poly1305::new(key, nonce);
cipher
.encrypt(message.as_mut_slice(), Some(&aad), buffer.as_mut_slice())
.unwrap();
let tag = cipher.encrypt(&mut message, Some(&aad)).unwrap();

let mut buffer = [0u8; 130];
buffer[..message.len()].copy_from_slice(&message);
buffer[message.len()..].copy_from_slice(&tag);

assert_eq!(&buffer.to_lower_hex_string(), "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691");
}
Expand Down
27 changes: 15 additions & 12 deletions protocol/src/fschacha20poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl FSChaCha20Poly1305 {
fn crypt(
&mut self,
aad: Vec<u8>,
mut contents: Vec<u8>,
contents: Vec<u8>,
crypt_type: CryptType,
) -> Result<Vec<u8>, Error> {
let mut counter_div = (self.message_counter / REKEY_INTERVAL)
Expand All @@ -73,18 +73,24 @@ impl FSChaCha20Poly1305 {
let converted_ciphertext: Vec<u8> = match crypt_type {
CryptType::Encrypt => {
let mut buffer = contents.clone();
buffer.extend([0u8; 16]);
cipher
.encrypt(&mut contents, Some(&aad), &mut buffer)
let tag = cipher
.encrypt(&mut buffer, Some(&aad))
.map_err(|_| Error::Encryption)?;
buffer.to_vec()
buffer.extend(tag);
buffer
}
CryptType::Decrypt => {
let mut ciphertext = contents.clone();
let ciphertext_len = ciphertext.len();
let (mut ciphertext, tag) = ciphertext.split_at_mut(ciphertext_len - 16);
cipher
.decrypt(&mut ciphertext, Some(&aad))
.decrypt(
&mut ciphertext,
tag.try_into().expect("16 byte tag"),
Some(&aad),
)
.map_err(|_| Error::Decryption)?;
ciphertext[..ciphertext.len() - 16].to_vec()
ciphertext.to_vec()
}
};
if (self.message_counter + 1) % REKEY_INTERVAL == 0 {
Expand All @@ -97,18 +103,15 @@ impl FSChaCha20Poly1305 {
let mut nonce = counter_mod.to_vec();
nonce.extend(counter_div);
rekey_nonce.extend(nonce[4..].to_vec());
let mut buffer = [0u8; 48];
let mut plaintext = [0u8; 32];
let cipher = ChaCha20Poly1305::new(
self.key,
rekey_nonce.try_into().expect("Nonce is malformed."),
);
cipher
.encrypt(&mut plaintext, Some(&aad), &mut buffer)
.encrypt(&mut plaintext, Some(&aad))
.map_err(|_| Error::Encryption)?;
self.key = buffer[0..32]
.try_into()
.expect("Cipher should be at least 32 bytes.");
self.key = plaintext;
}
self.message_counter += 1;
Ok(converted_ciphertext)
Expand Down
Loading