Skip to content
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
4 changes: 3 additions & 1 deletion elliptic-curve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ subtle = { version = "2.2.2", default-features = false }
zeroize = { version = "1", optional = true, default-features = false }

[features]
default = []
default = ["rand"]
ecdh = ["rand", "weierstrass", "zeroize"]
rand = ["rand_core"]
weierstrass = []
std = []

Expand Down
197 changes: 197 additions & 0 deletions elliptic-curve/src/ecdh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//! Elliptic Curve Diffie-Hellman (Ephemeral) Support.
//!
//! This module contains a generic ECDH implementation which is usable with
//! any elliptic curve which implements the [`Arithmetic`] trait (presently
//! the `k256` and `p256` crates)
//!
//! # Usage
//!
//! Have each participant generate an [`EphemeralSecret`] value, compute the
//! [`PublicKey`] for that value, exchange public keys, then each participant
//! uses their [`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.

use crate::{
consts::U1,
point::Generator,
scalar::NonZeroScalar,
weierstrass::{
point::{CompressedPoint, CompressedPointSize, UncompressedPoint, UncompressedPointSize},
public_key::{FromPublicKey, PublicKey},
Curve,
},
Arithmetic, ElementBytes, Error, Generate,
};
use core::ops::{Add, Mul};
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroize;

/// Ephemeral Diffie-Hellman Secret.
///
/// These are ephemeral "secret key" values which are deliberately designed
/// to avoid being persisted.
pub struct EphemeralSecret<C>
where
C: Curve + Arithmetic,
C::Scalar: Generate + Zeroize,
{
scalar: NonZeroScalar<C>,
}

impl<C> EphemeralSecret<C>
where
C: Curve + Arithmetic,
C::Scalar: Clone + Generate + Zeroize,
C::AffinePoint: FromPublicKey<C> + Mul<NonZeroScalar<C>, Output = C::AffinePoint> + Zeroize,
C::ElementSize: Add<U1>,
<C::ElementSize as Add>::Output: Add<U1>,
CompressedPoint<C>: From<C::AffinePoint>,
UncompressedPoint<C>: From<C::AffinePoint>,
CompressedPointSize<C>: ArrayLength<u8>,
UncompressedPointSize<C>: ArrayLength<u8>,
{
/// Generate a new [`EphemeralSecret`].
pub fn generate(rng: impl CryptoRng + RngCore) -> Self {
Self {
scalar: NonZeroScalar::generate(rng),
}
}

/// Get the public key associated with this ephemeral secret.
///
/// The `compress` flag enables point compression.
pub fn public_key(&self, compress: bool) -> PublicKey<C> {
let affine_point = C::AffinePoint::generator() * self.scalar.clone();

if compress {
PublicKey::Compressed(affine_point.into())
} else {
PublicKey::Uncompressed(affine_point.into())
}
}

/// Compute a Diffie-Hellman shared secret from an ephemeral secret and the
/// [`PublicKey`] of the other participant in the exchange.
pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> Result<SharedSecret<C>, Error> {
let affine_point = C::AffinePoint::from_public_key(public_key);

if affine_point.is_some().into() {
let shared_secret = affine_point.unwrap() * self.scalar.clone();
Ok(SharedSecret::new(shared_secret.into()))
} else {
Err(Error)
}
}
}

impl<C> From<&EphemeralSecret<C>> for PublicKey<C>
where
C: Curve + Arithmetic,
C::Scalar: Clone + Generate + Zeroize,
C::AffinePoint: FromPublicKey<C> + Mul<NonZeroScalar<C>, Output = C::AffinePoint> + Zeroize,
C::ElementSize: Add<U1>,
<C::ElementSize as Add>::Output: Add<U1>,
CompressedPoint<C>: From<C::AffinePoint>,
UncompressedPoint<C>: From<C::AffinePoint>,
CompressedPointSize<C>: ArrayLength<u8>,
UncompressedPointSize<C>: ArrayLength<u8>,
{
fn from(ephemeral_secret: &EphemeralSecret<C>) -> Self {
ephemeral_secret.public_key(C::COMPRESS_POINTS)
}
}

impl<C> Zeroize for EphemeralSecret<C>
where
C: Curve + Arithmetic,
C::Scalar: Generate + Zeroize,
{
fn zeroize(&mut self) {
self.scalar.zeroize()
}
}

impl<C> Drop for EphemeralSecret<C>
where
C: Curve + Arithmetic,
C::Scalar: Generate + Zeroize,
{
fn drop(&mut self) {
self.zeroize();
}
}

/// Shared secret value computed via ECDH key agreement.
///
/// This value contains the raw serialized x-coordinate of the elliptic curve
/// point computed from a Diffie-Hellman exchange.
///
/// # ⚠️ 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.
pub struct SharedSecret<C: Curve + Arithmetic> {
/// Computed secret value
secret_bytes: ElementBytes<C>,
}

