Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ECDH + PKCS8 to bign256 #1046

Merged
merged 10 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading