summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend
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 /openpgp/src/crypto/backend
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.
Diffstat (limited to 'openpgp/src/crypto/backend')
-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());
+ }
+}