impl<C> SharedSecret<C>
where
C: Curve + Arithmetic,
C::AffinePoint: Zeroize,
C::ElementSize: Add<U1>,
<C::ElementSize as Add>::Output: Add<U1>,
UncompressedPointSize<C>: ArrayLength<u8>,
{
/// Create a new shared secret from the given uncompressed curve point
fn new(mut serialized_point: UncompressedPoint<C>) -> Self {
let secret_bytes = GenericArray::clone_from_slice(
&serialized_point.as_ref()[1..(1 + C::ElementSize::to_usize())],
);

serialized_point.zeroize();
Self { secret_bytes }
}

/// Shared secret value, serialized as bytes.
///
/// As noted in the comments for this struct, this value is non-uniform and
/// should not be used directly as a symmetric encryption key, but instead
/// as input to a KDF (or failing that, a hash function) used to produce
/// a symmetric key.
pub fn as_bytes(&self) -> &ElementBytes<C> {
&self.secret_bytes
}
}

impl<C> Zeroize for SharedSecret<C>
where
C: Curve + Arithmetic,
{
fn zeroize(&mut self) {
self.secret_bytes.zeroize()
}
}

impl<C> Drop for SharedSecret<C>
where
C: Curve + Arithmetic,
{
fn drop(&mut self) {
self.zeroize();
}
}
12 changes: 8 additions & 4 deletions elliptic-curve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub mod point;
pub mod scalar;
pub mod secret_key;

#[cfg(feature = "ecdh")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdh")))]
pub mod ecdh;

// TODO(tarcieri): other curve forms
#[cfg(feature = "weierstrass")]
#[cfg_attr(docsrs, doc(cfg(feature = "weierstrass")))]
Expand All @@ -39,7 +43,7 @@ pub use subtle;
#[cfg(feature = "oid")]
pub use oid;

#[cfg(feature = "rand_core")]
#[cfg(feature = "rand")]
pub use rand_core;

#[cfg(feature = "zeroize")]
Expand All @@ -52,7 +56,7 @@ use core::{
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};

#[cfg(feature = "rand_core")]
#[cfg(feature = "rand")]
use rand_core::{CryptoRng, RngCore};

/// Byte array containing a serialized scalar value (i.e. an integer)
Expand Down Expand Up @@ -99,8 +103,8 @@ pub trait FromBytes: ConditionallySelectable + Sized {
/// Randomly generate a value.
///
/// Primarily intended for use with scalar types for a particular curve.
#[cfg(feature = "rand_core")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub trait Generate {
/// Generate a random element of this type using the provided [`CryptoRng`]
fn generate(rng: impl CryptoRng + RngCore) -> Self;
Expand Down
30 changes: 28 additions & 2 deletions elliptic-curve/src/scalar.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
//! Scalar types

use crate::{Arithmetic, Curve};
use crate::{Arithmetic, Curve, FromBytes};
use subtle::{ConstantTimeEq, CtOption};

#[cfg(feature = "rand")]
use crate::{
rand_core::{CryptoRng, RngCore},
Generate,
};

#[cfg(feature = "zeroize")]
use zeroize::Zeroize;

Expand All @@ -14,6 +20,7 @@ use zeroize::Zeroize;
///
/// In the context of ECC, it's useful for ensuring that scalar multiplication
/// cannot result in the point at infinity.
#[derive(Clone)]
pub struct NonZeroScalar<C: Curve + Arithmetic> {
scalar: C::Scalar,
}
Expand All @@ -25,7 +32,8 @@ where
/// Create a [`NonZeroScalar`] from a scalar, performing a constant-time
/// check that it's non-zero.
pub fn new(scalar: C::Scalar) -> CtOption<Self> {
let is_zero = scalar.ct_eq(&C::Scalar::default());
let zero = C::Scalar::from_bytes(&Default::default()).unwrap();
let is_zero = scalar.ct_eq(&zero);
CtOption::new(Self { scalar }, !is_zero)
}
}
Expand All @@ -39,6 +47,24 @@ where
}
}

#[cfg(feature = "rand")]
impl<C> Generate for NonZeroScalar<C>
where
C: Curve + Arithmetic,
C::Scalar: Generate,
{
fn generate(mut rng: impl CryptoRng + RngCore) -> Self {
// Use rejection sampling to eliminate zeroes
loop {
let result = Self::new(C::Scalar::generate(&mut rng));

if result.is_some().into() {
break result.unwrap();
}
}
}
}

#[cfg(feature = "zeroize")]
impl<C> Zeroize for NonZeroScalar<C>
where
Expand Down
6 changes: 3 additions & 3 deletions elliptic-curve/src/secret_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use core::{
};
use generic_array::{typenum::Unsigned, GenericArray};

#[cfg(feature = "rand_core")]
#[cfg(feature = "rand")]
use {
crate::{Arithmetic, Generate},
rand_core::{CryptoRng, RngCore},
Expand Down Expand Up @@ -68,8 +68,8 @@ impl<C: Curve> Debug for SecretKey<C> {
}
}

#[cfg(feature = "rand_core")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
impl<C> Generate for SecretKey<C>
where
C: Curve + Arithmetic,
Expand Down
5 changes: 4 additions & 1 deletion elliptic-curve/src/weierstrass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ pub use self::{
};

/// Marker trait for elliptic curves in short Weierstrass form
pub trait Curve: super::Curve {}
pub trait Curve: super::Curve {
/// Should point compression be applied by default?
const COMPRESS_POINTS: bool;
}
Loading