diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index e53469d98..022f5a34a 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs @@ -28,6 +28,8 @@ extern crate alloc; extern crate core; +use core::ffi::c_void; + /// Authenticated Encryption with Additional Data algorithms. pub mod aead; @@ -52,7 +54,6 @@ pub mod hmac; /// Random number generation. pub mod rand; -/// X25519 elliptic curve operations. pub mod x25519; /// Memory-manipulation operations. @@ -68,7 +69,70 @@ pub(crate) mod pkey; #[cfg(test)] mod test_helpers; +/// FfiSlice exists to provide `as_ffi_ptr` on slices. Calling `as_ptr` on an +/// empty Rust slice may return the alignment of the type, rather than NULL, as +/// the pointer. When passing pointers into C/C++ code, that is not a valid +/// pointer. Thus this method should be used whenever passing a pointer to a +/// slice into BoringSSL code. +trait FfiSlice { + fn as_ffi_ptr(&self) -> *const u8; + fn as_ffi_void_ptr(&self) -> *const c_void { + self.as_ffi_ptr() as *const c_void + } +} + +impl FfiSlice for [u8] { + fn as_ffi_ptr(&self) -> *const u8 { + if self.is_empty() { + core::ptr::null() + } else { + self.as_ptr() + } + } +} + +impl FfiSlice for [u8; N] { + fn as_ffi_ptr(&self) -> *const u8 { + if N == 0 { + core::ptr::null() + } else { + self.as_ptr() + } + } +} + +/// See the comment [`FfiSlice`]. +trait FfiMutSlice { + fn as_mut_ffi_ptr(&mut self) -> *mut u8; + fn as_ffi_void_ptr(&mut self) -> *mut c_void { + self.as_mut_ffi_ptr() as *mut c_void + } +} + +impl FfiMutSlice for [u8] { + fn as_mut_ffi_ptr(&mut self) -> *mut u8 { + if self.is_empty() { + core::ptr::null_mut() + } else { + self.as_mut_ptr() + } + } +} + +impl FfiMutSlice for [u8; N] { + fn as_mut_ffi_ptr(&mut self) -> *mut u8 { + if N == 0 { + core::ptr::null_mut() + } else { + self.as_mut_ptr() + } + } +} + /// This is a helper struct which provides functions for passing slices over FFI. +/// +/// Deprecated: use `FfiSlice` which adds less noise and lets one grep for `as_ptr` +/// as a sign of something to check. struct CSlice<'a>(&'a [u8]); impl<'a> From<&'a [u8]> for CSlice<'a> { @@ -93,6 +157,9 @@ impl CSlice<'_> { } /// This is a helper struct which provides functions for passing mutable slices over FFI. +/// +/// Deprecated: use `FfiMutSlice` which adds less noise and lets one grep for +/// `as_ptr` as a sign of something to check. struct CSliceMut<'a>(&'a mut [u8]); impl CSliceMut<'_> { @@ -179,3 +246,47 @@ unsafe trait ForeignType { /// Returns a raw pointer to the wrapped value. fn as_ptr(&self) -> *mut Self::CType; } + +/// Wrap a closure that initializes an output buffer and return that buffer as +/// an array. Requires that the closure fully initialize the given buffer. +/// +/// Safety: the closure must fully initialize the array. +unsafe fn with_output_array(func: F) -> [u8; N] +where + F: FnOnce(*mut u8, usize), +{ + let mut out_uninit = core::mem::MaybeUninit::<[u8; N]>::uninit(); + let out_ptr = if N != 0 { + out_uninit.as_mut_ptr() as *mut u8 + } else { + core::ptr::null_mut() + }; + func(out_ptr, N); + // Safety: `func` promises to fill all of `out_uninit`. + unsafe { out_uninit.assume_init() } +} + +/// Wrap a closure that initializes an output buffer and return that buffer as +/// an array. The closure returns a [`core::ffi::c_int`] and, if the return value +/// is not one, then the initialization is assumed to have failed and [None] is +/// returned. Otherwise, this function requires that the closure fully +/// initialize the given buffer. +/// +/// Safety: the closure must fully initialize the array if it returns one. +unsafe fn with_output_array_fallible(func: F) -> Option<[u8; N]> +where + F: FnOnce(*mut u8, usize) -> core::ffi::c_int, +{ + let mut out_uninit = core::mem::MaybeUninit::<[u8; N]>::uninit(); + let out_ptr = if N != 0 { + out_uninit.as_mut_ptr() as *mut u8 + } else { + core::ptr::null_mut() + }; + if func(out_ptr, N) == 1 { + // Safety: `func` promises to fill all of `out_uninit` if it returns one. + unsafe { Some(out_uninit.assume_init()) } + } else { + None + } +} diff --git a/rust/bssl-crypto/src/x25519.rs b/rust/bssl-crypto/src/x25519.rs index 9ee449bed..8f6440d5d 100644 --- a/rust/bssl-crypto/src/x25519.rs +++ b/rust/bssl-crypto/src/x25519.rs @@ -13,11 +13,40 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +//! Diffie-Hellman over curve25519. +//! //! X25519 is the Diffie-Hellman primitive built from curve25519. It is sometimes referred to as -//! “curve25519”, but “X25519” is a more precise name. See http://cr.yp.to/ecdh.html and -//! https://tools.ietf.org/html/rfc7748. - -use alloc::borrow::ToOwned; +//! “curve25519”, but “X25519” is a more precise name. See and +//! . +//! +//! ``` +//! use bssl_crypto::x25519; +//! +//! // Alice generates her key pair. +//! let (alice_public_key, alice_private_key) = x25519::PrivateKey::generate(); +//! // Bob generates his key pair. +//! let (bob_public_key, bob_private_key) = x25519::PrivateKey::generate(); +//! +//! // If Alice obtains Bob's public key somehow, she can compute their +//! // shared key: +//! let shared_key = alice_private_key.compute_shared_key(&bob_public_key); +//! +//! // Alice can then derive a key (e.g. by using HKDF), which should include +//! // at least the two public keys. Then shen can send a message to Bob +//! // including her public key and an AEAD-protected blob. Bob can compute the +//! // same shared key given Alice's public key: +//! let shared_key2 = bob_private_key.compute_shared_key(&alice_public_key); +//! assert_eq!(shared_key, shared_key2); +//! +//! // This is an _unauthenticated_ exchange which is vulnerable to an +//! // active attacker. See, for example, +//! // http://www.noiseprotocol.org/noise.html for an example of building +//! // real protocols from a Diffie-Hellman primitive. +//! ``` + +use crate::with_output_array; +use crate::with_output_array_fallible; +use crate::FfiSlice; /// Number of bytes in a private key in X25519 pub const PRIVATE_KEY_LEN: usize = bssl_sys::X25519_PRIVATE_KEY_LEN as usize; @@ -26,190 +55,99 @@ pub const PUBLIC_KEY_LEN: usize = bssl_sys::X25519_PUBLIC_VALUE_LEN as usize; /// Number of bytes in a shared secret derived with X25519 pub const SHARED_KEY_LEN: usize = bssl_sys::X25519_SHARED_KEY_LEN as usize; -/// Error while performing a X25519 Diffie-Hellman key exchange. -#[derive(Debug)] -pub struct DiffieHellmanError; +/// X25519 public keys are simply 32-byte strings. +pub type PublicKey = [u8; PUBLIC_KEY_LEN]; + +/// An X25519 private key (a 32-byte string). +pub struct PrivateKey(pub [u8; PRIVATE_KEY_LEN]); -/// A struct containing a X25519 key pair. -pub struct PrivateKey { - private_key: [u8; PRIVATE_KEY_LEN], - public_key: [u8; PUBLIC_KEY_LEN], +impl AsRef<[u8]> for PrivateKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } } impl PrivateKey { - /// Derives a shared secrect from this private key and the given public key. - pub fn diffie_hellman( - &self, - other_public_key: &PublicKey, - ) -> Result { - let mut shared_key_uninit = core::mem::MaybeUninit::<[u8; SHARED_KEY_LEN]>::uninit(); - // Safety: - // - private_key and other_public_key are Rust 32-byte arrays - // - shared_key_uninit is just initialized above to a 32 byte array - let result = unsafe { - bssl_sys::X25519( - shared_key_uninit.as_mut_ptr() as *mut u8, - self.private_key.as_ptr(), - other_public_key.0.as_ptr(), - ) - }; - if result == 1 { - // Safety: - // - `shared_key_uninit` is initialized by `X25519` above, and we checked that it - // succeeded - let shared_key = unsafe { shared_key_uninit.assume_init() }; - Ok(crate::ecdh::SharedSecret(shared_key)) - } else { - Err(DiffieHellmanError) + /// Derive the shared key between this private key and a peer's public key. + /// Don't use the shared key directly, rather use a KDF and also include + /// the two public values as inputs. + /// + /// Will fail and produce `None` if the peer's public key is a point of + /// small order. It is safe to react to this in non-constant time. + pub fn compute_shared_key(&self, other_public_key: &PublicKey) -> Option<[u8; SHARED_KEY_LEN]> { + // Safety: `X25519` indeed writes `SHARED_KEY_LEN` bytes. + unsafe { + with_output_array_fallible(|out, _| { + bssl_sys::X25519(out, self.0.as_ffi_ptr(), other_public_key.as_ffi_ptr()) + }) } } - /// Generate a new key pair for use in a Diffie-Hellman key exchange. - pub fn generate() -> Self { + /// Generate a new key pair. + pub fn generate() -> (PublicKey, PrivateKey) { let mut public_key_uninit = core::mem::MaybeUninit::<[u8; PUBLIC_KEY_LEN]>::uninit(); let mut private_key_uninit = core::mem::MaybeUninit::<[u8; PRIVATE_KEY_LEN]>::uninit(); // Safety: - // - private_key_uninit and public_key_uninit are allocated to 32-bytes - let (public_key, private_key) = unsafe { + // - private_key_uninit and public_key_uninit are the correct length. + unsafe { bssl_sys::X25519_keypair( public_key_uninit.as_mut_ptr() as *mut u8, private_key_uninit.as_mut_ptr() as *mut u8, ); - // Safety: Initialized by `X25519_keypair` above + // Safety: Initialized by `X25519_keypair` just above. ( public_key_uninit.assume_init(), - private_key_uninit.assume_init(), + PrivateKey(private_key_uninit.assume_init()), ) - }; - Self { - private_key, - public_key, } } - /// Tries to convert the given bytes into a private key. - pub fn from_private_bytes(private_key_bytes: &[u8; PRIVATE_KEY_LEN]) -> Self { - let mut public_key_uninit = core::mem::MaybeUninit::<[u8; PUBLIC_KEY_LEN]>::uninit(); - let private_key: [u8; PRIVATE_KEY_LEN] = private_key_bytes.to_owned(); - // Safety: - // - private_key and public_key are Rust 32-byte arrays - let public_key = unsafe { - bssl_sys::X25519_public_from_private( - public_key_uninit.as_mut_ptr() as *mut _, - private_key.as_ptr(), - ); - public_key_uninit.assume_init() - }; - Self { - private_key, - public_key, + /// Compute the public key corresponding to this private key. + pub fn to_public(&self) -> PublicKey { + // Safety: `X25519_public_from_private` indeed fills an entire [`PublicKey`]. + unsafe { + with_output_array(|out, _| { + bssl_sys::X25519_public_from_private(out, self.0.as_ffi_ptr()); + }) } } } -impl<'a> From<&'a PrivateKey> for PublicKey { - fn from(value: &'a PrivateKey) -> Self { - Self(value.public_key) - } -} - -/// A public key for X25519 elliptic curve. -#[derive(Debug, PartialEq, Eq)] -pub struct PublicKey([u8; PUBLIC_KEY_LEN]); - -impl PublicKey { - /// Converts this public key to its byte representation. - pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LEN] { - self.0 - } - - /// Returns a reference to the byte representation of this public key. - pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LEN] { - &self.0 - } -} - -impl From<&[u8; 32]> for PublicKey { - fn from(value: &[u8; 32]) -> Self { - Self(*value) - } -} - -/// Shared secret derived from a Diffie-Hellman key exchange. Don't use the shared key directly, -/// rather use a KDF and also include the two public values as inputs. -type SharedSecret = crate::ecdh::SharedSecret; - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::{ - test_helpers::decode_hex, - x25519::{PrivateKey, PublicKey}, - }; + use crate::{test_helpers::decode_hex, x25519::PrivateKey}; #[test] - fn x25519_test_diffie_hellman() { + fn known_vector() { // wycheproof/testvectors/x25519_test.json tcId 1 - let public_key_bytes: [u8; 32] = + let public_key: [u8; 32] = decode_hex("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); - let private_key = - decode_hex("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475"); + let private_key = PrivateKey(decode_hex( + "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475", + )); let expected_shared_secret: [u8; 32] = decode_hex("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320"); - let public_key = PublicKey::from(&public_key_bytes); - let private_key = PrivateKey::from_private_bytes(&private_key); - - let shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - assert_eq!(expected_shared_secret, shared_secret.to_bytes()); - } - - #[test] - fn x25519_generate_diffie_hellman_matches() { - let private_key_1 = PrivateKey::generate(); - let private_key_2 = PrivateKey::generate(); - let public_key_1 = PublicKey::from(&private_key_1); - let public_key_2 = PublicKey::from(&private_key_2); - - let diffie_hellman_1 = private_key_1.diffie_hellman(&public_key_2).unwrap(); - let diffie_hellman_2 = private_key_2.diffie_hellman(&public_key_1).unwrap(); - - assert_eq!(diffie_hellman_1.to_bytes(), diffie_hellman_2.to_bytes()); + let shared_secret = private_key.compute_shared_key(&public_key).unwrap(); + assert_eq!(expected_shared_secret, shared_secret); } #[test] - fn x25519_test_diffie_hellman_zero_public_key() { - // wycheproof/testvectors/x25519_test.json tcId 32 - let public_key_bytes = - decode_hex("0000000000000000000000000000000000000000000000000000000000000000"); - let private_key = - decode_hex("88227494038f2bb811d47805bcdf04a2ac585ada7f2f23389bfd4658f9ddd45e"); - let public_key = PublicKey::from(&public_key_bytes); - let private_key = PrivateKey::from_private_bytes(&private_key); - - let shared_secret = private_key.diffie_hellman(&public_key); - assert!(shared_secret.is_err()); + fn all_zero_public_key() { + assert!(PrivateKey::generate() + .1 + .compute_shared_key(&[0u8; 32]) + .is_none()); } #[test] - fn x25519_public_key_byte_conversion() { - let public_key_bytes = - decode_hex("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); - let public_key = PublicKey::from(&public_key_bytes); - assert_eq!(public_key_bytes, public_key.to_bytes()); - } - - #[test] - fn x25519_test_public_key_from_private_key() { + fn to_public() { // Taken from https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1 let public_key_bytes = decode_hex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); - let private_key_bytes = - decode_hex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes); - - assert_eq!( - PublicKey::from(&public_key_bytes), - PublicKey::from(&private_key) - ); + let private_key = PrivateKey(decode_hex( + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + )); + assert_eq!(public_key_bytes, private_key.to_public()); } }