Skip to content

Commit

Permalink
aead: Do not copy plaintext into dst_out before we know next keystrea…
Browse files Browse the repository at this point in the history
…m can be reliably produced (see #308)
  • Loading branch information
brycx committed Dec 1, 2022
1 parent 1ee15eb commit 4baa94d
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 9 deletions.
65 changes: 56 additions & 9 deletions src/hazardous/aead/chacha20poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,67 @@ pub fn seal(
None => return Err(UnknownCryptoError),
};

let mut enc_ctx =
let mut stream =
ChaCha20::new(secret_key.unprotected_as_bytes(), nonce.as_ref(), true).unwrap();
let mut tmp = Zeroizing::new([0u8; CHACHA_BLOCKSIZE]);

let pt_len = plaintext.len();
if pt_len != 0 {
dst_out[..pt_len].copy_from_slice(plaintext);
chacha20::xor_keystream(&mut enc_ctx, ENC_CTR, tmp.as_mut(), &mut dst_out[..pt_len])?;
let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut stream, &mut tmp));
let ad = ad.unwrap_or(&[0u8; 0]);
let ad_len = ad.len();
let ct_len = plaintext.len();

auth_ctx.process_pad_to_blocksize(ad)?;

if ct_len != 0 {
for (ctr, (p_block, c_block)) in plaintext
.chunks(CHACHA_BLOCKSIZE)
.zip(dst_out.chunks_mut(CHACHA_BLOCKSIZE))
.enumerate()
{
match ENC_CTR.checked_add(ctr as u32) {
Some(counter) => {
// See https://github.com/orion-rs/orion/issues/308
stream.next_produceable()?;
// We know at this point that:
// 1) ENC_CTR + ctr does __not__ overflow/panic
// 2) ChaCha20 instance will not panic on the next keystream produced

// Copy keystream directly into the dst_out block in there is
// enough space, too avoid copying from `tmp` each time and only
// on the last block instead. `c_block` must be full blocksize,
// or we leave behind keystream data in it, if `p_block` is not full length
// while `c_block` is.
if p_block.len() == CHACHA_BLOCKSIZE && c_block.len() == CHACHA_BLOCKSIZE {
stream.keystream_block(counter, c_block);
xor_slices!(p_block, c_block);
auth_ctx.update(&c_block)?;
}

// We only pad this last block to the Poly1305 blocksize since `CHACHA_BLOCKSIZE`
// already is evenly divisible by 16, so the previous full-length blocks do not matter.
if p_block.len() < CHACHA_BLOCKSIZE {
stream.keystream_block(counter, tmp.as_mut());
xor_slices!(p_block, tmp.as_mut());
c_block[..p_block.len()].copy_from_slice(&tmp.as_ref()[..p_block.len()]);
auth_ctx.process_pad_to_blocksize(&c_block[..p_block.len()])?;
}
}
None => return Err(UnknownCryptoError),
}
}
}

let mut auth_ctx = Poly1305::new(&poly1305_key_gen(&mut enc_ctx, &mut tmp));
let ad = ad.unwrap_or(&[0u8; 0]);
process_authentication(&mut auth_ctx, ad, &dst_out[..pt_len])?;
dst_out[pt_len..(pt_len + POLY1305_OUTSIZE)]
let (adlen, ctlen): (u64, u64) = match (ad_len.try_into(), ct_len.try_into()) {
(Ok(alen), Ok(clen)) => (alen, clen),
_ => return Err(UnknownCryptoError),
};

let mut tmp_pad = [0u8; 16];
tmp_pad[0..8].copy_from_slice(&adlen.to_le_bytes());
tmp_pad[8..16].copy_from_slice(&ctlen.to_le_bytes());
auth_ctx.update(tmp_pad.as_ref())?;

dst_out[ct_len..(ct_len + POLY1305_OUTSIZE)]
.copy_from_slice(auth_ctx.finalize()?.unprotected_as_bytes());

Ok(())
Expand Down
31 changes: 31 additions & 0 deletions src/hazardous/stream/chacha20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,17 @@ impl ChaCha20 {
})
}

/// Check that we can produce one more keystream block, given the current state.
///
/// If the internal counter would overflow, we return an error.
pub(crate) fn next_produceable(&self) -> Result<(), UnknownCryptoError> {
if self.internal_counter.checked_add(1).is_none() {
Err(UnknownCryptoError)
} else {
Ok(())
}
}

/// Process the next keystream and copy into destination array.
pub(crate) fn keystream_block(&mut self, block_counter: u32, inplace: &mut [u8]) {
debug_assert!(if self.is_ietf {
Expand Down Expand Up @@ -1029,6 +1040,26 @@ mod private {
chacha_state_ietf.keystream_block(0, &mut keystream_block);
}
}

#[test]
fn test_error_if_internal_counter_would_overflow() {
let mut chacha_state = ChaCha20 {
state: [
U32x4(0, 0, 0, 0),
U32x4(0, 0, 0, 0),
U32x4(0, 0, 0, 0),
U32x4(0, 0, 0, 0),
],
internal_counter: (u32::MAX - 2),
is_ietf: false,
};

assert!(chacha_state.next_produceable().is_ok());
chacha_state.internal_counter += 1;
assert!(chacha_state.next_produceable().is_ok());
chacha_state.internal_counter += 1;
assert!(chacha_state.next_produceable().is_err());
}
}
}

Expand Down

0 comments on commit 4baa94d

Please sign in to comment.