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
1 change: 1 addition & 0 deletions Cargo.lock

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

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

[dependencies]
ctutils = "0.4"
hkdf = "0.13"
kem = "0.3"
rand_core = "0.10"
Expand Down
84 changes: 81 additions & 3 deletions dhkem/src/ecdh_kem.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Generic Elliptic Curve Diffie-Hellman KEM adapter.

use crate::{DecapsulationKey, EncapsulationKey};
use crate::{DecapsulationKey, EncapsulationKey, Error, HpkeKemId};
use core::marker::PhantomData;
use elliptic_curve::{
AffinePoint, CurveArithmetic, Error, FieldBytes, FieldBytesSize, PublicKey, SecretKey,
AffinePoint, CurveArithmetic, FieldBytes, FieldBytesSize, PublicKey, SecretKey, bigint,
ecdh::EphemeralSecret,
sec1,
sec1::{FromSec1Point, ModulusSize, ToSec1Point, UncompressedPoint, UncompressedPointSize},
};
use kem::{
Expand Down Expand Up @@ -124,11 +125,14 @@ where
{
type Error = Error;

#[inline]
fn try_decapsulate(
&self,
encapsulated_key: &Ciphertext<EcdhKem<C>>,
) -> Result<SharedKey<EcdhKem<C>>, Error> {
let encapsulated_key = PublicKey::<C>::from_sec1_bytes(encapsulated_key)?;
let encapsulated_key =
PublicKey::<C>::from_sec1_bytes(encapsulated_key).map_err(|_| Error::Decapsulation)?;

let shared_secret = self.dk.diffie_hellman(&encapsulated_key);
Ok(*shared_secret.raw_secret_bytes())
}
Expand Down Expand Up @@ -225,3 +229,77 @@ where
(pk, *ss.raw_secret_bytes())
}
}

impl<C> FromSec1Point<C> for EcdhEncapsulationKey<C>
where
C: CurveArithmetic,
C::FieldBytesSize: ModulusSize,
PublicKey<C>: FromSec1Point<C>,
{
fn from_sec1_point(point: &sec1::Sec1Point<C>) -> bigint::CtOption<Self> {
PublicKey::<C>::from_sec1_point(point).map(Into::into)
}
}

impl<C> ToSec1Point<C> for EcdhEncapsulationKey<C>
where
C: CurveArithmetic,
C::FieldBytesSize: ModulusSize,
PublicKey<C>: ToSec1Point<C>,
{
fn to_sec1_point(&self, compress: bool) -> sec1::Sec1Point<C> {
self.0.to_sec1_point(compress)
}
}

/// 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 = "p256")]
pub type NistP256Kem = EcdhKem<p256::NistP256>;
#[cfg(feature = "p256")]
impl HpkeKemId for NistP256Kem {
const KEM_ID: u16 = 0x10;
}

/// 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-384 DHKEM.
#[cfg(feature = "p384")]
pub type NistP384Kem = EcdhKem<p384::NistP384>;
#[cfg(feature = "p384")]
impl HpkeKemId for NistP384Kem {
const KEM_ID: u16 = 0x11;
}

/// 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>;
/// NIST P-521 DHKEM.
#[cfg(feature = "p521")]
pub type NistP521Kem = EcdhKem<p521::NistP521>;
#[cfg(feature = "p521")]
impl HpkeKemId for NistP521Kem {
const KEM_ID: u16 = 0x12;
}

/// secp256k1 ECDH Decapsulation Key.
#[cfg(feature = "k256")]
pub type Secp256k1DecapsulationKey = EcdhDecapsulationKey<k256::Secp256k1>;
/// secp256k1 ECDH Encapsulation Key.
#[cfg(feature = "k256")]
pub type Secp256k1EncapsulationKey = EcdhEncapsulationKey<k256::Secp256k1>;
/// secp256k1 DHKEM.
#[cfg(feature = "k256")]
pub type Secp256k1Kem = EcdhKem<k256::Secp256k1>;
29 changes: 29 additions & 0 deletions dhkem/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use core::fmt;

/// Error type.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum Error {
/// Decapsulation failed.
Decapsulation,

/// Length invalid.
Length,
}

