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
20 changes: 10 additions & 10 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions dhkem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ keywords = ["crypto", "ecdh", "ecc"]
readme = "README.md"

[dependencies]
kem = "0.3.0-rc.2"
kem = "0.3.0-rc.3"
rand_core = "0.10.0-rc-6"

# optional dependencies
Expand All @@ -29,7 +29,7 @@ zeroize = { version = "1.8.1", optional = true, default-features = false }
[dev-dependencies]
getrandom = { version = "0.4.0-rc.1", features = ["sys_rng"] }
hex-literal = "1"
hkdf = "0.13.0-rc.3"
hkdf = "0.13.0-rc.4"
sha2 = "0.11.0-rc.4"

[features]
Expand Down
49 changes: 27 additions & 22 deletions dhkem/src/ecdh_kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use elliptic_curve::{
},
};
use kem::{
Ciphertext, Encapsulate, Generate, InvalidKey, KemParams, KeyExport, KeySizeUser, SharedSecret,
Ciphertext, Encapsulate, Generate, InvalidKey, Kem, KeyExport, KeySizeUser, SharedKey,
TryDecapsulate, TryKeyInit,
};
use rand_core::{CryptoRng, TryCryptoRng};
Expand All @@ -29,15 +29,20 @@ pub type EcdhEncapsulationKey<C> = EncapsulationKey<PublicKey<C>>;
/// traits from the `elliptic-curve` crate.
///
/// Implements a KEM interface that internally uses ECDH.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct EcdhKem<C: CurveArithmetic>(PhantomData<C>);

impl<C> KemParams for EcdhEncapsulationKey<C>
impl<C> Kem for EcdhKem<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
EcdhDecapsulationKey<C>: TryDecapsulate<Self> + Generate,
EcdhEncapsulationKey<C>: Encapsulate<Self> + Clone,
{
type DecapsulationKey = EcdhDecapsulationKey<C>;
type EncapsulationKey = EcdhEncapsulationKey<C>;
type CiphertextSize = UncompressedPointSize<C>;
type SharedSecretSize = FieldBytesSize<C>;
type SharedKeySize = FieldBytesSize<C>;
}

/// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`:
Expand Down Expand Up @@ -97,39 +102,39 @@ where
}
}

impl<C> Encapsulate for EcdhEncapsulationKey<C>
impl<C> Generate for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
{
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
Ok(EphemeralSecret::try_generate_from_rng(rng)?.into())
}
}

impl<C> Encapsulate<EcdhKem<C>> for EcdhEncapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext<Self>, SharedSecret<Self>)
fn encapsulate_with_rng<R>(
&self,
rng: &mut R,
) -> (Ciphertext<EcdhKem<C>>, SharedKey<EcdhKem<C>>)
where
R: CryptoRng + ?Sized,
{
// ECDH encapsulation involves creating a new ephemeral key pair and then doing DH
let sk = EphemeralSecret::generate_from_rng(rng);
let ss = sk.diffie_hellman(&self.0);

// TODO(tarcieri): sk.public_key().to_uncompressed_point()
let mut pk = UncompressedPoint::<C>::default();
pk.copy_from_slice(sk.public_key().to_encoded_point(false).as_bytes());

let pk = sk.public_key().to_uncompressed_point();
(pk, ss.raw_secret_bytes().clone())
}
}

impl<C> Generate for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
{
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
Ok(EphemeralSecret::try_generate_from_rng(rng)?.into())
}
}

impl<C> TryDecapsulate for EcdhDecapsulationKey<C>
impl<C> TryDecapsulate<EcdhKem<C>> for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
Expand All @@ -139,8 +144,8 @@ where

