add bindings for hkdf and update panic handler Change-Id: Ic0149a69cc27727c2302bc2a90d03839fd5637b5 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/57545 Commit-Queue: Bob Beck <bbe@google.com> Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/rust/bssl-crypto/src/aes.rs b/rust/bssl-crypto/src/aes.rs index 0a354db..c2eb4412 100644 --- a/rust/bssl-crypto/src/aes.rs +++ b/rust/bssl-crypto/src/aes.rs
@@ -79,18 +79,14 @@ // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below. // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public // key structs. - // The expect will never be hit since input key is always a valid AES key size. - #[allow(clippy::expect_used)] - unsafe { + let result = unsafe { bssl_sys::AES_set_encrypt_key( key.as_ptr(), key.len() as core::ffi::c_uint * 8, enc_key_uninit.as_mut_ptr(), ) - } - .eq(&0) - .then_some(()) - .expect("bssl_sys::AES_set_encrypt_key unexpectedly failed"); + }; + assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_encrypt_key"); // Safety: // - since we have checked above that initialization succeeded, this will never be UB @@ -108,18 +104,14 @@ // - key is guaranteed to point to bits/8 bytes determined by the len() * 8 used below. // - bits is always a valid AES key size, as defined by the new_aes_* fns defined on the public // key structs. - // The expect will never be hit since input key is always a valid AES key size. - #[allow(clippy::expect_used)] - unsafe { + let result = unsafe { bssl_sys::AES_set_decrypt_key( key.as_ptr(), key.len() as core::ffi::c_uint * 8, dec_key_uninit.as_mut_ptr(), ) - } - .eq(&0) - .then_some(()) - .expect("bssl_sys::AES_set_decrypt_key unexpectedly failed"); + }; + assert_eq!(result, 0, "Error occurred in bssl_sys::AES_set_decrypt_key"); // Safety: // - Since we have checked above that initialization succeeded, this will never be UB.
diff --git a/rust/bssl-crypto/src/digest.rs b/rust/bssl-crypto/src/digest.rs index cecdfdf..4f19419 100644 --- a/rust/bssl-crypto/src/digest.rs +++ b/rust/bssl-crypto/src/digest.rs
@@ -26,19 +26,24 @@ /// 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. -pub(crate) struct MdRef; +pub struct MdRef; unsafe impl ForeignTypeRef for MdRef { type CType = bssl_sys::EVP_MD; } /// Used internally to get a BoringSSL internal MD -pub(crate) trait Md { - /// gets a reference to a message digest algorithm to be used by the hkdf implementation +pub trait Md { + /// The output size of the hash operation. + const OUTPUT_SIZE: usize; + + /// Gets a reference to a message digest algorithm to be used by the hkdf implementation. fn get_md() -> &'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 @@ -47,6 +52,8 @@ } impl Md for Sha512 { + const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize; + fn get_md() -> &'static MdRef { // Safety: // - this always returns a valid pointer to an EVP_MD
diff --git a/rust/bssl-crypto/src/hkdf.rs b/rust/bssl-crypto/src/hkdf.rs new file mode 100644 index 0000000..db6c5b6 --- /dev/null +++ b/rust/bssl-crypto/src/hkdf.rs
@@ -0,0 +1,308 @@ +/* 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. + */ +use crate::digest::Md; +use crate::digest::{Sha256, Sha512}; +use crate::{CSlice, CSliceMut, ForeignTypeRef}; +use core::marker::PhantomData; + +/// Implementation of HKDF-SHA-256 +pub type HkdfSha256 = Hkdf<Sha256>; + +/// Implementation of HKDF-SHA-512 +pub type HkdfSha512 = Hkdf<Sha512>; + +/// Error type returned from the hkdf expand operations when the output key material has +/// an invalid length +#[derive(Debug)] +pub struct InvalidLength; + +/// 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<M: Md> { + salt: Option<Vec<u8>>, + ikm: Vec<u8>, + _marker: PhantomData<M>, +} + +impl<M: Md> Hkdf<M> { + /// 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 an 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::default(), + } + } + + /// The RFC5869 HKDF-Expand operation. 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 or panics if there is an allocation failure. + pub fn expand_multi_info( + &self, + info_components: &[&[u8]], + okm: &mut [u8], + ) -> Result<(), InvalidLength> { + self.expand(&info_components.concat(), okm) + } + + /// The RFC5869 HKDF-Expand operation. Returns InvalidLength if the output is too large, or + /// panics if there is an allocation failure + 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(); + + //validate the output size + (okm.len() <= Self::MAX_OUTPUT_LENGTH && !okm.is_empty()) + .then(|| { + let mut okm_cslice = CSliceMut::from(okm); + + // 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"); + }) + .ok_or(InvalidLength) + } +} + +#[cfg(test)] +mod tests { + use crate::hkdf::{HkdfSha256, HkdfSha512}; + use core::iter; + use hex_literal::hex; + + struct Test<'a> { + ikm: &'a [u8], + salt: &'a [u8], + info: &'a [u8], + okm: &'a [u8], + } + + #[test] + fn hkdf_sha_256_test() { + let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + let salt = hex!("000102030405060708090a0b0c"); + let info = hex!("f0f1f2f3f4f5f6f7f8f9"); + + let hk = HkdfSha256::new(Some(&salt[..]), &ikm); + let mut okm = [0u8; 42]; + hk.expand(&info, &mut okm) + .expect("42 is a valid length for Sha256 to output"); + + let expected = hex!( + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" + ); + assert_eq!(okm, expected); + } + + #[test] + fn hkdf_sha512_test() { + let ikm = hex!("5d3db20e8238a90b62a600fa57fdb318"); + let salt = hex!("1d6f3b38a1e607b5e6bcd4af1800a9d3"); + let info = hex!("2bc5f39032b6fc87da69ba8711ce735b169646fd"); + + let hk = HkdfSha512::new(Some(&salt[..]), &ikm); + let mut okm = [0u8; 42]; + hk.expand(&info, &mut okm).expect("Should succeed"); + + let expected = hex!( + "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae" + ); + assert_eq!(okm, expected); + } + + // Test Vectors from https://tools.ietf.org/html/rfc5869. + #[test] + fn test_rfc5869_sha256() { + let tests = [ + Test { + // Test Case 1 + ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + salt: &hex!("000102030405060708090a0b0c"), + info: &hex!("f0f1f2f3f4f5f6f7f8f9"), + okm: &hex!( + " + 3cb25f25faacd57a90434f64d0362f2a + 2d2d0a90cf1a5a4c5db02d56ecc4c5bf + 34007208d5b887185865 + " + ), + }, + Test { + // Test Case 2 + ikm: &hex!( + " + 000102030405060708090a0b0c0d0e0f + 101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f + 303132333435363738393a3b3c3d3e3f + 404142434445464748494a4b4c4d4e4f + " + ), + salt: &hex!( + " + 606162636465666768696a6b6c6d6e6f + 707172737475767778797a7b7c7d7e7f + 808182838485868788898a8b8c8d8e8f + 909192939495969798999a9b9c9d9e9f + a0a1a2a3a4a5a6a7a8a9aaabacadaeaf + " + ), + info: &hex!( + " + b0b1b2b3b4b5b6b7b8b9babbbcbdbebf + c0c1c2c3c4c5c6c7c8c9cacbcccdcecf + d0d1d2d3d4d5d6d7d8d9dadbdcdddedf + e0e1e2e3e4e5e6e7e8e9eaebecedeeef + f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff + " + ), + okm: &hex!( + " + b11e398dc80327a1c8e7f78c596a4934 + 4f012eda2d4efad8a050cc4c19afa97c + 59045a99cac7827271cb41c65e590e09 + da3275600c2f09b8367793a9aca3db71 + cc30c58179ec3e87c14c01d5c1f3434f + 1d87 + " + ), + }, + Test { + // Test Case 3 + ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + salt: &hex!(""), + info: &hex!(""), + okm: &hex!( + " + 8da4e775a563c18f715f802a063c5a31 + b8a11f5c5ee1879ec3454e5f3c738d2d + 9d201395faa4b61a96c8 + " + ), + }, + ]; + for Test { + ikm, + salt, + info, + okm, + } in tests.iter() + { + let salt = if salt.is_empty() { + None + } else { + Some(&salt[..]) + }; + let hkdf = HkdfSha256::new(salt, ikm); + let mut okm2 = vec![0u8; okm.len()]; + assert!(hkdf.expand(&info[..], &mut okm2).is_ok()); + assert_eq!(okm2[..], okm[..]); + } + } + + #[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; + + 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; + } + } +}
diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs index 3933966..3da5f31 100644 --- a/rust/bssl-crypto/src/hmac.rs +++ b/rust/bssl-crypto/src/hmac.rs
@@ -14,7 +14,7 @@ */ use crate::{ digest::{Md, Sha256, Sha512}, - CSlice, ForeignTypeRef as _, PanicResultHandler, + CSlice, ForeignTypeRef as _, }; use core::{ ffi::{c_uint, c_void}, @@ -22,25 +22,25 @@ ptr, }; -/// Computes the HMAC-SHA-256 of `data` as a one-shot operation. +/// Computes the HMAC-SHA256 of `data` as a one-shot operation. /// /// Calculates the HMAC of data, using the given `key` and returns the result. /// It returns the computed hmac. /// Can panic if memory allocation fails in the underlying BoringSSL code. -pub fn hmac_sha_256(key: &[u8], data: &[u8]) -> [u8; 32] { +pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { hmac::<32, Sha256>(key, data) } -/// Computes the HMAC-SHA-512 of `data` as a one-shot operation. +/// Computes the HMAC-SHA512 of `data` as a one-shot operation. /// /// Calculates the HMAC of data, using the given `key` and returns the result. /// It returns the computed hmac. /// Can panic if memory allocation fails in the underlying BoringSSL code. -pub fn hmac_sha_512(key: &[u8], data: &[u8]) -> [u8; 64] { +pub fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] { hmac::<64, Sha512>(key, data) } -/// The BoringSSL HMAC-SHA-256 implementation. The operations may panic if memory allocation fails +/// The BoringSSL HMAC-SHA256 implementation. The operations may panic if memory allocation fails /// in BoringSSL. pub struct HmacSha256(Hmac<32, Sha256>); @@ -81,7 +81,7 @@ } } -/// The BoringSSL HMAC-SHA-512 implementation. The operations may panic if memory allocation fails +/// The BoringSSL HMAC-SHA512 implementation. The operations may panic if memory allocation fails /// in BoringSSL. pub struct HmacSha512(Hmac<64, Sha512>); @@ -139,7 +139,7 @@ // Safety: // - buf always contains N bytes of space // - If NULL is returned on error we panic immediately - unsafe { + let result = unsafe { bssl_sys::HMAC( M::get_md().as_ptr(), CSlice::from(key).as_ptr(), @@ -149,8 +149,8 @@ out.as_mut_ptr(), &mut size as *mut c_uint, ) - } - .panic_if_error(); + }; + assert!(!result.is_null(), "Result of bssl_sys::HMAC was null"); out } @@ -177,7 +177,10 @@ // Safety: // - HMAC_CTX_new panics if allocation fails let ctx = unsafe { bssl_sys::HMAC_CTX_new() }; - ctx.panic_if_error(); + assert!( + !ctx.is_null(), + "result of bssl_sys::HMAC_CTX_new() was null" + ); // Safety: // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, @@ -185,7 +188,7 @@ // - HMAC_Init_ex may return an error if key is null but the md is different from // before. This is avoided here since key is guaranteed to be non-null. // - HMAC_Init_ex returns 0 on allocation failure in which case we panic - unsafe { + let result = unsafe { bssl_sys::HMAC_Init_ex( ctx, CSlice::from(key).as_ptr() as *const c_void, @@ -193,8 +196,8 @@ M::get_md().as_ptr(), ptr::null_mut(), ) - } - .panic_if_error(); + }; + assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Init_ex"); Self { ctx, @@ -204,11 +207,11 @@ /// Update state using the provided data, can be called repeatedly. fn update(&mut self, data: &[u8]) { - unsafe { + let result = unsafe { // Safety: HMAC_Update will always return 1, in case it doesnt we panic bssl_sys::HMAC_Update(self.ctx, data.as_ptr(), data.len()) - } - .panic_if_error() + }; + assert_eq!(result, 1, "failure in bssl_sys::HMAC_Update"); } /// Obtain the hmac computation consuming the hmac instance. @@ -219,8 +222,9 @@ // - hmac has a fixed size output of N which will never exceed the length of an N // length array // - on allocation failure we panic - unsafe { bssl_sys::HMAC_Final(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) } - .panic_if_error(); + let result = + unsafe { bssl_sys::HMAC_Final(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) }; + assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Final"); buf } @@ -338,7 +342,7 @@ ]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; - let hmac_result = hmac_sha_256(&key, data); + let hmac_result = hmac_sha256(&key, data); assert_eq!(&hmac_result, &expected_hmac); }
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index 2c8ac72..9c4a214 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs
@@ -24,46 +24,30 @@ //! Rust boringssl binding extern crate core; -use core::ops::Not; /// BoringSSL implemented plain aes operations. pub mod aes; -/// BoringSSL implemented hmac operations. -pub mod hmac; - /// BoringSSL implemented hash functions. pub mod digest; -/// Used for handling result types from C APIs. -trait PanicResultHandler { - /// Panics if a C api returns an invalid result - /// Used for APIs which return error codes for allocation failures. - fn panic_if_error(&self); -} +/// BoringSSL implemented hkdf operations. +pub mod hkdf; -impl PanicResultHandler for i32 { - /// BoringSSL APIs return 1 on success or 0 on allocation failure. - #[allow(clippy::expect_used)] - fn panic_if_error(&self) { - self.gt(&0).then_some(()).expect("allocation failed!") - } -} +/// BoringSSL implemented hmac operations. +pub mod hmac; -impl<T> PanicResultHandler for *mut T { - /// Boringssl APIs return NULL on allocation failure for APIs that return a CTX. - #[allow(clippy::expect_used)] - fn panic_if_error(&self) { - self.is_null() - .not() - .then_some(()) - .expect("allocation failed!") - } -} - +/// This is a helper struct which provides functions for passing slices over FFI. struct CSlice<'a>(&'a [u8]); +impl<'a> From<&'a [u8]> for CSlice<'a> { + fn from(value: &'a [u8]) -> Self { + Self(value) + } +} + impl CSlice<'_> { + /// Returns a raw pointer to the value, which is safe to pass over FFI. pub fn as_ptr<T>(&self) -> *const T { if self.0.is_empty() { std::ptr::null() @@ -73,8 +57,26 @@ } } -impl<'a> From<&'a [u8]> for CSlice<'a> { - fn from(value: &'a [u8]) -> Self { +/// This is a helper struct which provides functions for passing mutable slices over FFI. +struct CSliceMut<'a>(&'a mut [u8]); + +impl CSliceMut<'_> { + /// Returns a raw pointer to the value, which is safe to pass over FFI. + pub fn as_mut_ptr<T>(&mut self) -> *mut T { + if self.0.is_empty() { + std::ptr::null_mut() + } else { + self.0.as_mut_ptr() as *mut T + } + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl<'a> From<&'a mut [u8]> for CSliceMut<'a> { + fn from(value: &'a mut [u8]) -> Self { Self(value) } }