Skip to content

Commit 4b29427

Browse files
committed
kem: use byte arrays for EK and SS
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 `Kem` trait which defines two associated `ArraySize`s: - `Kem::CiphertextSize`: size of the ciphertext - `Kem::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 0dabd78 commit 4b29427

2 files changed

Lines changed: 96 additions & 18 deletions

File tree

kem/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ Traits for Key Encapsulation Mechanisms (KEMs): public-key cryptosystems designe
1717
"""
1818

1919
[dependencies]
20-
crypto-common = { version = "0.2.0-rc.12", features = ["rand_core"] }
20+
common = { package = "crypto-common", version = "0.2.0-rc.12", features = ["rand_core"] }
2121
rand_core = "0.10.0-rc-5"
2222

2323
[features]
24-
getrandom = ["crypto-common/getrandom"]
24+
getrandom = ["common/getrandom"]
2525

2626
[package.metadata.docs.rs]
2727
all-features = true

kem/src/lib.rs

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,123 @@
88
#![forbid(unsafe_code)]
99
#![warn(missing_docs, unused_qualifications, missing_debug_implementations)]
1010

11-
pub use crypto_common::{Generate, KeyExport, KeySizeUser, TryKeyInit, typenum::consts};
11+
pub use common::{self, Generate, InvalidKey, KeyExport, KeySizeUser, TryKeyInit, typenum::consts};
1212

13+
use common::array::{self, ArraySize};
14+
use core::{array::TryFromSliceError, convert::Infallible};
1315
use rand_core::TryCryptoRng;
1416

1517
#[cfg(feature = "getrandom")]
16-
use {crypto_common::getrandom::SysRng, rand_core::TryRngCore};
18+
use {common::getrandom::SysRng, rand_core::TryRngCore};
19+
20+
/// Ciphertext message (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`] which is
21+
/// an encrypted [`SharedSecret`] that can be decrypted using [`Decapsulate::decapsulate`].
22+
///
23+
/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator.
24+
pub type Ciphertext<K> = array::Array<u8, <K as Kem>::CiphertextSize>;
25+
26+
/// Shared secret: plaintext produced after decapsulation by [`Decapsulate::decapsulate`] which is
27+
/// also returned by [`Encapsulate::encapsulate`].
28+
///
29+
/// `K` is expected to be a type that impls [`Kem`], such as an encapsulator or decapsulator.
30+
pub type SharedSecret<K> = array::Array<u8, <K as Kem>::SharedSecretSize>;
31+
32+
/// Key encapsulation mechanism.
33+
///
34+
/// This trait is impl'd by types that impl either [`Encapsulate`] or [`Decapsulate`] and defines
35+
/// the sizes of the encapsulated key and shared secret.
36+
pub trait Kem {
37+
/// Size of the ciphertext (a.k.a. "encapsulated key") produced by [`Encapsulate::encapsulate`].
38+
type CiphertextSize: ArraySize;
39+
40+
/// Size of the shared secret after decapsulation by [`Decapsulate::decapsulate`].
41+
type SharedSecretSize: ArraySize;
42+
}
1743

