diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-10-05 01:10:30 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-12-04 16:10:59 +0100 |
commit | 6de3f7f02ef14d7d84e6d0a7aff6aaee12444392 (patch) | |
tree | fb9544f1e1d54be0bf86ea2a6e4e6edb4b3dac94 /openpgp/src/crypto/backend | |
parent | 3dacda4a93b1d3b1ef44484ac2691eec0aa759c9 (diff) |
openpgp: Use pure Rust EAX implementation under CNG backend
- Fixes #556.
Diffstat (limited to 'openpgp/src/crypto/backend')
-rw-r--r-- | openpgp/src/crypto/backend/cng/aead.rs | 222 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/symmetric.rs | 180 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/nettle/aead.rs | 3 |
3 files changed, 85 insertions, 320 deletions
diff --git a/openpgp/src/crypto/backend/cng/aead.rs b/openpgp/src/crypto/backend/cng/aead.rs index d30073ca..ff302dad 100644 --- a/openpgp/src/crypto/backend/cng/aead.rs +++ b/openpgp/src/crypto/backend/cng/aead.rs @@ -1,16 +1,22 @@ //! Implementation of AEAD using Windows CNG API. use crate::{Error, Result}; -use crate::crypto::aead::Aead; +use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; -use super::symmetric::Ctr; -use win_crypto_ng::hash::{Hash, HashAlgorithm, MacAlgorithmId}; -use win_crypto_ng::symmetric::SymmetricAlgorithmId; +use eax::online::{EaxOnline, Encrypt, Decrypt}; +use win_crypto_ng::symmetric::{BlockCipherKey, Aes}; +use win_crypto_ng::symmetric::block_cipher::generic_array::{GenericArray, ArrayLength}; +use win_crypto_ng::symmetric::block_cipher::generic_array::typenum::{U128, U192, U256}; -const EAX_BLOCK_SIZE: usize = 16; -const EAX_DIGEST_SIZE: usize = 16; +trait GenericArrayExt { + const LEN: usize; +} + +impl<T, N: ArrayLength<T>> GenericArrayExt for GenericArray<T, N> { + const LEN: usize = N::USIZE; +} impl AEADAlgorithm { pub(crate) fn context( @@ -18,14 +24,40 @@ impl AEADAlgorithm { sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], + op: CipherOp, ) -> Result<Box<dyn Aead>> { + + let nonce = GenericArray::from_slice(nonce); + match self { AEADAlgorithm::EAX => match sym_algo { - | SymmetricAlgorithm::AES128 - | SymmetricAlgorithm::AES192 - | SymmetricAlgorithm::AES256 => { - Ok(Box::new(EaxAes::with_key_and_nonce(key, nonce)?)) - }, + | SymmetricAlgorithm::AES128 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } + SymmetricAlgorithm::AES192 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } + SymmetricAlgorithm::AES256 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, _ => Err(Error::UnsupportedAEADAlgorithm(self.clone()).into()), @@ -33,135 +65,47 @@ impl AEADAlgorithm { } } -/// EAX-AES mode. -/// -/// See https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf. -struct EaxAes { - omac_nonce: Vec<u8>, - omac_data: Hash, - omac_msg: Hash, - ctr: Ctr, -} - -impl EaxAes { - fn with_key_and_nonce(key: &[u8], nonce: &[u8]) -> Result<Self> { - fn omac_init_with_iv( - prov: &HashAlgorithm<MacAlgorithmId>, - key: &[u8], - iv: u8, - ) -> Result<Hash> { - let mut omac = prov.new_mac(key, None)?; - // Prepend the IV - omac.hash(&[0; EAX_BLOCK_SIZE - 1])?; - omac.hash(&[iv])?; - Ok(omac) +macro_rules! impl_aead { + ($($type: ty),*) => { + $( + impl Aead for EaxOnline<$type, Encrypt> { + fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } + fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } + fn digest(&mut self, digest: &mut [u8]) { + let tag = self.tag_clone(); + digest[..tag.len()].copy_from_slice(&tag[..]); + } + fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { + let len = core::cmp::min(dst.len(), src.len()); + dst[..len].copy_from_slice(&src[..len]); + EaxOnline::<$type, Encrypt>::encrypt(self, &mut dst[..len]) + } + fn decrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { + panic!("AEAD decryption called in the encryption context") + } } - - let provider = HashAlgorithm::open(MacAlgorithmId::AesCmac)?; - // N ← OMAC_0^K(N) - let mut omac_nonce = omac_init_with_iv(&provider, key, 0)?; - omac_nonce.hash(nonce)?; - let omac_nonce = omac_nonce.finish()?.into_inner(); - // H ← OMAC_1^K(H), init with 1 but hash online associated data later - let omac_data = omac_init_with_iv(&provider, key, 1)?; - // C ← OMAC_2^K(C), init with 2 but hash online resulting ciphertext later - let omac_msg = omac_init_with_iv(&provider, key, 2)?; - - let ctr = Ctr::with_cipher_and_iv(SymmetricAlgorithmId::Aes, key, &omac_nonce)?; - - Ok(EaxAes { omac_nonce, omac_data, omac_msg, ctr }) - } -} - - -impl seal::Sealed for EaxAes {} -impl Aead for EaxAes { - /// Adds associated data `ad`. - fn update(&mut self, ad: &[u8]) { - let _ = self.omac_data.hash(ad); - } - - /// Encrypts one block `src` to `dst`. - fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { - let _ = Ctr::encrypt(&mut self.ctr, dst, src); - let _ = self.omac_msg.hash(dst); - } - /// Decrypts one block `src` to `dst`. - fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { - let _ = self.omac_msg.hash(src); - let _ = Ctr::decrypt(&mut self.ctr, dst, src); - } - - /// Produce the digest. - fn digest(&mut self, digest: &mut [u8]) { - // TODO: It'd be great if wouldn't have to clone - let omac_data = self.omac_data.clone().finish().unwrap().into_inner(); - let omac_msg = self.omac_msg.clone().finish().unwrap().into_inner(); - - let (nonce, data, msg) = (self.omac_nonce.iter(), omac_data.iter(), omac_msg.iter()); - for (((out, n), d), m) in digest.iter_mut().zip(nonce).zip(data).zip(msg) { - *out = n ^ d ^ m; + impl seal::Sealed for EaxOnline<$type, Encrypt> {} + )* + $( + impl Aead for EaxOnline<$type, Decrypt> { + fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } + fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } + fn digest(&mut self, digest: &mut [u8]) { + let tag = self.tag_clone(); + digest[..tag.len()].copy_from_slice(&tag[..]); + } + fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { + panic!("AEAD encryption called in the decryption context") + } + fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { + let len = core::cmp::min(dst.len(), src.len()); + dst[..len].copy_from_slice(&src[..len]); + self.decrypt_unauthenticated_hazmat(&mut dst[..len]) + } } - } - - /// Length of the digest in bytes. - fn digest_size(&self) -> usize { EAX_DIGEST_SIZE } + impl seal::Sealed for EaxOnline<$type, Decrypt> {} + )* + }; } - -#[cfg(test)] -mod tests { - use super::*; - use crate::crypto::aead::Aead; - - trait HexSlice: std::borrow::Borrow<str> { - fn as_hex(&self) -> Vec<u8> { - let res: Vec<u8> = self.borrow().as_bytes().rchunks(2) - .map(|slice| std::str::from_utf8(slice).unwrap()) - .map(|chr| u8::from_str_radix(chr, 16).unwrap()) - .rev() - .collect(); - res - } - } - impl<'a> HexSlice for &'a str {} - - #[test] - fn eax() { - // Test vectors from https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf - let mut eax = EaxAes::with_key_and_nonce( - &"233952DEE4D5ED5F9B9C6D6FF80FF478".as_hex(), - &"62EC67F9C3A4A407FCB2A8C49031A8B3".as_hex() - ).unwrap(); - eax.update(&"6BFB914FD07EAE6B".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - assert_eq!(&digest, &*"E037830E8389F27B025A2D6527E79D01".as_hex()); - - let mut eax = EaxAes::with_key_and_nonce( - &"91945D3F4DCBEE0BF45EF52255F095A4".as_hex(), - &"BECAF043B0A23D843194BA972C66DEBD".as_hex() - ).unwrap(); - eax.update(&"FA3BFD4806EB53FA".as_hex()); - let mut out = [0; 2]; - eax.encrypt(&mut out, &"F7FB".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - let output = [&out[..], &digest[..]].concat(); - assert_eq!(output, &*"19DD5C4C9331049D0BDAB0277408F67967E5".as_hex()); - - let mut eax = EaxAes::with_key_and_nonce( - &"8395FCF1E95BEBD697BD010BC766AAC3".as_hex(), - &"22E7ADD93CFC6393C57EC0B3C17D6B44".as_hex() - ).unwrap(); - eax.update(&"126735FCC320D25A".as_hex()); - let mut out1 = [0; 16]; - eax.encrypt(&mut out1, &"CA40D7446E545FFAED3BD12A740A659F".as_hex()); - let mut out2 = [0; 5]; - eax.encrypt(&mut out2, &"FBBB3CEAB7".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - let output = [&out1[..], &out2[..], &digest[..]].concat(); - assert_eq!(output, &*"CB8920F87A6C75CFF39627B56E3ED197C552D295A7CFC46AFC253B4652B1AF3795B124AB6E".as_hex()); - } -} +impl_aead!(BlockCipherKey<Aes, U128>, BlockCipherKey<Aes, U192>, BlockCipherKey<Aes, U256>); diff --git a/openpgp/src/crypto/backend/cng/symmetric.rs b/openpgp/src/crypto/backend/cng/symmetric.rs index a4a140eb..7d8281e7 100644 --- a/openpgp/src/crypto/backend/cng/symmetric.rs +++ b/openpgp/src/crypto/backend/cng/symmetric.rs @@ -68,74 +68,6 @@ impl Mode for cng::SymmetricAlgorithmKey { } } -/// CTR mode using a block cipher. CNG doesn't implement one so roll our own -/// using CNG's ECB mode. -pub struct Ctr { - key: cng::SymmetricAlgorithmKey, - ctr: Option<Box<[u8]>>, -} - -impl Ctr { - pub fn with_cipher_and_iv(algo: cng::SymmetricAlgorithmId, key: &[u8], iv: &[u8]) -> Result<Ctr> { - let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Ecb)?; - let key = algo.new_key(key)?; - // TODO: Check iv len - Ok(Ctr { key, ctr: Some(iv.into()) }) - } - - pub fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { - let mut ctr = self.ctr.take().unwrap(); - Mode::encrypt(self, &mut ctr, dst, src)?; - self.ctr = Some(ctr); - Ok(()) - } - - pub fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { - let mut ctr = self.ctr.take().unwrap(); - Mode::decrypt(self, &mut ctr, dst, src)?; - self.ctr = Some(ctr); - Ok(()) - } -} - -impl Mode for Ctr { - fn block_size(&self) -> usize { - self.key.block_size().expect("CNG not to fail internally") - } - - fn encrypt(&mut self, iv: &mut [u8], dst: &mut [u8], src: &[u8]) -> Result<()> { - let block = self.block_size(); - - // Ciphertext_i <- BlockCipher(Counter_i) ⊕ Plaintext_i - for (dst, src) in dst.chunks_mut(block).zip(src.chunks(block)) { - let res = cng::SymmetricAlgorithmKey::encrypt(&self.key, None, iv, None)?; - wrapping_increment_be(iv.as_mut()); - - for (dst, (res, src)) in dst.iter_mut().zip(res.as_slice().iter().zip(src.iter())) { - *dst = res ^ src; - } - } - - Ok(()) - } - - fn decrypt(&mut self, iv: &mut [u8], dst: &mut [u8], src: &[u8]) -> Result<()> { - let block = self.block_size(); - - // Plaintext_i <- BlockCipher(Counter_i) ⊕ Ciphertext_i - for (dst, src) in dst.chunks_mut(block).zip(src.chunks(block)) { - let res = cng::SymmetricAlgorithmKey::encrypt(&self.key, None, iv, None)?; - wrapping_increment_be(iv.as_mut()); - - for (dst, (res, src)) in dst.iter_mut().zip(res.as_slice().iter().zip(src.iter())) { - *dst = res ^ src; - } - } - - Ok(()) - } -} - #[derive(Debug, thiserror::Error)] #[error("Unsupported algorithm: {0}")] pub struct UnsupportedAlgorithm(SymmetricAlgorithm); @@ -253,115 +185,3 @@ impl SymmetricAlgorithm { )) } } - -fn wrapping_increment_be(value: &mut [u8]) -> &mut [u8] { - for val in value.iter_mut().rev() { - *val = val.wrapping_add(1); - // Stop carryover - if *val != 0x00 { - break; - } - } - - value -} - -#[cfg(test)] -mod tests { - use super::*; - use std::borrow::Borrow; - - trait HexSlice: Borrow<str> { - fn as_hex(&self) -> Vec<u8> { - let res: Vec<u8> = self.borrow().as_bytes().rchunks(2) - .map(|slice| std::str::from_utf8(slice).unwrap()) - .map(|chr| u8::from_str_radix(chr, 16).unwrap()) - .rev() - .collect(); - res - } - } - impl<'a> HexSlice for &'a str {} - - #[test] - fn hex_slice() { - assert_eq!("0a".as_hex(), &[0x0A]); - assert_eq!("a".as_hex(), &[0x0A]); - assert_eq!("FE0a".as_hex(), &[0xFE, 0x0A]); - assert_eq!("E0a".as_hex(), &[0x0E, 0x0A]); - } - - #[test] - fn wrapping_increment_be() { - assert_eq!(super::wrapping_increment_be(&mut [0x00]), [0x01]); - assert_eq!(super::wrapping_increment_be(&mut [0xFF, 0xFF]), [0x00, 0x00]); - assert_eq!(super::wrapping_increment_be(&mut [0xFD, 0xFF]), [0xFE, 0x00]); - - let input = &mut "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(); - let expected = &"f0f1f2f3f4f5f6f7f8f9fafbfcfdff00".as_hex(); - - assert_eq!(super::wrapping_increment_be(input).as_ref(), expected.as_slice()); - } - - #[test] - fn ctr_aes_128() { - // NIST SP800-38a test vectors - // F.5.1 CTR-AES128.Encrypt - let key = &"2b7e151628aed2a6abf7158809cf4f3c".as_hex(); - assert_eq!(key.len(), 16); - let mut ctr = Ctr::with_cipher_and_iv( - cng::SymmetricAlgorithmId::Aes, - key, - &"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(), - ).unwrap(); - - let plain = &"6bc1bee22e409f96e93d7e117393172a".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"874d6191b620e3261bef6864990db6ce".as_hex()); - - let plain = &"ae2d8a571e03ac9c9eb76fac45af8e51".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"9806f66b7970fdff8617187bb9fffdff".as_hex()); - - let plain = &"30c81c46a35ce411e5fbc1191a0a52ef".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"5ae4df3edbd5d35e5b4f09020db03eab".as_hex()); - - let plain = &"f69f2445df4f9b17ad2b417be66c3710".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"1e031dda2fbe03d1792170a0f3009cee".as_hex()); - - // F.5.2 CTR-AES128.Decrypt - let key = &"2b7e151628aed2a6abf7158809cf4f3c".as_hex(); - assert_eq!(key.len(), 16); - let mut ctr = Ctr::with_cipher_and_iv( - cng::SymmetricAlgorithmId::Aes, - key, - &"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(), - ).unwrap(); - - let plain = &"874d6191b620e3261bef6864990db6ce".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.decrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"6bc1bee22e409f96e93d7e117393172a".as_hex()); - - let plain = &"9806f66b7970fdff8617187bb9fffdff".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.decrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"ae2d8a571e03ac9c9eb76fac45af8e51".as_hex()); - - let plain = &"5ae4df3edbd5d35e5b4f09020db03eab".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.decrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"30c81c46a35ce411e5fbc1191a0a52ef".as_hex()); - - let plain = &"1e031dda2fbe03d1792170a0f3009cee".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.decrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"f69f2445df4f9b17ad2b417be66c3710".as_hex()); - } -} diff --git a/openpgp/src/crypto/backend/nettle/aead.rs b/openpgp/src/crypto/backend/nettle/aead.rs index 797dd040..f0b70b53 100644 --- a/openpgp/src/crypto/backend/nettle/aead.rs +++ b/openpgp/src/crypto/backend/nettle/aead.rs @@ -3,7 +3,7 @@ use nettle::{aead, cipher}; use crate::{Error, Result}; -use crate::crypto::aead::Aead; +use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; @@ -32,6 +32,7 @@ impl AEADAlgorithm { sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], + _op: CipherOp, ) -> Result<Box<dyn Aead>> { match self { AEADAlgorithm::EAX => match sym_algo { |