diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-04-10 18:55:13 +0200 |
---|---|---|
committer | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-08-13 13:15:04 +0200 |
commit | 9a367f4d5049709b35de12f4879a72c0ada674b0 (patch) | |
tree | 668cb54a4a9800abed64c266ed0969b87b8765e0 /openpgp | |
parent | dbd94bcf2ffc9a5badc023a234d332e44349bc2c (diff) |
openpgp: Implement symmetric algorithms using Windows CNG
Diffstat (limited to 'openpgp')
-rw-r--r-- | openpgp/src/crypto/backend/cng.rs | 1 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/symmetric.rs | 163 | ||||
-rw-r--r-- | openpgp/src/crypto/symmetric.rs | 44 |
3 files changed, 208 insertions, 0 deletions
diff --git a/openpgp/src/crypto/backend/cng.rs b/openpgp/src/crypto/backend/cng.rs index 93434e2e..017ca117 100644 --- a/openpgp/src/crypto/backend/cng.rs +++ b/openpgp/src/crypto/backend/cng.rs @@ -3,6 +3,7 @@ use win_crypto_ng::random::RandomNumberGenerator; pub mod hash; +pub mod symmetric; /// Fills the given buffer with random data. pub fn random<B: AsMut<[u8]>>(mut buf: B) { diff --git a/openpgp/src/crypto/backend/cng/symmetric.rs b/openpgp/src/crypto/backend/cng/symmetric.rs new file mode 100644 index 00000000..50c041d5 --- /dev/null +++ b/openpgp/src/crypto/backend/cng/symmetric.rs @@ -0,0 +1,163 @@ +use std::convert::TryFrom; + +use win_crypto_ng::symmetric as cng; + +use crate::crypto::symmetric::Mode; + +use crate::{Error, Result}; +use crate::types::SymmetricAlgorithm; + + +impl Mode for cng::SymmetricAlgorithmKey { + fn block_size(&self) -> usize { + self.block_size().expect("CNG not to fail internally") + } + + fn encrypt( + &mut self, + iv: &mut [u8], + dst: &mut [u8], + src: &[u8], + ) -> Result<()> { + let block_size = Mode::block_size(self); + // If necessary, round up to the next block size and pad with zeroes + // NOTE: In theory CFB doesn't need this but CNG always requires + // passing full blocks. + let mut _src = vec![]; + let missing = (block_size - (src.len() % block_size)) % block_size; + let src = if missing != 0 { + _src = vec![0u8; src.len() + missing]; + &mut _src[..src.len()].copy_from_slice(src); + &_src + } else { + src + }; + + let len = std::cmp::min(src.len(), dst.len()); + // NOTE: `None` IV is required for ECB mode but we don't ever use it. + let buffer = cng::SymmetricAlgorithmKey::encrypt(self, Some(iv), src, None)?; + Ok(dst[..len].copy_from_slice(&buffer.as_slice()[..len])) + } + + fn decrypt( + &mut self, + iv: &mut [u8], + dst: &mut [u8], + src: &[u8], + ) -> Result<()> { + let block_size = Mode::block_size(self); + // If necessary, round up to the next block size and pad with zeroes + // NOTE: In theory CFB doesn't need this but CNG always requires + // passing full blocks. + let mut _src = vec![]; + let missing = (block_size - (src.len() % block_size)) % block_size; + let src = if missing != 0 { + _src = vec![0u8; src.len() + missing]; + &mut _src[..src.len()].copy_from_slice(src); + &_src + } else { + src + }; + + let len = std::cmp::min(src.len(), dst.len()); + // NOTE: `None` IV is required for ECB mode but we don't ever use it. + let buffer = cng::SymmetricAlgorithmKey::decrypt(self, Some(iv), src, None)?; + dst[..len].copy_from_slice(&buffer.as_slice()[..len]); + + Ok(()) + } +} + + +#[derive(Debug, thiserror::Error)] +#[error("Unsupported algorithm: {0}")] +pub struct UnsupportedAlgorithm(SymmetricAlgorithm); + +impl From<UnsupportedAlgorithm> for Error { + fn from(value: UnsupportedAlgorithm) -> Error { + Error::UnsupportedSymmetricAlgorithm(value.0) + } +} + +impl TryFrom<SymmetricAlgorithm> for (cng::SymmetricAlgorithmId, usize) { + type Error = UnsupportedAlgorithm; + fn try_from(value: SymmetricAlgorithm) -> std::result::Result<Self, Self::Error> { + Ok(match value { + SymmetricAlgorithm::TripleDES => (cng::SymmetricAlgorithmId::TripleDes, 168), + SymmetricAlgorithm::AES128 => (cng::SymmetricAlgorithmId::Aes, 128), + SymmetricAlgorithm::AES192 => (cng::SymmetricAlgorithmId::Aes, 192), + SymmetricAlgorithm::AES256 => (cng::SymmetricAlgorithmId::Aes, 256), + algo => Err(UnsupportedAlgorithm(algo))?, + }) + } +} + +impl SymmetricAlgorithm { + /// Length of a key for this algorithm in bytes. Fails if Sequoia + /// does not support this algorithm. + pub fn key_size(self) -> Result<usize> { + Ok(match self { + SymmetricAlgorithm::TripleDES => 24, + SymmetricAlgorithm::AES128 => 16, + SymmetricAlgorithm::AES192 => 24, + SymmetricAlgorithm::AES256 => 32, + _ => Err(UnsupportedAlgorithm(self))?, + }) + } + + /// Length of a block for this algorithm in bytes. Fails if + /// Sequoia does not support this algorithm. + pub fn block_size(self) -> Result<usize> { + Ok(match self { + SymmetricAlgorithm::TripleDES => 8, + SymmetricAlgorithm::AES128 => 16, + SymmetricAlgorithm::AES192 => 16, + SymmetricAlgorithm::AES256 => 16, + _ => Err(UnsupportedAlgorithm(self))?, + }) + } + + /// Creates a symmetric cipher context for encrypting in CFB mode. + pub(crate) fn make_encrypt_cfb(self, key: &[u8]) -> Result<Box<dyn Mode>> { + let (algo, _) = TryFrom::try_from(self)?; + + let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Cfb)?; + let mut key = algo.new_key(key)?; + // Use full-block CFB mode as expected everywhere else (by default it's + // set to 8-bit CFB) + key.set_msg_block_len(key.block_size()?)?; + + Ok(Box::new(key)) + } + + /// Creates a symmetric cipher context for decrypting in CFB mode. + pub(crate) fn make_decrypt_cfb(self, key: &[u8]) -> Result<Box<dyn Mode>> { + Self::make_encrypt_cfb(self, key) + } + + /// Creates a Nettle context for encrypting in CBC mode. + pub(crate) fn make_encrypt_cbc(self, key: &[u8]) -> Result<Box<dyn Mode>> { + let (algo, _) = TryFrom::try_from(self)?; + + let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Cbc)?; + + Ok(Box::new( + algo.new_key(key).expect( + "CNG to successfully create a symmetric key for valid/supported algorithm" + ) + )) + } + + /// Creates a Nettle context for decrypting in CBC mode. + pub(crate) fn make_decrypt_cbc(self, key: &[u8]) -> Result<Box<dyn Mode>> { + let (algo, _) = TryFrom::try_from(self)?; + + let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Cbc)?; + + Ok(Box::new( + algo.new_key(key).expect( + "CNG to successfully create a symmetric key for valid/supported algorithm" + ) + )) + } +} diff --git a/openpgp/src/crypto/symmetric.rs b/openpgp/src/crypto/symmetric.rs index 27738993..81d4a61d 100644 --- a/openpgp/src/crypto/symmetric.rs +++ b/openpgp/src/crypto/symmetric.rs @@ -436,6 +436,50 @@ mod tests { use super::*; use std::io::{Cursor, Read, Write}; + #[test] + fn smoke_test() { + use crate::fmt::hex; + + let algo = SymmetricAlgorithm::AES128; + let key = &hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + assert_eq!(key.len(), 16); + // Ensure we use CFB128 by default + let mut cfb = algo.make_encrypt_cfb(&key).unwrap(); + + let mut iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let msg = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap(); + let mut dst = vec![0; msg.len()]; + + cfb.encrypt(&mut iv, &mut dst, &*msg).unwrap(); + assert_eq!(&iv, &hex::decode("3b3fd92eb72dad20333449f8e83cfb4a").unwrap()); + assert_eq!(&dst[..16], &*hex::decode("3b3fd92eb72dad20333449f8e83cfb4a").unwrap()); + // 32-byte long message + let mut iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let msg = b"This is a very important message"; + let mut dst = vec![0; msg.len()]; + cfb.encrypt(&mut iv, &mut dst, &*msg).unwrap(); + assert_eq!(&dst, &hex::decode( + "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded" + ).unwrap()); + // 33-byte (uneven) long message + let mut iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let msg = b"This is a very important message!"; + let mut dst = vec![0; msg.len()]; + cfb.encrypt(&mut iv, &mut dst, &*msg).unwrap(); + assert_eq!(&dst, &hex::decode( + "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded0b" + ).unwrap()); + // 33-byte (uneven) long message, chunked + let mut iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let mut dst = vec![0; msg.len()]; + for (mut dst, msg) in dst.chunks_mut(16).zip(msg.chunks(16)) { + cfb.encrypt(&mut iv, &mut dst, msg).unwrap(); + } + assert_eq!(&dst, &hex::decode( + "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded0b" + ).unwrap()); + } + /// This test is designed to test the buffering logic in Decryptor /// by reading directly from it (i.e. without any buffering /// introduced by the BufferedReaderDecryptor or any other source |