diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-06-12 05:31:04 +0200 |
---|---|---|
committer | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-08-13 13:15:04 +0200 |
commit | 033540d9708b0f1de61572bf43212368599d891a (patch) | |
tree | 2a500429be246e59911dc211c56ee81c2153dd09 | |
parent | 976424a29509e194e10d277f15425fb67bb17493 (diff) |
openpgp: Implement CTR and EAX (AEAD) modes on top of CNG's AES
Unfortunately, we need AEAD since it's used in quite a few of the tests.
The implementation only works with AES since that's the only block
cipher that's readily available in the CNG from the list of generally
supported in Sequoia.
-rw-r--r-- | openpgp/src/crypto/backend/cng/aead.rs | 155 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/symmetric.rs | 179 |
2 files changed, 330 insertions, 4 deletions
diff --git a/openpgp/src/crypto/backend/cng/aead.rs b/openpgp/src/crypto/backend/cng/aead.rs index 4251fb5b..d0208221 100644 --- a/openpgp/src/crypto/backend/cng/aead.rs +++ b/openpgp/src/crypto/backend/cng/aead.rs @@ -1,10 +1,15 @@ //! Implementation of AEAD using Windows CNG API. -#![allow(unused_variables)] - -use crate::Result; +use crate::{Error, Result}; use crate::crypto::aead::Aead; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; +use super::symmetric::Ctr; + +use win_crypto_ng::hash::{Hash, HashAlgorithm, MacAlgorithmId}; +use win_crypto_ng::symmetric::SymmetricAlgorithmId; + +const EAX_BLOCK_SIZE: usize = 16; +const EAX_DIGEST_SIZE: usize = 16; impl AEADAlgorithm { pub(crate) fn context( @@ -13,6 +18,148 @@ impl AEADAlgorithm { key: &[u8], nonce: &[u8], ) -> Result<Box<dyn Aead>> { - unimplemented!() + match self { + AEADAlgorithm::EAX => match sym_algo { + | SymmetricAlgorithm::AES128 + | SymmetricAlgorithm::AES192 + | SymmetricAlgorithm::AES256 => { + Ok(Box::new(EaxAes::with_key_and_nonce(key, nonce)?)) + }, + _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), + }, + _ => Err(Error::UnsupportedAEADAlgorithm(self.clone()).into()), + } + } +} + +/// 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) + } + + 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 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; + } + } + + /// Length of the digest in bytes. + fn digest_size(&self) -> usize { EAX_DIGEST_SIZE } +} + + +#[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()); } } diff --git a/openpgp/src/crypto/backend/cng/symmetric.rs b/openpgp/src/crypto/backend/cng/symmetric.rs index 50c041d5..2d3382a0 100644 --- a/openpgp/src/crypto/backend/cng/symmetric.rs +++ b/openpgp/src/crypto/backend/cng/symmetric.rs @@ -68,6 +68,73 @@ 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}")] @@ -161,3 +228,115 @@ 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()); + } +} |