fn try_decapsulate(
&self,
encapsulated_key: &Ciphertext<Self>,
) -> Result<SharedSecret<Self>, Error> {
encapsulated_key: &Ciphertext<EcdhKem<C>>,
) -> Result<SharedKey<EcdhKem<C>>, Error> {
let encapsulated_key = PublicKey::<C>::from_sec1_bytes(encapsulated_key)?;
let shared_secret = self.dk.diffie_hellman(&encapsulated_key);
Ok(shared_secret.raw_secret_bytes().clone())
Expand Down
24 changes: 15 additions & 9 deletions dhkem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
//! [RFC9180]: https://datatracker.ietf.org/doc/html/rfc9180#name-dh-based-kem-dhkem
//! [TLS KEM combiner]: https://datatracker.ietf.org/doc/html/draft-ietf-tls-hybrid-design-10

pub use kem::{self, Decapsulator, Encapsulate, Generate, KemParams, TryDecapsulate};
pub use kem::{self, Encapsulate, Generate, Kem, TryDecapsulate};

#[cfg(feature = "ecdh")]
mod ecdh_kem;
Expand Down Expand Up @@ -60,21 +60,15 @@ pub struct DecapsulationKey<DK, EK> {
ek: EncapsulationKey<EK>,
}

impl<DK, EK> Decapsulator for DecapsulationKey<DK, EK>
where
EncapsulationKey<EK>: Encapsulate + Clone,
{
type Encapsulator = EncapsulationKey<EK>;

fn encapsulator(&self) -> &EncapsulationKey<EK> {
impl<DK, EK> AsRef<EncapsulationKey<EK>> for DecapsulationKey<DK, EK> {
fn as_ref(&self) -> &EncapsulationKey<EK> {
&self.ek
}
}

impl<DK, EK> From<DK> for DecapsulationKey<DK, EK>
where
EK: for<'a> From<&'a DK>,
EncapsulationKey<EK>: KemParams,
{
fn from(dk: DK) -> Self {
let ek = EncapsulationKey(EK::from(&dk));
Expand Down Expand Up @@ -140,27 +134,39 @@ impl<DK: Zeroize, EK> Zeroize for DecapsulationKey<DK, EK> {
#[cfg(feature = "zeroize")]
impl<DK: ZeroizeOnDrop, EK> ZeroizeOnDrop for DecapsulationKey<DK, EK> {}

/// NIST P-256 DHKEM.
#[cfg(feature = "p256")]
pub type NistP256Kem = EcdhKem<p256::NistP256>;
/// NIST P-256 ECDH Decapsulation Key.
#[cfg(feature = "p256")]
pub type NistP256DecapsulationKey = EcdhDecapsulationKey<p256::NistP256>;
/// NIST P-256 ECDH Encapsulation Key.
#[cfg(feature = "p256")]
pub type NistP256EncapsulationKey = EcdhEncapsulationKey<p256::NistP256>;

/// NIST P-256 DHKEM.
#[cfg(feature = "p384")]
pub type NistP384Kem = EcdhKem<p384::NistP384>;
/// NIST P-384 ECDH Decapsulation Key.
#[cfg(feature = "p384")]
pub type NistP384DecapsulationKey = EcdhDecapsulationKey<p384::NistP384>;
/// NIST P-384 ECDH Encapsulation Key.
#[cfg(feature = "p384")]
pub type NistP384EncapsulationKey = EcdhEncapsulationKey<p384::NistP384>;

/// NIST P-521 DHKEM.
#[cfg(feature = "p521")]
pub type NistP521Kem = EcdhKem<p521::NistP521>;
/// NIST P-521 ECDH Decapsulation Key.
#[cfg(feature = "p521")]
pub type NistP521DecapsulationKey = EcdhDecapsulationKey<p521::NistP521>;
/// NIST P-521 ECDH Encapsulation Key.
#[cfg(feature = "p521")]
pub type NistP521EncapsulationKey = EcdhEncapsulationKey<p521::NistP521>;

/// secp256k1 DHKEM.
#[cfg(feature = "p521")]
pub type Secp256k1Kem = EcdhKem<k256::Secp256k1>;
/// secp256k1 ECDH Decapsulation Key.
#[cfg(feature = "k256")]
pub type Secp256k1DecapsulationKey = EcdhDecapsulationKey<k256::Secp256k1>;
Expand Down
39 changes: 21 additions & 18 deletions dhkem/src/x25519_kem.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{DecapsulationKey, EncapsulationKey};
use kem::{
Decapsulate, Encapsulate, Generate, InvalidKey, KemParams, Key, KeyExport, KeySizeUser,
TryKeyInit, common::array::Array, consts::U32,
Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit,
common::array::Array, consts::U32,
};
use rand_core::{CryptoRng, TryCryptoRng, UnwrapErr};
use x25519::{PublicKey, ReusableSecret};
Expand All @@ -20,16 +20,19 @@ pub type X25519EncapsulationKey = EncapsulationKey<PublicKey>;
type Ciphertext = Array<u8, U32>;

/// X25519 shared secrets are also compressed Montgomery x/u-coordinates.
type SharedSecret = Array<u8, U32>;
type SharedKey = Array<u8, U32>;

/// X22519 Diffie-Hellman KEM adapter.
///
/// Implements a KEM interface that internally uses X25519 ECDH.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct X25519Kem;

impl KemParams for EncapsulationKey<PublicKey> {
impl Kem for X25519Kem {
type DecapsulationKey = X25519DecapsulationKey;
type EncapsulationKey = X25519EncapsulationKey;
type CiphertextSize = U32;
type SharedSecretSize = U32;
type SharedKeySize = U32;
}

/// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`:
Expand Down Expand Up @@ -69,8 +72,17 @@ impl KeyExport for X25519EncapsulationKey {
}
}

impl Encapsulate for X25519EncapsulationKey {
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext, SharedSecret)
impl Generate for X25519DecapsulationKey {
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
// TODO(tarcieri): don't panic! Fallible `ReusableSecret` generation?
Ok(Self::from(ReusableSecret::random_from_rng(&mut UnwrapErr(
rng,
))))
}
}

impl Encapsulate<X25519Kem> for X25519EncapsulationKey {
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext, SharedKey)
where
R: CryptoRng + ?Sized,
{
Expand All @@ -82,17 +94,8 @@ impl Encapsulate for X25519EncapsulationKey {
}
}

impl Generate for X25519DecapsulationKey {
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
// TODO(tarcieri): don't panic! Fallible `ReusableSecret` generation?
Ok(Self::from(ReusableSecret::random_from_rng(&mut UnwrapErr(
rng,
))))
}
}

impl Decapsulate for X25519DecapsulationKey {
fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedSecret {
impl Decapsulate<X25519Kem> for X25519DecapsulationKey {
fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedKey {
let public_key = PublicKey::from(encapsulated_key.0);
self.dk.diffie_hellman(&public_key).to_bytes().into()
}
Expand Down
4 changes: 2 additions & 2 deletions dhkem/tests/hpke_p256_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use dhkem::NistP256DecapsulationKey;
use elliptic_curve::Generate;
use hex_literal::hex;
use hkdf::Hkdf;
use kem::{Decapsulator, Encapsulate, KeyExport, TryDecapsulate};
use kem::{Encapsulate, KeyExport, TryDecapsulate};
use rand_core::{TryCryptoRng, TryRng};
use sha2::Sha256;

Expand Down Expand Up @@ -83,7 +83,7 @@ fn test_dhkem_p256_hkdf_sha256() {
"f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2"
)))
.unwrap();
let pkr = skr.encapsulator();
let pkr = skr.as_ref();
assert_eq!(&pkr.to_bytes(), &example_pkr);

let (pke, ss1) = pkr.encapsulate_with_rng(&mut ConstantRng(&hex!(
Expand Down
18 changes: 9 additions & 9 deletions dhkem/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
feature = "x25519"
))]

use kem::{Decapsulator, Encapsulate, Generate, TryDecapsulate};
use kem::{Encapsulate, Generate, Kem, TryDecapsulate};

fn test_kem<DK: Decapsulator + Generate + TryDecapsulate>() {
let dk = DK::generate();
let ek = dk.encapsulator();
fn test_kem<K: Kem>() {
let dk = K::DecapsulationKey::generate();
let ek = dk.as_ref().clone();
let (ek, ss1) = ek.encapsulate();
let ss2 = dk.try_decapsulate(&ek).unwrap();
assert_eq!(ss1.as_slice(), ss2.as_slice());
Expand All @@ -19,29 +19,29 @@ fn test_kem<DK: Decapsulator + Generate + TryDecapsulate>() {
#[cfg(feature = "x25519")]
#[test]
fn test_x25519() {
test_kem::<dhkem::X25519DecapsulationKey>();
test_kem::<dhkem::X25519Kem>();
}

#[cfg(feature = "k256")]
#[test]
fn test_k256() {
test_kem::<dhkem::Secp256k1DecapsulationKey>();
test_kem::<dhkem::Secp256k1Kem>();
}

#[cfg(feature = "p256")]
#[test]
fn test_p256() {
test_kem::<dhkem::NistP256DecapsulationKey>();
test_kem::<dhkem::NistP256Kem>();
}

#[cfg(feature = "p384")]
#[test]
fn test_p384() {
test_kem::<dhkem::NistP384DecapsulationKey>();
test_kem::<dhkem::NistP384Kem>();
}

#[cfg(feature = "p521")]
#[test]
fn test_p521() {
test_kem::<dhkem::NistP521DecapsulationKey>();
test_kem::<dhkem::NistP521Kem>();
}
Loading