/* 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, vec::Vec}; use bssl_sys::EVP_CIPHER; use core::{ffi::c_int, 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, 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, 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 { ctx: *mut bssl_sys::EVP_CIPHER_CTX, _marker: PhantomData, } impl Cipher { 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, 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 = ::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, 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 = ::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 Drop for Cipher { 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::::new(&[0; 16], &[0; 16], CipherInitPurpose::Encrypt) .cipher_mode(), bssl_sys::EVP_CIPH_CTR_MODE as u32 ); assert_eq!( Cipher::::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::::new(&[0; 16], &[0; 16], CipherInitPurpose::Encrypt); let mut buf = [0; 16]; let _ = cipher.apply_keystream_in_place(&mut buf); // This should panic } }