Reworking bssl_crypto: ECDH This change also adds scoped.rs, which contains scoped objects for `EVP_PKEY` and `EC_KEY`, for when we're holding temporary objects of those types. Due to an accident with git rebase, it also renames `crypto_memcmp` to `constant_time_compare` and promotes it to the top-level of the crate. Change-Id: I629c051a244e3f9dcf64d8a36846528f10a31f50 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65174 Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/rust/bssl-crypto/src/bn.rs b/rust/bssl-crypto/src/bn.rs deleted file mode 100644 index 35a196a..0000000 --- a/rust/bssl-crypto/src/bn.rs +++ /dev/null
@@ -1,61 +0,0 @@ -/* 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::{CSlice, ForeignType}; - -pub(crate) struct BigNum { - ptr: *mut bssl_sys::BIGNUM, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr() == x` -unsafe impl ForeignType for BigNum { - type CType = bssl_sys::BIGNUM; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -impl BigNum { - pub(crate) fn new() -> Self { - // Safety: There are no preconditions for BN_new() - unsafe { Self::from_ptr(bssl_sys::BN_new()) } - } -} - -impl From<&[u8]> for BigNum { - fn from(value: &[u8]) -> Self { - let value_ffi = CSlice(value); - // Safety: - // - `value` is a CSlice from safe Rust. - // - The `ret` argument can be null to request allocating a new result. - let ptr = unsafe { - bssl_sys::BN_bin2bn(value_ffi.as_ptr(), value_ffi.len(), core::ptr::null_mut()) - }; - assert!(!ptr.is_null()); - Self { ptr } - } -} - -impl Drop for BigNum { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by `self`. - unsafe { bssl_sys::BN_free(self.ptr) } - } -}
diff --git a/rust/bssl-crypto/src/ec.rs b/rust/bssl-crypto/src/ec.rs index 8bd8bda..4730643 100644 --- a/rust/bssl-crypto/src/ec.rs +++ b/rust/bssl-crypto/src/ec.rs
@@ -13,409 +13,544 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -//! `EcKey` and `EcGroup` structs for working with elliptic curve cryptography. This module is -//! intended for internal use within this crate only, to create higher-level abstractions suitable -//! to be exposed externally. +//! Definitions of NIST elliptic curves. +//! +//! If you're looking for curve25519, see the `x25519` and `ed25519` modules. -use alloc::{borrow::ToOwned, vec, vec::Vec}; -use core::{borrow::Borrow, fmt::Debug, ops::Deref, panic}; +// This module is substantially internal-only and is only public for the +// [`Curve`] trait, which is shared by ECDH and ECDSA. -use crate::{bn::BigNum, CSlice, CSliceMut, ForeignType, ForeignTypeRef}; +use crate::{cbb_to_buffer, parse_with_cbs, scoped, sealed, Buffer, FfiSlice}; +use alloc::fmt::Debug; +use core::ptr::{null, null_mut}; -#[derive(Debug)] -pub(crate) struct EcKey { - ptr: *mut bssl_sys::EC_KEY, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr() == x` -unsafe impl ForeignType for EcKey { - type CType = bssl_sys::EC_KEY; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -// Safety: -// - `EC_KEY`'s documentation says "A given object may be used concurrently on multiple threads by -// non-mutating functions, provided no other thread is concurrently calling a mutating function.", -// which matches Rust's aliasing rules. -// - `ptr(&self)` and `ptr_mut(&mut self)` ensures that only a mutable reference can get a mutable -// `EC_KEY` pointer outside of this module. -unsafe impl Send for EcKey {} - -impl Clone for EcKey { - fn clone(&self) -> Self { - // Safety: - // - EcKey makes sure self.ptr is a valid pointer. - let ptr = unsafe { bssl_sys::EC_KEY_dup(self.ptr) }; - Self { ptr } - } -} - -/// Error type returned when conversion to or from an `EcKey` failed. -pub(crate) struct ConversionFailed; - -impl EcKey { - pub fn new_by_ec_group(ec_group: &EcGroupRef) -> Self { - // Safety: `EC_KEY_new` does not have preconditions - let eckey = unsafe { bssl_sys::EC_KEY_new() }; - assert!(!eckey.is_null()); - // Safety: - // - `eckey` is just allocated and doesn't have its group set yet - // - `EcGroup` ensures the `ptr` it contains is valid - unsafe { - assert_eq!( - bssl_sys::EC_KEY_set_group(eckey, ec_group.as_ptr()), - 1, - "EC_KEY_set_group failed" - ); - } - // Safety: `eckey` is allocated and null-checked - unsafe { Self::from_ptr(eckey) } - } - - /// Try to create a public-key version of `EcKey` from the given `value`. Returns error if the - /// slice is not a valid representation of a public key for the given curve. - /// - /// `curve_nid` should be a value defined in `bssl_sys::NID_*`. - #[allow(clippy::panic)] - pub(crate) fn try_new_public_key_from_bytes( - ec_group: &EcGroupRef, - value: &[u8], - ) -> Result<Self, ConversionFailed> { - let eckey = Self::new_by_ec_group(ec_group); - let value_ffi = CSlice(value); - - // Safety: The input slice `value_ffi` is a CSlice from safe Rust. - let result = unsafe { - bssl_sys::EC_KEY_oct2key( - eckey.ptr, - value_ffi.as_ptr(), - value_ffi.len(), - core::ptr::null_mut(), - ) - }; - match result { - 0 => Err(ConversionFailed), - 1 => Ok(eckey), - _ => panic!("Unexpected return value {result} from EC_KEY_oct2key"), - } - } - - pub(crate) fn to_affine_coordinates(&self) -> (BigNum, BigNum) { - let ecpoint = unsafe { bssl_sys::EC_KEY_get0_public_key(self.ptr) }; - let bn_x = BigNum::new(); - let bn_y = BigNum::new(); - - // Safety: - // - `EcKey` and `BigNum` structs ensures validity of their pointers. - let result = unsafe { - bssl_sys::EC_POINT_get_affine_coordinates( - bssl_sys::EC_KEY_get0_group(self.ptr), - ecpoint, - bn_x.as_ptr(), - bn_y.as_ptr(), - core::ptr::null_mut(), - ) - }; - assert_eq!( - result, 1, - "bssl_sys::EC_POINT_get_affine_coordinates failed" - ); - (bn_x, bn_y) - } - - pub(crate) fn generate(ec_group: &EcGroupRef) -> Self { - let eckey = EcKey::new_by_ec_group(ec_group); - // Safety: `EcKey` ensures eckey.ptr is valid. - let result = unsafe { bssl_sys::EC_KEY_generate_key(eckey.as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EC_KEY_generate_key failed"); - eckey - } - - pub(crate) fn try_new_public_key_from_affine_coordinates( - ec_group: &EcGroupRef, - x: &[u8], - y: &[u8], - ) -> Result<Self, ConversionFailed> { - let bn_x = BigNum::from(x); - let bn_y = BigNum::from(y); - - let eckey = EcKey::new_by_ec_group(ec_group); - // Safety: - // - Wrapper classes `EcKey` and `BigNum` ensures validity of the pointers - let result = unsafe { - bssl_sys::EC_KEY_set_public_key_affine_coordinates( - eckey.as_ptr(), - bn_x.as_ptr(), - bn_y.as_ptr(), - ) - }; - if result == 1 { - Ok(eckey) - } else { - Err(ConversionFailed) - } - } - - /// Tries to convert the given bytes into a private key contained within `EcKey`. - /// - /// `private_key_bytes` must be padded to the size of `curve_nid`'s group order, otherwise the - /// conversion will fail. - pub(crate) fn try_from_raw_bytes( - ec_group: &EcGroupRef, - private_key_bytes: &[u8], - ) -> Result<Self, ConversionFailed> { - let eckey = EcKey::new_by_ec_group(ec_group); - let private_key_bytes_ffi = CSlice(private_key_bytes); - // Safety: - // - `EcKey` ensures `eckey.ptr` is valid. - // - `private_key_bytes` is a CSlice from safe-rust. - let result = unsafe { - bssl_sys::EC_KEY_oct2priv( - eckey.as_ptr(), - private_key_bytes_ffi.as_ptr(), - private_key_bytes_ffi.len(), - ) - }; - if result != 1 { - return Err(ConversionFailed); - } - - Ok(eckey) - } - - /// Converts between the private key component of `eckey` and octet form. The octet form - /// consists of the content octets of the `privateKey` `OCTET STRING` in an `ECPrivateKey` ASN.1 - /// structure - pub(crate) fn to_raw_bytes(&self) -> Vec<u8> { - let mut output = vec![0_u8; 66]; - let mut private_key_bytes_ffi = CSliceMut::from(&mut output[..]); - // Safety: - // - `EcKey` ensures `self.ptr` is valid. - // - `private_key_bytes_ffi` is a CSliceMut we just allocated. - // - 66 bytes is guaranteed to be sufficient to store an EC private key - let num_octets_stored = unsafe { - bssl_sys::EC_KEY_priv2oct( - self.as_ptr(), - private_key_bytes_ffi.as_mut_ptr(), - private_key_bytes_ffi.len(), - ) - }; - // Safety: `EC_KEY_priv2oct` just wrote `num_octets_stored` into the buffer. - unsafe { output.set_len(num_octets_stored) } - output - } - - pub(crate) fn public_key_eq(&self, other: &Self) -> bool { - let result = unsafe { - bssl_sys::EC_POINT_cmp( - bssl_sys::EC_KEY_get0_group(self.ptr), - bssl_sys::EC_KEY_get0_public_key(self.ptr), - bssl_sys::EC_KEY_get0_public_key(other.ptr), - core::ptr::null_mut(), - ) - }; - assert_ne!(result, -1, "bssl_sys::EC_POINT_cmp failed"); - result == 0 - } - - pub(crate) fn to_vec(&self) -> Vec<u8> { - // Safety: `self.ptr` is owned by `self` - let ecgroup = unsafe { bssl_sys::EC_KEY_get0_group(self.ptr) }; - let ecpoint = unsafe { bssl_sys::EC_KEY_get0_public_key(self.ptr) }; - let conv_form = unsafe { bssl_sys::EC_KEY_get_conv_form(self.ptr) }; - // Safety: - // - When passing null to EC_POINT_point2oct's `buf` argument, it returns the size of the - // resulting buffer. - let output_size = unsafe { - bssl_sys::EC_POINT_point2oct( - ecgroup, - ecpoint, - conv_form, - core::ptr::null_mut(), - 0, - core::ptr::null_mut(), - ) - }; - assert_ne!(output_size, 0, "bssl_sys::EC_POINT_point2oct failed"); - let mut result_vec = Vec::<u8>::with_capacity(output_size); - let buf_len = unsafe { - bssl_sys::EC_POINT_point2oct( - ecgroup, - ecpoint, - conv_form, - result_vec.as_mut_ptr(), - output_size, - core::ptr::null_mut(), - ) - }; - assert_ne!(buf_len, 0, "bssl_sys::EC_POINT_point2oct failed"); - // Safety: The length is what EC_POINT_point2oct just told us it filled into the buffer. - unsafe { result_vec.set_len(buf_len) } - result_vec - } -} - -impl Drop for EcKey { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by this struct - unsafe { bssl_sys::EC_KEY_free(self.ptr) } - } -} - -/// Describes an elliptic curve. -#[non_exhaustive] -pub struct EcGroupRef; - -// Safety: Default implementation in ForeignTypeRef ensures the preconditions -// required by that trait holds. -unsafe impl ForeignTypeRef for EcGroupRef { - type CType = bssl_sys::EC_GROUP; -} - -impl Borrow<EcGroupRef> for EcGroup { - fn borrow(&self) -> &EcGroupRef { - unsafe { EcGroupRef::from_ptr(self.ptr) } - } -} - -impl ToOwned for EcGroupRef { - type Owned = EcGroup; - - fn to_owned(&self) -> Self::Owned { - // Safety: `EcGroupRef` is a valid pointer - let new_ec_group = unsafe { bssl_sys::EC_GROUP_dup(self.as_ptr()) }; - assert!(!new_ec_group.is_null(), "EC_GROUP_dup failed"); - EcGroup { ptr: new_ec_group } - } -} - -impl AsRef<EcGroupRef> for EcGroup { - fn as_ref(&self) -> &EcGroupRef { - self.deref() - } -} - -impl PartialEq for EcGroupRef { - fn eq(&self, other: &Self) -> bool { - // Safety: - // - Self and other are valid pointers since they come from `EcGroupRef` - // - Third argument is ignored - unsafe { - bssl_sys::EC_GROUP_cmp( - self.as_ptr(), - other.as_ptr(), - /* ignored */ core::ptr::null_mut(), - ) == 0 - } - } -} - -impl Eq for EcGroupRef {} - -pub struct EcGroup { - ptr: *mut bssl_sys::EC_GROUP, -} - -impl Deref for EcGroup { - type Target = EcGroupRef; - - fn deref(&self) -> &Self::Target { - unsafe { EcGroupRef::from_ptr(self.ptr) } - } -} - -impl Drop for EcGroup { - fn drop(&mut self) { - unsafe { bssl_sys::EC_GROUP_free(self.ptr) } - } -} - -/// An elliptic curve, used as the type parameter for [`PublicKey`] and [`PrivateKey`]. +/// An elliptic curve. pub trait Curve: Debug { - /// The size of the affine coordinates for this curve. - const AFFINE_COORDINATE_SIZE: usize; - - /// Create a new [`EcGroup`] for this curve. - fn ec_group() -> &'static EcGroupRef; + #[doc(hidden)] + fn group(_: sealed::Sealed) -> Group; } -/// The P-224 curve, corresponding to `NID_secp224r1`. -#[derive(Debug)] -pub struct P224; - -impl Curve for P224 { - const AFFINE_COORDINATE_SIZE: usize = 28; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p224 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p224() as *mut _) } - } -} - -/// The P-256 curve, corresponding to `NID_X9_62_prime256v1`. +/// The NIST P-256 curve, also called secp256r1. #[derive(Debug)] pub struct P256; impl Curve for P256 { - const AFFINE_COORDINATE_SIZE: usize = 32; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p256 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p256() as *mut _) } + fn group(_: sealed::Sealed) -> Group { + Group::P256 } } -/// The P-384 curve, corresponding to `NID_secp384r1`. +/// The NIST P-384 curve, also called secp384r1. #[derive(Debug)] pub struct P384; impl Curve for P384 { - const AFFINE_COORDINATE_SIZE: usize = 48; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p384 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p384() as *mut _) } + fn group(_: sealed::Sealed) -> Group { + Group::P384 } } -/// The P-521 curve, corresponding to `NID_secp521r1`. -#[derive(Debug)] -pub struct P521; +#[derive(Copy, Clone)] +#[doc(hidden)] +pub enum Group { + P256, + P384, +} -impl Curve for P521 { - const AFFINE_COORDINATE_SIZE: usize = 66; - - fn ec_group() -> &'static EcGroupRef { - // Safety: EC_group_p521 does not have any preconditions - unsafe { EcGroupRef::from_ptr(bssl_sys::EC_group_p521() as *mut _) } +impl Group { + fn as_ffi_ptr(self) -> *const bssl_sys::EC_GROUP { + // Safety: `group` is an address-space constant. These functions + // cannot fail and no resources need to be released in the future. + match self { + Group::P256 => unsafe { bssl_sys::EC_group_p256() }, + Group::P384 => unsafe { bssl_sys::EC_group_p384() }, + } } } +/// Point is a valid, finite point on some curve. +pub(crate) struct Point { + group: *const bssl_sys::EC_GROUP, + point: *mut bssl_sys::EC_POINT, +} + +impl Point { + /// Construct an uninitialized curve point. This is not public and all + /// callers must ensure that the point is initialized before being returned. + fn new(group: Group) -> Self { + let group = group.as_ffi_ptr(); + // Safety: `group` is valid because it was constructed just above. + let point = unsafe { bssl_sys::EC_POINT_new(group) }; + // `EC_POINT_new` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!point.is_null()); + Self { group, point } + } + + /// Construct a point by multipling the curve's base point by the given + /// scalar. + unsafe fn from_scalar(group: Group, scalar: *const bssl_sys::BIGNUM) -> Option<Self> { + let point = Self::new(group); + // Safety: the members of `point` are valid by construction. `scalar` + // is assumed to be valid. + let result = unsafe { + bssl_sys::EC_POINT_mul( + point.group, + point.point, + scalar, + /*q=*/ null(), + /*m=*/ null(), + /*ctx=*/ null_mut(), + ) + }; + if result != 1 { + return None; + } + if 1 == unsafe { bssl_sys::EC_POINT_is_at_infinity(point.group, point.point) } { + return None; + } + Some(point) + } + + /// Duplicate the given finite point. + unsafe fn clone_from_ptr( + group: *const bssl_sys::EC_GROUP, + point: *const bssl_sys::EC_POINT, + ) -> Point { + assert_eq!(0, unsafe { + bssl_sys::EC_POINT_is_at_infinity(group, point) + }); + + // Safety: we assume that the caller is passing valid pointers + let new_point = unsafe { bssl_sys::EC_POINT_dup(point, group) }; + // `EC_POINT_dup` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!new_point.is_null()); + + Self { + group, + point: new_point, + } + } + + pub fn as_ffi_ptr(&self) -> *const bssl_sys::EC_POINT { + self.point + } + + /// Create a new point from an uncompressed X9.62 representation. + /// + /// (X9.62 is the standard representation of an elliptic-curve point that + /// starts with an 0x04 byte.) + pub fn from_x962_uncompressed(group: Group, x962: &[u8]) -> Option<Self> { + const UNCOMPRESSED: u8 = + bssl_sys::point_conversion_form_t::POINT_CONVERSION_UNCOMPRESSED as u8; + if x962.first()? != &UNCOMPRESSED { + return None; + } + + let point = Self::new(group); + // Safety: `point` is valid by construction. `x962` is a valid memory + // buffer. + let result = unsafe { + bssl_sys::EC_POINT_oct2point( + point.group, + point.point, + x962.as_ffi_ptr(), + x962.len(), + /*bn_ctx=*/ null_mut(), + ) + }; + if result == 1 { + // X9.62 format cannot represent the point at infinity, so this + // should be moot, but `Point` must never contain infinity. + assert_eq!(0, unsafe { + bssl_sys::EC_POINT_is_at_infinity(point.group, point.point) + }); + Some(point) + } else { + None + } + } + + pub fn to_x962_uncompressed(&self) -> Buffer { + // Safety: arguments are valid, `EC_KEY` ensures that the the group is + // correct for the point, and a `Point` is always finite. + unsafe { to_x962_uncompressed(self.group, self.point) } + } + + pub fn from_der_subject_public_key_info(group: Group, spki: &[u8]) -> Option<Self> { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + spki, + // Safety: if called, `pkey` is the non-null result of `EVP_parse_public_key`. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: `cbs` is a valid pointer in this context. + |cbs| unsafe { bssl_sys::EVP_parse_public_key(cbs) }, + )?); + let ec_key = unsafe { bssl_sys::EVP_PKEY_get0_EC_KEY(pkey.as_ffi_ptr()) }; + if ec_key.is_null() { + // Not an ECC key. + return None; + } + let parsed_group = unsafe { bssl_sys::EC_KEY_get0_group(ec_key) }; + if parsed_group != group.as_ffi_ptr() { + // ECC key for a different curve. + return None; + } + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(ec_key) }; + if point.is_null() { + return None; + } + // Safety: `ec_key` is still owned by `pkey` and doesn't need to be freed. + Some(unsafe { Self::clone_from_ptr(parsed_group, point) }) + } + + /// Calls `func` with an `EC_KEY` that contains a copy of this point. + pub fn with_point_as_ec_key<F, T>(&self, func: F) -> T + where + F: FnOnce(*mut bssl_sys::EC_KEY) -> T, + { + let mut ec_key = scoped::EcKey::new(); + // Safety: `self.group` is always valid by construction and this doesn't + // pass ownership. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_group(ec_key.as_ffi_ptr(), self.group) + }); + // Safety: `self.point` is always valid by construction and this doesn't + // pass ownership. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_public_key(ec_key.as_ffi_ptr(), self.point) + }); + func(ec_key.as_ffi_ptr()) + } + + pub fn to_der_subject_public_key_info(&self) -> Buffer { + // Safety: `ec_key` is a valid pointer in this context. + self.with_point_as_ec_key(|ec_key| unsafe { to_der_subject_public_key_info(ec_key) }) + } +} + +impl Drop for Point { + fn drop(&mut self) { + // Safety: `self.point` must be valid because only valid `Point`s can + // be constructed. `self.group` does not need to be freed. + unsafe { bssl_sys::EC_POINT_free(self.point) } + } +} + +/// Key holds both a public and private key. While BoringSSL allows an `EC_KEY` +/// to also be a) empty, b) holding only a private scalar, or c) holding only +// a public key, those cases are never exposed as a `Key`. +pub(crate) struct Key(*mut bssl_sys::EC_KEY); + +impl Key { + /// Construct an uninitialized key. This is not public and all + /// callers must ensure that the key is initialized before being returned. + fn new(group: Group) -> Self { + let key = unsafe { bssl_sys::EC_KEY_new() }; + // `EC_KEY_new` only fails if out of memory, which is not a case that + // is handled short of panicking. + assert!(!key.is_null()); + + // Setting the group on a fresh `EC_KEY` never fails. + assert_eq!(1, unsafe { + bssl_sys::EC_KEY_set_group(key, group.as_ffi_ptr()) + }); + + Self(key) + } + + pub fn as_ffi_ptr(&self) -> *const bssl_sys::EC_KEY { + self.0 + } + + /// Generate a random private key. + pub fn generate(group: Group) -> Self { + let key = Self::new(group); + // Generation only fails if out of memory, which is only handled by + // panicking. + assert_eq!(1, unsafe { bssl_sys::EC_KEY_generate_key(key.0) }); + // `EC_KEY_generate_key` is documented as also setting the public key. + key + } + + /// Construct a private key from a big-endian representation of the private + /// scalar. The scalar must be zero padded to the correct length for the + /// curve. + pub fn from_big_endian(group: Group, scalar: &[u8]) -> Option<Self> { + let key = Self::new(group); + // Safety: `key.0` is always valid by construction. + let result = unsafe { bssl_sys::EC_KEY_oct2priv(key.0, scalar.as_ffi_ptr(), scalar.len()) }; + if result != 1 { + return None; + } + + // BoringSSL allows an `EC_KEY` to have a private scalar without a + // public point, but `Key` is never exposed in that state. + + // Safety: `key.0` is valid by construction. The returned value is + // still owned the `EC_KEY`. + let scalar = unsafe { bssl_sys::EC_KEY_get0_private_key(key.0) }; + assert!(!scalar.is_null()); + + // Safety: `scalar` is a valid pointer. + let point = unsafe { Point::from_scalar(group, scalar)? }; + // Safety: `key.0` is valid by construction, as is `point.point`. The + // point is copied into the `EC_KEY` so ownership isn't being moved. + let result = unsafe { bssl_sys::EC_KEY_set_public_key(key.0, point.point) }; + // Setting the public key should only fail if out of memory, which this + // crate doesn't handle, or if the groups don't match, which is + // impossible. + assert_eq!(result, 1); + + Some(key) + } + + pub fn to_big_endian(&self) -> Buffer { + let mut ptr: *mut u8 = null_mut(); + // Safety: `self.0` is valid by construction. If this returns non-zero + // then ptr holds ownership of a buffer. + let len = unsafe { bssl_sys::EC_KEY_priv2buf(self.0, &mut ptr) }; + assert!(len != 0); + Buffer { ptr, len } + } + + /// Parses an ECPrivateKey structure (from RFC 5915). + pub fn from_der_ec_private_key(group: Group, der: &[u8]) -> Option<Self> { + let key = parse_with_cbs( + der, + // Safety: in this context, `key` is the non-null result of + // `EC_KEY_parse_private_key`. + |key| unsafe { bssl_sys::EC_KEY_free(key) }, + // Safety: `cbs` is valid per `parse_with_cbs` and `group` always + // returns a valid pointer. + |cbs| unsafe { bssl_sys::EC_KEY_parse_private_key(cbs, group.as_ffi_ptr()) }, + )?; + Some(Self(key)) + } + + /// Serializes this private key as an ECPrivateKey structure from RFC 5915. + pub fn to_der_ec_private_key(&self) -> Buffer { + cbb_to_buffer(64, |cbb| unsafe { + // Safety: the `EC_KEY` is always valid so `EC_KEY_marshal_private_key` + // should only fail if out of memory, which this crate doesn't handle. + assert_eq!( + 1, + bssl_sys::EC_KEY_marshal_private_key( + cbb, + self.0, + bssl_sys::EC_PKEY_NO_PARAMETERS as u32 + ) + ); + }) + } + + /// Parses a PrivateKeyInfo structure (from RFC 5208). + pub fn from_der_private_key_info(group: Group, der: &[u8]) -> Option<Self> { + let mut pkey = scoped::EvpPkey::from_ptr(parse_with_cbs( + der, + // Safety: in this context, `pkey` is the non-null result of + // `EVP_parse_private_key`. + |pkey| unsafe { bssl_sys::EVP_PKEY_free(pkey) }, + // Safety: `cbs` is valid per `parse_with_cbs`. + |cbs| unsafe { bssl_sys::EVP_parse_private_key(cbs) }, + )?); + let ec_key = unsafe { bssl_sys::EVP_PKEY_get1_EC_KEY(pkey.as_ffi_ptr()) }; + if ec_key.is_null() { + return None; + } + // Safety: `ec_key` is now owned by this function. + let parsed_group = unsafe { bssl_sys::EC_KEY_get0_group(ec_key) }; + if parsed_group == group.as_ffi_ptr() { + // Safety: parsing an EC_KEY always set the public key. It should + // be impossible for the public key to be infinity, but double-check. + let is_infinite = unsafe { + bssl_sys::EC_POINT_is_at_infinity( + bssl_sys::EC_KEY_get0_group(ec_key), + bssl_sys::EC_KEY_get0_public_key(ec_key), + ) + }; + if is_infinite == 0 { + // Safety: `EVP_PKEY_get1_EC_KEY` returned ownership, which we can move + // into the returned object. + return Some(Self(ec_key)); + } + } + unsafe { bssl_sys::EC_KEY_free(ec_key) }; + None + } + + /// Serializes this private key as a PrivateKeyInfo structure from RFC 5208. + pub fn to_der_private_key_info(&self) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + // Safety: `pkey` was just allocated above; the `EC_KEY` is valid by + // construction. This call takes a reference to the `EC_KEY` and so + // hasn't stolen ownership from `self`. + assert_eq!(1, unsafe { + bssl_sys::EVP_PKEY_set1_EC_KEY(pkey.as_ffi_ptr(), self.0) + }); + cbb_to_buffer(64, |cbb| unsafe { + // `EVP_marshal_private_key` should always return one because this + // key is valid by construction. + assert_eq!(1, bssl_sys::EVP_marshal_private_key(cbb, pkey.as_ffi_ptr())); + }) + } + + pub fn to_point(&self) -> Point { + // Safety: `self.0` is valid by construction. + let group = unsafe { bssl_sys::EC_KEY_get0_group(self.0) }; + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(self.0) }; + // A `Key` is never constructed without a public key. + assert!(!point.is_null()); + // Safety: pointers are valid and `clone_from_ptr` doesn't take + // ownership. + unsafe { Point::clone_from_ptr(group, point) } + } + + pub fn to_x962_uncompressed(&self) -> Buffer { + // Safety: `self.0` is valid by construction. + let group = unsafe { bssl_sys::EC_KEY_get0_group(self.0) }; + let point = unsafe { bssl_sys::EC_KEY_get0_public_key(self.0) }; + // Safety: arguments are valid, `EC_KEY` ensures that the the group is + // correct for the point, and a `Key` always holds a finite public point. + unsafe { to_x962_uncompressed(group, point) } + } + + pub fn to_der_subject_public_key_info(&self) -> Buffer { + // Safety: `self.0` is always valid by construction. + unsafe { to_der_subject_public_key_info(self.0) } + } +} + +impl Drop for Key { + fn drop(&mut self) { + // Safety: `self.0` must be valid because only valid `Key`s can + // be constructed. + unsafe { bssl_sys::EC_KEY_free(self.0) } + } +} + +/// Serialize a finite point to uncompressed X9.62 format. +/// +/// Callers must ensure that the arguments are valid, that the point has the +/// specified group, and that the point is finite. +unsafe fn to_x962_uncompressed( + group: *const bssl_sys::EC_GROUP, + point: *const bssl_sys::EC_POINT, +) -> Buffer { + cbb_to_buffer(65, |cbb| unsafe { + // Safety: the caller must ensure that the arguments are valid. + let result = bssl_sys::EC_POINT_point2cbb( + cbb, + group, + point, + bssl_sys::point_conversion_form_t::POINT_CONVERSION_UNCOMPRESSED, + /*bn_ctx=*/ null_mut(), + ); + // The public key is always finite, so `EC_POINT_point2cbb` only fails + // if out of memory, which isn't handled by this crate. + assert_eq!(result, 1); + }) +} + +unsafe fn to_der_subject_public_key_info(ec_key: *mut bssl_sys::EC_KEY) -> Buffer { + let mut pkey = scoped::EvpPkey::new(); + // Safety: this takes a reference to `ec_key` and so doesn't steal ownership. + assert_eq!(1, unsafe { + bssl_sys::EVP_PKEY_set1_EC_KEY(pkey.as_ffi_ptr(), ec_key) + }); + cbb_to_buffer(65, |cbb| unsafe { + // The arguments are valid so this will only fail if out of memory, + // which this crate doesn't handle. + assert_eq!(1, bssl_sys::EVP_marshal_public_key(cbb, pkey.as_ffi_ptr())); + }) +} + #[cfg(test)] mod test { - use crate::ec::P521; + use super::*; - use super::{Curve, EcGroupRef, P256}; + fn test_point_format<Serialize, Parse>(serialize_func: Serialize, parse_func: Parse) + where + Serialize: FnOnce(&Point) -> Buffer, + Parse: Fn(&[u8]) -> Option<Point>, + { + let key = Key::generate(Group::P256); + let point = key.to_point(); - #[test] - fn test_ec_group_clone_and_eq() { - let group = P256::ec_group(); - let group_clone = group.to_owned(); - let group2: &EcGroupRef = &group_clone; - assert!(group == group2); + let mut vec = serialize_func(&point).as_ref().to_vec(); + let point2 = parse_func(vec.as_slice()).unwrap(); + assert_eq!( + point.to_x962_uncompressed().as_ref(), + point2.to_x962_uncompressed().as_ref() + ); + + assert!(parse_func(&vec.as_slice()[0..16]).is_none()); + + vec[10] ^= 1; + assert!(parse_func(vec.as_slice()).is_none()); + vec[10] ^= 1; + + assert!(parse_func(b"").is_none()); } #[test] - fn test_ec_group_not_equal() { - let group = P256::ec_group(); - let group2 = P521::ec_group(); - assert!(group != group2) + fn x962() { + let x962 = b"\x04\x74\xcf\x69\xcb\xd1\x2b\x75\x07\x42\x85\xcf\x69\x6f\xc2\x56\x4b\x90\xe7\xeb\xbc\xd0\xe7\x20\x36\x86\x66\xbe\xcc\x94\x75\xa2\xa4\x4c\x2a\xf8\xa2\x56\xb8\x92\xb7\x7d\x17\xba\x97\x93\xbb\xf2\x9f\x52\x26\x7d\x90\xf9\x2c\x37\x26\x02\xbb\x4e\xd1\x89\x7c\xad\x54"; + assert!(Point::from_x962_uncompressed(Group::P256, x962).is_some()); + + test_point_format( + |point| point.to_x962_uncompressed(), + |buf| Point::from_x962_uncompressed(Group::P256, buf), + ); + } + + #[test] + fn spki() { + test_point_format( + |point| point.to_der_subject_public_key_info(), + |buf| Point::from_der_subject_public_key_info(Group::P256, buf), + ); + } + + fn test_key_format<Serialize, Parse>(serialize_func: Serialize, parse_func: Parse) + where + Serialize: FnOnce(&Key) -> Buffer, + Parse: Fn(&[u8]) -> Option<Key>, + { + let key = Key::generate(Group::P256); + + let vec = serialize_func(&key).as_ref().to_vec(); + let key2 = parse_func(vec.as_slice()).unwrap(); + assert_eq!( + key.to_x962_uncompressed().as_ref(), + key2.to_x962_uncompressed().as_ref() + ); + + assert!(parse_func(&vec.as_slice()[0..16]).is_none()); + assert!(parse_func(b"").is_none()); + } + + #[test] + fn der_ec_private_key() { + test_key_format( + |key| key.to_der_ec_private_key(), + |buf| Key::from_der_ec_private_key(Group::P256, buf), + ); + } + + #[test] + fn der_private_key_info() { + test_key_format( + |key| key.to_der_private_key_info(), + |buf| Key::from_der_private_key_info(Group::P256, buf), + ); + } + + #[test] + fn big_endian() { + test_key_format( + |key| key.to_big_endian(), + |buf| Key::from_big_endian(Group::P256, buf), + ); } }
diff --git a/rust/bssl-crypto/src/ecdh.rs b/rust/bssl-crypto/src/ecdh.rs index aca711b..85476a9 100644 --- a/rust/bssl-crypto/src/ecdh.rs +++ b/rust/bssl-crypto/src/ecdh.rs
@@ -13,403 +13,192 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +//! Elliptic Curve Diffie-Hellman operations. +//! +//! This module implements ECDH over the NIST curves P-256 and P-384. +//! +//! ``` +//! use bssl_crypto::{ecdh, ec::P256}; +//! +//! let alice_private_key = ecdh::PrivateKey::<P256>::generate(); +//! let alice_public_key_serialized = alice_private_key.to_x962_uncompressed(); +//! +//! // Somehow, Alice's public key is sent to Bob. +//! let bob_private_key = ecdh::PrivateKey::<P256>::generate(); +//! let alice_public_key = +//! ecdh::PublicKey::<P256>::from_x962_uncompressed( +//! alice_public_key_serialized.as_ref()) +//! .unwrap(); +//! let shared_key1 = bob_private_key.compute_shared_key(&alice_public_key); +//! +//! // Likewise, Alice gets Bob's public key and computes the same shared key. +//! let bob_public_key = bob_private_key.to_public_key(); +//! let shared_key2 = alice_private_key.compute_shared_key(&bob_public_key); +//! assert_eq!(shared_key1, shared_key2); +//! ``` + +use crate::{ec, sealed, with_output_vec, Buffer}; use alloc::vec::Vec; use core::marker::PhantomData; -use crate::{ - ec::{Curve, EcKey}, - pkey::{Pkey, PkeyCtx}, - CSliceMut, ForeignType, -}; - -pub use crate::ec::P256; - -/// Private key used in a elliptic curve Diffie-Hellman. -pub struct PrivateKey<C: Curve> { - /// An EcKey containing the private-public key pair - eckey: EcKey, +/// An ECDH public key over the given curve. +pub struct PublicKey<C: ec::Curve> { + point: ec::Point, marker: PhantomData<C>, } -/// Error type for ECDH operations. -#[derive(Debug)] -pub enum Error { - /// Failed when trying to convert between representations. - ConversionFailed, - /// The Diffie-Hellman key exchange failed. - DiffieHellmanFailed, -} - -impl<C: Curve> PrivateKey<C> { - /// Derives a shared secret from this private key and the given public key. - /// - /// # Panics - /// When `OUTPUT_SIZE` is insufficient to store the output of the shared secret. - #[allow(clippy::expect_used)] - pub fn diffie_hellman<const OUTPUT_SIZE: usize>( - &self, - other_public_key: &PublicKey<C>, - ) -> Result<SharedSecret<OUTPUT_SIZE>, Error> { - let pkey: Pkey = (&self.eckey).into(); - let pkey_ctx = PkeyCtx::new(&pkey); - let other_pkey: Pkey = (&other_public_key.eckey).into(); - let mut output = [0_u8; OUTPUT_SIZE]; - pkey_ctx - .diffie_hellman(&other_pkey, CSliceMut(&mut output)) - .map(|_| SharedSecret(output)) - .map_err(|_| Error::DiffieHellmanFailed) +impl<C: ec::Curve> PublicKey<C> { + /// Parse a public key in uncompressed X9.62 format. (This is the common + /// format for elliptic curve points beginning with an 0x04 byte.) + pub fn from_x962_uncompressed(x962: &[u8]) -> Option<Self> { + let point = ec::Point::from_x962_uncompressed(C::group(sealed::Sealed), x962)?; + Some(Self { + point, + marker: PhantomData, + }) } - /// Generate a new private key for use in a Diffie-Hellman key exchange. + /// Serialize this key as uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.point.to_x962_uncompressed() + } +} + +/// An ECDH private key over the given curve. +pub struct PrivateKey<C: ec::Curve> { + key: ec::Key, + marker: PhantomData<C>, +} + +impl<C: ec::Curve> PrivateKey<C> { + /// Generate a random private key. pub fn generate() -> Self { Self { - eckey: EcKey::generate(C::ec_group()), + key: ec::Key::generate(C::group(sealed::Sealed)), marker: PhantomData, } } - /// Tries to convert the given bytes into an private key. - /// - /// `private_key_bytes` is the octet form that consists of the content octets of the - /// `privateKey` `OCTET STRING` in an `ECPrivateKey` ASN.1 structure. - /// - /// Returns an error if the given bytes is not a valid representation of a P-256 private key. - pub fn from_private_bytes(private_key_bytes: &[u8]) -> Result<Self, Error> { - EcKey::try_from_raw_bytes(C::ec_group(), private_key_bytes) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } - - /// Serializes this private key as a big-endian integer, zero-padded to the size of key's group - /// order and returns the result. - pub fn to_bytes(&self) -> Vec<u8> { - self.eckey.to_raw_bytes() - } -} - -impl<'a, C: Curve> From<&'a PrivateKey<C>> for PublicKey<C> { - fn from(value: &'a PrivateKey<C>) -> Self { - Self { - eckey: value.eckey.clone(), + /// Parse a `PrivateKey` from a zero-padded, big-endian representation of the secret scalar. + pub fn from_big_endian(scalar: &[u8]) -> Option<Self> { + let key = ec::Key::from_big_endian(C::group(sealed::Sealed), scalar)?; + Some(Self { + key, marker: PhantomData, + }) + } + + /// Return the private scalar as zero-padded, big-endian bytes. + pub fn to_big_endian(&self) -> Buffer { + self.key.to_big_endian() + } + + /// Parse an ECPrivateKey structure (from RFC 5915). The key must be on the + /// specified curve. + pub fn from_der_ec_private_key(der: &[u8]) -> Option<Self> { + let key = ec::Key::from_der_ec_private_key(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as an ECPrivateKey structure (from RFC 5915). + pub fn to_der_ec_private_key(&self) -> Buffer { + self.key.to_der_ec_private_key() + } + + /// Parse a PrivateKeyInfo structure (from RFC 5208). The key must be on the + /// specified curve. + pub fn from_der_private_key_info(der: &[u8]) -> Option<Self> { + let key = ec::Key::from_der_private_key_info(C::group(sealed::Sealed), der)?; + Some(Self { + key, + marker: PhantomData, + }) + } + + /// Serialize this private key as a PrivateKeyInfo structure (from RFC 5208). + pub fn to_der_private_key_info(&self) -> Buffer { + self.key.to_der_private_key_info() + } + + /// Serialize the _public_ part of this key in uncompressed X9.62 format. + pub fn to_x962_uncompressed(&self) -> Buffer { + self.key.to_x962_uncompressed() + } + + /// Compute the shared key between this private key and the given public key. + /// The result should be used with a key derivation function that includes + /// the two public keys. + pub fn compute_shared_key(&self, other_public_key: &PublicKey<C>) -> Vec<u8> { + // 384 bits is the largest curve supported. The buffer is sized to be + // larger than this so that truncation of the output can be noticed. + let max_output = 384 / 8 + 1; + unsafe { + with_output_vec(max_output, |out_buf| { + // Safety: + // - `out_buf` points to at least `max_output` bytes, as + // required. + // - The `EC_POINT` and `EC_KEY` pointers are valid by construction. + let num_out_bytes = bssl_sys::ECDH_compute_key( + out_buf as *mut core::ffi::c_void, + max_output, + other_public_key.point.as_ffi_ptr(), + self.key.as_ffi_ptr(), + None, + ); + // Out of memory is not handled by this crate. + assert!(num_out_bytes > 0); + let num_out_bytes = num_out_bytes as usize; + // If the buffer was completely filled then it was probably + // truncated, which should never happen. + assert!(num_out_bytes < max_output); + num_out_bytes + }) } } -} -/// A public key for elliptic curve. -#[derive(Clone, Debug)] -pub struct PublicKey<C: Curve> { - /// An EcKey containing the public key - eckey: EcKey, - marker: PhantomData<C>, -} - -impl<C: Curve> Eq for PublicKey<C> {} - -impl<C: Curve> PartialEq for PublicKey<C> { - fn eq(&self, other: &Self) -> bool { - self.eckey.public_key_eq(&other.eckey) - } -} - -impl<C: Curve> PublicKey<C> { - /// Converts this public key to its byte representation. - pub fn to_vec(&self) -> Vec<u8> { - self.eckey.to_vec() - } - - /// Converts the given affine coordinates into a public key. - pub fn from_affine_coordinates<const AFFINE_COORDINATE_SIZE: usize>( - x: &[u8; AFFINE_COORDINATE_SIZE], - y: &[u8; AFFINE_COORDINATE_SIZE], - ) -> Result<Self, Error> { - assert_eq!(AFFINE_COORDINATE_SIZE, C::AFFINE_COORDINATE_SIZE); - EcKey::try_new_public_key_from_affine_coordinates(C::ec_group(), &x[..], &y[..]) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } - - /// Converts this public key to its affine coordinates. - pub fn to_affine_coordinates<const AFFINE_COORDINATE_SIZE: usize>( - &self, - ) -> ([u8; AFFINE_COORDINATE_SIZE], [u8; AFFINE_COORDINATE_SIZE]) { - assert_eq!(AFFINE_COORDINATE_SIZE, C::AFFINE_COORDINATE_SIZE); - let (bn_x, bn_y) = self.eckey.to_affine_coordinates(); - - let mut x_bytes_uninit = core::mem::MaybeUninit::<[u8; AFFINE_COORDINATE_SIZE]>::uninit(); - let mut y_bytes_uninit = core::mem::MaybeUninit::<[u8; AFFINE_COORDINATE_SIZE]>::uninit(); - // Safety: - // - `BigNum` guarantees the validity of its ptr - // - The size of `x/y_bytes_uninit` and the length passed to `BN_bn2bin_padded` are both - // `AFFINE_COORDINATE_SIZE` - let (result_x, result_y) = unsafe { - ( - bssl_sys::BN_bn2bin_padded( - x_bytes_uninit.as_mut_ptr() as *mut _, - AFFINE_COORDINATE_SIZE, - bn_x.as_ptr(), - ), - bssl_sys::BN_bn2bin_padded( - y_bytes_uninit.as_mut_ptr() as *mut _, - AFFINE_COORDINATE_SIZE, - bn_y.as_ptr(), - ), - ) - }; - assert_eq!(result_x, 1, "bssl_sys::BN_bn2bin_padded failed"); - assert_eq!(result_y, 1, "bssl_sys::BN_bn2bin_padded failed"); - - // Safety: Fields initialized by `BN_bn2bin_padded` above. - unsafe { (x_bytes_uninit.assume_init(), y_bytes_uninit.assume_init()) } - } -} - -impl<C: Curve> TryFrom<&[u8]> for PublicKey<C> { - type Error = Error; - - fn try_from(value: &[u8]) -> Result<Self, Error> { - EcKey::try_new_public_key_from_bytes(C::ec_group(), value) - .map(|eckey| Self { - eckey, - marker: PhantomData, - }) - .map_err(|_| Error::ConversionFailed) - } -} - -/// 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. -pub struct SharedSecret<const SIZE: usize>(pub(crate) [u8; SIZE]); - -impl<const SIZE: usize> SharedSecret<SIZE> { - /// Gets a copy of the shared secret. - pub fn to_bytes(&self) -> [u8; SIZE] { - self.0 - } - - /// Gets a reference to the underlying data in this shared secret. - pub fn as_bytes(&self) -> &[u8; SIZE] { - &self.0 + /// Return the public key corresponding to this private key. + pub fn to_public_key(&self) -> PublicKey<C> { + PublicKey { + point: self.key.to_point(), + marker: PhantomData, + } } } #[cfg(test)] -#[allow(clippy::unwrap_used, clippy::expect_used)] -mod tests { - use crate::{ - ec::{Curve, P224, P256, P384, P521}, - ecdh::{PrivateKey, PublicKey}, - test_helpers::decode_hex, - }; +mod test { + use super::*; + use crate::ec::{P256, P384}; - #[test] - fn p224_test_diffie_hellman() { - // From wycheproof ecdh_secp224r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 57] = decode_hex(concat!( - "047d8ac211e1228eb094e285a957d9912e93deee433ed777440ae9fc719b01d0", - "50dfbe653e72f39491be87fb1a2742daa6e0a2aada98bb1aca", - )); - let private_key_bytes: [u8; 28] = - decode_hex("565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328"); - let expected_shared_secret: [u8; 28] = - decode_hex("b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f"); + fn check_curve<C: ec::Curve>() { + let alice_private_key = PrivateKey::<C>::generate(); + let alice_public_key = alice_private_key.to_public_key(); + let alice_private_key = + PrivateKey::<C>::from_big_endian(alice_private_key.to_big_endian().as_ref()).unwrap(); + let alice_private_key = PrivateKey::<C>::from_der_ec_private_key( + alice_private_key.to_der_ec_private_key().as_ref(), + ) + .unwrap(); - let public_key: PublicKey<P224> = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); + let bob_private_key = PrivateKey::<C>::generate(); + let bob_public_key = bob_private_key.to_public_key(); - assert_eq!(actual_shared_secret.0, expected_shared_secret); + let shared_key1 = alice_private_key.compute_shared_key(&bob_public_key); + let shared_key2 = bob_private_key.compute_shared_key(&alice_public_key); + + assert_eq!(shared_key1, shared_key2); } #[test] - fn p256_test_diffie_hellman() { - // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 65] = decode_hex(concat!( - "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f", - "26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf", - )); - let private_key_bytes: [u8; 32] = - decode_hex("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"); - let expected_shared_secret: [u8; 32] = - decode_hex("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285"); - - let public_key: PublicKey<P256> = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); + fn p256() { + check_curve::<P256>(); } #[test] - fn p384_test_diffie_hellman() { - // From wycheproof ecdh_secp384r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 97] = decode_hex(concat!( - "04790a6e059ef9a5940163183d4a7809135d29791643fc43a2f17ee8bf677ab8", - "4f791b64a6be15969ffa012dd9185d8796d9b954baa8a75e82df711b3b56eadf", - "f6b0f668c3b26b4b1aeb308a1fcc1c680d329a6705025f1c98a0b5e5bfcb163caa", - )); - let private_key_bytes: [u8; 48] = decode_hex(concat!( - "766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1", - "fb1dee4edd64356b9f21c588b75dfd81" - )); - let expected_shared_secret: [u8; 48] = decode_hex(concat!( - "6461defb95d996b24296f5a1832b34db05ed031114fbe7d98d098f93859866e4", - "de1e229da71fef0c77fe49b249190135" - )); - - let public_key: PublicKey<P384> = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); - } - - #[test] - fn p521_test_diffie_hellman() { - // From wycheproof ecdh_secp521r1_ecpoint_test.json, tcId 1 - // sec1 public key manually extracted from the ASN encoded test data - let public_key_bytes: [u8; 133] = decode_hex(concat!( - "040064da3e94733db536a74a0d8a5cb2265a31c54a1da6529a198377fbd38575", - "d9d79769ca2bdf2d4c972642926d444891a652e7f492337251adf1613cf30779", - "99b5ce00e04ad19cf9fd4722b0c824c069f70c3c0e7ebc5288940dfa92422152", - "ae4a4f79183ced375afb54db1409ddf338b85bb6dbfc5950163346bb63a90a70", - "c5aba098f7", - )); - let private_key_bytes: [u8; 66] = decode_hex(concat!( - "01939982b529596ce77a94bc6efd03e92c21a849eb4f87b8f619d506efc9bb22", - "e7c61640c90d598f795b64566dc6df43992ae34a1341d458574440a7371f611c", - "7dcd" - )); - let expected_shared_secret: [u8; 66] = decode_hex(concat!( - "01f1e410f2c6262bce6879a3f46dfb7dd11d30eeee9ab49852102e1892201dd1", - "0f27266c2cf7cbccc7f6885099043dad80ff57f0df96acf283fb090de53df95f", - "7d87", - )); - - let public_key: PublicKey<P521> = (&public_key_bytes[..]).try_into().unwrap(); - let private_key = PrivateKey::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - let actual_shared_secret = private_key.diffie_hellman(&public_key).unwrap(); - - assert_eq!(actual_shared_secret.0, expected_shared_secret); - } - - #[test] - fn p224_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::<P224, 28>() - } - - #[test] - fn p256_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::<P256, 32>() - } - - #[test] - fn p384_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::<P384, 48>() - } - - #[test] - fn p521_generate_diffie_hellman_matches() { - generate_diffie_hellman_matches::<P521, 66>() - } - - fn generate_diffie_hellman_matches<C: Curve, const OUTPUT_SIZE: usize>() { - let private_key_1 = PrivateKey::<C>::generate(); - let private_key_2 = PrivateKey::<C>::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::<OUTPUT_SIZE>(&public_key_2) - .unwrap(); - let diffie_hellman_2 = private_key_2 - .diffie_hellman::<OUTPUT_SIZE>(&public_key_1) - .unwrap(); - - assert_eq!(diffie_hellman_1.to_bytes(), diffie_hellman_2.to_bytes()); - } - - #[test] - fn p224_to_private_bytes() { - let private_key_bytes: [u8; 28] = - decode_hex("565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328"); - let private_key = PrivateKey::<P224>::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p256_to_private_bytes() { - let private_key_bytes: [u8; 32] = - decode_hex("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"); - let private_key = PrivateKey::<P256>::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p384_to_private_bytes() { - let private_key_bytes: [u8; 48] = decode_hex(concat!( - "766e61425b2da9f846c09fc3564b93a6f8603b7392c785165bf20da948c49fd1", - "fb1dee4edd64356b9f21c588b75dfd81" - )); - let private_key = PrivateKey::<P384>::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p521_to_private_bytes() { - let private_key_bytes: [u8; 66] = decode_hex(concat!( - "01939982b529596ce77a94bc6efd03e92c21a849eb4f87b8f619d506efc9bb22", - "e7c61640c90d598f795b64566dc6df43992ae34a1341d458574440a7371f611c", - "7dcd", - )); - let private_key = PrivateKey::<P521>::from_private_bytes(&private_key_bytes) - .expect("Input private key should be valid"); - assert_eq!(&private_key.to_bytes()[..], &private_key_bytes[..]); - } - - #[test] - fn p224_affine_coordinates_test() { - affine_coordinates_test::<P224, { P224::AFFINE_COORDINATE_SIZE }>(); - } - - #[test] - fn p256_affine_coordinates_test() { - affine_coordinates_test::<P256, { P256::AFFINE_COORDINATE_SIZE }>(); - } - - #[test] - fn p384_affine_coordinates_test() { - affine_coordinates_test::<P384, { P384::AFFINE_COORDINATE_SIZE }>(); - } - - #[test] - fn p521_affine_coordinates_test() { - affine_coordinates_test::<P521, { P521::AFFINE_COORDINATE_SIZE }>(); - } - - fn affine_coordinates_test<C: Curve, const AFFINE_COORDINATE_SIZE: usize>() { - let private_key = PrivateKey::<C>::generate(); - let public_key = PublicKey::from(&private_key); - - let (x, y) = public_key.to_affine_coordinates::<AFFINE_COORDINATE_SIZE>(); - - let recreated_public_key = PublicKey::from_affine_coordinates(&x, &y); - assert_eq!(public_key, recreated_public_key.unwrap()); + fn p384() { + check_curve::<P384>(); } }
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index 38096c3..61a8dbc 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs
@@ -55,15 +55,13 @@ pub mod x25519; -/// Memory-manipulation operations. -pub mod mem; - -/// Elliptic curve diffie-hellman operations. +pub mod ec; pub mod ecdh; -pub(crate) mod bn; -pub(crate) mod ec; -pub(crate) mod pkey; +mod scoped; + +mod mem; +pub use mem::constant_time_compare; #[cfg(test)] mod test_helpers; @@ -365,6 +363,85 @@ Some(ret) } +/// Buffer represents an owned chunk of memory on the BoringSSL heap. +/// Call `as_ref()` to get a `&[u8]` from it. +pub struct Buffer { + // This pointer is always allocated by BoringSSL and must be freed using + // `OPENSSL_free`. + pub(crate) ptr: *mut u8, + pub(crate) len: usize, +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + if self.len == 0 { + return &[]; + } + // Safety: `ptr` and `len` describe a valid area of memory and `ptr` + // must be Rust-valid because `len` is non-zero. + unsafe { core::slice::from_raw_parts(self.ptr, self.len) } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + // Safety: `ptr` is owned by this object and is on the BoringSSL heap. + unsafe { + bssl_sys::OPENSSL_free(self.ptr as *mut core::ffi::c_void); + } + } +} + +/// Calls `parse_func` with a `CBS` structure pointing at `data`. +/// If that returns a null pointer then it returns [None]. +/// Otherwise, if there's still data left in CBS, it calls `free_func` on the +/// pointer and returns [None]. Otherwise it returns the pointer. +fn parse_with_cbs<T, Parse, Free>(data: &[u8], free_func: Free, parse_func: Parse) -> Option<*mut T> +where + Parse: FnOnce(*mut bssl_sys::CBS) -> *mut T, + Free: FnOnce(*mut T), +{ + // Safety: type checking ensures that `cbs` is the correct size. + let mut cbs = + unsafe { initialized_struct(|cbs| bssl_sys::CBS_init(cbs, data.as_ffi_ptr(), data.len())) }; + let ptr = parse_func(&mut cbs); + if ptr.is_null() { + return None; + } + // Safety: `cbs` is still valid after parsing. + if unsafe { bssl_sys::CBS_len(&cbs) } != 0 { + // Safety: `ptr` is still owned by this function. + free_func(ptr); + return None; + } + Some(ptr) +} + +/// Calls `func` with a `CBB` pointer and returns a [Buffer] of the ultimate +/// contents of that CBB. +#[allow(clippy::unwrap_used)] +fn cbb_to_buffer<F: FnOnce(*mut bssl_sys::CBB)>(initial_capacity: usize, func: F) -> Buffer { + // Safety: type checking ensures that `cbb` is the correct size. + let mut cbb = unsafe { + initialized_struct_fallible(|cbb| bssl_sys::CBB_init(cbb, initial_capacity) == 1) + } + // `CBB_init` only fails if out of memory, which isn't something that this crate handles. + .unwrap(); + func(&mut cbb); + + let mut ptr: *mut u8 = core::ptr::null_mut(); + let mut len: usize = 0; + // `CBB_finish` only fails on programming error, which we convert into a + // panic. + assert_eq!(1, unsafe { + bssl_sys::CBB_finish(&mut cbb, &mut ptr, &mut len) + }); + + // Safety: `ptr` is on the BoringSSL heap and ownership is returned by + // `CBB_finish`. + Buffer { ptr, len } +} + /// Used to prevent external implementations of internal traits. mod sealed { pub struct Sealed;
diff --git a/rust/bssl-crypto/src/mem.rs b/rust/bssl-crypto/src/mem.rs index a9031c4..9426dff 100644 --- a/rust/bssl-crypto/src/mem.rs +++ b/rust/bssl-crypto/src/mem.rs
@@ -13,21 +13,23 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +use crate::FfiSlice; + /// Returns true iff `a` and `b` contain the same bytes. It takes an amount of time dependent on the /// lengths, but independent of the contents of the slices `a` and `b`. The return type is a `bool`, /// since unlike `memcmp` in C this function cannot be used to put elements into a defined order. -pub fn crypto_memcmp(a: &[u8], b: &[u8]) -> bool { +pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { return false; } - if a.is_empty() && b.is_empty() { + if a.is_empty() { // Avoid FFI issues with empty slices that may potentially cause UB return true; } // Safety: // - The lengths of a and b are checked above. let result = - unsafe { bssl_sys::CRYPTO_memcmp(a.as_ptr() as *const _, b.as_ptr() as *const _, a.len()) }; + unsafe { bssl_sys::CRYPTO_memcmp(a.as_ffi_void_ptr(), b.as_ffi_void_ptr(), a.len()) }; result == 0 } @@ -37,26 +39,26 @@ #[test] fn test_different_length() { - assert!(!crypto_memcmp(&[0, 1, 2], &[0])) + assert!(!constant_time_compare(&[0, 1, 2], &[0])) } #[test] fn test_same_length_different_content() { - assert!(!crypto_memcmp(&[0, 1, 2], &[1, 2, 3])) + assert!(!constant_time_compare(&[0, 1, 2], &[1, 2, 3])) } #[test] fn test_same_content() { - assert!(crypto_memcmp(&[0, 1, 2], &[0, 1, 2])) + assert!(constant_time_compare(&[0, 1, 2], &[0, 1, 2])) } #[test] fn test_empty_slices() { - assert!(crypto_memcmp(&[], &[])) + assert!(constant_time_compare(&[], &[])) } #[test] fn test_empty_slices_different() { - assert!(!crypto_memcmp(&[], &[0, 1, 2])) + assert!(!constant_time_compare(&[], &[0, 1, 2])) } }
diff --git a/rust/bssl-crypto/src/pkey.rs b/rust/bssl-crypto/src/pkey.rs deleted file mode 100644 index aa60a9f..0000000 --- a/rust/bssl-crypto/src/pkey.rs +++ /dev/null
@@ -1,102 +0,0 @@ -/* 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. - */ - -//! `Pkey` and `PkeyCtx` classes for holding asymmetric keys. This module is intended for internal -//! use within this crate only, to create higher-level abstractions suitable to be exposed -//! externally. - -use crate::{ec::EcKey, CSliceMut, ForeignType}; -use alloc::{borrow::ToOwned, string::String}; - -pub(crate) struct Pkey { - ptr: *mut bssl_sys::EVP_PKEY, -} - -// Safety: Implementation ensures `from_ptr(x).as_ptr == x` -unsafe impl ForeignType for Pkey { - type CType = bssl_sys::EVP_PKEY; - - unsafe fn from_ptr(ptr: *mut Self::CType) -> Self { - Self { ptr } - } - - fn as_ptr(&self) -> *mut Self::CType { - self.ptr - } -} - -impl From<&EcKey> for Pkey { - fn from(eckey: &EcKey) -> Self { - // Safety: EVP_PKEY_new does not have any preconditions - let pkey = unsafe { bssl_sys::EVP_PKEY_new() }; - assert!(!pkey.is_null()); - // Safety: - // - pkey is just allocated and is null-checked - // - EcKey ensures eckey.ptr is valid during its lifetime - // - EVP_PKEY_set1_EC_KEY doesn't take ownership - let result = unsafe { bssl_sys::EVP_PKEY_set1_EC_KEY(pkey, eckey.as_ptr()) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_set1_EC_KEY failed"); - Self { ptr: pkey } - } -} - -impl Drop for Pkey { - fn drop(&mut self) { - // Safety: `self.ptr` is owned by this struct - unsafe { bssl_sys::EVP_PKEY_free(self.ptr) } - } -} - -pub(crate) struct PkeyCtx { - ptr: *mut bssl_sys::EVP_PKEY_CTX, -} - -impl PkeyCtx { - pub fn new(pkey: &Pkey) -> Self { - // Safety: - // - `Pkey` ensures `pkey.ptr` is valid, and EVP_PKEY_CTX_new does not take ownership. - let pkeyctx = unsafe { bssl_sys::EVP_PKEY_CTX_new(pkey.ptr, core::ptr::null_mut()) }; - assert!(!pkeyctx.is_null()); - Self { ptr: pkeyctx } - } - - #[allow(clippy::panic)] - pub(crate) fn diffie_hellman( - self, - other_public_key: &Pkey, - mut output: CSliceMut, - ) -> Result<(), String> { - let result = unsafe { bssl_sys::EVP_PKEY_derive_init(self.ptr) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_derive_init failed"); - - let result = unsafe { bssl_sys::EVP_PKEY_derive_set_peer(self.ptr, other_public_key.ptr) }; - assert_eq!(result, 1, "bssl_sys::EVP_PKEY_derive_set_peer failed"); - - let result = - unsafe { bssl_sys::EVP_PKEY_derive(self.ptr, output.as_mut_ptr(), &mut output.len()) }; - match result { - 0 => Err("bssl_sys::EVP_PKEY_derive failed".to_owned()), - 1 => Ok(()), - _ => panic!("Unexpected result {result:?} from bssl_sys::EVP_PKEY_derive"), - } - } -} - -impl Drop for PkeyCtx { - fn drop(&mut self) { - // Safety: self.ptr is owned by this struct - unsafe { bssl_sys::EVP_PKEY_CTX_free(self.ptr) } - } -}
diff --git a/rust/bssl-crypto/src/scoped.rs b/rust/bssl-crypto/src/scoped.rs new file mode 100644 index 0000000..fd889a5 --- /dev/null +++ b/rust/bssl-crypto/src/scoped.rs
@@ -0,0 +1,66 @@ +/* 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. + */ + +//! Helpers to ensure that some temporary objects are always freed. + +/// A scoped `EC_KEY`. +pub struct EvpPkey(*mut bssl_sys::EVP_PKEY); + +impl EvpPkey { + pub fn new() -> Self { + let ptr = unsafe { bssl_sys::EVP_PKEY_new() }; + // `ptr` is only NULL if we're out of memory, which this crate + // doesn't handle. + assert!(!ptr.is_null()); + EvpPkey(ptr) + } + + pub fn from_ptr(ptr: *mut bssl_sys::EVP_PKEY) -> Self { + EvpPkey(ptr) + } + + pub fn as_ffi_ptr(&mut self) -> *mut bssl_sys::EVP_PKEY { + self.0 + } +} + +impl Drop for EvpPkey { + fn drop(&mut self) { + unsafe { bssl_sys::EVP_PKEY_free(self.0) } + } +} + +/// A scoped `EC_KEY`. +pub struct EcKey(*mut bssl_sys::EC_KEY); + +impl EcKey { + pub fn new() -> Self { + let ptr = unsafe { bssl_sys::EC_KEY_new() }; + // `ptr` is only NULL if we're out of memory, which this crate + // doesn't handle. + assert!(!ptr.is_null()); + EcKey(ptr) + } + + pub fn as_ffi_ptr(&mut self) -> *mut bssl_sys::EC_KEY { + self.0 + } +} + +impl Drop for EcKey { + fn drop(&mut self) { + unsafe { bssl_sys::EC_KEY_free(self.0) } + } +}