hpke/
setup.rs

1use crate::{
2    aead::{Aead, AeadCtx, AeadCtxR, AeadCtxS},
3    kdf::{labeled_extract, DigestArray, Kdf as KdfTrait, LabeledExpand, MAX_DIGEST_SIZE},
4    kem::{Kem as KemTrait, SharedSecret},
5    op_mode::{OpMode, OpModeR, OpModeS},
6    util::full_suite_id,
7    HpkeError,
8};
9
10use rand_core::{CryptoRng, RngCore};
11use zeroize::Zeroize;
12
13/// Secret generated in `derive_enc_ctx` and stored in `AeadCtx`
14pub(crate) struct ExporterSecret<K: KdfTrait>(pub(crate) DigestArray<K>);
15
16// We use this to get an empty buffer we can read secret bytes into
17impl<K: KdfTrait> Default for ExporterSecret<K> {
18    fn default() -> ExporterSecret<K> {
19        ExporterSecret(DigestArray::<K>::default())
20    }
21}
22
23impl<K: KdfTrait> Clone for ExporterSecret<K> {
24    fn clone(&self) -> ExporterSecret<K> {
25        ExporterSecret(self.0.clone())
26    }
27}
28
29// Zero exporter secrets on drop
30impl<K: KdfTrait> Drop for ExporterSecret<K> {
31    fn drop(&mut self) {
32        self.0.zeroize();
33    }
34}
35
36// RFC 9180 §5.1
37// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
38//   VerifyPSKInputs(mode, psk, psk_id)
39//
40//   psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
41//   info_hash = LabeledExtract("", "info_hash", info)
42//   key_schedule_context = concat(mode, psk_id_hash, info_hash)
43//
44//   secret = LabeledExtract(shared_secret, "secret", psk)
45//
46//   key = LabeledExpand(secret, "key", key_schedule_context, Nk)
47//   base_nonce = LabeledExpand(secret, "base_nonce",
48//                              key_schedule_context, Nn)
49//   exporter_secret = LabeledExpand(secret, "exp",
50//                                   key_schedule_context, Nh)
51//
52//   return Context<ROLE>(key, base_nonce, 0, exporter_secret)
53
54// This is the KeySchedule function. It runs a KDF over all the parameters, inputs, and secrets,
55// and spits out a key-nonce pair to be used for symmetric encryption.
56fn derive_enc_ctx<A, Kdf, Kem, O>(
57    mode: &O,
58    shared_secret: SharedSecret<Kem>,
59    info: &[u8],
60) -> AeadCtx<A, Kdf, Kem>
61where
62    A: Aead,
63    Kdf: KdfTrait,
64    Kem: KemTrait,
65    O: OpMode<Kem>,
66{
67    // Put together the binding context used for all KDF operations
68    let suite_id = full_suite_id::<A, Kdf, Kem>();
69
70    // In KeySchedule(),
71    //   psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
72    //   info_hash = LabeledExtract("", "info_hash", info)
73    //   key_schedule_context = concat(mode, psk_id_hash, info_hash)
74
75    // We concat without allocation by making a buffer of the maximum possible size, then
76    // taking the appropriately sized slice.
77    let (sched_context_buf, sched_context_size) = {
78        let (psk_id_hash, _) =
79            labeled_extract::<Kdf>(&[], &suite_id, b"psk_id_hash", mode.get_psk_id());
80        let (info_hash, _) = labeled_extract::<Kdf>(&[], &suite_id, b"info_hash", info);
81
82        // Yes it's overkill to bound the first input by MAX_DIGEST_SIZE, since it's only 1 byte.
83        // But whatever, this is pretty clean.
84        concat_with_known_maxlen!(
85            MAX_DIGEST_SIZE,
86            &[mode.mode_id()],
87            psk_id_hash.as_slice(),
88            info_hash.as_slice()
89        )
90    };
91    let sched_context = &sched_context_buf[..sched_context_size];
92
93    // In KeySchedule(),
94    //   secret = LabeledExtract(shared_secret, "secret", psk)
95    //   key = LabeledExpand(secret, "key", key_schedule_context, Nk)
96    //   base_nonce = LabeledExpand(secret, "base_nonce", key_schedule_context, Nn)
97    //   exporter_secret = LabeledExpand(secret, "exp", key_schedule_context, Nh)
98    // Instead of `secret` we derive an HKDF context which we run .expand() on to derive the
99    // key-nonce pair.
100    let (_, secret_ctx) =
101        labeled_extract::<Kdf>(&shared_secret.0, &suite_id, b"secret", mode.get_psk_bytes());
102
103    // Empty fixed-size buffers
104    let mut key = crate::aead::AeadKey::<A>::default();
105    let mut base_nonce = crate::aead::AeadNonce::<A>::default();
106    let mut exporter_secret = <ExporterSecret<Kdf> as Default>::default();
107
108    // Fill the key, base nonce, and exporter secret. This only errors if the output values are
109    // 255x the digest size of the hash function. Since these values are fixed at compile time, we
110    // don't worry about it.
111    secret_ctx
112        .labeled_expand(&suite_id, b"key", sched_context, key.0.as_mut_slice())
113        .expect("aead key len is way too big");
114    secret_ctx
115        .labeled_expand(
116            &suite_id,
117            b"base_nonce",
118            sched_context,
119            base_nonce.0.as_mut_slice(),
120        )
121        .expect("nonce len is way too big");
122    secret_ctx
123        .labeled_expand(
124            &suite_id,
125            b"exp",
126            sched_context,
127            exporter_secret.0.as_mut_slice(),
128        )
129        .expect("exporter secret len is way too big");
130
131    AeadCtx::new(&key, base_nonce, exporter_secret)
132}
133
134// RFC 9180 §5.1.4:
135// def SetupAuthPSKS(pkR, info, psk, psk_id, skS):
136//   shared_secret, enc = AuthEncap(pkR, skS)
137//   return enc, KeyScheduleS(mode_auth_psk, shared_secret, info,
138//                            psk, psk_id)
139
140/// Initiates an encryption context to the given recipient public key
141///
142/// Return Value
143/// ============
144/// On success, returns an encapsulated public key (intended to be sent to the recipient), and an
145/// encryption context. If an error happened during key encapsulation, returns
146/// `Err(HpkeError::EncapError)`. This is the only possible error.
147pub fn setup_sender<A, Kdf, Kem, R>(
148    mode: &OpModeS<Kem>,
149    pk_recip: &Kem::PublicKey,
150    info: &[u8],
151    csprng: &mut R,
152) -> Result<(Kem::EncappedKey, AeadCtxS<A, Kdf, Kem>), HpkeError>
153where
154    A: Aead,
155    Kdf: KdfTrait,
156    Kem: KemTrait,
157    R: CryptoRng + RngCore,
158{
159    // If the identity key is set, use it
160    let sender_id_keypair = mode.get_sender_id_keypair();
161    // Do the encapsulation
162    let (shared_secret, encapped_key) = Kem::encap(pk_recip, sender_id_keypair, csprng)?;
163    // Use everything to derive an encryption context
164    let enc_ctx = derive_enc_ctx::<_, _, Kem, _>(mode, shared_secret, info);
165
166    Ok((encapped_key, enc_ctx.into()))
167}
168
169// RFC 9180 §5.1.4
170// def SetupAuthPSKR(enc, skR, info, psk, psk_id, pkS):
171//   shared_secret = AuthDecap(enc, skR, pkS)
172//   return KeyScheduleR(mode_auth_psk, shared_secret, info,
173//                       psk, psk_id)
174
175/// Initiates a decryption context given a private key `sk_recip` and an encapsulated key which
176/// was encapsulated to `sk_recip`'s corresponding public key
177///
178/// Return Value
179/// ============
180/// On success, returns a decryption context. If an error happened during key decapsulation,
181/// returns `Err(HpkeError::DecapError)`. This is the only possible error.
182pub fn setup_receiver<A, Kdf, Kem>(
183    mode: &OpModeR<Kem>,
184    sk_recip: &Kem::PrivateKey,
185    encapped_key: &Kem::EncappedKey,
186    info: &[u8],
187) -> Result<AeadCtxR<A, Kdf, Kem>, HpkeError>
188where
189    A: Aead,
190    Kdf: KdfTrait,
191    Kem: KemTrait,
192{
193    // If the identity key is set, use it
194    let pk_sender_id: Option<&Kem::PublicKey> = mode.get_pk_sender_id();
195    // Do the decapsulation
196    let shared_secret = Kem::decap(sk_recip, pk_sender_id, encapped_key)?;
197
198    // Use everything to derive an encryption context
199    let enc_ctx = derive_enc_ctx::<_, _, Kem, _>(mode, shared_secret, info);
200    Ok(enc_ctx.into())
201}
202
203#[cfg(test)]
204mod test {
205    use super::{setup_receiver, setup_sender};
206    use crate::test_util::{aead_ctx_eq, gen_rand_buf, new_op_mode_pair, OpModeKind};
207    use crate::{aead::ChaCha20Poly1305, kdf::HkdfSha256, kem::Kem as KemTrait};
208
209    use rand::{rngs::StdRng, SeedableRng};
210
211    /// This tests that `setup_sender` and `setup_receiver` derive the same context. We do this by
212    /// testing that `gen_ctx_kem_pair` returns identical encryption contexts
213    macro_rules! test_setup_correctness {
214        ($test_name:ident, $aead_ty:ty, $kdf_ty:ty, $kem_ty:ty) => {
215            #[test]
216            fn $test_name() {
217                type A = $aead_ty;
218                type Kdf = $kdf_ty;
219                type Kem = $kem_ty;
220
221                let mut csprng = StdRng::from_os_rng();
222
223                let info = b"why would you think in a million years that that would actually work";
224
225                // Generate the receiver's long-term keypair
226                let (sk_recip, pk_recip) = Kem::gen_keypair(&mut csprng);
227
228                // Try a full setup for all the op modes
229                for op_mode_kind in &[
230                    OpModeKind::Base,
231                    OpModeKind::Auth,
232                    OpModeKind::Psk,
233                    OpModeKind::AuthPsk,
234                ] {
235                    // Generate a mutually agreeing op mode pair
236                    let (psk, psk_id) = (gen_rand_buf(), gen_rand_buf());
237                    let (sender_mode, receiver_mode) =
238                        new_op_mode_pair::<Kdf, Kem>(*op_mode_kind, &psk, &psk_id);
239
240                    // Construct the sender's encryption context, and get an encapped key
241                    let (encapped_key, mut aead_ctx1) = setup_sender::<A, Kdf, Kem, _>(
242                        &sender_mode,
243                        &pk_recip,
244                        &info[..],
245                        &mut csprng,
246                    )
247                    .unwrap();
248
249                    // Use the encapped key to derive the reciever's encryption context
250                    let mut aead_ctx2 = setup_receiver::<A, Kdf, Kem>(
251                        &receiver_mode,
252                        &sk_recip,
253                        &encapped_key,
254                        &info[..],
255                    )
256                    .unwrap();
257
258                    // Ensure that the two derived contexts are equivalent
259                    assert!(aead_ctx_eq(&mut aead_ctx1, &mut aead_ctx2));
260                }
261            }
262        };
263    }
264
265    /// Tests that using different input data gives you different encryption contexts
266    macro_rules! test_setup_soundness {
267        ($test_name:ident, $aead:ty, $kdf:ty, $kem:ty) => {
268            #[test]
269            fn $test_name() {
270                type A = $aead;
271                type Kdf = $kdf;
272                type Kem = $kem;
273
274                let mut csprng = StdRng::from_os_rng();
275
276                let info = b"why would you think in a million years that that would actually work";
277
278                // Generate the receiver's long-term keypair
279                let (sk_recip, pk_recip) = Kem::gen_keypair(&mut csprng);
280
281                // Generate a mutually agreeing op mode pair
282                let (psk, psk_id) = (gen_rand_buf(), gen_rand_buf());
283                let (sender_mode, receiver_mode) =
284                    new_op_mode_pair::<Kdf, Kem>(OpModeKind::Base, &psk, &psk_id);
285
286                // Construct the sender's encryption context normally
287                let (encapped_key, sender_ctx) =
288                    setup_sender::<A, Kdf, Kem, _>(&sender_mode, &pk_recip, &info[..], &mut csprng)
289                        .unwrap();
290
291                // Now make a receiver with the wrong info string and ensure it doesn't match the
292                // sender
293                let bad_info = b"something else";
294                let mut receiver_ctx = setup_receiver::<_, _, Kem>(
295                    &receiver_mode,
296                    &sk_recip,
297                    &encapped_key,
298                    &bad_info[..],
299                )
300                .unwrap();
301                assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut receiver_ctx));
302
303                // Now make a receiver with the wrong secret key and ensure it doesn't match the
304                // sender
305                let (bad_sk, _) = Kem::gen_keypair(&mut csprng);
306                let mut aead_ctx2 =
307                    setup_receiver::<_, _, Kem>(&receiver_mode, &bad_sk, &encapped_key, &info[..])
308                        .unwrap();
309                assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
310
311                // Now make a receiver with the wrong encapped key and ensure it doesn't match the
312                // sender. The reason `bad_encapped_key` is bad is because its underlying key is
313                // uniformly random, and therefore different from the key that the sender sent.
314                let (bad_encapped_key, _) =
315                    setup_sender::<A, Kdf, Kem, _>(&sender_mode, &pk_recip, &info[..], &mut csprng)
316                        .unwrap();
317                let mut aead_ctx2 = setup_receiver::<_, _, Kem>(
318                    &receiver_mode,
319                    &sk_recip,
320                    &bad_encapped_key,
321                    &info[..],
322                )
323                .unwrap();
324                assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
325
326                // Now make sure that this test was a valid test by ensuring that doing everything
327                // the right way makes it pass
328                let mut aead_ctx2 = setup_receiver::<_, _, Kem>(
329                    &receiver_mode,
330                    &sk_recip,
331                    &encapped_key,
332                    &info[..],
333                )
334                .unwrap();
335                assert!(aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
336            }
337        };
338    }
339
340    #[cfg(feature = "x25519")]
341    mod x25519_tests {
342        use super::*;
343
344        test_setup_correctness!(
345            test_setup_correctness_x25519,
346            ChaCha20Poly1305,
347            HkdfSha256,
348            crate::kem::x25519_hkdfsha256::X25519HkdfSha256
349        );
350        test_setup_soundness!(
351            test_setup_soundness_x25519,
352            ChaCha20Poly1305,
353            HkdfSha256,
354            crate::kem::x25519_hkdfsha256::X25519HkdfSha256
355        );
356    }
357
358    #[cfg(feature = "p256")]
359    mod p256_tests {
360        use super::*;
361
362        test_setup_correctness!(
363            test_setup_correctness_p256,
364            ChaCha20Poly1305,
365            HkdfSha256,
366            crate::kem::dhp256_hkdfsha256::DhP256HkdfSha256
367        );
368        test_setup_soundness!(
369            test_setup_soundness_p256,
370            ChaCha20Poly1305,
371            HkdfSha256,
372            crate::kem::dhp256_hkdfsha256::DhP256HkdfSha256
373        );
374    }
375
376    #[cfg(feature = "p384")]
377    mod p384_tests {
378        use super::*;
379        use crate::kdf::HkdfSha384;
380
381        test_setup_correctness!(
382            test_setup_correctness_p384,
383            ChaCha20Poly1305,
384            HkdfSha384,
385            crate::kem::dhp384_hkdfsha384::DhP384HkdfSha384
386        );
387        test_setup_soundness!(
388            test_setup_soundness_p384,
389            ChaCha20Poly1305,
390            HkdfSha384,
391            crate::kem::dhp384_hkdfsha384::DhP384HkdfSha384
392        );
393    }
394
395    #[cfg(feature = "p521")]
396    mod p521_tests {
397        use super::*;
398        use crate::kdf::HkdfSha512;
399
400        test_setup_correctness!(
401            test_setup_correctness_p521,
402            ChaCha20Poly1305,
403            HkdfSha512,
404            crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512
405        );
406        test_setup_soundness!(
407            test_setup_soundness_p521,
408            ChaCha20Poly1305,
409            HkdfSha512,
410            crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512
411        );
412    }
413}