hpke/
kem.rs

1//! Traits and structs for key encapsulation mechanisms
2
3use crate::{Deserializable, HpkeError, Serializable};
4
5use core::fmt::Debug;
6
7use generic_array::{ArrayLength, GenericArray};
8use rand_core::{CryptoRng, RngCore};
9use zeroize::Zeroize;
10
11mod dhkem;
12pub use dhkem::*;
13
14/// Represents authenticated encryption functionality
15pub trait Kem: Sized {
16    /// The key exchange's public key type. If you want to generate a keypair, see
17    /// `Kem::gen_keypair` or `Kem::derive_keypair`
18    type PublicKey: Clone + Debug + PartialEq + Eq + Serializable + Deserializable;
19
20    /// The key exchange's private key type. If you want to generate a keypair, see
21    /// `Kem::gen_keypair` or `Kem::derive_keypair`
22    type PrivateKey: Clone + PartialEq + Eq + Serializable + Deserializable;
23
24    /// Computes the public key of a given private key
25    fn sk_to_pk(sk: &Self::PrivateKey) -> Self::PublicKey;
26    /// The encapsulated key for this KEM. This is used by the recipient to derive the shared
27    /// secret.
28    type EncappedKey: Clone + Serializable + Deserializable;
29
30    /// The size of a shared secret in this KEM
31    #[doc(hidden)]
32    type NSecret: ArrayLength<u8>;
33
34    /// The algorithm identifier for a KEM implementation
35    const KEM_ID: u16;
36
37    /// Deterministically derives a keypair from the given input keying material
38    ///
39    /// Requirements
40    /// ============
41    /// This keying material SHOULD have as many bits of entropy as the bit length of a secret key,
42    /// i.e., `8 * Self::PrivateKey::size()`. For X25519 and P-256, this is 256 bits of
43    /// entropy.
44    fn derive_keypair(ikm: &[u8]) -> (Self::PrivateKey, Self::PublicKey);
45
46    /// Generates a random keypair using the given RNG
47    fn gen_keypair<R: CryptoRng + RngCore>(csprng: &mut R) -> (Self::PrivateKey, Self::PublicKey) {
48        // Make some keying material that's the size of a private key
49        let mut ikm: GenericArray<u8, <Self::PrivateKey as Serializable>::OutputSize> =
50            GenericArray::default();
51        // Fill it with randomness
52        csprng.fill_bytes(&mut ikm);
53        // Run derive_keypair using the KEM's KDF
54        Self::derive_keypair(&ikm)
55    }
56
57    /// Derives a shared secret given the encapsulated key and the recipients secret key. If
58    /// `pk_sender_id` is given, the sender's identity will be tied to the shared secret.
59    ///
60    /// Return Value
61    /// ============
62    /// Returns a shared secret on success. If an error happened during key exchange, returns
63    /// `Err(HpkeError::DecapError)`.
64    #[doc(hidden)]
65    fn decap(
66        sk_recip: &Self::PrivateKey,
67        pk_sender_id: Option<&Self::PublicKey>,
68        encapped_key: &Self::EncappedKey,
69    ) -> Result<SharedSecret<Self>, HpkeError>;
70
71    /// Derives a shared secret and an ephemeral pubkey that the owner of the reciepint's pubkey
72    /// can use to derive the same shared secret. If `sk_sender_id` is given, the sender's identity
73    /// will be tied to the shared secret. All this does is generate an ephemeral keypair and pass
74    /// to `encap_with_eph`.
75    ///
76    /// Return Value
77    /// ============
78    /// Returns a shared secret and encapped key on success. If an error happened during key
79    /// exchange, returns `Err(HpkeError::EncapError)`.
80    #[doc(hidden)]
81    fn encap<R: CryptoRng + RngCore>(
82        pk_recip: &Self::PublicKey,
83        sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>,
84        csprng: &mut R,
85    ) -> Result<(SharedSecret<Self>, Self::EncappedKey), HpkeError>;
86}
87
88// Kem is used as a type parameter everywhere. To avoid confusion, alias it
89use Kem as KemTrait;
90
91/// A convenience type for `[u8; NSecret]` for any given KEM
92#[doc(hidden)]
93pub struct SharedSecret<Kem: KemTrait>(pub GenericArray<u8, Kem::NSecret>);
94
95impl<Kem: KemTrait> Default for SharedSecret<Kem> {
96    fn default() -> SharedSecret<Kem> {
97        SharedSecret(GenericArray::<u8, Kem::NSecret>::default())
98    }
99}
100
101// SharedSecrets should zeroize on drop
102impl<Kem: KemTrait> Zeroize for SharedSecret<Kem> {
103    fn zeroize(&mut self) {
104        self.0.zeroize()
105    }
106}
107impl<Kem: KemTrait> Drop for SharedSecret<Kem> {
108    fn drop(&mut self) {
109        self.zeroize();
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use crate::{kem::Kem as KemTrait, Deserializable, Serializable};
116
117    use rand::{rngs::StdRng, SeedableRng};
118
119    macro_rules! test_encap_correctness {
120        ($test_name:ident, $kem_ty:ty) => {
121            /// Tests that encap and decap produce the same shared secret when composed
122            #[test]
123            fn $test_name() {
124                type Kem = $kem_ty;
125
126                let mut csprng = StdRng::from_os_rng();
127                let (sk_recip, pk_recip) = Kem::gen_keypair(&mut csprng);
128
129                // Encapsulate a random shared secret
130                let (auth_shared_secret, encapped_key) =
131                    Kem::encap(&pk_recip, None, &mut csprng).unwrap();
132
133                // Decap it
134                let decapped_auth_shared_secret =
135                    Kem::decap(&sk_recip, None, &encapped_key).unwrap();
136
137                // Ensure that the encapsulated secret is what decap() derives
138                assert_eq!(auth_shared_secret.0, decapped_auth_shared_secret.0);
139
140                //
141                // Now do it with the auth, i.e., using the sender's identity keys
142                //
143
144                // Make a sender identity keypair
145                let (sk_sender_id, pk_sender_id) = Kem::gen_keypair(&mut csprng);
146
147                // Encapsulate a random shared secret
148                let (auth_shared_secret, encapped_key) = Kem::encap(
149                    &pk_recip,
150                    Some((&sk_sender_id, &pk_sender_id.clone())),
151                    &mut csprng,
152                )
153                .unwrap();
154
155                // Decap it
156                let decapped_auth_shared_secret =
157                    Kem::decap(&sk_recip, Some(&pk_sender_id), &encapped_key).unwrap();
158
159                // Ensure that the encapsulated secret is what decap() derives
160                assert_eq!(auth_shared_secret.0, decapped_auth_shared_secret.0);
161            }
162        };
163    }
164
165    /// Tests that an deserialize-serialize round trip on an encapped key ends up at the same value
166    macro_rules! test_encapped_serialize {
167        ($test_name:ident, $kem_ty:ty) => {
168            #[test]
169            fn $test_name() {
170                type Kem = $kem_ty;
171
172                // Encapsulate a random shared secret
173                let encapped_key = {
174                    let mut csprng = StdRng::from_os_rng();
175                    let (_, pk_recip) = Kem::gen_keypair(&mut csprng);
176                    Kem::encap(&pk_recip, None, &mut csprng).unwrap().1
177                };
178                // Serialize it
179                let encapped_key_bytes = encapped_key.to_bytes();
180                // Deserialize it
181                let new_encapped_key =
182                    <<Kem as KemTrait>::EncappedKey as Deserializable>::from_bytes(
183                        &encapped_key_bytes,
184                    )
185                    .unwrap();
186
187                assert_eq!(
188                    new_encapped_key.0, encapped_key.0,
189                    "encapped key doesn't serialize correctly"
190                );
191            }
192        };
193    }
194
195    #[cfg(feature = "x25519")]
196    mod x25519_tests {
197        use super::*;
198
199        test_encap_correctness!(test_encap_correctness_x25519, crate::kem::X25519HkdfSha256);
200        test_encapped_serialize!(test_encapped_serialize_x25519, crate::kem::X25519HkdfSha256);
201    }
202
203    #[cfg(feature = "p256")]
204    mod p256_tests {
205        use super::*;
206
207        test_encap_correctness!(test_encap_correctness_p256, crate::kem::DhP256HkdfSha256);
208        test_encapped_serialize!(test_encapped_serialize_p256, crate::kem::DhP256HkdfSha256);
209    }
210
211    #[cfg(feature = "p384")]
212    mod p384_tests {
213        use super::*;
214
215        test_encap_correctness!(test_encap_correctness_p384, crate::kem::DhP384HkdfSha384);
216        test_encapped_serialize!(test_encapped_serialize_p384, crate::kem::DhP384HkdfSha384);
217    }
218
219    #[cfg(feature = "p521")]
220    mod p521_tests {
221        use super::*;
222
223        test_encap_correctness!(test_encap_correctness_p521, crate::kem::DhP521HkdfSha512);
224        test_encapped_serialize!(test_encapped_serialize_p521, crate::kem::DhP521HkdfSha512);
225    }
226}