1844
/// Encapsulator for shared secrets.
1945
///
2046
/// Often, this will just be a public key. However, it can also be a bundle of public keys, or it
2147
/// can include a sender's private key for authenticated encapsulation.
22-
pub trait Encapsulate<EK, SS>: TryKeyInit + KeyExport {
23-
/// Encapsulates a fresh shared secret
24-
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> Result<(EK, SS), R::Error>
25-
where
26-
R: TryCryptoRng + ?Sized;
48+
pub trait Encapsulate: Kem + TryKeyInit + KeyExport {
49+
/// Encapsulates a fresh [`SharedSecret`] generated using the supplied random number
50+
/// generator `R`.
51+
fn encapsulate_with_rng<R: TryCryptoRng + ?Sized>(
52+
&self,
53+
rng: &mut R,
54+
) -> Result<(Ciphertext<Self>, SharedSecret<Self>), R::Error>;
2755

2856
/// Encapsulate a fresh shared secret generated using the system's secure RNG.
2957
#[cfg(feature = "getrandom")]
30-
fn encapsulate(&self) -> (EK, SS) {
58+
fn encapsulate(&self) -> (Ciphertext<Self>, SharedSecret<Self>) {
3159
let Ok(ret) = self.encapsulate_with_rng(&mut SysRng.unwrap_err());
3260
ret
3361
}
3462
}
3563

36-
/// Decapsulator for an encapsulated keys, with an associated encapsulator.
64+
/// Trait for decapsulators, which is a supertrait bound of both [`Decapsulate`] and
65+
/// [`TryDecapsulate`].
66+
pub trait Decapsulator: Kem {
67+
/// Encapsulator which corresponds to this decapsulator.
68+
type Encapsulator: Encapsulate
69+
+ Kem<CiphertextSize = Self::CiphertextSize, SharedSecretSize = Self::SharedSecretSize>;
70+
71+
/// Retrieve the encapsulator associated with this decapsulator.
72+
fn encapsulator(&self) -> Self::Encapsulator;
73+
}
74+
75+
/// Decapsulator for encapsulated keys, with an associated `Encapsulator` bounded by the
76+
/// [`Encapsulate`] trait.
3777
///
3878
/// Often, this will just be a secret key. But, as with [`Encapsulate`], it can be a bundle
3979
/// of secret keys, or it can include a sender's private key for authenticated encapsulation.
80+
/// It could also be a hardware device like an HSM, TPM, or SEP.
4081
///
4182
/// When possible (i.e. for software / non-HSM implementations) types which impl this trait should
4283
/// also impl the [`Generate`] trait to support key generation.
43-
pub trait Decapsulate<EK, SS> {
44-
/// Encapsulator which corresponds to this decapsulator.
45-
type Encapsulator: Encapsulate<EK, SS>;
84+
pub trait Decapsulate: Decapsulator + TryDecapsulate<Error = Infallible> {
85+
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
86+
fn decapsulate(&self, ct: &Ciphertext<Self>) -> SharedSecret<Self>;
4687

47-
/// Decapsulates the given encapsulated key
48-
fn decapsulate(&self, encapsulated_key: &EK) -> SS;
88+
/// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key".
89+
///
90+
/// # Errors
91+
/// - If the length of `ct` is not equal to `<Self as Kem>::CiphertextSize`.
92+
fn decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, TryFromSliceError> {
93+
ct.try_into().map(|ct| self.decapsulate(&ct))
94+
}
95+
}
4996

50-
/// Retrieve the encapsulator associated with this decapsulator.
51-
fn encapsulator(&self) -> Self::Encapsulator;
97+
/// Decapsulator for encapsulated keys with failure handling, with an associated `Encapsulator`
98+
/// bounded by the [`Encapsulate`] trait.
99+
///
100+
/// Prefer to implement the [`Decapsulate`] trait if possible. See that trait's documentation for
101+
/// more information.
102+
pub trait TryDecapsulate: Decapsulator {
103+
/// Decapsulation error
104+
type Error: core::error::Error;
105+
106+
/// Decapsulates the given [`Ciphertext`] a.k.a. "encapsulated key".
107+
fn try_decapsulate(&self, ct: &Ciphertext<Self>) -> Result<SharedSecret<Self>, Self::Error>;
108+
109+
/// Decapsulate the given byte slice containing a [`Ciphertext`] a.k.a. "encapsulated key".
110+
///
111+
/// # Errors
112+
/// - If the length of `ct` is not equal to `<Self as Kem>::CiphertextSize`.
113+
fn try_decapsulate_slice(&self, ct: &[u8]) -> Result<SharedSecret<Self>, Self::Error>
114+
where
115+
Self::Error: From<TryFromSliceError>,
116+
{
117+
self.try_decapsulate(ct.try_into()?)
118+
}
119+
}
120+
121+
impl<D> TryDecapsulate for D
122+
where
123+
D: Decapsulate,
124+
{
125+
type Error = Infallible;
126+
127+
fn try_decapsulate(&self, ct: &Ciphertext<Self>) -> Result<SharedSecret<Self>, Infallible> {
128+
Ok(self.decapsulate(ct))
129+
}
52130
}

0 commit comments

Comments
 (0)