From 232415b943997d51b676ef00dc874534e827ef3a Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:08:51 -0700 Subject: [PATCH 1/2] Add ChaCha20Poly1305 encryption helper --- src/crypto/chacha20.rs | 465 +++++++++++++++++++++++++++++++++ src/crypto/chacha20poly1305.rs | 190 ++++++++++++++ src/crypto/mod.rs | 3 + src/crypto/poly1305.rs | 362 +++++++++++++++++++++++++ src/lib.rs | 3 + 5 files changed, 1023 insertions(+) create mode 100644 src/crypto/chacha20.rs create mode 100644 src/crypto/chacha20poly1305.rs create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/poly1305.rs diff --git a/src/crypto/chacha20.rs b/src/crypto/chacha20.rs new file mode 100644 index 0000000..3e3b874 --- /dev/null +++ b/src/crypto/chacha20.rs @@ -0,0 +1,465 @@ +// This file was stolen from rust-crypto. +// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +mod real_chacha { + use core::cmp; + use core::convert::TryInto; + + #[derive(Clone, Copy, PartialEq, Eq)] + #[allow(non_camel_case_types)] + struct u32x4(pub u32, pub u32, pub u32, pub u32); + + impl ::core::ops::Add for u32x4 { + type Output = u32x4; + fn add(self, rhs: u32x4) -> u32x4 { + u32x4( + self.0.wrapping_add(rhs.0), + self.1.wrapping_add(rhs.1), + self.2.wrapping_add(rhs.2), + self.3.wrapping_add(rhs.3), + ) + } + } + + impl ::core::ops::Sub for u32x4 { + type Output = u32x4; + fn sub(self, rhs: u32x4) -> u32x4 { + u32x4( + self.0.wrapping_sub(rhs.0), + self.1.wrapping_sub(rhs.1), + self.2.wrapping_sub(rhs.2), + self.3.wrapping_sub(rhs.3), + ) + } + } + + impl ::core::ops::BitXor for u32x4 { + type Output = u32x4; + fn bitxor(self, rhs: u32x4) -> u32x4 { + u32x4(self.0 ^ rhs.0, self.1 ^ rhs.1, self.2 ^ rhs.2, self.3 ^ rhs.3) + } + } + + impl ::core::ops::Shr for u32x4 { + type Output = u32x4; + fn shr(self, rhs: u32x4) -> u32x4 { + u32x4(self.0 >> rhs.0, self.1 >> rhs.1, self.2 >> rhs.2, self.3 >> rhs.3) + } + } + + impl ::core::ops::Shl for u32x4 { + type Output = u32x4; + fn shl(self, rhs: u32x4) -> u32x4 { + u32x4(self.0 << rhs.0, self.1 << rhs.1, self.2 << rhs.2, self.3 << rhs.3) + } + } + + impl u32x4 { + fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 4 * 4); + Self( + u32::from_le_bytes(bytes[0 * 4..1 * 4].try_into().expect("len is 4")), + u32::from_le_bytes(bytes[1 * 4..2 * 4].try_into().expect("len is 4")), + u32::from_le_bytes(bytes[2 * 4..3 * 4].try_into().expect("len is 4")), + u32::from_le_bytes(bytes[3 * 4..4 * 4].try_into().expect("len is 4")), + ) + } + } + + const BLOCK_SIZE: usize = 64; + + #[derive(Clone, Copy)] + struct ChaChaState { + a: u32x4, + b: u32x4, + c: u32x4, + d: u32x4, + } + + #[derive(Copy)] + pub struct ChaCha20 { + state: ChaChaState, + output: [u8; BLOCK_SIZE], + offset: usize, + } + + impl Clone for ChaCha20 { + fn clone(&self) -> ChaCha20 { + *self + } + } + + macro_rules! swizzle { + ($b: expr, $c: expr, $d: expr) => {{ + let u32x4(b10, b11, b12, b13) = $b; + $b = u32x4(b11, b12, b13, b10); + let u32x4(c10, c11, c12, c13) = $c; + $c = u32x4(c12, c13, c10, c11); + let u32x4(d10, d11, d12, d13) = $d; + $d = u32x4(d13, d10, d11, d12); + }}; + } + + macro_rules! state_to_buffer { + ($state: expr, $output: expr) => {{ + let u32x4(a1, a2, a3, a4) = $state.a; + let u32x4(b1, b2, b3, b4) = $state.b; + let u32x4(c1, c2, c3, c4) = $state.c; + let u32x4(d1, d2, d3, d4) = $state.d; + let lens = [a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4, d1, d2, d3, d4]; + for i in 0..lens.len() { + $output[i * 4..(i + 1) * 4].copy_from_slice(&lens[i].to_le_bytes()); + } + }}; + } + + macro_rules! round { + ($state: expr) => {{ + $state.a = $state.a + $state.b; + rotate!($state.d, $state.a, S16); + $state.c = $state.c + $state.d; + rotate!($state.b, $state.c, S12); + $state.a = $state.a + $state.b; + rotate!($state.d, $state.a, S8); + $state.c = $state.c + $state.d; + rotate!($state.b, $state.c, S7); + }}; + } + + macro_rules! rotate { + ($a: expr, $b: expr, $c:expr) => {{ + let v = $a ^ $b; + let r = S32 - $c; + let right = v >> r; + $a = (v << $c) ^ right + }}; + } + + const S32: u32x4 = u32x4(32, 32, 32, 32); + const S16: u32x4 = u32x4(16, 16, 16, 16); + const S12: u32x4 = u32x4(12, 12, 12, 12); + const S8: u32x4 = u32x4(8, 8, 8, 8); + const S7: u32x4 = u32x4(7, 7, 7, 7); + + impl ChaCha20 { + pub fn new(key: &[u8], nonce: &[u8]) -> ChaCha20 { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 8 || nonce.len() == 12); + + ChaCha20 { state: ChaCha20::expand(key, nonce), output: [0u8; BLOCK_SIZE], offset: 64 } + } + + fn expand(key: &[u8], nonce: &[u8]) -> ChaChaState { + let constant = match key.len() { + 16 => b"expand 16-byte k", + 32 => b"expand 32-byte k", + _ => unreachable!(), + }; + ChaChaState { + a: u32x4::from_bytes(&constant[0..16]), + b: u32x4::from_bytes(&key[0..16]), + c: if key.len() == 16 { u32x4::from_bytes(&key[0..16]) } else { u32x4::from_bytes(&key[16..32]) }, + d: if nonce.len() == 16 { + u32x4::from_bytes(&nonce[0..16]) + } else if nonce.len() == 12 { + let mut nonce4 = [0; 4 * 4]; + nonce4[4..].copy_from_slice(nonce); + u32x4::from_bytes(&nonce4) + } else { + let mut nonce4 = [0; 4 * 4]; + nonce4[8..].copy_from_slice(nonce); + u32x4::from_bytes(&nonce4) + }, + } + } + + // put the the next BLOCK_SIZE keystream bytes into self.output + fn update(&mut self) { + let mut state = self.state; + + for _ in 0..10 { + round!(state); + swizzle!(state.b, state.c, state.d); + round!(state); + swizzle!(state.d, state.c, state.b); + } + state.a = state.a + self.state.a; + state.b = state.b + self.state.b; + state.c = state.c + self.state.c; + state.d = state.d + self.state.d; + + state_to_buffer!(state, self.output); + + self.state.d = self.state.d + u32x4(1, 0, 0, 0); + let u32x4(c12, _, _, _) = self.state.d; + if c12 == 0 { + // we could increment the other counter word with an 8 byte nonce + // but other implementations like boringssl have this same + // limitation + panic!("counter is exhausted"); + } + + self.offset = 0; + } + + #[inline] // Useful cause input may be 0s on stack that should be optimized out + pub fn process(&mut self, input: &[u8], output: &mut [u8]) { + assert!(input.len() == output.len()); + let len = input.len(); + let mut i = 0; + while i < len { + // If there is no keystream available in the output buffer, + // generate the next block. + if self.offset == BLOCK_SIZE { + self.update(); + } + + // Process the min(available keystream, remaining input length). + let count = cmp::min(BLOCK_SIZE - self.offset, len - i); + // explicitly assert lengths to avoid bounds checks: + assert!(output.len() >= i + count); + assert!(input.len() >= i + count); + assert!(self.output.len() >= self.offset + count); + for j in 0..count { + output[i + j] = input[i + j] ^ self.output[self.offset + j]; + } + i += count; + self.offset += count; + } + } + + pub fn process_in_place(&mut self, input_output: &mut [u8]) { + let len = input_output.len(); + let mut i = 0; + while i < len { + // If there is no keystream available in the output buffer, + // generate the next block. + if self.offset == BLOCK_SIZE { + self.update(); + } + + // Process the min(available keystream, remaining input length). + let count = cmp::min(BLOCK_SIZE - self.offset, len - i); + // explicitly assert lengths to avoid bounds checks: + assert!(input_output.len() >= i + count); + assert!(self.output.len() >= self.offset + count); + for j in 0..count { + input_output[i + j] ^= self.output[self.offset + j]; + } + i += count; + self.offset += count; + } + } + } +} + +pub use self::real_chacha::ChaCha20; + +#[cfg(test)] +mod test { + use core::iter::repeat; + + use super::ChaCha20; + + #[test] + fn test_chacha20_256_tls_vectors() { + struct TestVector { + key: [u8; 32], + nonce: [u8; 8], + keystream: Vec, + } + // taken from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 + let test_vectors = vec![ + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0x45, 0x40, 0xf0, 0x5a, 0x9f, 0x1f, 0xb2, 0x96, 0xd7, 0x73, 0x6e, 0x7b, 0x20, 0x8e, 0x3c, 0x96, + 0xeb, 0x4f, 0xe1, 0x83, 0x46, 0x88, 0xd2, 0x60, 0x4f, 0x45, 0x09, 0x52, 0xed, 0x43, 0x2d, 0x41, + 0xbb, 0xe2, 0xa0, 0xb6, 0xea, 0x75, 0x66, 0xd2, 0xa5, 0xd1, 0xe7, 0xe2, 0x0d, 0x42, 0xaf, 0x2c, + 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81, 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + keystream: vec![ + 0xde, 0x9c, 0xba, 0x7b, 0xf3, 0xd6, 0x9e, 0xf5, 0xe7, 0x86, 0xdc, 0x63, 0x97, 0x3f, 0x65, 0x3a, + 0x0b, 0x49, 0xe0, 0x15, 0xad, 0xbf, 0xf7, 0x13, 0x4f, 0xcb, 0x7d, 0xf1, 0x37, 0x82, 0x10, 0x31, + 0xe8, 0x5a, 0x05, 0x02, 0x78, 0xa7, 0x08, 0x45, 0x27, 0x21, 0x4f, 0x73, 0xef, 0xc7, 0xfa, 0x5b, + 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e, 0x44, 0x5f, 0x41, 0xe3, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0xef, 0x3f, 0xdf, 0xd6, 0xc6, 0x15, 0x78, 0xfb, 0xf5, 0xcf, 0x35, 0xbd, 0x3d, 0xd3, 0x3b, 0x80, + 0x09, 0x63, 0x16, 0x34, 0xd2, 0x1e, 0x42, 0xac, 0x33, 0x96, 0x0b, 0xd1, 0x38, 0xe5, 0x0d, 0x32, + 0x11, 0x1e, 0x4c, 0xaf, 0x23, 0x7e, 0xe5, 0x3c, 0xa8, 0xad, 0x64, 0x26, 0x19, 0x4a, 0x88, 0x54, + 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d, 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b, + ], + }, + TestVector { + key: [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ], + nonce: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + keystream: vec![ + 0xf7, 0x98, 0xa1, 0x89, 0xf1, 0x95, 0xe6, 0x69, 0x82, 0x10, 0x5f, 0xfb, 0x64, 0x0b, 0xb7, 0x75, + 0x7f, 0x57, 0x9d, 0xa3, 0x16, 0x02, 0xfc, 0x93, 0xec, 0x01, 0xac, 0x56, 0xf8, 0x5a, 0xc3, 0xc1, + 0x34, 0xa4, 0x54, 0x7b, 0x73, 0x3b, 0x46, 0x41, 0x30, 0x42, 0xc9, 0x44, 0x00, 0x49, 0x17, 0x69, + 0x05, 0xd3, 0xbe, 0x59, 0xea, 0x1c, 0x53, 0xf1, 0x59, 0x16, 0x15, 0x5c, 0x2b, 0xe8, 0x24, 0x1a, + 0x38, 0x00, 0x8b, 0x9a, 0x26, 0xbc, 0x35, 0x94, 0x1e, 0x24, 0x44, 0x17, 0x7c, 0x8a, 0xde, 0x66, + 0x89, 0xde, 0x95, 0x26, 0x49, 0x86, 0xd9, 0x58, 0x89, 0xfb, 0x60, 0xe8, 0x46, 0x29, 0xc9, 0xbd, + 0x9a, 0x5a, 0xcb, 0x1c, 0xc1, 0x18, 0xbe, 0x56, 0x3e, 0xb9, 0xb3, 0xa4, 0xa4, 0x72, 0xf8, 0x2e, + 0x09, 0xa7, 0xe7, 0x78, 0x49, 0x2b, 0x56, 0x2e, 0xf7, 0x13, 0x0e, 0x88, 0xdf, 0xe0, 0x31, 0xc7, + 0x9d, 0xb9, 0xd4, 0xf7, 0xc7, 0xa8, 0x99, 0x15, 0x1b, 0x9a, 0x47, 0x50, 0x32, 0xb6, 0x3f, 0xc3, + 0x85, 0x24, 0x5f, 0xe0, 0x54, 0xe3, 0xdd, 0x5a, 0x97, 0xa5, 0xf5, 0x76, 0xfe, 0x06, 0x40, 0x25, + 0xd3, 0xce, 0x04, 0x2c, 0x56, 0x6a, 0xb2, 0xc5, 0x07, 0xb1, 0x38, 0xdb, 0x85, 0x3e, 0x3d, 0x69, + 0x59, 0x66, 0x09, 0x96, 0x54, 0x6c, 0xc9, 0xc4, 0xa6, 0xea, 0xfd, 0xc7, 0x77, 0xc0, 0x40, 0xd7, + 0x0e, 0xaf, 0x46, 0xf7, 0x6d, 0xad, 0x39, 0x79, 0xe5, 0xc5, 0x36, 0x0c, 0x33, 0x17, 0x16, 0x6a, + 0x1c, 0x89, 0x4c, 0x94, 0xa3, 0x71, 0x87, 0x6a, 0x94, 0xdf, 0x76, 0x28, 0xfe, 0x4e, 0xaa, 0xf2, + 0xcc, 0xb2, 0x7d, 0x5a, 0xaa, 0xe0, 0xad, 0x7a, 0xd0, 0xf9, 0xd4, 0xb6, 0xad, 0x3b, 0x54, 0x09, + 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a, 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9, + ], + }, + ]; + + for tv in test_vectors.iter() { + let mut c = ChaCha20::new(&tv.key, &tv.nonce); + let input: Vec = repeat(0).take(tv.keystream.len()).collect(); + let mut output: Vec = repeat(0).take(input.len()).collect(); + c.process(&input[..], &mut output[..]); + assert_eq!(output, tv.keystream); + } + } + + #[test] + fn test_chacha20_256_tls_vectors_96_nonce() { + struct TestVector { + key: [u8; 32], + nonce: [u8; 12], + keystream: Vec, + } + // taken from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 + let test_vectors = vec![ + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0x45, 0x40, 0xf0, 0x5a, 0x9f, 0x1f, 0xb2, 0x96, 0xd7, 0x73, 0x6e, 0x7b, 0x20, 0x8e, 0x3c, 0x96, + 0xeb, 0x4f, 0xe1, 0x83, 0x46, 0x88, 0xd2, 0x60, 0x4f, 0x45, 0x09, 0x52, 0xed, 0x43, 0x2d, 0x41, + 0xbb, 0xe2, 0xa0, 0xb6, 0xea, 0x75, 0x66, 0xd2, 0xa5, 0xd1, 0xe7, 0xe2, 0x0d, 0x42, 0xaf, 0x2c, + 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81, 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + keystream: vec![ + 0xde, 0x9c, 0xba, 0x7b, 0xf3, 0xd6, 0x9e, 0xf5, 0xe7, 0x86, 0xdc, 0x63, 0x97, 0x3f, 0x65, 0x3a, + 0x0b, 0x49, 0xe0, 0x15, 0xad, 0xbf, 0xf7, 0x13, 0x4f, 0xcb, 0x7d, 0xf1, 0x37, 0x82, 0x10, 0x31, + 0xe8, 0x5a, 0x05, 0x02, 0x78, 0xa7, 0x08, 0x45, 0x27, 0x21, 0x4f, 0x73, 0xef, 0xc7, 0xfa, 0x5b, + 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e, 0x44, 0x5f, 0x41, 0xe3, + ], + }, + TestVector { + key: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + keystream: vec![ + 0xef, 0x3f, 0xdf, 0xd6, 0xc6, 0x15, 0x78, 0xfb, 0xf5, 0xcf, 0x35, 0xbd, 0x3d, 0xd3, 0x3b, 0x80, + 0x09, 0x63, 0x16, 0x34, 0xd2, 0x1e, 0x42, 0xac, 0x33, 0x96, 0x0b, 0xd1, 0x38, 0xe5, 0x0d, 0x32, + 0x11, 0x1e, 0x4c, 0xaf, 0x23, 0x7e, 0xe5, 0x3c, 0xa8, 0xad, 0x64, 0x26, 0x19, 0x4a, 0x88, 0x54, + 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d, 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b, + ], + }, + TestVector { + key: [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ], + nonce: [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + keystream: vec![ + 0xf7, 0x98, 0xa1, 0x89, 0xf1, 0x95, 0xe6, 0x69, 0x82, 0x10, 0x5f, 0xfb, 0x64, 0x0b, 0xb7, 0x75, + 0x7f, 0x57, 0x9d, 0xa3, 0x16, 0x02, 0xfc, 0x93, 0xec, 0x01, 0xac, 0x56, 0xf8, 0x5a, 0xc3, 0xc1, + 0x34, 0xa4, 0x54, 0x7b, 0x73, 0x3b, 0x46, 0x41, 0x30, 0x42, 0xc9, 0x44, 0x00, 0x49, 0x17, 0x69, + 0x05, 0xd3, 0xbe, 0x59, 0xea, 0x1c, 0x53, 0xf1, 0x59, 0x16, 0x15, 0x5c, 0x2b, 0xe8, 0x24, 0x1a, + 0x38, 0x00, 0x8b, 0x9a, 0x26, 0xbc, 0x35, 0x94, 0x1e, 0x24, 0x44, 0x17, 0x7c, 0x8a, 0xde, 0x66, + 0x89, 0xde, 0x95, 0x26, 0x49, 0x86, 0xd9, 0x58, 0x89, 0xfb, 0x60, 0xe8, 0x46, 0x29, 0xc9, 0xbd, + 0x9a, 0x5a, 0xcb, 0x1c, 0xc1, 0x18, 0xbe, 0x56, 0x3e, 0xb9, 0xb3, 0xa4, 0xa4, 0x72, 0xf8, 0x2e, + 0x09, 0xa7, 0xe7, 0x78, 0x49, 0x2b, 0x56, 0x2e, 0xf7, 0x13, 0x0e, 0x88, 0xdf, 0xe0, 0x31, 0xc7, + 0x9d, 0xb9, 0xd4, 0xf7, 0xc7, 0xa8, 0x99, 0x15, 0x1b, 0x9a, 0x47, 0x50, 0x32, 0xb6, 0x3f, 0xc3, + 0x85, 0x24, 0x5f, 0xe0, 0x54, 0xe3, 0xdd, 0x5a, 0x97, 0xa5, 0xf5, 0x76, 0xfe, 0x06, 0x40, 0x25, + 0xd3, 0xce, 0x04, 0x2c, 0x56, 0x6a, 0xb2, 0xc5, 0x07, 0xb1, 0x38, 0xdb, 0x85, 0x3e, 0x3d, 0x69, + 0x59, 0x66, 0x09, 0x96, 0x54, 0x6c, 0xc9, 0xc4, 0xa6, 0xea, 0xfd, 0xc7, 0x77, 0xc0, 0x40, 0xd7, + 0x0e, 0xaf, 0x46, 0xf7, 0x6d, 0xad, 0x39, 0x79, 0xe5, 0xc5, 0x36, 0x0c, 0x33, 0x17, 0x16, 0x6a, + 0x1c, 0x89, 0x4c, 0x94, 0xa3, 0x71, 0x87, 0x6a, 0x94, 0xdf, 0x76, 0x28, 0xfe, 0x4e, 0xaa, 0xf2, + 0xcc, 0xb2, 0x7d, 0x5a, 0xaa, 0xe0, 0xad, 0x7a, 0xd0, 0xf9, 0xd4, 0xb6, 0xad, 0x3b, 0x54, 0x09, + 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a, 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9, + ], + }, + ]; + + for tv in test_vectors.iter() { + let mut c = ChaCha20::new(&tv.key, &tv.nonce); + let input: Vec = repeat(0).take(tv.keystream.len()).collect(); + let mut output: Vec = repeat(0).take(input.len()).collect(); + c.process(&input[..], &mut output[..]); + assert_eq!(output, tv.keystream); + } + } +} diff --git a/src/crypto/chacha20poly1305.rs b/src/crypto/chacha20poly1305.rs new file mode 100644 index 0000000..8b6be00 --- /dev/null +++ b/src/crypto/chacha20poly1305.rs @@ -0,0 +1,190 @@ +// ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305 +// Instead, we steal rust-crypto's implementation and tweak it to match the RFC. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. +// +// This is a port of Andrew Moons poly1305-donna +// https://github.com/floodyberry/poly1305-donna + +mod real_chachapoly { + use crate::crypto::chacha20::ChaCha20; + use crate::crypto::poly1305::Poly1305; + use core::ptr::{read_volatile, write_volatile}; + + #[derive(Clone, Copy)] + pub struct ChaCha20Poly1305 { + cipher: ChaCha20, + mac: Poly1305, + finished: bool, + data_len: usize, + aad_len: u64, + } + + #[allow(dead_code, unused)] + impl ChaCha20Poly1305 { + #[inline] + fn pad_mac_16(mac: &mut Poly1305, len: usize) { + if len % 16 != 0 { + mac.input(&[0; 16][0..16 - (len % 16)]); + } + } + pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305 { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut cipher = ChaCha20::new(key, &nonce[4..]); + let mut mac_key = [0u8; 64]; + let zero_key = [0u8; 64]; + cipher.process(&zero_key, &mut mac_key); + + let mut mac = Poly1305::new(&mac_key[..32]); + mac.input(aad); + ChaCha20Poly1305::pad_mac_16(&mut mac, aad.len()); + + ChaCha20Poly1305 { cipher, mac, finished: false, data_len: 0, aad_len: aad.len() as u64 } + } + + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(self.finished == false); + self.cipher.process(input, output); + self.data_len += input.len(); + self.mac.input(output); + ChaCha20Poly1305::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + pub fn encrypt_inplace(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + assert!(self.finished == false); + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } + + pub fn decrypt_inplace(&mut self, input_output: &mut [u8], tag: &[u8]) -> bool { + assert!(self.finished == false); + self.decrypt_in_place(input_output); + self.finish_and_check_tag(tag) + } + + // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` + // below. + pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.cipher.process_in_place(input_output); + self.data_len += input_output.len(); + self.mac.input(input_output); + } + + // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish + // encrypting and calculate the tag. + pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + debug_assert!(self.finished == false); + ChaCha20Poly1305::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + self.mac.raw_result(out_tag); + } + + pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool { + assert!(input.len() == output.len()); + assert!(self.finished == false); + + self.finished = true; + + self.mac.input(input); + + self.data_len += input.len(); + ChaCha20Poly1305::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if ChaCha20Poly1305::fixed_time_eq(&calc_tag, tag) { + self.cipher.process(input, output); + true + } else { + false + } + } + + // Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it + // later when decryption finishes. + // + // Should never be `pub` because the public API should always enforce tag checking. + pub(super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(self.finished == false); + self.mac.input(input_output); + self.data_len += input_output.len(); + self.cipher.process_in_place(input_output); + } + + // If we were previously decrypting with `decrypt_in_place`, this method must be used to finish + // decrypting and check the tag. Returns whether the tag is valid. + pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + debug_assert!(self.finished == false); + self.finished = true; + ChaCha20Poly1305::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let mut calc_tag = [0u8; 16]; + self.mac.raw_result(&mut calc_tag); + if ChaCha20Poly1305::fixed_time_eq(&calc_tag, tag) { + true + } else { + false + } + } + pub(super) fn fixed_time_eq(a: &[u8], b: &[u8]) -> bool { + assert!(a.len() == b.len()); + let count = a.len(); + let lhs = &a[..count]; + let rhs = &b[..count]; + + let mut r: u8 = 0; + for i in 0..count { + let mut rs = unsafe { read_volatile(&r) }; + rs |= lhs[i] ^ rhs[i]; + unsafe { + write_volatile(&mut r, rs); + } + } + { + let mut t = unsafe { read_volatile(&r) }; + t |= t >> 4; + unsafe { + write_volatile(&mut r, t); + } + } + { + let mut t = unsafe { read_volatile(&r) }; + t |= t >> 2; + unsafe { + write_volatile(&mut r, t); + } + } + { + let mut t = unsafe { read_volatile(&r) }; + t |= t >> 1; + unsafe { + write_volatile(&mut r, t); + } + } + unsafe { (read_volatile(&r) & 1) == 0 } + } + } +} + +pub use self::real_chachapoly::ChaCha20Poly1305; diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..a5d7bc7 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod chacha20; +pub(crate) mod chacha20poly1305; +pub(crate) mod poly1305; diff --git a/src/crypto/poly1305.rs b/src/crypto/poly1305.rs new file mode 100644 index 0000000..384a724 --- /dev/null +++ b/src/crypto/poly1305.rs @@ -0,0 +1,362 @@ +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This is a port of Andrew Moons poly1305-donna +// https://github.com/floodyberry/poly1305-donna + +use core::cmp::min; +use core::convert::TryInto; + +#[derive(Clone, Copy)] +pub struct Poly1305 { + r: [u32; 5], + h: [u32; 5], + pad: [u32; 4], + leftover: usize, + buffer: [u8; 16], + finalized: bool, +} + +#[allow(dead_code, unused)] +impl Poly1305 { + pub fn new(key: &[u8]) -> Poly1305 { + assert!(key.len() == 32); + let mut poly = + Poly1305 { r: [0u32; 5], h: [0u32; 5], pad: [0u32; 4], leftover: 0, buffer: [0u8; 16], finalized: false }; + + // r &= 0xffffffc0ffffffc0ffffffc0fffffff + poly.r[0] = (u32::from_le_bytes(key[0..4].try_into().expect("len is 4"))) & 0x3ffffff; + poly.r[1] = (u32::from_le_bytes(key[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffff03; + poly.r[2] = (u32::from_le_bytes(key[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffc0ff; + poly.r[3] = (u32::from_le_bytes(key[9..13].try_into().expect("len is 4")) >> 6) & 0x3f03fff; + poly.r[4] = (u32::from_le_bytes(key[12..16].try_into().expect("len is 4")) >> 8) & 0x00fffff; + + poly.pad[0] = u32::from_le_bytes(key[16..20].try_into().expect("len is 4")); + poly.pad[1] = u32::from_le_bytes(key[20..24].try_into().expect("len is 4")); + poly.pad[2] = u32::from_le_bytes(key[24..28].try_into().expect("len is 4")); + poly.pad[3] = u32::from_le_bytes(key[28..32].try_into().expect("len is 4")); + + poly + } + + fn block(&mut self, m: &[u8]) { + let hibit: u32 = if self.finalized { 0 } else { 1 << 24 }; + + let r0 = self.r[0]; + let r1 = self.r[1]; + let r2 = self.r[2]; + let r3 = self.r[3]; + let r4 = self.r[4]; + + let s1 = r1 * 5; + let s2 = r2 * 5; + let s3 = r3 * 5; + let s4 = r4 * 5; + + let mut h0 = self.h[0]; + let mut h1 = self.h[1]; + let mut h2 = self.h[2]; + let mut h3 = self.h[3]; + let mut h4 = self.h[4]; + + // h += m + h0 += (u32::from_le_bytes(m[0..4].try_into().expect("len is 4"))) & 0x3ffffff; + h1 += (u32::from_le_bytes(m[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffffff; + h2 += (u32::from_le_bytes(m[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffffff; + h3 += (u32::from_le_bytes(m[9..13].try_into().expect("len is 4")) >> 6) & 0x3ffffff; + h4 += (u32::from_le_bytes(m[12..16].try_into().expect("len is 4")) >> 8) | hibit; + + // h *= r + let d0 = (h0 as u64 * r0 as u64) + + (h1 as u64 * s4 as u64) + + (h2 as u64 * s3 as u64) + + (h3 as u64 * s2 as u64) + + (h4 as u64 * s1 as u64); + let mut d1 = (h0 as u64 * r1 as u64) + + (h1 as u64 * r0 as u64) + + (h2 as u64 * s4 as u64) + + (h3 as u64 * s3 as u64) + + (h4 as u64 * s2 as u64); + let mut d2 = (h0 as u64 * r2 as u64) + + (h1 as u64 * r1 as u64) + + (h2 as u64 * r0 as u64) + + (h3 as u64 * s4 as u64) + + (h4 as u64 * s3 as u64); + let mut d3 = (h0 as u64 * r3 as u64) + + (h1 as u64 * r2 as u64) + + (h2 as u64 * r1 as u64) + + (h3 as u64 * r0 as u64) + + (h4 as u64 * s4 as u64); + let mut d4 = (h0 as u64 * r4 as u64) + + (h1 as u64 * r3 as u64) + + (h2 as u64 * r2 as u64) + + (h3 as u64 * r1 as u64) + + (h4 as u64 * r0 as u64); + + // (partial) h %= p + let mut c: u32; + c = (d0 >> 26) as u32; + h0 = d0 as u32 & 0x3ffffff; + d1 += c as u64; + c = (d1 >> 26) as u32; + h1 = d1 as u32 & 0x3ffffff; + d2 += c as u64; + c = (d2 >> 26) as u32; + h2 = d2 as u32 & 0x3ffffff; + d3 += c as u64; + c = (d3 >> 26) as u32; + h3 = d3 as u32 & 0x3ffffff; + d4 += c as u64; + c = (d4 >> 26) as u32; + h4 = d4 as u32 & 0x3ffffff; + h0 += c * 5; + c = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += c; + + self.h[0] = h0; + self.h[1] = h1; + self.h[2] = h2; + self.h[3] = h3; + self.h[4] = h4; + } + + pub fn finish(&mut self) { + if self.leftover > 0 { + self.buffer[self.leftover] = 1; + for i in self.leftover + 1..16 { + self.buffer[i] = 0; + } + self.finalized = true; + let tmp = self.buffer; + self.block(&tmp); + } + + // fully carry h + let mut h0 = self.h[0]; + let mut h1 = self.h[1]; + let mut h2 = self.h[2]; + let mut h3 = self.h[3]; + let mut h4 = self.h[4]; + + let mut c: u32; + c = h1 >> 26; + h1 = h1 & 0x3ffffff; + h2 += c; + c = h2 >> 26; + h2 = h2 & 0x3ffffff; + h3 += c; + c = h3 >> 26; + h3 = h3 & 0x3ffffff; + h4 += c; + c = h4 >> 26; + h4 = h4 & 0x3ffffff; + h0 += c * 5; + c = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += c; + + // compute h + -p + let mut g0 = h0.wrapping_add(5); + c = g0 >> 26; + g0 &= 0x3ffffff; + let mut g1 = h1.wrapping_add(c); + c = g1 >> 26; + g1 &= 0x3ffffff; + let mut g2 = h2.wrapping_add(c); + c = g2 >> 26; + g2 &= 0x3ffffff; + let mut g3 = h3.wrapping_add(c); + c = g3 >> 26; + g3 &= 0x3ffffff; + let mut g4 = h4.wrapping_add(c).wrapping_sub(1 << 26); + + // select h if h < p, or h + -p if h >= p + let mut mask = (g4 >> (32 - 1)).wrapping_sub(1); + g0 &= mask; + g1 &= mask; + g2 &= mask; + g3 &= mask; + g4 &= mask; + mask = !mask; + h0 = (h0 & mask) | g0; + h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; + h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + // h = h % (2^128) + h0 = ((h0) | (h1 << 26)) & 0xffffffff; + h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; + h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; + h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; + + // h = mac = (h + pad) % (2^128) + let mut f: u64; + f = h0 as u64 + self.pad[0] as u64; + h0 = f as u32; + f = h1 as u64 + self.pad[1] as u64 + (f >> 32); + h1 = f as u32; + f = h2 as u64 + self.pad[2] as u64 + (f >> 32); + h2 = f as u32; + f = h3 as u64 + self.pad[3] as u64 + (f >> 32); + h3 = f as u32; + + self.h[0] = h0; + self.h[1] = h1; + self.h[2] = h2; + self.h[3] = h3; + } + + pub fn input(&mut self, data: &[u8]) { + assert!(!self.finalized); + let mut m = data; + + if self.leftover > 0 { + let want = min(16 - self.leftover, m.len()); + for i in 0..want { + self.buffer[self.leftover + i] = m[i]; + } + m = &m[want..]; + self.leftover += want; + + if self.leftover < 16 { + return; + } + + // self.block(self.buffer[..]); + let tmp = self.buffer; + self.block(&tmp); + + self.leftover = 0; + } + + while m.len() >= 16 { + self.block(&m[0..16]); + m = &m[16..]; + } + + for i in 0..m.len() { + self.buffer[i] = m[i]; + } + self.leftover = m.len(); + } + + pub fn raw_result(&mut self, output: &mut [u8]) { + assert!(output.len() >= 16); + if !self.finalized { + self.finish(); + } + output[0..4].copy_from_slice(&self.h[0].to_le_bytes()); + output[4..8].copy_from_slice(&self.h[1].to_le_bytes()); + output[8..12].copy_from_slice(&self.h[2].to_le_bytes()); + output[12..16].copy_from_slice(&self.h[3].to_le_bytes()); + } +} + +#[cfg(test)] +mod test { + use core::iter::repeat; + + use crate::crypto::poly1305::Poly1305; + + fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8]) { + let mut poly = Poly1305::new(key); + poly.input(msg); + poly.raw_result(mac); + } + + #[test] + fn test_nacl_vector() { + let key = [ + 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, 0x3c, 0x25, 0x25, 0x39, + 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, 0x2d, 0x65, 0x1f, 0xa4, 0xc8, 0xcf, 0xf8, 0x80, + ]; + + let msg = [ + 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce, 0x48, 0x33, + 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a, 0xc0, 0xdf, 0xc1, 0x7c, + 0x98, 0xdc, 0xe8, 0x7b, 0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, + 0x8f, 0xe2, 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a, + 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea, 0xbd, 0x6b, + 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, 0x56, 0x24, 0x4a, 0x9e, + 0x88, 0xd5, 0xf9, 0xb3, 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, + 0x5a, 0x74, 0xe3, 0x55, 0xa5, + ]; + + let expected = [0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9]; + + let mut mac = [0u8; 16]; + poly1305(&key, &msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); + + let mut poly = Poly1305::new(&key); + poly.input(&msg[0..32]); + poly.input(&msg[32..96]); + poly.input(&msg[96..112]); + poly.input(&msg[112..120]); + poly.input(&msg[120..124]); + poly.input(&msg[124..126]); + poly.input(&msg[126..127]); + poly.input(&msg[127..128]); + poly.input(&msg[128..129]); + poly.input(&msg[129..130]); + poly.input(&msg[130..131]); + poly.raw_result(&mut mac); + assert_eq!(&mac[..], &expected[..]); + } + + #[test] + fn donna_self_test() { + let wrap_key = [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let wrap_msg = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; + + let wrap_mac = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + let mut mac = [0u8; 16]; + poly1305(&wrap_key, &wrap_msg, &mut mac); + assert_eq!(&mac[..], &wrap_mac[..]); + + let total_key = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + ]; + + let total_mac = + [0x64, 0xaf, 0xe2, 0xe8, 0xd6, 0xad, 0x7b, 0xbd, 0xd2, 0x87, 0xf9, 0x7c, 0x44, 0x62, 0x3d, 0x39]; + + let mut tpoly = Poly1305::new(&total_key); + for i in 0..256 { + let key: Vec = repeat(i as u8).take(32).collect(); + let msg: Vec = repeat(i as u8).take(256).collect(); + let mut mac = [0u8; 16]; + poly1305(&key[..], &msg[0..i], &mut mac); + tpoly.input(&mac); + } + tpoly.raw_result(&mut mac); + assert_eq!(&mac[..], &total_mac[..]); + } + + #[test] + fn test_tls_vectors() { + // from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 + let key = b"this is 32-byte key for Poly1305"; + let msg = [0u8; 32]; + let expected = [0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07]; + let mut mac = [0u8; 16]; + poly1305(key, &msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); + + let msg = b"Hello world!"; + let expected = [0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0]; + poly1305(key, msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); + } +} diff --git a/src/lib.rs b/src/lib.rs index 237d6fe..6711dd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub mod error; /// Contains request/response types generated from the API definition of VSS. pub mod types; + +// Encryption-Decryption related crate-only helpers. +pub(crate) mod crypto; From 10630c133906d8badf4ca0ea6dc300b6008c6225 Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:40:03 -0800 Subject: [PATCH 2/2] Add storable_builder helper --- src/lib.rs | 4 ++ src/util/mod.rs | 4 ++ src/util/storable_builder.rs | 101 +++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/util/mod.rs create mode 100644 src/util/storable_builder.rs diff --git a/src/lib.rs b/src/lib.rs index 6711dd5..288dca5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +#![deny(missing_docs)] /// Implements a thin-client ([`client::VssClient`]) to access a hosted instance of Versioned Storage Service (VSS). pub mod client; @@ -19,5 +20,8 @@ pub mod error; /// Contains request/response types generated from the API definition of VSS. pub mod types; +/// Contains utils for encryption, requests-retries etc. +pub mod util; + // Encryption-Decryption related crate-only helpers. pub(crate) mod crypto; diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..341a85f --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,4 @@ +/// Contains [`StorableBuilder`] utility. +/// +/// [`StorableBuilder`]: storable_builder::StorableBuilder +pub mod storable_builder; diff --git a/src/util/storable_builder.rs b/src/util/storable_builder.rs new file mode 100644 index 0000000..20304be --- /dev/null +++ b/src/util/storable_builder.rs @@ -0,0 +1,101 @@ +use crate::crypto::chacha20poly1305::ChaCha20Poly1305; +use crate::types::{EncryptionMetadata, PlaintextBlob, Storable}; +use ::prost::Message; +use std::borrow::Borrow; +use std::io; +use std::io::{Error, ErrorKind}; + +/// [`StorableBuilder`] is a utility to build and deconstruct [`Storable`] objects. +/// It provides client-side Encrypt-then-MAC using ChaCha20-Poly1305. +pub struct StorableBuilder { + data_encryption_key: [u8; 32], + entropy_source: T, +} + +/// A trait representing a source for generating entropy/randomness. +pub trait EntropySource { + /// Fills a buffer with random bytes. + /// + /// This method must generate the specified number of random bytes and write them into the given + /// buffer. It is expected that this method will be cryptographically secure and suitable for use + /// cases requiring strong randomness, such as generating nonces or secret keys. + fn fill_bytes(&self, buffer: &mut [u8]); +} + +const CHACHA20_CIPHER_NAME: &'static str = "ChaCha20Poly1305"; + +impl StorableBuilder { + /// Creates a [`Storable`] that can be serialized and stored as `value` in [`PutObjectRequest`]. + /// + /// Uses ChaCha20 for encrypting `input` and Poly1305 for generating a mac/tag. + /// + /// Refer to docs on [`Storable`] for more information. + /// + /// [`PutObjectRequest`]: crate::types::PutObjectRequest + pub fn build(&self, input: Vec, version: i64) -> Storable { + let mut nonce = vec![0u8; 12]; + self.entropy_source.fill_bytes(&mut nonce[4..]); + + let mut data_blob = PlaintextBlob { value: input, version }.encode_to_vec(); + + let mut cipher = ChaCha20Poly1305::new(&self.data_encryption_key, &nonce, &[]); + let mut tag = vec![0u8; 16]; + cipher.encrypt_inplace(&mut data_blob, &mut tag); + Storable { + data: data_blob, + encryption_metadata: Some(EncryptionMetadata { + nonce, + tag, + cipher_format: CHACHA20_CIPHER_NAME.to_string(), + }), + } + } + + /// Deconstructs the provided [`Storable`] and returns constituent decrypted data and its + /// corresponding version as stored at the time of [`PutObjectRequest`]. + /// + /// [`PutObjectRequest`]: crate::types::PutObjectRequest + pub fn deconstruct(&self, mut storable: Storable) -> io::Result<(Vec, i64)> { + let encryption_metadata = storable.encryption_metadata.unwrap(); + let mut cipher = ChaCha20Poly1305::new(&self.data_encryption_key, &encryption_metadata.nonce, &[]); + + if cipher.decrypt_inplace(&mut storable.data, encryption_metadata.tag.borrow()) { + let data_blob = + PlaintextBlob::decode(&storable.data[..]).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + Ok((data_blob.value, data_blob.version)) + } else { + Err(Error::new(ErrorKind::InvalidData, "Invalid Tag")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub struct TestEntropyProvider; + impl EntropySource for TestEntropyProvider { + /// A terrible implementation which fills a buffer with bytes from a simple counter for testing + /// purposes. + fn fill_bytes(&self, buffer: &mut [u8]) { + for (i, byte) in buffer.iter_mut().enumerate() { + *byte = (i % 256) as u8; + } + } + } + + #[test] + fn encrypt_decrypt() { + let test_entropy_provider = TestEntropyProvider; + let mut data_key = [0u8; 32]; + test_entropy_provider.fill_bytes(&mut data_key); + let storable_builder = StorableBuilder { data_encryption_key: data_key, entropy_source: test_entropy_provider }; + let expected_data = b"secret".to_vec(); + let expected_version = 8; + let storable = storable_builder.build(expected_data.clone(), expected_version); + + let (actual_data, actual_version) = storable_builder.deconstruct(storable).unwrap(); + assert_eq!(actual_data, expected_data); + assert_eq!(actual_version, expected_version); + } +}