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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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