diff options
author | Maurice Lam <yukl@google.com> | 2023-06-01 02:07:35 +0000 |
---|---|---|
committer | Boringssl LUCI CQ <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-09-05 18:30:42 +0000 |
commit | 37be47b0cc1a71c5027634453ae3fcaaf2dcd92e (patch) | |
tree | 606d80b44bf7483605d234bac5f0904971caa696 /rust | |
parent | 6ca49385b168f47a50e7172d82a590b218f55e4d (diff) | |
download | boringssl-37be47b0cc1a71c5027634453ae3fcaaf2dcd92e.zip boringssl-37be47b0cc1a71c5027634453ae3fcaaf2dcd92e.tar.gz boringssl-37be47b0cc1a71c5027634453ae3fcaaf2dcd92e.tar.bz2 |
Add ecdh and P256 bindings to bssl-crypto
Bug: 285223043
Change-Id: Ia997b9765476d05c58649ee49ebf04905e65c478
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60267
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
Diffstat (limited to 'rust')
-rw-r--r-- | rust/bssl-crypto/src/bn.rs | 61 | ||||
-rw-r--r-- | rust/bssl-crypto/src/ec.rs | 421 | ||||
-rw-r--r-- | rust/bssl-crypto/src/ecdh.rs | 412 | ||||
-rw-r--r-- | rust/bssl-crypto/src/hkdf.rs | 6 | ||||
-rw-r--r-- | rust/bssl-crypto/src/lib.rs | 32 | ||||
-rw-r--r-- | rust/bssl-crypto/src/pkey.rs | 102 | ||||
-rw-r--r-- | rust/bssl-crypto/src/test_helpers.rs | 2 |
7 files changed, 1035 insertions, 1 deletions
diff --git a/rust/bssl-crypto/src/bn.rs b/rust/bssl-crypto/src/bn.rs new file mode 100644 index 0000000..35a196a --- /dev/null +++ b/rust/bssl-crypto/src/bn.rs @@ -0,0 +1,61 @@ +/* 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 new file mode 100644 index 0000000..06f74d3 --- /dev/null +++ b/rust/bssl-crypto/src/ec.rs @@ -0,0 +1,421 @@ +/* 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. + */ + +//! `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. + +use core::panic; +use std::{borrow::Borrow, fmt::Debug, ops::Deref}; + +use crate::{bn::BigNum, CSlice, CSliceMut, ForeignType, ForeignTypeRef}; + +#[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`]. +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; +} + +/// 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`. +#[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 _) } + } +} + +/// The P-384 curve, corresponding to `NID_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 _) } + } +} + +/// The P-521 curve, corresponding to `NID_secp521r1`. +#[derive(Debug)] +pub struct P521; + +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 _) } + } +} + +#[cfg(test)] +mod test { + use crate::ec::P521; + + use super::{Curve, EcGroupRef, P256}; + + #[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); + } + + #[test] + fn test_ec_group_not_equal() { + let group = P256::ec_group(); + let group2 = P521::ec_group(); + assert!(group != group2) + } +} diff --git a/rust/bssl-crypto/src/ecdh.rs b/rust/bssl-crypto/src/ecdh.rs new file mode 100644 index 0000000..ec8944c --- /dev/null +++ b/rust/bssl-crypto/src/ecdh.rs @@ -0,0 +1,412 @@ +/* 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 std::marker::PhantomData; + +use crate::{ + ec::{Curve, EcKey}, + pkey::{Pkey, PkeyCtx}, + CSliceMut, ForeignType, +}; + +/// Private key used in a elliptic curve Diffie-Hellman. +pub struct PrivateKey<C: Curve> { + /// An EcKey containing the private-public key pair + eckey: EcKey, + 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) + } + + /// Generate a new private key for use in a Diffie-Hellman key exchange. + pub fn generate() -> Self { + Self { + eckey: EcKey::generate(C::ec_group()), + 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(), + marker: PhantomData, + } + } +} + +/// 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 + } +} + +#[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, + }; + + #[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"); + + 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(); + + assert_eq!(actual_shared_secret.0, expected_shared_secret); + } + + #[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); + } + + #[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()); + } +} diff --git a/rust/bssl-crypto/src/hkdf.rs b/rust/bssl-crypto/src/hkdf.rs index efd569b..8eadcd0 100644 --- a/rust/bssl-crypto/src/hkdf.rs +++ b/rust/bssl-crypto/src/hkdf.rs @@ -94,6 +94,12 @@ impl<M: Md> Hkdf<M> { } #[cfg(test)] +#[allow( + clippy::expect_used, + clippy::panic, + clippy::indexing_slicing, + clippy::unwrap_used +)] mod tests { use crate::hkdf::{HkdfSha256, HkdfSha512}; use crate::test_helpers::{decode_hex, decode_hex_into_vec}; diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index 99140b7..0ad352d 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs @@ -53,6 +53,13 @@ pub mod rand; /// Memory-manipulation operations. pub mod mem; +/// Elliptic curve diffie-hellman operations. +pub mod ecdh; + +pub(crate) mod bn; +pub(crate) mod ec; +pub(crate) mod pkey; + #[cfg(test)] mod test_helpers; @@ -111,7 +118,7 @@ impl<'a> From<&'a mut [u8]> for CSliceMut<'a> { /// Implementations of `ForeignTypeRef` must guarantee the following: /// /// - `Self::from_ptr(x).as_ptr() == x` -/// - `Self::from_mut_ptr(x).as_ptr() == x` +/// - `Self::from_ptr_mut(x).as_ptr() == x` unsafe trait ForeignTypeRef: Sized { /// The raw C type. type CType; @@ -144,3 +151,26 @@ unsafe trait ForeignTypeRef: Sized { self as *const _ as *mut _ } } + +/// A helper trait implemented by types which has an owned reference to foreign types. +/// +/// # Safety +/// +/// Implementations of `ForeignType` must guarantee the following: +/// +/// - `Self::from_ptr(x).as_ptr() == x` +unsafe trait ForeignType { + /// The raw C type. + type CType; + + /// Constructs an instance of this type from its raw type. + /// + /// # Safety + /// + /// - `ptr` must be a valid, immutable, instance of `CType`. + /// - Ownership of `ptr` is passed to the implementation, and will free `ptr` when dropped. + unsafe fn from_ptr(ptr: *mut Self::CType) -> Self; + + /// Returns a raw pointer to the wrapped value. + fn as_ptr(&self) -> *mut Self::CType; +} diff --git a/rust/bssl-crypto/src/pkey.rs b/rust/bssl-crypto/src/pkey.rs new file mode 100644 index 0000000..f72c13c --- /dev/null +++ b/rust/bssl-crypto/src/pkey.rs @@ -0,0 +1,102 @@ +/* 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}; + +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/test_helpers.rs b/rust/bssl-crypto/src/test_helpers.rs index ea2d9db..e4b4afb 100644 --- a/rust/bssl-crypto/src/test_helpers.rs +++ b/rust/bssl-crypto/src/test_helpers.rs @@ -13,6 +13,7 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#[allow(clippy::expect_used, clippy::unwrap_used, clippy::indexing_slicing)] pub(crate) fn decode_hex<const N: usize>(s: &str) -> [u8; N] { (0..s.len()) .step_by(2) @@ -23,6 +24,7 @@ pub(crate) fn decode_hex<const N: usize>(s: &str) -> [u8; N] { .unwrap() } +#[allow(clippy::expect_used, clippy::unwrap_used, clippy::indexing_slicing)] pub(crate) fn decode_hex_into_vec(s: &str) -> Vec<u8> { (0..s.len()) .step_by(2) |