diff --git a/src/rust/bssl-crypto/src/.rustfmt.toml b/src/rust/bssl-crypto/src/.rustfmt.toml new file mode 100644 index 000000000..c3c8c3753 --- /dev/null +++ b/src/rust/bssl-crypto/src/.rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Crate" diff --git a/src/rust/bssl-crypto/src/aes.rs b/src/rust/bssl-crypto/src/aes.rs index e5a16078c..090042071 100644 --- a/src/rust/bssl-crypto/src/aes.rs +++ b/src/rust/bssl-crypto/src/aes.rs @@ -122,8 +122,10 @@ fn new_decrypt_key(key: [u8; N]) -> AesDecryptKey { #[cfg(test)] mod tests { - use crate::aes::{Aes, AesDecryptKey, AesEncryptKey}; - use crate::test_helpers::decode_hex; + use crate::{ + aes::{Aes, AesDecryptKey, AesEncryptKey}, + test_helpers::decode_hex, + }; // test data from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1 #[test] diff --git a/src/rust/bssl-crypto/src/cipher/mod.rs b/src/rust/bssl-crypto/src/cipher/mod.rs index 16def56bd..b8c3d804e 100644 --- a/src/rust/bssl-crypto/src/cipher/mod.rs +++ b/src/rust/bssl-crypto/src/cipher/mod.rs @@ -16,11 +16,9 @@ extern crate alloc; use crate::{CSlice, CSliceMut}; -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use bssl_sys::EVP_CIPHER; -use core::ffi::c_int; -use core::marker::PhantomData; +use core::{ffi::c_int, marker::PhantomData}; /// AES-CTR stream cipher operations. pub mod aes_ctr; diff --git a/src/rust/bssl-crypto/src/digest.rs b/src/rust/bssl-crypto/src/digest.rs index 72402976a..a10b5ab6e 100644 --- a/src/rust/bssl-crypto/src/digest.rs +++ b/src/rust/bssl-crypto/src/digest.rs @@ -13,162 +13,135 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use core::marker::PhantomData; +//! Hash functions. +//! +//! ``` +//! use bssl_crypto::digest; +//! +//! // One-shot hashing. +//! let digest: [u8; 32] = digest::Sha256::hash(b"hello"); +//! +//! // Incremental hashing. +//! let mut ctx = digest::Sha256::new(); +//! ctx.update(b"hel"); +//! ctx.update(b"lo"); +//! let digest2: [u8; 32] = ctx.digest(); +//! +//! assert_eq!(digest, digest2); +//! ``` -use crate::{CSlice, ForeignTypeRef}; +use crate::{sealed, FfiSlice, ForeignTypeRef}; +use bssl_sys; -/// The SHA-256 digest algorithm. -#[derive(Clone)] -pub struct Sha256 {} - -/// The SHA-512 digest algorithm. -#[derive(Clone)] -pub struct Sha512 {} - -/// A reference to an [`Md`], which abstracts the details of a specific hash function allowing code -/// to deal with the concept of a "hash function" without needing to know exactly which hash function -/// it is. #[non_exhaustive] +#[doc(hidden)] pub struct MdRef; unsafe impl ForeignTypeRef for MdRef { type CType = bssl_sys::EVP_MD; } -/// Used internally to get a BoringSSL internal MD -pub trait Md { - /// The output size of the hash operation. - const OUTPUT_SIZE: usize; +/// Used internally to parameterize other primitives. +pub trait Algorithm { + /// The size of the resulting digest. + const OUTPUT_LEN: usize; /// Gets a reference to a message digest algorithm to be used by the HKDF implementation. - fn get_md() -> &'static MdRef; + #[doc(hidden)] + fn get_md(_: sealed::Sealed) -> &'static MdRef; } -impl Md for Sha256 { - const OUTPUT_SIZE: usize = bssl_sys::SHA256_DIGEST_LENGTH as usize; - - fn get_md() -> &'static MdRef { - // Safety: - // - this always returns a valid pointer to an EVP_MD - unsafe { MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _) } - } +/// The insecure SHA-1 hash algorithm. +/// +/// Some existing protocols depend on SHA-1 and so it is provided here, but it +/// does not provide collision resistance and should not be used if at all +/// avoidable. Use SHA-256 instead. +#[derive(Clone)] +pub struct InsecureSha1 { + ctx: bssl_sys::SHA_CTX, } -impl Sha256 { - /// Creates a new [Digest] to compute a SHA-256 hash. - pub fn new_digest() -> Digest { - // Note: This cannot be in the trait because using associated constants exprs there - // requires nightly. - Digest::::new() - } +unsafe_iuf_algo!( + InsecureSha1, + 20, + EVP_sha1, + SHA1, + SHA1_Init, + SHA1_Update, + SHA1_Final +); + +/// The SHA-256 hash algorithm. +#[derive(Clone)] +pub struct Sha256 { + ctx: bssl_sys::SHA256_CTX, } -impl Md for Sha512 { - const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize; +unsafe_iuf_algo!( + Sha256, + 32, + EVP_sha256, + SHA256, + SHA256_Init, + SHA256_Update, + SHA256_Final +); - fn get_md() -> &'static MdRef { - // Safety: - // - this always returns a valid pointer to an EVP_MD - unsafe { MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _) } - } +/// The SHA-384 hash algorithm. +#[derive(Clone)] +pub struct Sha384 { + ctx: bssl_sys::SHA512_CTX, } -impl Sha512 { - /// Create a new [Digest] to compute a SHA-512 hash. - pub fn new_digest() -> Digest { - // Note: This cannot be in the trait because using associated constants exprs there - // requires nightly. - Digest::::new() - } +unsafe_iuf_algo!( + Sha384, + 48, + EVP_sha384, + SHA384, + SHA384_Init, + SHA384_Update, + SHA384_Final +); + +/// The SHA-512 hash algorithm. +#[derive(Clone)] +pub struct Sha512 { + ctx: bssl_sys::SHA512_CTX, } -/// A pending digest operation. -pub struct Digest(bssl_sys::EVP_MD_CTX, PhantomData); +unsafe_iuf_algo!( + Sha512, + 64, + EVP_sha512, + SHA512, + SHA512_Init, + SHA512_Update, + SHA512_Final +); -impl Digest { - /// Creates a new Digest from the given `Md` type parameter. - /// - /// Panics: - /// - If `Md::OUTPUT_SIZE` is not the same as `OUTPUT_SIZE`. - fn new() -> Self { - // Note: runtime assertion needed here since using {M::OUTPUT_SIZE} in return type requires - // unstable Rust feature. - assert_eq!(M::OUTPUT_SIZE, OUTPUT_SIZE); - let mut md_ctx_uninit = core::mem::MaybeUninit::::uninit(); - // Safety: - // - `EVP_DigestInit` initializes `md_ctx_uninit` - // - `MdRef` ensures the validity of `md.as_ptr` - let result = - unsafe { bssl_sys::EVP_DigestInit(md_ctx_uninit.as_mut_ptr(), M::get_md().as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestInit failed"); - // Safety: - // - md_ctx_uninit initialized with EVP_DigestInit, and the function returned 1 (success) - let md_ctx = unsafe { md_ctx_uninit.assume_init() }; - Self(md_ctx, PhantomData) - } - - /// Hashes the provided input into the current digest operation. - pub fn update(&mut self, data: &[u8]) { - let data_ffi = CSlice(data); - // Safety: - // - `data` is a CSlice from safe Rust. - let result = unsafe { - bssl_sys::EVP_DigestUpdate(&mut self.0, data_ffi.as_ptr() as *const _, data_ffi.len()) - }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestUpdate failed"); - } - - /// Computes the final digest value, consuming the object. - #[allow(clippy::expect_used)] - pub fn finalize(mut self) -> [u8; OUTPUT_SIZE] { - let mut digest_uninit = - core::mem::MaybeUninit::<[u8; bssl_sys::EVP_MAX_MD_SIZE as usize]>::uninit(); - let mut len_uninit = core::mem::MaybeUninit::::uninit(); - // Safety: - // - `digest_uninit` is allocated to `EVP_MAX_MD_SIZE` bytes long, as required by - // EVP_DigestFinal_ex - // - `self.0` is owned by `self`, and is going to be cleaned up on drop. - let result = unsafe { - bssl_sys::EVP_DigestFinal_ex( - &mut self.0, - digest_uninit.as_mut_ptr() as *mut _, - len_uninit.as_mut_ptr(), - ) - }; - assert_eq!(result, 1, "bssl_sys::EVP_DigestFinal_ex failed"); - // Safety: - // - `len_uninit` is initialized by `EVP_DigestFinal_ex`, and we checked the result above - let len = unsafe { len_uninit.assume_init() }; - assert_eq!( - OUTPUT_SIZE, len as usize, - "bssl_sys::EVP_DigestFinal_ex failed" - ); - // Safety: Result of DigestFinal_ex was checked above - let digest = unsafe { digest_uninit.assume_init() }; - digest - .get(..OUTPUT_SIZE) - .and_then(|digest| digest.try_into().ok()) - .expect("The length of `digest` was checked above") - } +/// The SHA-512/256 hash algorithm. +#[derive(Clone)] +pub struct Sha512_256 { + ctx: bssl_sys::SHA512_CTX, } -impl Drop for Digest { - fn drop(&mut self) { - // Safety: `self.0` is owned by `self`, and is invalidated after `drop`. - unsafe { - bssl_sys::EVP_MD_CTX_cleanup(&mut self.0); - } - } -} +unsafe_iuf_algo!( + Sha512_256, + 32, + EVP_sha512_256, + SHA512_256, + SHA512_256_Init, + SHA512_256_Update, + SHA512_256_Final +); #[cfg(test)] mod test { + use super::*; use crate::test_helpers::decode_hex; - use super::*; - #[test] - fn test_sha256_c_type() { + fn sha256_c_type() { unsafe { assert_eq!( MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _).as_ptr(), @@ -178,7 +151,7 @@ mod test { } #[test] - fn test_sha512_c_type() { + fn sha512_c_type() { unsafe { assert_eq!( MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _).as_ptr(), @@ -188,31 +161,60 @@ mod test { } #[test] - fn test_digest_sha256() { - let mut digest = Sha256::new_digest(); - let msg: [u8; 4] = decode_hex("74ba2521"); - digest.update(&msg); - let expected_digest: [u8; 32] = - decode_hex("b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e"); - assert_eq!(expected_digest, digest.finalize()); + fn sha1() { + assert_eq!( + decode_hex("a9993e364706816aba3e25717850c26c9cd0d89d"), + InsecureSha1::hash(b"abc") + ); } #[test] - fn test_digest_sha512() { - let mut digest = Sha512::new_digest(); + fn sha256() { + let msg: [u8; 4] = decode_hex("74ba2521"); + let expected_digest: [u8; 32] = + decode_hex("b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e"); + + assert_eq!(Sha256::hash(&msg), expected_digest); + + let mut ctx = Sha256::new(); + ctx.update(&msg); + assert_eq!(expected_digest, ctx.digest()); + + let mut ctx = Sha256::new(); + ctx.update(&msg[0..1]); + let mut ctx2 = ctx.clone(); + ctx2.update(&msg[1..]); + assert_eq!(expected_digest, ctx2.digest()); + } + + #[test] + fn sha384() { + assert_eq!( + decode_hex("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"), + Sha384::hash(b"abc") + ); + } + + #[test] + fn sha512() { let msg: [u8; 4] = decode_hex("23be86d5"); - digest.update(&msg); let expected_digest: [u8; 64] = decode_hex(concat!( "76d42c8eadea35a69990c63a762f330614a4699977f058adb988f406fb0be8f2", "ea3dce3a2bbd1d827b70b9b299ae6f9e5058ee97b50bd4922d6d37ddc761f8eb" )); - assert_eq!(expected_digest, digest.finalize()); + + assert_eq!(Sha512::hash(&msg), expected_digest); + + let mut ctx = Sha512::new(); + ctx.update(&msg); + assert_eq!(expected_digest, ctx.digest()); } #[test] - #[should_panic] - fn test_digest_wrong_size() { - // This should not happen since we don't externally expose Digest::new - Digest::::new(); + fn sha512_256() { + assert_eq!( + decode_hex("53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"), + Sha512_256::hash(b"abc") + ); } } diff --git a/src/rust/bssl-crypto/src/ec.rs b/src/rust/bssl-crypto/src/ec.rs index 55fe4e97b..8bd8bda4a 100644 --- a/src/rust/bssl-crypto/src/ec.rs +++ b/src/rust/bssl-crypto/src/ec.rs @@ -17,11 +17,8 @@ //! intended for internal use within this crate only, to create higher-level abstractions suitable //! to be exposed externally. -use alloc::borrow::ToOwned; -use alloc::vec; -use alloc::vec::Vec; -use core::panic; -use core::{borrow::Borrow, fmt::Debug, ops::Deref}; +use alloc::{borrow::ToOwned, vec, vec::Vec}; +use core::{borrow::Borrow, fmt::Debug, ops::Deref, panic}; use crate::{bn::BigNum, CSlice, CSliceMut, ForeignType, ForeignTypeRef}; diff --git a/src/rust/bssl-crypto/src/hkdf.rs b/src/rust/bssl-crypto/src/hkdf.rs index e4e9c0133..973ed88af 100644 --- a/src/rust/bssl-crypto/src/hkdf.rs +++ b/src/rust/bssl-crypto/src/hkdf.rs @@ -12,85 +12,225 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -use crate::digest::Md; -use crate::digest::{Sha256, Sha512}; -use crate::{CSlice, CSliceMut, ForeignTypeRef}; -use alloc::vec::Vec; + +//! Implements the HMAC-based Key Derivation Function from +//! . +//! +//! One-shot operation: +//! +//! ``` +//! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; +//! +//! let key: [u8; 32] = HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), +//! b"info"); +//! ``` +//! +//! If deriving several keys that vary only in the `info` parameter, then part +//! of the computation can be shared by calculating the "pseudo-random key". +//! This is purely a performance optimisation. +//! +//! ``` +//! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; +//! +//! let prk = HkdfSha256::extract(b"secret", hkdf::Salt::NonEmpty(b"salt")); +//! let key1 : [u8; 32] = prk.expand(b"info1"); +//! let key2 : [u8; 32] = prk.expand(b"info2"); +//! +//! assert_eq!(key1, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), +//! b"info1")); +//! assert_eq!(key2, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), +//! b"info2")); +//! ``` +//! +//! The above examples assume that the size of the outputs is known at compile +//! time. (And only output lengths less than 256 bytes are supported.) +//! +//! ```compile_fail +//! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; +//! +//! let key: [u8; 256] = HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info"); +//! ``` +//! +//! To use HKDF with longer, or run-time, lengths, use `derive_into` and +//! `extract_into`: +//! +//! ``` +//! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; +//! +//! let mut out = [0u8; 50]; +//! HkdfSha256::derive_into(b"secret", hkdf::Salt::None, b"info", &mut out).expect( +//! "HKDF can't produce that much"); +//! +//! assert_eq!(out, HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info")); +//! ``` + +use crate::{digest, sealed, with_output_array, FfiMutSlice, FfiSlice, ForeignTypeRef}; use core::marker::PhantomData; /// Implementation of HKDF-SHA-256 -pub type HkdfSha256 = Hkdf; +pub type HkdfSha256 = Hkdf; /// Implementation of HKDF-SHA-512 -pub type HkdfSha512 = Hkdf; +pub type HkdfSha512 = Hkdf; -/// Error type returned from the HKDF-Expand operations when the output key material has -/// an invalid length +/// Error type returned when too much output is requested from an HKDF operation. #[derive(Debug)] -pub struct InvalidLength; +pub struct TooLong; -/// Implementation of HKDF operations which are generic over a provided hashing functions. Type -/// aliases are provided above for convenience of commonly used hashes -pub struct Hkdf { - salt: Option>, - ikm: Vec, - _marker: PhantomData, +/// HKDF's optional salt values. See +pub enum Salt<'a> { + /// No salt. + None, + /// An explicit salt. Note that an empty value here is interpreted the same + /// as if passing `None`. + NonEmpty(&'a [u8]), } -impl Hkdf { - /// The max length of the output key material used for expanding - pub const MAX_OUTPUT_LENGTH: usize = M::OUTPUT_SIZE * 255; - - /// Creates a new instance of HKDF from a salt and key material - pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self { - Self { - salt: salt.map(Vec::from), - ikm: Vec::from(ikm), - _marker: PhantomData, +impl Salt<'_> { + fn as_ffi_ptr(&self) -> *const u8 { + match self { + Salt::None => core::ptr::null(), + Salt::NonEmpty(salt) => salt.as_ffi_ptr(), } } - /// Computes HKDF-Expand operation from RFC 5869. The info argument for the expand is set to - /// the concatenation of all the elements of info_components. Returns InvalidLength if the - /// output is too large. - pub fn expand_multi_info( - &self, - info_components: &[&[u8]], - okm: &mut [u8], - ) -> Result<(), InvalidLength> { - self.expand(&info_components.concat(), okm) + fn len(&self) -> usize { + match self { + Salt::None => 0, + Salt::NonEmpty(salt) => salt.len(), + } + } +} + +/// HKDF for any of the implemented hash functions. The aliases [`HkdfSha256`] +/// and [`HkdfSha512`] are provided for the most common cases. +pub struct Hkdf(PhantomData); + +impl Hkdf { + /// The maximum number of bytes of key material that can be produced. + pub const MAX_OUTPUT_LEN: usize = MD::OUTPUT_LEN * 255; + + /// Derive key material from the given secret, salt, and info. Attempting + /// to derive more than 255 bytes is a compile-time error, see `derive_into` + /// for longer outputs. + /// + /// The semantics of the arguments are complex. See + /// . + pub fn derive(secret: &[u8], salt: Salt, info: &[u8]) -> [u8; N] { + Self::extract(secret, salt).expand(info) } - /// Computes HKDF-Expand operation from RFC 5869. Returns InvalidLength if the output is too large. - pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> { - // extract the salt bytes from the option, or empty slice if option is None - let salt = self.salt.as_deref().unwrap_or_default(); + /// Derive key material from the given secret, salt, and info. Attempting + /// to derive more than `MAX_OUTPUT_LEN` bytes is a run-time error. + /// + /// The semantics of the arguments are complex. See + /// . + pub fn derive_into( + secret: &[u8], + salt: Salt, + info: &[u8], + out: &mut [u8], + ) -> Result<(), TooLong> { + Self::extract(secret, salt).expand_into(info, out) + } - //validate the output size - (okm.len() <= Self::MAX_OUTPUT_LENGTH && !okm.is_empty()) - .then(|| { - let mut okm_cslice = CSliceMut::from(okm); + /// Extract a pseudo-random key from the given secret and salt. This can + /// be used to avoid redoing computation when computing several keys that + /// vary only in the `info` parameter. + pub fn extract(secret: &[u8], salt: Salt) -> Prk { + let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize]; + let mut prk_len = 0usize; + let evp_md = MD::get_md(sealed::Sealed).as_ptr(); + unsafe { + // Safety: `EVP_MAX_MD_SIZE` is the maximum output size of + // `HKDF_extract` so it'll never overrun the buffer. + bssl_sys::HKDF_extract( + prk.as_mut_ffi_ptr(), + &mut prk_len, + evp_md, + secret.as_ffi_ptr(), + secret.len(), + salt.as_ffi_ptr(), + salt.len(), + ); + } + // This is documented to be always be true. + assert!(prk_len <= prk.len()); + Prk { + prk, + len: prk_len, + evp_md, + } + } +} - // Safety: - // - We validate the output length above, so invalid length errors will never be hit - // which leaves allocation failures as the only possible error case, in which case - // we panic immediately - let result = unsafe { - bssl_sys::HKDF( - okm_cslice.as_mut_ptr(), - okm_cslice.len(), - M::get_md().as_ptr(), - CSlice::from(self.ikm.as_slice()).as_ptr(), - self.ikm.as_slice().len(), - CSlice::from(salt).as_ptr(), - salt.len(), - CSlice::from(info).as_ptr(), - info.len(), - ) - }; - assert!(result > 0, "Allocation failure in bssl_sys::HKDF"); +/// A pseudo-random key, an intermediate value in the HKDF computation. +pub struct Prk { + prk: [u8; bssl_sys::EVP_MAX_MD_SIZE as usize], + len: usize, + evp_md: *const bssl_sys::EVP_MD, +} + +#[allow(clippy::let_unit_value)] +impl Prk { + /// Derive key material for the given info parameter. Attempting + /// to derive more than 255 bytes is a compile-time error, see `expand_into` + /// for longer outputs. + pub fn expand(&self, info: &[u8]) -> [u8; N] { + // This is the odd way to write a static assertion that uses a const + // parameter in Rust. Even then, Rust cannot reference `MAX_OUTPUT_LEN`. + // But if we safely assume that all hash functions output at least a + // byte then 255 is a safe lower bound on `MAX_OUTPUT_LEN`. + // A doctest at the top of the module checks that this assert is effective. + struct StaticAssert; + impl StaticAssert { + const BOUNDS_CHECK: () = assert!(N < BOUND, "Large outputs not supported"); + } + let _ = StaticAssert::::BOUNDS_CHECK; + + unsafe { + with_output_array(|out, out_len| { + // Safety: `HKDF_expand` writes exactly `out_len` bytes or else + // returns zero. `evp_md` is valid by construction. + let result = bssl_sys::HKDF_expand( + out, + out_len, + self.evp_md, + self.prk.as_ffi_ptr(), + self.len, + info.as_ffi_ptr(), + info.len(), + ); + // The output length is known to be within bounds so the only other + // possibily is an allocation failure, which we don't attempt to + // handle. + assert_eq!(result, 1); }) - .ok_or(InvalidLength) + } + } + + /// Derive key material from the given info parameter. Attempting + /// to derive more than the HKDF's `MAX_OUTPUT_LEN` bytes is a run-time + /// error. + pub fn expand_into(&self, info: &[u8], out: &mut [u8]) -> Result<(), TooLong> { + // Safety: writes at most `out.len()` bytes into `out`. + // `evp_md` is valid by construction. + let result = unsafe { + bssl_sys::HKDF_expand( + out.as_mut_ffi_ptr(), + out.len(), + self.evp_md, + self.prk.as_ffi_ptr(), + self.len, + info.as_ffi_ptr(), + info.len(), + ) + }; + if result == 1 { + Ok(()) + } else { + Err(TooLong) + } } } @@ -102,28 +242,18 @@ impl Hkdf { clippy::unwrap_used )] mod tests { - use crate::hkdf::{HkdfSha256, HkdfSha512}; - use crate::test_helpers::{decode_hex, decode_hex_into_vec}; - use core::iter; - - struct Test { - ikm: Vec, - salt: Vec, - info: Vec, - okm: Vec, - } + use crate::{ + hkdf::{HkdfSha256, HkdfSha512, Salt}, + test_helpers::{decode_hex, decode_hex_into_vec}, + }; #[test] - fn hkdf_sha_256_test() { + fn sha256() { let ikm = decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); - let salt = decode_hex_into_vec("000102030405060708090a0b0c"); + let salt_vec = decode_hex_into_vec("000102030405060708090a0b0c"); + let salt = Salt::NonEmpty(&salt_vec); let info = decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9"); - - let hk = HkdfSha256::new(Some(salt.as_slice()), ikm.as_slice()); - let mut okm = [0u8; 42]; - hk.expand(&info, &mut okm) - .expect("42 is a valid length for Sha256 to output"); - + let okm: [u8; 42] = HkdfSha256::derive(ikm.as_slice(), salt, info.as_slice()); let expected = decode_hex( "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", ); @@ -131,15 +261,12 @@ mod tests { } #[test] - fn hkdf_sha512_test() { + fn sha512() { let ikm = decode_hex_into_vec("5d3db20e8238a90b62a600fa57fdb318"); - let salt = decode_hex_into_vec("1d6f3b38a1e607b5e6bcd4af1800a9d3"); + let salt_vec = decode_hex_into_vec("1d6f3b38a1e607b5e6bcd4af1800a9d3"); + let salt = Salt::NonEmpty(&salt_vec); let info = decode_hex_into_vec("2bc5f39032b6fc87da69ba8711ce735b169646fd"); - - let hk = HkdfSha512::new(Some(salt.as_slice()), ikm.as_slice()); - let mut okm = [0u8; 42]; - hk.expand(&info, &mut okm).expect("Should succeed"); - + let okm: [u8; 42] = HkdfSha512::derive(ikm.as_slice(), salt, info.as_slice()); let expected = decode_hex( "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae", ); @@ -148,7 +275,13 @@ mod tests { // Test Vectors from https://tools.ietf.org/html/rfc5869. #[test] - fn test_rfc5869_sha256() { + fn rfc5869_sha256() { + struct Test { + ikm: Vec, + salt: Vec, + info: Vec, + okm: Vec, + } let tests = [ Test { // Test Case 1 @@ -198,6 +331,7 @@ mod tests { "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"), }, ]; + for Test { ikm, salt, @@ -206,90 +340,25 @@ mod tests { } in tests.iter() { let salt = if salt.is_empty() { - None + Salt::None } else { - Some(salt.as_slice()) + Salt::NonEmpty(&salt) }; - let hkdf = HkdfSha256::new(salt, ikm.as_slice()); let mut okm2 = vec![0u8; okm.len()]; - assert!(hkdf.expand(info.as_slice(), &mut okm2).is_ok()); + assert!( + HkdfSha256::derive_into(ikm.as_slice(), salt, info.as_slice(), &mut okm2).is_ok() + ); assert_eq!(okm2.as_slice(), okm.as_slice()); } } #[test] - fn test_lengths() { - let hkdf = HkdfSha256::new(None, &[]); - let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH]; - assert!(hkdf.expand(&[], &mut longest).is_ok()); - // start at 1 since 0 is an invalid length - let lengths = 1..HkdfSha256::MAX_OUTPUT_LENGTH + 1; + fn max_output() { + let hkdf = HkdfSha256::extract(b"", Salt::None); + let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN]; + assert!(hkdf.expand_into(b"", &mut longest).is_ok()); - for length in lengths { - let mut okm = vec![0u8; length]; - - assert!(hkdf.expand(&[], &mut okm).is_ok()); - assert_eq!(okm.len(), length); - assert_eq!(okm[..], longest[..length]); - } - } - - #[test] - fn test_max_length() { - let hkdf = HkdfSha256::new(Some(&[]), &[]); - let mut okm = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH]; - assert!(hkdf.expand(&[], &mut okm).is_ok()); - } - - #[test] - fn test_max_length_exceeded() { - let hkdf = HkdfSha256::new(Some(&[]), &[]); - let mut okm = vec![0u8; HkdfSha256::MAX_OUTPUT_LENGTH + 1]; - assert!(hkdf.expand(&[], &mut okm).is_err()); - } - - #[test] - fn test_unsupported_length() { - let hkdf = HkdfSha256::new(Some(&[]), &[]); - let mut okm = vec![0u8; 90000]; - assert!(hkdf.expand(&[], &mut okm).is_err()); - } - - #[test] - fn test_expand_multi_info() { - let info_components = &[ - &b"09090909090909090909090909090909090909090909"[..], - &b"8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a"[..], - &b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0"[..], - &b"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4"[..], - &b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d"[..], - ]; - - let hkdf = HkdfSha256::new(None, b"some ikm here"); - - // Compute HKDF-Expand on the concatenation of all the info components - let mut oneshot_res = [0u8; 16]; - hkdf.expand(&info_components.concat(), &mut oneshot_res) - .unwrap(); - - // Now iteratively join the components of info_components until it's all 1 component. The value - // of HKDF-Expand should be the same throughout - let mut num_concatted = 0; - let mut info_head = Vec::new(); - - while num_concatted < info_components.len() { - info_head.extend(info_components[num_concatted]); - - // Build the new input to be the info head followed by the remaining components - let input: Vec<&[u8]> = iter::once(info_head.as_slice()) - .chain(info_components.iter().cloned().skip(num_concatted + 1)) - .collect(); - - // Compute and compare to the one-shot answer - let mut multipart_res = [0u8; 16]; - hkdf.expand_multi_info(&input, &mut multipart_res).unwrap(); - assert_eq!(multipart_res, oneshot_res); - num_concatted += 1; - } + let mut too_long = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN + 1]; + assert!(hkdf.expand_into(b"", &mut too_long).is_err()); } } diff --git a/src/rust/bssl-crypto/src/hmac.rs b/src/rust/bssl-crypto/src/hmac.rs index 167e92e59..56b06e6f7 100644 --- a/src/rust/bssl-crypto/src/hmac.rs +++ b/src/rust/bssl-crypto/src/hmac.rs @@ -13,8 +13,9 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ use crate::{ - digest::{Md, Sha256, Sha512}, - CSlice, ForeignTypeRef as _, + digest, + digest::{Sha256, Sha512}, + sealed, CSlice, ForeignTypeRef as _, }; use core::{ ffi::{c_uint, c_void}, @@ -154,11 +155,11 @@ pub struct MacError; /// Private generically implemented function for computing hmac as a oneshot operation. /// This should only be exposed publicly by types with the correct output size `N` which corresponds -/// to the output size of the provided generic hash function. Ideally `N` would just come from `M`, +/// to the output size of the provided generic hash function. Ideally `N` would just come from `MD`, /// but this is not possible until the Rust language can support the `min_const_generics` feature. /// Until then we will have to pass both separately: https://github.com/rust-lang/rust/issues/60551 #[inline] -fn hmac(key: &[u8], data: &[u8]) -> [u8; N] { +fn hmac(key: &[u8], data: &[u8]) -> [u8; N] { let mut out = [0_u8; N]; let mut size: c_uint = 0; @@ -167,7 +168,7 @@ fn hmac(key: &[u8], data: &[u8]) -> [u8; N] { // - If NULL is returned on error we panic immediately let result = unsafe { bssl_sys::HMAC( - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), CSlice::from(key).as_ptr(), key.len(), CSlice::from(data).as_ptr(), @@ -184,15 +185,15 @@ fn hmac(key: &[u8], data: &[u8]) -> [u8; N] { /// Private generically implemented hmac instance given a generic hash function and a length `N`, /// where `N` is the output size of the hash function. This should only be exposed publicly by /// wrapper types with the correct output size `N` which corresponds to the output size of the -/// provided generic hash function. Ideally `N` would just come from `M`, but this is not possible +/// provided generic hash function. Ideally `N` would just come from `MD`, but this is not possible /// until the Rust language can support the `min_const_generics` feature. Until then we will have to /// pass both separately: https://github.com/rust-lang/rust/issues/60551 -struct Hmac { +struct Hmac { ctx: *mut bssl_sys::HMAC_CTX, - _marker: PhantomData, + _marker: PhantomData, } -impl Hmac { +impl Hmac { /// Creates a new HMAC operation from a fixed-length key. fn new(key: [u8; N]) -> Self { Self::new_from_slice(&key) @@ -219,7 +220,7 @@ impl Hmac { ctx, CSlice::from(key).as_ptr() as *const c_void, key.len(), - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), ptr::null_mut(), ) }; @@ -311,7 +312,7 @@ impl Hmac { self.ctx, ptr::null_mut(), 0, - M::get_md().as_ptr(), + MD::get_md(sealed::Sealed).as_ptr(), ptr::null_mut(), ) }; @@ -319,7 +320,7 @@ impl Hmac { } } -impl Drop for Hmac { +impl Drop for Hmac { fn drop(&mut self) { unsafe { bssl_sys::HMAC_CTX_free(self.ctx) } } diff --git a/src/rust/bssl-crypto/src/lib.rs b/src/rust/bssl-crypto/src/lib.rs index 022f5a34a..c16a68bc5 100644 --- a/src/rust/bssl-crypto/src/lib.rs +++ b/src/rust/bssl-crypto/src/lib.rs @@ -30,6 +30,9 @@ extern crate core; use core::ffi::c_void; +#[macro_use] +mod macros; + /// Authenticated Encryption with Additional Data algorithms. pub mod aead; @@ -39,13 +42,11 @@ pub mod aes; /// Ciphers. pub mod cipher; -/// Hash functions. pub mod digest; /// Ed25519, a signature scheme. pub mod ed25519; -/// HKDF, a hash-based key derivation function. pub mod hkdf; /// HMAC, a hash-based message authentication code. @@ -247,6 +248,20 @@ unsafe trait ForeignType { fn as_ptr(&self) -> *mut Self::CType; } +/// Returns a BoringSSL structure that is initialized by some function. +/// Requires that the given function completely initializes the value. +/// +/// (Tagged `unsafe` because a no-op argument would otherwise expose +/// uninitialized memory.) +unsafe fn initialized_struct(init: F) -> T +where + F: FnOnce(*mut T), +{ + let mut out_uninit = core::mem::MaybeUninit::::uninit(); + init(out_uninit.as_mut_ptr()); + unsafe { out_uninit.assume_init() } +} + /// Wrap a closure that initializes an output buffer and return that buffer as /// an array. Requires that the closure fully initialize the given buffer. /// @@ -290,3 +305,8 @@ where None } } + +/// Used to prevent external implementations of internal traits. +mod sealed { + pub struct Sealed; +} diff --git a/src/rust/bssl-crypto/src/macros.rs b/src/rust/bssl-crypto/src/macros.rs new file mode 100644 index 000000000..79049d4a3 --- /dev/null +++ b/src/rust/bssl-crypto/src/macros.rs @@ -0,0 +1,105 @@ +/* Copyright (c) 2024, 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. + */ + +// Generates a hash function from init/update/final-style FFI functions. Rust +// doesn't accept function pointers as a generic arguments so this is the only +// mechanism to avoid duplicating the code. +// +// The name is prefixed with "unsafe_" because it contains unsafe blocks. +// +// Safety: see the "Safety" sections within about the requirements for the +// functions named in the macro parameters. +macro_rules! unsafe_iuf_algo { + ($name:ident, $output_len:expr, $evp_md:ident, $one_shot:ident, $init:ident, $update:ident, $final_func:ident) => { + impl Algorithm for $name { + const OUTPUT_LEN: usize = $output_len as usize; + + fn get_md(_: sealed::Sealed) -> &'static MdRef { + // Safety: + // - this always returns a valid pointer to an EVP_MD. + unsafe { MdRef::from_ptr(bssl_sys::$evp_md() as *mut _) } + } + } + + impl $name { + /// Digest `input` in a single operation. + pub fn hash(input: &[u8]) -> [u8; $output_len] { + // Safety: it is assumed that `$one_shot` indeed writes + // `$output_len` bytes. + unsafe { + crate::with_output_array(|out, _| { + bssl_sys::$one_shot(input.as_ffi_ptr(), input.len(), out); + }) + } + } + + /// Create a new context for incremental hashing. + pub fn new() -> Self { + unsafe { + Self { + ctx: crate::initialized_struct(|ctx| { + // Safety: type checking will ensure that `ctx` is the + // correct type for `$init` to write into. + bssl_sys::$init(ctx); + }), + } + } + } + + /// Hash the contents of `input`. + pub fn update(&mut self, input: &[u8]) { + // Safety: arguments point to a valid buffer. + unsafe { + bssl_sys::$update(&mut self.ctx, input.as_ffi_void_ptr(), input.len()); + } + } + + /// Finish the hashing and return the digest. + pub fn digest(mut self) -> [u8; $output_len] { + // Safety: it is assumed that `$final_func` indeed writes + // `$output_len` bytes. + unsafe { + crate::with_output_array(|out, _| { + bssl_sys::$final_func(out, &mut self.ctx); + }) + } + } + } + + impl From<$name> for [u8; $output_len] { + fn from(ctx: $name) -> [u8; $output_len] { + ctx.digest() + } + } + + impl From<$name> for alloc::vec::Vec { + fn from(ctx: $name) -> alloc::vec::Vec { + ctx.digest().into() + } + } + + #[cfg(feature = "std")] + impl std::io::Write for $name { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.update(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + }; +} diff --git a/src/rust/bssl-crypto/src/pkey.rs b/src/rust/bssl-crypto/src/pkey.rs index 3d4a62b56..aa60a9f8f 100644 --- a/src/rust/bssl-crypto/src/pkey.rs +++ b/src/rust/bssl-crypto/src/pkey.rs @@ -18,8 +18,7 @@ //! externally. use crate::{ec::EcKey, CSliceMut, ForeignType}; -use alloc::borrow::ToOwned; -use alloc::string::String; +use alloc::{borrow::ToOwned, string::String}; pub(crate) struct Pkey { ptr: *mut bssl_sys::EVP_PKEY, diff --git a/src/rust/bssl-crypto/src/x25519.rs b/src/rust/bssl-crypto/src/x25519.rs index 8f6440d5d..b91af5460 100644 --- a/src/rust/bssl-crypto/src/x25519.rs +++ b/src/rust/bssl-crypto/src/x25519.rs @@ -44,9 +44,7 @@ //! // real protocols from a Diffie-Hellman primitive. //! ``` -use crate::with_output_array; -use crate::with_output_array_fallible; -use crate::FfiSlice; +use crate::{with_output_array, with_output_array_fallible, FfiSlice}; /// Number of bytes in a private key in X25519 pub const PRIVATE_KEY_LEN: usize = bssl_sys::X25519_PRIVATE_KEY_LEN as usize;