impl core::error::Error for Error {}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Decapsulation => write!(f, "decapsulation error"),
Error::Length => write!(f, "invalid length"),
}
}
}

impl From<hkdf::InvalidLength> for Error {
fn from(_: hkdf::InvalidLength) -> Self {
Error::Length
}
}
47 changes: 31 additions & 16 deletions dhkem/src/expander.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub use hkdf::InvalidLength;
pub use hkdf::{InvalidLength, hmac::digest::block_api::EagerHash};

use core::iter;
use hkdf::{Hkdf, hmac::digest::block_api::EagerHash};
use hkdf::Hkdf;

#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
Expand All @@ -12,17 +12,32 @@ const PREFIXES_MAX: usize = 256;
/// Maximum size of input key material or info after applying prefixes.
const LABELED_INPUT_MAX: usize = PREFIXES_MAX + 64;

/// HPKE version identifier from `RFC9810 §4`.
/// HPKE version identifier from `RFC9180 §4`.
const HPKE_VERSION_ID: &[u8] = b"HPKE-v1";

/// HPKE suite ID from `RFC9810 §4`.
const HPKE_SUITE_ID: &[u8] = b"KEM\x00\x10";
/// Type representing an HPKE suite identifier with `KEM` prefix.
type HpkeSuiteId = [u8; 5];

/// Associates a given HPKE KEM ID with the given type.
pub trait HpkeKemId {
/// Identifier for a particular KEM, from [RFC9180 §7.1].
///
/// [RFC9180 §7.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1
const KEM_ID: u16;

/// Compute the full HPKE suite ID for this type.
#[must_use]
fn suite_id() -> HpkeSuiteId {
let [b0, b1] = Self::KEM_ID.to_be_bytes();
[b'K', b'E', b'M', b0, b1]
}
}

/// Expander: wrapper for [RFC5869] HKDF-Expand operation which can be used for HPKE's
/// `LabeledExtract` and `LabeledExpand` as described in [RFC9810 §4].
/// `LabeledExtract` and `LabeledExpand` as described in [RFC9180 §4].
///
/// [RFC5869]: https://datatracker.ietf.org/doc/html/rfc5869
/// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
/// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
#[derive(Debug)]
pub struct Expander<D: EagerHash> {
/// Inner HKDF instance
Expand Down Expand Up @@ -65,20 +80,20 @@ impl<D: EagerHash> Expander<D> {
}

/// Create a new expander which uses the prefixes that implement HPKE `LabeledExtract` as
/// described in [RFC9810 §4].
/// described in [RFC9180 §4].
///
/// # Errors
/// Returns [`InvalidLength`] if the concatenated prefixes are too long.
///
/// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
pub fn new_labeled_hpke(
/// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
pub fn new_labeled_hpke<K: HpkeKemId>(
salt: &[u8],
label: &[u8],
input_key_material: &[u8],
) -> Result<Self, InvalidLength> {
Self::new_prefixed(
salt,
&[HPKE_VERSION_ID, HPKE_SUITE_ID, label],
&[HPKE_VERSION_ID, &K::suite_id(), label],
input_key_material,
)
}
Expand All @@ -92,7 +107,7 @@ impl<D: EagerHash> Expander<D> {
/// Returns [`InvalidLength`] if info is too long.
///
/// [RFC5869]: https://datatracker.ietf.org/doc/html/rfc5869
/// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
/// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
self.hkdf.expand(info, okm)
}
Expand All @@ -115,13 +130,13 @@ impl<D: EagerHash> Expander<D> {
}

/// Create a new expander which uses the prefixes that implement HPKE `LabeledExpand` as
/// described in [RFC9810 §4].
/// described in [RFC9180 §4].
///
/// # Errors
/// Returns [`InvalidLength`] if label and/or info is too long.
///
/// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
pub fn expand_labeled_hpke(
/// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4
pub fn expand_labeled_hpke<K: HpkeKemId>(
&self,
label: &[u8],
info: &[u8],
Expand All @@ -132,7 +147,7 @@ impl<D: EagerHash> Expander<D> {
&[
&okm_len.to_be_bytes(),
HPKE_VERSION_ID,
HPKE_SUITE_ID,
&K::suite_id(),
label,
info,
],
Expand Down
Loading