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) }
+ }
+}