From dcbb45cc9d18261b8a4153ec19cb5462ea346640 Mon Sep 17 00:00:00 2001 From: Mateusz Lenik Date: Thu, 8 Mar 2018 10:02:43 +0100 Subject: [PATCH 1/3] Implement AES-{128,256}-CCM bindings --- openssl-sys/src/lib.rs | 2 + openssl/src/symm.rs | 140 ++++++++++++++++++++++++++++++++++++++++- systest/build.rs | 3 +- 3 files changed, 141 insertions(+), 4 deletions(-) diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 77f69188..3b58ac99 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -1904,6 +1904,7 @@ extern "C" { pub fn EVP_aes_128_xts() -> *const EVP_CIPHER; pub fn EVP_aes_128_ctr() -> *const EVP_CIPHER; pub fn EVP_aes_128_gcm() -> *const EVP_CIPHER; + pub fn EVP_aes_128_ccm() -> *const EVP_CIPHER; pub fn EVP_aes_128_cfb1() -> *const EVP_CIPHER; pub fn EVP_aes_128_cfb128() -> *const EVP_CIPHER; pub fn EVP_aes_128_cfb8() -> *const EVP_CIPHER; @@ -1912,6 +1913,7 @@ extern "C" { pub fn EVP_aes_256_xts() -> *const EVP_CIPHER; pub fn EVP_aes_256_ctr() -> *const EVP_CIPHER; pub fn EVP_aes_256_gcm() -> *const EVP_CIPHER; + pub fn EVP_aes_256_ccm() -> *const EVP_CIPHER; pub fn EVP_aes_256_cfb1() -> *const EVP_CIPHER; pub fn EVP_aes_256_cfb128() -> *const EVP_CIPHER; pub fn EVP_aes_256_cfb8() -> *const EVP_CIPHER; diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs index 630f4ab6..b82371db 100644 --- a/openssl/src/symm.rs +++ b/openssl/src/symm.rs @@ -71,7 +71,7 @@ pub enum Mode { /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. /// /// [`EVP_EncryptInit`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_EncryptInit.html -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub struct Cipher(*const ffi::EVP_CIPHER); impl Cipher { @@ -107,6 +107,10 @@ impl Cipher { unsafe { Cipher(ffi::EVP_aes_128_gcm()) } } + pub fn aes_128_ccm() -> Cipher { + unsafe { Cipher(ffi::EVP_aes_128_ccm()) } + } + pub fn aes_256_ecb() -> Cipher { unsafe { Cipher(ffi::EVP_aes_256_ecb()) } } @@ -139,6 +143,10 @@ impl Cipher { unsafe { Cipher(ffi::EVP_aes_256_gcm()) } } + pub fn aes_256_ccm() -> Cipher { + unsafe { Cipher(ffi::EVP_aes_256_ccm()) } + } + pub fn bf_cbc() -> Cipher { unsafe { Cipher(ffi::EVP_bf_cbc()) } } @@ -221,6 +229,12 @@ impl Cipher { pub fn block_size(&self) -> usize { unsafe { EVP_CIPHER_block_size(self.0) as usize } } + + /// Determines whether the cipher is using CCM mode + fn is_ccm(&self) -> bool { + // NOTE: OpenSSL returns pointers to static structs, which makes this work as expected + *self == Cipher::aes_128_ccm() || *self == Cipher::aes_256_ccm() + } } /// Represents a symmetric cipher context. @@ -387,6 +401,41 @@ impl Crypter { } } + /// Sets the length of the authentication tag to generate in AES CCM. + /// + /// When encrypting with AES CCM, the tag length needs to be explicitly set in order + /// to use a value different than the default 12 bytes. + pub fn set_tag_len(&mut self, tag: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(tag.len() <= c_int::max_value() as usize); + // NB: this constant is actually more general than just GCM. + cvt(ffi::EVP_CIPHER_CTX_ctrl( + self.ctx, + ffi::EVP_CTRL_GCM_SET_TAG, + tag.len() as c_int, + ptr::null_mut(), + )).map(|_| ()) + } + } + + /// Feeds total plaintext length to the cipher. + /// + /// The total plaintext or ciphertext length MUST be passed to the cipher when it operates in + /// CCM mode. + pub fn ccm_update(&mut self, input: &[u8])-> Result<(), ErrorStack> { + unsafe { + assert!(input.len() <= c_int::max_value() as usize); + let mut len = 0; + cvt(ffi::EVP_CipherUpdate( + self.ctx, + ptr::null_mut(), + &mut len, + ptr::null_mut(), + input.len() as c_int, + )).map(|_| ()) + } + } + /// Feeds Additional Authenticated Data (AAD) through the cipher. /// /// This can only be used with AEAD ciphers such as AES GCM. Data fed in is not encrypted, but @@ -601,6 +650,12 @@ pub fn encrypt_aead( ) -> Result, ErrorStack> { let mut c = Crypter::new(t, Mode::Encrypt, key, iv)?; let mut out = vec![0; data.len() + t.block_size()]; + + if t.is_ccm() { + c.set_tag_len(tag)?; + c.ccm_update(data)?; + } + c.aad_update(aad)?; let count = c.update(data, &mut out)?; let rest = c.finalize(&mut out[count..])?; @@ -623,10 +678,21 @@ pub fn decrypt_aead( ) -> Result, ErrorStack> { let mut c = Crypter::new(t, Mode::Decrypt, key, iv)?; let mut out = vec![0; data.len() + t.block_size()]; + + if t.is_ccm() { + c.set_tag(tag)?; + c.ccm_update(data)?; + } + c.aad_update(aad)?; let count = c.update(data, &mut out)?; - c.set_tag(tag)?; - let rest = c.finalize(&mut out[count..])?; + let mut rest = 0; + + if !t.is_ccm() { + c.set_tag(tag)?; + rest = c.finalize(&mut out[count..])?; + } + out.truncate(count + rest); Ok(out) } @@ -1012,6 +1078,74 @@ mod tests { assert_eq!(pt, hex::encode(out)); } + #[test] + fn test_aes128_ccm() { + let key = "3ee186594f110fb788a8bf8aa8be5d4a"; + let nonce = "44f705d52acf27b7f17196aa9b"; + let aad = "2c16724296ff85e079627be3053ea95adf35722c21886baba343bd6c79b5cb57"; + + let pt = "d71864877f2578db092daba2d6a1f9f4698a9c356c7830a1"; + let ct = "b4dd74e7a0cc51aea45dfb401a41d5822c96901a83247ea0"; + let tag = "d6965f5aa6e31302a9cc2b36"; + + let mut actual_tag = [0; 12]; + let out = encrypt_aead( + Cipher::aes_128_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(pt).unwrap(), + &mut actual_tag, + ).unwrap(); + + assert_eq!(ct, hex::encode(out)); + assert_eq!(tag, hex::encode(actual_tag)); + + let out = decrypt_aead( + Cipher::aes_128_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(ct).unwrap(), + &Vec::from_hex(tag).unwrap(), + ).unwrap(); + assert_eq!(pt, hex::encode(out)); + } + + #[test] + fn test_aes256_ccm() { + let key = "7f4af6765cad1d511db07e33aaafd57646ec279db629048aa6770af24849aa0d"; + let nonce = "dde2a362ce81b2b6913abc3095"; + let aad = "404f5df97ece7431987bc098cce994fc3c063b519ffa47b0365226a0015ef695"; + + let pt = "7ebef26bf4ecf6f0ebb2eb860edbf900f27b75b4a6340fdb"; + let ct = "353022db9c568bd7183a13c40b1ba30fcc768c54264aa2cd"; + let tag = "2927a053c9244d3217a7ad05"; + + let mut actual_tag = [0; 12]; + let out = encrypt_aead( + Cipher::aes_256_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(pt).unwrap(), + &mut actual_tag, + ).unwrap(); + + assert_eq!(ct, hex::encode(out)); + assert_eq!(tag, hex::encode(actual_tag)); + + let out = decrypt_aead( + Cipher::aes_256_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(ct).unwrap(), + &Vec::from_hex(tag).unwrap(), + ).unwrap(); + assert_eq!(pt, hex::encode(out)); + } + #[test] #[cfg(any(all(ossl110, feature = "v110"), all(ossl111, feature = "v111")))] fn test_chacha20() { diff --git a/systest/build.rs b/systest/build.rs index 76d0a950..58a1663e 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -59,7 +59,8 @@ fn main() { .header("openssl/pkcs12.h") .header("openssl/bn.h") .header("openssl/aes.h") - .header("openssl/ocsp.h"); + .header("openssl/ocsp.h") + .header("openssl/evp.h"); if !is_libressl { cfg.header("openssl/cms.h"); From 4866e9ff8ab46c0ac9d617fbee544bf677e65943 Mon Sep 17 00:00:00 2001 From: Mateusz Lenik Date: Thu, 8 Mar 2018 21:57:39 +0100 Subject: [PATCH 2/3] fixup! Implement AES-{128,256}-CCM bindings --- openssl/src/symm.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs index b82371db..099c2deb 100644 --- a/openssl/src/symm.rs +++ b/openssl/src/symm.rs @@ -405,14 +405,14 @@ impl Crypter { /// /// When encrypting with AES CCM, the tag length needs to be explicitly set in order /// to use a value different than the default 12 bytes. - pub fn set_tag_len(&mut self, tag: &[u8]) -> Result<(), ErrorStack> { + pub fn set_tag_len(&mut self, tag_len: usize) -> Result<(), ErrorStack> { unsafe { - assert!(tag.len() <= c_int::max_value() as usize); + assert!(tag_len <= c_int::max_value() as usize); // NB: this constant is actually more general than just GCM. cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, - tag.len() as c_int, + tag_len as c_int, ptr::null_mut(), )).map(|_| ()) } @@ -422,16 +422,16 @@ impl Crypter { /// /// The total plaintext or ciphertext length MUST be passed to the cipher when it operates in /// CCM mode. - pub fn ccm_update(&mut self, input: &[u8])-> Result<(), ErrorStack> { + pub fn set_data_len(&mut self, data_len: usize)-> Result<(), ErrorStack> { unsafe { - assert!(input.len() <= c_int::max_value() as usize); + assert!(data_len <= c_int::max_value() as usize); let mut len = 0; cvt(ffi::EVP_CipherUpdate( self.ctx, ptr::null_mut(), &mut len, ptr::null_mut(), - input.len() as c_int, + data_len as c_int, )).map(|_| ()) } } @@ -652,8 +652,8 @@ pub fn encrypt_aead( let mut out = vec![0; data.len() + t.block_size()]; if t.is_ccm() { - c.set_tag_len(tag)?; - c.ccm_update(data)?; + c.set_tag_len(tag.len())?; + c.set_data_len(data.len())?; } c.aad_update(aad)?; @@ -681,7 +681,7 @@ pub fn decrypt_aead( if t.is_ccm() { c.set_tag(tag)?; - c.ccm_update(data)?; + c.set_data_len(data.len())?; } c.aad_update(aad)?; From cefad46cf59dc3f12cfdbc30041bb96a43072374 Mon Sep 17 00:00:00 2001 From: Mateusz Lenik Date: Sun, 11 Mar 2018 22:04:01 +0100 Subject: [PATCH 3/3] fixup! Implement AES-{128,256}-CCM bindings --- openssl/src/symm.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs index 099c2deb..a1b674de 100644 --- a/openssl/src/symm.rs +++ b/openssl/src/symm.rs @@ -1112,6 +1112,26 @@ mod tests { assert_eq!(pt, hex::encode(out)); } + #[test] + fn test_aes128_ccm_verify_fail() { + let key = "3ee186594f110fb788a8bf8aa8be5d4a"; + let nonce = "44f705d52acf27b7f17196aa9b"; + let aad = "2c16724296ff85e079627be3053ea95adf35722c21886baba343bd6c79b5cb57"; + + let ct = "b4dd74e7a0cc51aea45dfb401a41d5822c96901a83247ea0"; + let tag = "00005f5aa6e31302a9cc2b36"; + + let out = decrypt_aead( + Cipher::aes_128_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(ct).unwrap(), + &Vec::from_hex(tag).unwrap(), + ); + assert!(out.is_err()); + } + #[test] fn test_aes256_ccm() { let key = "7f4af6765cad1d511db07e33aaafd57646ec279db629048aa6770af24849aa0d"; @@ -1146,6 +1166,26 @@ mod tests { assert_eq!(pt, hex::encode(out)); } + #[test] + fn test_aes256_ccm_verify_fail() { + let key = "7f4af6765cad1d511db07e33aaafd57646ec279db629048aa6770af24849aa0d"; + let nonce = "dde2a362ce81b2b6913abc3095"; + let aad = "404f5df97ece7431987bc098cce994fc3c063b519ffa47b0365226a0015ef695"; + + let ct = "353022db9c568bd7183a13c40b1ba30fcc768c54264aa2cd"; + let tag = "0000a053c9244d3217a7ad05"; + + let out = decrypt_aead( + Cipher::aes_256_ccm(), + &Vec::from_hex(key).unwrap(), + Some(&Vec::from_hex(nonce).unwrap()), + &Vec::from_hex(aad).unwrap(), + &Vec::from_hex(ct).unwrap(), + &Vec::from_hex(tag).unwrap(), + ); + assert!(out.is_err()); + } + #[test] #[cfg(any(all(ossl110, feature = "v110"), all(ossl111, feature = "v111")))] fn test_chacha20() {