Skip to content

Commit ff1cdb6

Browse files
authored
kem: use byte arrays for EK and SS (#2220)
Some terminology: - `EK`: encapsulated key, i.e. ciphertext, not to be confused with "encapsulation key", the public key - `SS`: shared secret, output of the decapsulator when given a ciphertext a.k.a. encapsulated key `EK` and `SK` were previously generic parameters on the `Encapsulate` and `Decapsulate` traits, however KEMs don't benefit from overlapping impls and have relatively fixed notions of what these types should be. This commit replaces them with new type aliases `Ciphertext` and `SharedSecret`: - `Ciphertext<K>`: type alias for `Array<u8, K::CiphertextSize>`, a.k.a. "encapsulated key", where `Array` is from `hybrid-array`. - `SharedSecret<K>`: type alias for `Array<u8, K::SharedSecretSize>` This means consumers of the traits always use bytestrings, which should hopefully make it dramatically simpler to implement things generically across KEMs. The `K` generic parameter above is for types which impl a new `KemParams` trait which defines two associated `ArraySize`s: - `KemParams::CiphertextSize`: size of the ciphertext - `KemParams::SharedSecretSize`: size of the shared secret This was split out into its own trait so the `Ciphertext<K>` and `SharedSecret<K>` type aliases work with either encapsulators or decapsulators. Next, `Decapsulate` was split into three(!) traits to handle fallible decapsulation: - `Decapsulate`: what we had before with the `Decapsulate::Encapsulator` associated type extracted into a supertrait `Decapsulator`, which it now bounds on. It has a provided `decapsulate_slice` method which returns `core::array::TryFromSliceError` in the event the provided slice does not match `CiphertextSize` - `TryDecapsulate`: fallible equivalent of `Decapsulate`, kind of like what we had prior to #2216, with an associated `Error` type and with a `try_decapsulate` method that returns a result. It also bounds on `Decapsulator` as its supertrait. - `Decapsulator`: common supertrait of `Decapsulate` and `TryDecapsulate` which defines the associated `Encapsulator` and provides the `Decapsulator::encapsulator` method for retrieving it. A blanket impl of `TryDecapsulate` is provided for types which impl `Decapsulate` which uses `Infallible` as the error type, so any type which impls `Decapsulate` can be used as a `TryDecapsulate`-bounded argument. Likewise `Decapsulate` carries a `TryDecapsulate<Error = Infallible>` bound which is satisfied by the blanket impl but also enforces this property. The reason we need to reintroduce fallible decapsulation is `dhkem`: when I was scanning over our KEMs repo looking at the error types, `dhkem` has `Error = Infallible`, but this hides that it was using `elliptic_curve::PublicKey<C>` as its "encapsulated key" / ciphertext type, which is a well-typed wrapper for a valid curve point. With this type now being a raw byte slice, `dhkem` needs to handle decoding the curve point in the `TryDecapsulate` impl, and if the point fails to decode return an error (there are ways we could pseudorandomly select a different point in constant time to compute a rejection symbol, but having it return an error in this case seems like a straightforward way to start). Closes #2219
1 parent 5537635 commit ff1cdb6

1 file changed

Lines changed: 101 additions & 14 deletions

File tree

kem/src/lib.rs

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,44 +12,131 @@ pub use common::{
1212
self, Generate, InvalidKey, Key, KeyExport, KeyInit, KeySizeUser, TryKeyInit, typenum::consts,
1313
};
1414

15+
use common::array::{self, ArraySize};
16+
use core::{array::TryFromSliceError, convert::Infallible};
1517
use rand_core::TryCryptoRng;
1618

1719
#[cfg(feature = "getrandom")]
1820
use {common::getrandom, rand_core::TryRngCore};
1921

22+
/// Ciphertext message (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`] which is
23+
/// an encrypted [`SharedSecret`] that can be decrypted using [`Decapsulate::decapsulate`].
24+
///
25+
/// `K` is expected to be a type that impls [`KemParams`], such as an encapsulator or decapsulator.
26+
pub type Ciphertext<K> = array::Array<u8, <K as KemParams>::CiphertextSize>;
27+
28+
/// Shared secret: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is
29+
/// also returned by [`Encapsulate::encapsulate`].
30+
///
31+
/// `K` is expected to be a type that impls [`KemParams`], such as an encapsulator or decapsulator.
32+
pub type SharedSecret<K> = array::Array<u8, <K as KemParams>::SharedSecretSize>;
33+
34+
/// Key encapsulation mechanism parameters: sizes of the ciphertext and decrypted plaintext.
35+
///
36+
/// This trait is impl'd by types that impl either [`Encapsulate`] or [`Decapsulate`] and defines
37+
/// the sizes of the encapsulated key and shared secret.
38+
pub trait KemParams {
39+
/// Size of the ciphertext (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`].
40+
type CiphertextSize: ArraySize;
41+
42+
/// Size of the shared secret after decapsulation by [`Decapsulate::decapsulate`].
43+
type SharedSecretSize: ArraySize;
44+
}
45+
2046
/// Encapsulator for shared secrets.
2147
///
2248
/// Often, this will just be a public key. However, it can also be a bundle of public keys, or it
2349
/// can include a sender's private key for authenticated encapsulation.
24-
pub trait Encapsulate<EK, SS>: TryKeyInit + KeyExport {
25-
/// Encapsulates a fresh shared secret
26-
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> Result<(EK, SS), R::Error>
27-
where
28-
R: TryCryptoRng + ?Sized;
50+
pub trait Encapsulate: KemParams + TryKeyInit + KeyExport {
51+
/// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number
52+
/// generator `R`.
53+
fn encapsulate_with_rng<R: TryCryptoRng + ?Sized>(
54+
&self,
55+
rng: &mut R,
56+
) -> Result<(Ciphertext<Self>, SharedSecret<Self>), R::Error>;
2957

3058
/// Encapsulate a fresh shared secret generated using the system's secure RNG.
3159
#[cfg(feature = "getrandom")]
32-
fn encapsulate(&self) -> (EK, SS) {
60+
fn encapsulate(&self) -> (Ciphertext<Self>, SharedSecret<Self>) {
3361
match self.encapsulate_with_rng(&mut getrandom::SysRng.unwrap_err()) {
3462
Ok(ret) => ret,
3563
}
3664
}
3765
}
3866

39-
/// Decapsulator for an encapsulated keys, with an associated encapsulator.
67+
/// Trait for decapsulators, which is a supertrait bound of both [`Decapsulate`] and
68+
/// [`TryDecapsulate`].
69+
pub trait Decapsulator:
70+
KemParams<
71+
CiphertextSize = <Self::Encapsulator as KemParams>::CiphertextSize,
72+
SharedSecretSize = <Self::Encapsulator as KemParams>::SharedSecretSize,
73+
>
74+
{
75+
/// Encapsulator which corresponds to this decapsulator.
76+
type Encapsulator: Encapsulate + Clone + KemParams;
77+
78+
/// Retrieve the encapsulator associated with this decapsulator.
79+
fn encapsulator(&self) -> &Self::Encapsulator;
80+
}
81+
82+
impl<K: Decapsulator> KemParams for K {
83+
type CiphertextSize = <K::Encapsulator as KemParams>::CiphertextSize;
84+
type SharedSecretSize = <K::Encapsulator as KemParams>::SharedSecretSize;
85+
}
86+
87+
/// Decapsulator for encapsulated keys, with an associated `Encapsulator` bounded by the
88+
/// [`Encapsulate`] trait.
4089
///
4190
/// Often, this will just be a secret key. But, as with [`Encapsulate`], it can be a bundle
4291
/// of secret keys, or it can include a sender's private key for authenticated encapsulation.
92+
/// It could also be a hardware device like an HSM, TPM, or SEP.
4393
///
4494
/// When possible (i.e. for software / non-HSM implementations) types which impl this trait should
4595
/// also impl the [`Generate`] trait to support key generation.
46-
pub trait Decapsulate<EK, SS> {
47-
/// Encapsulator which corresponds to this decapsulator.
48-
type Encapsulator: Encapsulate<EK, SS>;
96+
pub trait Decapsulate: Decapsulator + TryDecapsulate<Error = Infallible> {
97+
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
98+
fn decapsulate(&self, ct: &Ciphertext<Self>) -> SharedSecret<Self>;
4999

50-
/// Decapsulates the given encapsulated key
51-
fn decapsulate(&self, encapsulated_key: &EK) -> SS;
100+
/// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key".
101+
///
102+
/// # Errors
103+
/// - If the length of `ct` is not equal to `<Self as Kem>::CiphertextSize`.
104+
fn decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, TryFromSliceError> {
105+
ct.try_into().map(|ct| self.decapsulate(&ct))
106+
}
107+
}
52108

53-
/// Retrieve the encapsulator associated with this decapsulator.
54-
fn encapsulator(&self) -> Self::Encapsulator;
109+
/// Decapsulator for encapsulated keys with failure handling, with an associated `Encapsulator`
110+
/// bounded by the [`Encapsulate`] trait.
111+
///
112+
/// Prefer to implement the [`Decapsulate`] trait if possible. See that trait's documentation for
113+
/// more information.
114+
pub trait TryDecapsulate: Decapsulator {
115+
/// Decapsulation error
116+
type Error: core::error::Error;
117+
118+
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
119+
fn try_decapsulate(&self, ct: &Ciphertext<Self>) -> Result<SharedSecret<Self>, Self::Error>;
120+
121+
/// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key".
122+
///
123+
/// # Errors
124+
/// - If the length of `ct` is not equal to `<Self as Kem>::CiphertextSize`.
125+
fn try_decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, Self::Error>
126+
where
127+
Self::Error: From<TryFromSliceError>,
128+
{
129+
self.try_decapsulate(ct.try_into()?)
130+
}
131+
}
132+
133+
impl<D> TryDecapsulate for D
134+
where
135+
D: Decapsulate,
136+
{
137+
type Error = Infallible;
138+
139+
fn try_decapsulate(&self, ct: &Ciphertext<Self>) -> Result<SharedSecret<Self>, Infallible> {
140+
Ok(self.decapsulate(ct))
141+
}
55142
}

0 commit comments

Comments
 (0)