| /* Copyright (c) 2023, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| extern crate alloc; |
| |
| use crate::{CSlice, CSliceMut}; |
| use alloc::vec; |
| use alloc::vec::Vec; |
| use bssl_sys::EVP_CIPHER; |
| use core::ffi::c_int; |
| use core::marker::PhantomData; |
| |
| /// AES-CTR stream cipher operations. |
| pub mod aes_ctr; |
| |
| /// AES-CBC stream cipher operations. |
| pub mod aes_cbc; |
| |
| /// Error returned in the event of an unsuccessful cipher operation. |
| #[derive(Debug)] |
| pub struct CipherError; |
| |
| /// Synchronous stream cipher trait. |
| pub trait StreamCipher { |
| /// The byte array key type which specifies the size of the key used to instantiate the cipher. |
| type Key: AsRef<[u8]>; |
| |
| /// The byte array nonce type which specifies the size of the nonce used in the cipher |
| /// operations. |
| type Nonce: AsRef<[u8]>; |
| |
| /// Instantiate a new instance of a stream cipher from a `key` and `iv`. |
| fn new(key: &Self::Key, iv: &Self::Nonce) -> Self; |
| |
| /// Applies the cipher keystream to `buffer` in place, returning CipherError on an unsuccessful |
| /// operation. |
| fn apply_keystream(&mut self, buffer: &mut [u8]) -> Result<(), CipherError>; |
| } |
| |
| /// Synchronous block cipher trait. |
| pub trait BlockCipher { |
| /// The byte array key type which specifies the size of the key used to instantiate the cipher. |
| type Key: AsRef<[u8]>; |
| |
| /// The byte array nonce type which specifies the size of the nonce used in the cipher |
| /// operations. |
| type Nonce: AsRef<[u8]>; |
| |
| /// Instantiate a new instance of a block cipher for encryption from a `key` and `iv`. |
| fn new_encrypt(key: &Self::Key, iv: &Self::Nonce) -> Self; |
| |
| /// Instantiate a new instance of a block cipher for decryption from a `key` and `iv`. |
| fn new_decrypt(key: &Self::Key, iv: &Self::Nonce) -> Self; |
| |
| /// Encrypts the given data in `buffer`, and returns the result (with padding) in a newly |
| /// allocated vector, or a [`CipherError`] if the operation was unsuccessful. |
| fn encrypt_padded(self, buffer: &[u8]) -> Result<Vec<u8>, CipherError>; |
| |
| /// Decrypts the given data in a `buffer`, and returns the result (with padding removed) in a |
| /// newly allocated vector, or a [`CipherError`] if the operation was unsuccessful. |
| fn decrypt_padded(self, buffer: &[u8]) -> Result<Vec<u8>, CipherError>; |
| } |
| |
| /// A cipher type, where `Key` is the size of the Key and `Nonce` is the size of the nonce or IV. |
| /// This must only be exposed publicly by types who ensure that `Key` is the correct size for the |
| /// given CipherType. This can be checked via `bssl_sys::EVP_CIPHER_key_length`. |
| trait EvpCipherType { |
| type Key: AsRef<[u8]>; |
| type Nonce: AsRef<[u8]>; |
| fn evp_cipher() -> *const EVP_CIPHER; |
| } |
| |
| struct EvpAes128Ctr; |
| impl EvpCipherType for EvpAes128Ctr { |
| type Key = [u8; 16]; |
| type Nonce = [u8; 16]; |
| fn evp_cipher() -> *const EVP_CIPHER { |
| // Safety: |
| // - this just returns a constant value |
| unsafe { bssl_sys::EVP_aes_128_ctr() } |
| } |
| } |
| |
| struct EvpAes256Ctr; |
| impl EvpCipherType for EvpAes256Ctr { |
| type Key = [u8; 32]; |
| type Nonce = [u8; 16]; |
| fn evp_cipher() -> *const EVP_CIPHER { |
| // Safety: |
| // - this just returns a constant value |
| unsafe { bssl_sys::EVP_aes_256_ctr() } |
| } |
| } |
| |
| struct EvpAes128Cbc; |
| impl EvpCipherType for EvpAes128Cbc { |
| type Key = [u8; 16]; |
| type Nonce = [u8; 16]; |
| fn evp_cipher() -> *const EVP_CIPHER { |
| // Safety: |
| // - this just returns a constant value |
| unsafe { bssl_sys::EVP_aes_128_cbc() } |
| } |
| } |
| |
| struct EvpAes256Cbc; |
| impl EvpCipherType for EvpAes256Cbc { |
| type Key = [u8; 32]; |
| type Nonce = [u8; 16]; |
| fn evp_cipher() -> *const EVP_CIPHER { |
| // Safety: |
| // - this just returns a constant value |
| unsafe { bssl_sys::EVP_aes_256_cbc() } |
| } |
| } |
| |
| enum CipherInitPurpose { |
| Encrypt, |
| Decrypt, |
| } |
| |
| /// Internal cipher implementation which wraps `EVP_CIPHER_*` |
| struct Cipher<C: EvpCipherType> { |
| ctx: *mut bssl_sys::EVP_CIPHER_CTX, |
| _marker: PhantomData<C>, |
| } |
| |
| impl<C: EvpCipherType> Cipher<C> { |
| fn new(key: &C::Key, iv: &C::Nonce, purpose: CipherInitPurpose) -> Self { |
| // Safety: |
| // - Panics on allocation failure. |
| let ctx = unsafe { bssl_sys::EVP_CIPHER_CTX_new() }; |
| assert!(!ctx.is_null()); |
| |
| let key_cslice = CSlice::from(key.as_ref()); |
| let iv_cslice = CSlice::from(iv.as_ref()); |
| |
| // Safety: |
| // - Key size and iv size must be properly set by the higher level wrapper types. |
| // - Panics on allocation failure. |
| let result = match purpose { |
| CipherInitPurpose::Encrypt => unsafe { |
| bssl_sys::EVP_EncryptInit_ex( |
| ctx, |
| C::evp_cipher(), |
| core::ptr::null_mut(), |
| key_cslice.as_ptr(), |
| iv_cslice.as_ptr(), |
| ) |
| }, |
| CipherInitPurpose::Decrypt => unsafe { |
| bssl_sys::EVP_DecryptInit_ex( |
| ctx, |
| C::evp_cipher(), |
| core::ptr::null_mut(), |
| key_cslice.as_ptr(), |
| iv_cslice.as_ptr(), |
| ) |
| }, |
| }; |
| assert_eq!(result, 1); |
| |
| Self { |
| ctx, |
| _marker: Default::default(), |
| } |
| } |
| |
| fn cipher_mode(&self) -> u32 { |
| // Safety: |
| // - The cipher context is initialized with EVP_EncryptInit_ex in `new` |
| unsafe { bssl_sys::EVP_CIPHER_CTX_mode(self.ctx) } |
| } |
| |
| fn apply_keystream_in_place(&mut self, buffer: &mut [u8]) -> Result<(), CipherError> { |
| // WARNING: This is not safe to re-use for the CBC mode of operation since it is applying |
| // the key stream in-place. |
| assert_eq!( |
| self.cipher_mode(), |
| bssl_sys::EVP_CIPH_CTR_MODE as u32, |
| "Cannot use apply_keystraem_in_place for non-CTR modes" |
| ); |
| let mut cslice_buf_mut = CSliceMut::from(buffer); |
| let mut out_len = 0; |
| |
| let buff_len_int = c_int::try_from(cslice_buf_mut.len()).map_err(|_| CipherError)?; |
| |
| // Safety: |
| // - The output buffer provided is always large enough for an in-place operation. |
| let result = unsafe { |
| bssl_sys::EVP_EncryptUpdate( |
| self.ctx, |
| cslice_buf_mut.as_mut_ptr(), |
| &mut out_len, |
| cslice_buf_mut.as_mut_ptr(), |
| buff_len_int, |
| ) |
| }; |
| if result == 1 { |
| assert_eq!(out_len as usize, cslice_buf_mut.len()); |
| Ok(()) |
| } else { |
| Err(CipherError) |
| } |
| } |
| |
| #[allow(clippy::expect_used)] |
| fn encrypt(self, buffer: &[u8]) -> Result<Vec<u8>, CipherError> { |
| // Safety: self.ctx is initialized with a cipher in `new()`. |
| let block_size_u32 = unsafe { bssl_sys::EVP_CIPHER_CTX_block_size(self.ctx) }; |
| let block_size: usize = block_size_u32 |
| .try_into() |
| .expect("Block size should always fit in usize"); |
| // Allocate an output vec that is large enough for both EncryptUpdate and EncryptFinal |
| // operations |
| let max_encrypt_update_output_size = buffer.len() + block_size - 1; |
| let max_encrypt_final_output_size = block_size; |
| let mut output_vec = |
| vec![0_u8; max_encrypt_update_output_size + max_encrypt_final_output_size]; |
| // EncryptUpdate block |
| let update_out_len_usize = { |
| let mut cslice_out_buf_mut = CSliceMut::from(&mut output_vec[..]); |
| let mut update_out_len = 0; |
| |
| let cslice_in_buf = CSlice::from(buffer); |
| let in_buff_len_int = c_int::try_from(cslice_in_buf.len()).map_err(|_| CipherError)?; |
| |
| // Safety: |
| // - `EVP_EncryptUpdate` requires that "The number of output bytes may be up to `in_len` |
| // plus the block length minus one and `out` must have sufficient space". This is the |
| // `max_encrypt_update_output_size` part of the output_vec's capacity. |
| let update_result = unsafe { |
| bssl_sys::EVP_EncryptUpdate( |
| self.ctx, |
| cslice_out_buf_mut.as_mut_ptr(), |
| &mut update_out_len, |
| cslice_in_buf.as_ptr(), |
| in_buff_len_int, |
| ) |
| }; |
| if update_result != 1 { |
| return Err(CipherError); |
| } |
| update_out_len |
| .try_into() |
| .expect("Output length should always fit in usize") |
| }; |
| |
| // EncryptFinal block |
| { |
| // Slice indexing here will not panic because we ensured `output_vec` is larger than |
| // what `EncryptUpdate` will write. |
| #[allow(clippy::indexing_slicing)] |
| let mut cslice_finalize_buf_mut = |
| CSliceMut::from(&mut output_vec[update_out_len_usize..]); |
| let mut final_out_len = 0; |
| let final_result = unsafe { |
| bssl_sys::EVP_EncryptFinal_ex( |
| self.ctx, |
| cslice_finalize_buf_mut.as_mut_ptr(), |
| &mut final_out_len, |
| ) |
| }; |
| let final_put_len_usize = |
| <usize>::try_from(final_out_len).expect("Output length should always fit in usize"); |
| if final_result == 1 { |
| output_vec.truncate(update_out_len_usize + final_put_len_usize) |
| } else { |
| return Err(CipherError); |
| } |
| } |
| Ok(output_vec) |
| } |
| |
| #[allow(clippy::expect_used)] |
| fn decrypt(self, in_buffer: &[u8]) -> Result<Vec<u8>, CipherError> { |
| // Safety: self.ctx is initialized with a cipher in `new()`. |
| let block_size_u32 = unsafe { bssl_sys::EVP_CIPHER_CTX_block_size(self.ctx) }; |
| let block_size: usize = block_size_u32 |
| .try_into() |
| .expect("Block size should always fit in usize"); |
| // Allocate an output vec that is large enough for both DecryptUpdate and DecryptFinal |
| // operations |
| let max_decrypt_update_output_size = in_buffer.len() + block_size - 1; |
| let max_decrypt_final_output_size = block_size; |
| let mut output_vec = |
| vec![0_u8; max_decrypt_update_output_size + max_decrypt_final_output_size]; |
| |
| // DecryptUpdate block |
| let update_out_len_usize = { |
| let mut cslice_out_buf_mut = CSliceMut::from(&mut output_vec[..]); |
| let mut update_out_len = 0; |
| |
| let cslice_in_buf = CSlice::from(in_buffer); |
| let in_buff_len_int = c_int::try_from(cslice_in_buf.len()).map_err(|_| CipherError)?; |
| |
| // Safety: |
| // - `EVP_DecryptUpdate` requires that "The number of output bytes may be up to `in_len` |
| // plus the block length minus one and `out` must have sufficient space". This is the |
| // `max_decrypt_update_output_size` part of the output_vec's capacity. |
| let update_result = unsafe { |
| bssl_sys::EVP_DecryptUpdate( |
| self.ctx, |
| cslice_out_buf_mut.as_mut_ptr(), |
| &mut update_out_len, |
| cslice_in_buf.as_ptr(), |
| in_buff_len_int, |
| ) |
| }; |
| if update_result != 1 { |
| return Err(CipherError); |
| } |
| update_out_len |
| .try_into() |
| .expect("Output length should always fit in usize") |
| }; |
| |
| // DecryptFinal block |
| { |
| // Slice indexing here will not panic because we ensured `output_vec` is larger than |
| // what `DecryptUpdate` will write. |
| #[allow(clippy::indexing_slicing)] |
| let mut cslice_final_buf_mut = CSliceMut::from(&mut output_vec[update_out_len_usize..]); |
| let mut final_out_len = 0; |
| let final_result = unsafe { |
| bssl_sys::EVP_DecryptFinal_ex( |
| self.ctx, |
| cslice_final_buf_mut.as_mut_ptr(), |
| &mut final_out_len, |
| ) |
| }; |
| let final_put_len_usize = |
| <usize>::try_from(final_out_len).expect("Output length should always fit in usize"); |
| |
| if final_result == 1 { |
| output_vec.truncate(update_out_len_usize + final_put_len_usize) |
| } else { |
| return Err(CipherError); |
| } |
| } |
| Ok(output_vec) |
| } |
| } |
| |
| impl<C: EvpCipherType> Drop for Cipher<C> { |
| fn drop(&mut self) { |
| // Safety: |
| // - `self.ctx` was allocated by `EVP_CIPHER_CTX_new` and has not yet been freed. |
| unsafe { bssl_sys::EVP_CIPHER_CTX_free(self.ctx) } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use crate::cipher::{CipherInitPurpose, EvpAes128Cbc, EvpAes128Ctr}; |
| |
| use super::Cipher; |
| |
| #[test] |
| fn test_cipher_mode() { |
| assert_eq!( |
| Cipher::<EvpAes128Ctr>::new(&[0; 16], &[0; 16], CipherInitPurpose::Encrypt) |
| .cipher_mode(), |
| bssl_sys::EVP_CIPH_CTR_MODE as u32 |
| ); |
| |
| assert_eq!( |
| Cipher::<EvpAes128Cbc>::new(&[0; 16], &[0; 16], CipherInitPurpose::Encrypt) |
| .cipher_mode(), |
| bssl_sys::EVP_CIPH_CBC_MODE as u32 |
| ); |
| } |
| |
| #[should_panic] |
| #[test] |
| fn test_apply_keystream_on_cbc() { |
| let mut cipher = |
| Cipher::<EvpAes128Cbc>::new(&[0; 16], &[0; 16], CipherInitPurpose::Encrypt); |
| let mut buf = [0; 16]; |
| let _ = cipher.apply_keystream_in_place(&mut buf); // This should panic |
| } |
| } |