Skip to content

Commit

Permalink
bign256: add ECDH + PKCS8 support (#1046)
Browse files Browse the repository at this point in the history
  • Loading branch information
makavity authored Jul 24, 2024
1 parent e6ea0bd commit 893f5cc
Show file tree
Hide file tree
Showing 16 changed files with 800 additions and 58 deletions.
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions bign256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,32 @@ primeorder = { version = "=0.14.0-pre.0", optional = true, path = "../primeorder
signature = { version = "=2.3.0-pre.3", optional = true }
belt-hash = { version = "=0.2.0-pre.3", optional = true, default-features = false }
rfc6979 = { version = "=0.5.0-pre.3", optional = true }
rand_core = "0.6.4"
pkcs8 = { version = "0.11.0-pre.0", optional = true }
sec1 = { version = "0.8.0-pre.1", optional = true }
der = { version = "0.8.0-pre.0" }

digest = { version = "0.11.0-pre.8", optional = true }
hkdf = { version = "0.13.0-pre.3", optional = true }
hmac = { version = "0.13.0-pre.3", optional = true }

[dev-dependencies]
criterion = "0.5"
hex-literal = "0.4"
proptest = "1"
rand_core = { version = "0.6", features = ["getrandom"] }
hex = {version = "0.4" }
hex = { version = "0.4" }

[features]
default = ["arithmetic", "pkcs8", "std", "dsa"]
default = ["arithmetic", "pkcs8", "std", "ecdsa", "pem", "ecdh"]
alloc = ["elliptic-curve/alloc", "primeorder?/alloc"]
std = ["alloc", "elliptic-curve/std", "signature?/std"]

dsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"]
ecdsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"]
arithmetic = ["dep:primeorder", "elliptic-curve/arithmetic"]
pkcs8 = ["elliptic-curve/pkcs8"]
pem = ["pkcs8", "sec1/pem"]
pkcs8 = ["dep:pkcs8"]
ecdh = ["arithmetic", "elliptic-curve/ecdh", "dep:digest", "dep:hkdf", "dep:hmac", "dep:belt-hash", "alloc"]

[[bench]]
name = "field"
Expand Down
218 changes: 218 additions & 0 deletions bign256/src/ecdh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//! Elliptic Curve Diffie-Hellman Support.
//!
//! # ECDH Ephemeral (ECDHE) Usage
//!
//! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers
//! using a randomly generated set of keys for each exchange.
//!
//! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE]
//! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship
//! can be used to determine the authenticity of the ephemeral keys, such as
//! a digital signature. Without such an additional step, ECDHE is insecure!
//! (see security warning below)
//!
//! See the documentation for the [`EphemeralSecret`] type for more information
//! on performing ECDH ephemeral key exchanges.
//!
//! # Static ECDH Usage
//!
//! Static ECDH key exchanges are supported via the low-level
//! [`diffie_hellman`] function.
//!
//! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange
//! [SIGMA]: https://webee.technion.ac.il/~hugo/sigma-pdf.pdf
// use crate::{
// point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar,
// ProjectivePoint, PublicKey,
// };
// use core::borrow::Borrow;
// use digest::{crypto_common::BlockSizeUser, Digest};
// use group::Curve as _;
// use hkdf::{hmac::SimpleHmac, Hkdf};
// use rand_core::CryptoRngCore;
// use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::{AffinePoint, FieldBytes, NonZeroScalar, ProjectivePoint, PublicKey};
use belt_hash::BeltHash;
use core::borrow::Borrow;
use elliptic_curve::point::AffineCoordinates;
use elliptic_curve::zeroize::{Zeroize, ZeroizeOnDrop};
use hkdf::Hkdf;
use hmac::SimpleHmac;
use rand_core::CryptoRngCore;

/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function.
///
/// Whenever possible, we recommend using the high-level ECDH ephemeral API
/// provided by [`EphemeralSecret`].
///
/// However, if you are implementing a protocol which requires a static scalar
/// value as part of an ECDH exchange, this API can be used to compute a
/// [`SharedSecret`] from that value.
///
/// Note that this API operates on the low-level [`NonZeroScalar`] and
/// [`AffinePoint`] types. If you are attempting to use the higher-level
/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will
/// need to use the following conversions:
///
/// ```ignore
/// let shared_secret = elliptic_curve::ecdh::diffie_hellman(
/// secret_key.to_nonzero_scalar(),
/// public_key.as_affine()
/// );
/// ```
pub fn diffie_hellman(
secret_key: impl Borrow<NonZeroScalar>,
public_key: impl Borrow<AffinePoint>,
) -> SharedSecret {
let public_point = ProjectivePoint::from(*public_key.borrow());
#[allow(clippy::arithmetic_side_effects)]
let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine();
SharedSecret::new(secret_point)
}

/// Ephemeral Diffie-Hellman Secret.
///
/// These are ephemeral "secret key" values which are deliberately designed
/// to avoid being persisted.
///
/// To perform an ephemeral Diffie-Hellman exchange, do the following:
///
/// - Have each participant generate an [`EphemeralSecret`] value
/// - Compute the [`PublicKey`] for that value
/// - Have each peer provide their [`PublicKey`] to their counterpart
/// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`]
/// to compute a [`SharedSecret`] value.
///
/// # ⚠️ SECURITY WARNING ⚠️
///
/// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a
/// further authentication step are trivially vulnerable to man-in-the-middle
/// attacks!
///
/// These exchanges should be performed in the context of a protocol which
/// takes further steps to authenticate the peers in a key exchange.
pub struct EphemeralSecret {
scalar: NonZeroScalar,
}

impl EphemeralSecret {
/// Generate a cryptographically random [`EphemeralSecret`].
pub fn random(rng: &mut impl CryptoRngCore) -> Self {
Self {
scalar: NonZeroScalar::random(rng),
}
}

/// Get the public key associated with this ephemeral secret.
///
/// The `compress` flag enables point compression.
pub fn public_key(&self) -> PublicKey {
PublicKey::from_secret_scalar(&self.scalar)
}

/// Compute a Diffie-Hellman shared secret from an ephemeral secret and the
/// public key of the other participant in the exchange.
pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret {
diffie_hellman(self.scalar, public_key.as_affine())
}
}

impl From<&EphemeralSecret> for PublicKey {
fn from(ephemeral_secret: &EphemeralSecret) -> Self {
ephemeral_secret.public_key()
}
}

impl Zeroize for EphemeralSecret {
fn zeroize(&mut self) {
self.scalar.zeroize()
}
}

impl ZeroizeOnDrop for EphemeralSecret {}

impl Drop for EphemeralSecret {
fn drop(&mut self) {
self.zeroize();
}
}

/// Shared secret value computed via ECDH key agreement.
pub struct SharedSecret {
/// Computed secret value
secret_bytes: FieldBytes,
}

impl SharedSecret {
/// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve.
#[inline]
fn new(point: AffinePoint) -> Self {
Self {
secret_bytes: point.x(),
}
}

/// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to
/// extract entropy from this shared secret.
///
/// This method can be used to transform the shared secret into uniformly
/// random values which are suitable as key material.
///
/// The `D` type parameter is a cryptographic digest function.
/// `sha2::Sha256` is a common choice for use with HKDF.
///
/// The `salt` parameter can be used to supply additional randomness.
/// Some examples include:
///
/// - randomly generated (but authenticated) string
/// - fixed application-specific value
/// - previous shared secret used for rekeying (as in TLS 1.3 and Noise)
///
/// After initializing HKDF, use [`Hkdf::expand`] to obtain output key
/// material.
///
/// [HKDF]: https://en.wikipedia.org/wiki/HKDF
pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf<BeltHash, SimpleHmac<BeltHash>> {
Hkdf::new(salt, &self.secret_bytes)
}

/// This value contains the raw serialized x-coordinate of the elliptic curve
/// point computed from a Diffie-Hellman exchange, serialized as bytes.
///
/// When in doubt, use [`SharedSecret::extract`] instead.
///
/// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️
///
/// This value is not uniformly random and should not be used directly
/// as a cryptographic key for anything which requires that property
/// (e.g. symmetric ciphers).
///
/// Instead, the resulting value should be used as input to a Key Derivation
/// Function (KDF) or cryptographic hash function to produce a symmetric key.
/// The [`SharedSecret::extract`] function will do this for you.
pub fn raw_secret_bytes(&self) -> &FieldBytes {
&self.secret_bytes
}
}

impl From<FieldBytes> for SharedSecret {
/// NOTE: this impl is intended to be used by curve implementations to
/// instantiate a [`SharedSecret`] value from their respective
/// [`AffinePoint`] type.
///
/// Curve implementations should provide the field element representing
/// the affine x-coordinate as `secret_bytes`.
fn from(secret_bytes: FieldBytes) -> Self {
Self { secret_bytes }
}
}

impl ZeroizeOnDrop for SharedSecret {}

impl Drop for SharedSecret {
fn drop(&mut self) {
self.secret_bytes.zeroize()
}
}
8 changes: 4 additions & 4 deletions bign256/src/dsa.rs → bign256/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
//! use rand_core::OsRng; // requires 'getrandom` feature
//! use bign256::{
//! dsa::{Signature, SigningKey, signature::Signer},
//! ecdsa::{Signature, SigningKey, signature::Signer},
//! SecretKey
//! };
//!
//! // Signing
//! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()`
//! let signing_key = SigningKey::new(&secret_key)?;
//! let verifying_key_bytes = signing_key.verifying_key().to_sec1_bytes();
//! let verifying_key_bytes = signing_key.verifying_key().to_bytes();
//! let message = b"test message";
//! let signature: Signature = signing_key.sign(message);
//!
//! // Verifying
//! use bign256::dsa::{VerifyingKey, signature::Verifier};
//! use bign256::ecdsa::{VerifyingKey, signature::Verifier};
//!
//! let verifying_key = VerifyingKey::from_sec1_bytes(&verifying_key_bytes)?;
//! let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)?;
//! verifying_key.verify(message, &signature)?;
//! # Ok(())
//! # }
Expand Down
File renamed without changes.
24 changes: 10 additions & 14 deletions bign256/src/dsa/verifying.rs → bign256/src/ecdsa/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
//! 9. Return YES.
//! ```
#[cfg(feature = "alloc")]
use alloc::boxed::Box;

use super::{Signature, BELT_OID};
use crate::{
AffinePoint, BignP256, EncodedPoint, FieldBytes, Hash, ProjectivePoint, PublicKey, Scalar,
Expand All @@ -26,13 +29,11 @@ use elliptic_curve::{
array::{consts::U32, typenum::Unsigned, Array},
group::GroupEncoding,
ops::{LinearCombination, Reduce},
sec1::ToEncodedPoint,
Curve, Field, Group,
};
use signature::{hazmat::PrehashVerifier, Error, Result, Verifier};

#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use elliptic_curve::sec1::ToEncodedPoint;

/// Bign256 public key used for verifying signatures are valid for a given
/// message.
Expand Down Expand Up @@ -89,21 +90,16 @@ impl VerifyingKey {
hasher.finalize_fixed()
}

/// Initialize [`VerifyingKey`] from a SEC1-encoded public key.
pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
let public_key = PublicKey::from_sec1_bytes(bytes).map_err(|_| Error::new())?;
/// Parse a [`VerifyingKey`] from a byte slice.
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
let public_key = PublicKey::from_bytes(bytes).map_err(|_| Error::new())?;
Self::new(public_key)
}

/// Convert this [`VerifyingKey`] into the
/// `Elliptic-Curve-Point-to-Octet-String` encoding described in
/// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3
/// (page 10).
///
/// <http://www.secg.org/sec1-v2.pdf>
/// Serialize the [`VerifyingKey`] as a byte array.
#[cfg(feature = "alloc")]
pub fn to_sec1_bytes(&self) -> Box<[u8]> {
self.public_key.to_sec1_bytes()
pub fn to_bytes(&self) -> Box<[u8]> {
self.public_key.to_bytes()
}
}

Expand Down
Loading

0 comments on commit 893f5cc

Please sign in to comment.