summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Matuszewski <igor@sequoia-pgp.org>2020-06-12 05:31:04 +0200
committerIgor Matuszewski <igor@sequoia-pgp.org>2020-08-13 13:15:04 +0200
commit033540d9708b0f1de61572bf43212368599d891a (patch)
tree2a500429be246e59911dc211c56ee81c2153dd09
parent976424a29509e194e10d277f15425fb67bb17493 (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.rs155
-rw-r--r--openpgp/src/crypto/backend/cng/symmetric.rs179
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());
+ }
+}