aws_lc_rs/agreement/
ephemeral.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use crate::agreement::{agree, Algorithm, PrivateKey, PublicKey, UnparsedPublicKey};
5use crate::error::Unspecified;
6use crate::rand::SecureRandom;
7use core::fmt;
8use core::fmt::{Debug, Formatter};
9
10/// An ephemeral private key for use (only) with `agree_ephemeral`. The
11/// signature of `agree_ephemeral` ensures that an `PrivateKey` can be
12/// used for at most one key agreement.
13#[allow(clippy::module_name_repetitions)]
14pub struct EphemeralPrivateKey(PrivateKey);
15
16impl Debug for EphemeralPrivateKey {
17    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
18        f.write_str(&format!(
19            "EphemeralPrivateKey {{ algorithm: {:?} }}",
20            self.0.inner_key.algorithm()
21        ))
22    }
23}
24
25impl EphemeralPrivateKey {
26    #[inline]
27    /// Generate a new ephemeral private key for the given algorithm.
28    ///
29    /// # *ring* Compatibility
30    ///  Our implementation ignores the `SecureRandom` parameter.
31    // # FIPS
32    // Use this function with one of the following algorithms:
33    // * `ECDH_P256`
34    // * `ECDH_P384`
35    // * `ECDH_P521`
36    //
37    /// # Errors
38    /// `error::Unspecified` when operation fails due to internal error.
39    pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result<Self, Unspecified> {
40        Ok(Self(PrivateKey::generate(alg)?))
41    }
42
43    #[cfg(test)]
44    #[allow(clippy::missing_errors_doc, missing_docs)]
45    pub fn generate_for_test(
46        alg: &'static Algorithm,
47        rng: &dyn SecureRandom,
48    ) -> Result<Self, Unspecified> {
49        Ok(Self(PrivateKey::generate_for_test(alg, rng)?))
50    }
51
52    /// Computes the public key from the private key.
53    ///
54    /// # Errors
55    /// `error::Unspecified` when operation fails due to internal error.
56    pub fn compute_public_key(&self) -> Result<PublicKey, Unspecified> {
57        self.0.compute_public_key()
58    }
59
60    /// The algorithm for the private key.
61    #[inline]
62    #[must_use]
63    pub fn algorithm(&self) -> &'static Algorithm {
64        self.0.algorithm()
65    }
66}
67
68/// Performs a key agreement with an ephemeral private key and the given public
69/// key.
70///
71/// `my_private_key` is the ephemeral private key to use. Since it is moved, it
72/// will not be usable after calling `agree_ephemeral`, thus guaranteeing that
73/// the key is used for only one key agreement.
74///
75/// `peer_public_key` is the peer's public key. `agree_ephemeral` will return
76/// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve.
77/// `agree_ephemeral` verifies that it is encoded in the standard form for the
78/// algorithm and that the key is *valid*; see the algorithm's documentation for
79/// details on how keys are to be encoded and what constitutes a valid key for
80/// that algorithm.
81///
82/// `error_value` is the value to return if an error occurs before `kdf` is
83/// called, e.g. when decoding of the peer's public key fails or when the public
84/// key is otherwise invalid.
85///
86/// After the key agreement is done, `agree_ephemeral` calls `kdf` with the raw
87/// key material from the key agreement operation and then returns what `kdf`
88/// returns.
89// # FIPS
90// Use this function with one of the following key algorithms:
91// * `ECDH_P256`
92// * `ECDH_P384`
93// * `ECDH_P521`
94//
95/// # Errors
96/// `error_value` on internal failure.
97#[inline]
98#[allow(clippy::needless_pass_by_value)]
99#[allow(clippy::missing_panics_doc)]
100#[allow(clippy::module_name_repetitions)]
101pub fn agree_ephemeral<B: AsRef<[u8]>, F, R, E>(
102    my_private_key: EphemeralPrivateKey,
103    peer_public_key: &UnparsedPublicKey<B>,
104    error_value: E,
105    kdf: F,
106) -> Result<R, E>
107where
108    F: FnOnce(&[u8]) -> Result<R, E>,
109{
110    agree(&my_private_key.0, peer_public_key, error_value, kdf)
111}
112
113#[cfg(test)]
114mod tests {
115    use crate::agreement::{AlgorithmID, PublicKey};
116    use crate::encoding::{
117        AsBigEndian, AsDer, EcPublicKeyCompressedBin, EcPublicKeyUncompressedBin, PublicKeyX509Der,
118    };
119    use crate::error::Unspecified;
120    use crate::{agreement, rand, test, test_file};
121
122    #[test]
123    fn test_agreement_ecdh_x25519_rfc_iterated() {
124        fn expect_iterated_x25519(
125            expected_result: &str,
126            range: core::ops::Range<usize>,
127            k: &mut Vec<u8>,
128            u: &mut Vec<u8>,
129        ) {
130            for _ in range {
131                let new_k = x25519(k, u);
132                u.clone_from(k);
133                *k = new_k;
134            }
135            assert_eq!(&from_hex(expected_result), k);
136        }
137
138        let mut k = from_hex("0900000000000000000000000000000000000000000000000000000000000000");
139        let mut u = k.clone();
140
141        expect_iterated_x25519(
142            "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079",
143            0..1,
144            &mut k,
145            &mut u,
146        );
147        expect_iterated_x25519(
148            "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51",
149            1..1_000,
150            &mut k,
151            &mut u,
152        );
153
154        // The spec gives a test vector for 1,000,000 iterations but it takes
155        // too long to do 1,000,000 iterations by default right now. This
156        // 10,000 iteration vector is self-computed.
157        #[cfg(not(disable_slow_tests))]
158        expect_iterated_x25519(
159            "2c125a20f639d504a7703d2e223c79a79de48c4ee8c23379aa19a62ecd211815",
160            1_000..10_000,
161            &mut k,
162            &mut u,
163        );
164        /*
165               expect_iterated_x25519(
166                   "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424",
167                   10_000..1_000_000,
168                   &mut k,
169                   &mut u,
170               );
171        */
172    }
173
174    #[test]
175    fn test_agreement_x25519() {
176        let alg = &agreement::X25519;
177        let peer_public = agreement::UnparsedPublicKey::new(
178            alg,
179            test::from_dirty_hex(
180                "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c",
181            ),
182        );
183
184        let my_private = test::from_dirty_hex(
185            "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4",
186        );
187
188        let my_private = {
189            let rng = test::rand::FixedSliceRandom { bytes: &my_private };
190            agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap()
191        };
192
193        let my_public = test::from_dirty_hex(
194            "1c9fd88f45606d932a80c71824ae151d15d73e77de38e8e000852e614fae7019",
195        );
196        let output = test::from_dirty_hex(
197            "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552",
198        );
199
200        assert_eq!(my_private.algorithm(), alg);
201
202        let computed_public = my_private.compute_public_key().unwrap();
203        assert_eq!(computed_public.as_ref(), &my_public[..]);
204
205        assert_eq!(computed_public.algorithm(), alg);
206
207        let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
208            assert_eq!(key_material, &output[..]);
209            Ok(())
210        });
211        assert_eq!(result, Ok(()));
212    }
213
214    #[test]
215    fn test_agreement_ecdh_p256() {
216        let alg = &agreement::ECDH_P256;
217        let peer_public = agreement::UnparsedPublicKey::new(
218            alg,
219            test::from_dirty_hex(
220                "04D12DFB5289C8D4F81208B70270398C342296970A0BCCB74C736FC7554494BF6356FBF3CA366CC23E8157854C13C58D6AAC23F046ADA30F8353E74F33039872AB",
221            ),
222        );
223        assert_eq!(peer_public.algorithm(), alg);
224        assert_eq!(peer_public.bytes(), &peer_public.bytes);
225
226        let my_private = test::from_dirty_hex(
227            "C88F01F510D9AC3F70A292DAA2316DE544E9AAB8AFE84049C62A9C57862D1433",
228        );
229
230        let my_private = {
231            let rng = test::rand::FixedSliceRandom { bytes: &my_private };
232            agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap()
233        };
234
235        let my_public = test::from_dirty_hex(
236            "04DAD0B65394221CF9B051E1FECA5787D098DFE637FC90B9EF945D0C37725811805271A0461CDB8252D61F1C456FA3E59AB1F45B33ACCF5F58389E0577B8990BB3",
237        );
238        let output = test::from_dirty_hex(
239            "D6840F6B42F6EDAFD13116E0E12565202FEF8E9ECE7DCE03812464D04B9442DE",
240        );
241
242        assert_eq!(my_private.algorithm(), alg);
243
244        let computed_public = my_private.compute_public_key().unwrap();
245        assert_eq!(computed_public.as_ref(), &my_public[..]);
246
247        assert_eq!(computed_public.algorithm(), alg);
248
249        let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
250            assert_eq!(key_material, &output[..]);
251            Ok(())
252        });
253        assert_eq!(result, Ok(()));
254    }
255
256    #[test]
257    fn test_agreement_ecdh_p384() {
258        let alg = &agreement::ECDH_P384;
259        let peer_public = agreement::UnparsedPublicKey::new(
260            alg,
261            test::from_dirty_hex(
262                "04E558DBEF53EECDE3D3FCCFC1AEA08A89A987475D12FD950D83CFA41732BC509D0D1AC43A0336DEF96FDA41D0774A3571DCFBEC7AACF3196472169E838430367F66EEBE3C6E70C416DD5F0C68759DD1FFF83FA40142209DFF5EAAD96DB9E6386C",
263            ),
264        );
265
266        let my_private = test::from_dirty_hex(
267            "099F3C7034D4A2C699884D73A375A67F7624EF7C6B3C0F160647B67414DCE655E35B538041E649EE3FAEF896783AB194",
268        );
269
270        let my_private = {
271            let rng = test::rand::FixedSliceRandom { bytes: &my_private };
272            agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap()
273        };
274
275        let my_public = test::from_dirty_hex(
276            "04667842D7D180AC2CDE6F74F37551F55755C7645C20EF73E31634FE72B4C55EE6DE3AC808ACB4BDB4C88732AEE95F41AA9482ED1FC0EEB9CAFC4984625CCFC23F65032149E0E144ADA024181535A0F38EEB9FCFF3C2C947DAE69B4C634573A81C",
277        );
278        let output = test::from_dirty_hex(
279            "11187331C279962D93D604243FD592CB9D0A926F422E47187521287E7156C5C4D603135569B9E9D09CF5D4A270F59746",
280        );
281
282        assert_eq!(my_private.algorithm(), alg);
283
284        let computed_public = my_private.compute_public_key().unwrap();
285        assert_eq!(computed_public.as_ref(), &my_public[..]);
286
287        assert_eq!(computed_public.algorithm(), alg);
288
289        let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
290            assert_eq!(key_material, &output[..]);
291            Ok(())
292        });
293        assert_eq!(result, Ok(()));
294    }
295
296    #[test]
297    fn test_agreement_ecdh_p521() {
298        let alg = &agreement::ECDH_P521;
299        let peer_public = agreement::UnparsedPublicKey::new(
300            alg,
301            test::from_dirty_hex(
302                "0401a32099b02c0bd85371f60b0dd20890e6c7af048c8179890fda308b359dbbc2b7a832bb8c6526c4af99a7ea3f0b3cb96ae1eb7684132795c478ad6f962e4a6f446d017627357b39e9d7632a1370b3e93c1afb5c851b910eb4ead0c9d387df67cde85003e0e427552f1cd09059aad0262e235cce5fba8cedc4fdc1463da76dcd4b6d1a46",
303            ),
304        );
305
306        let my_private = test::from_dirty_hex(
307            "00df14b1f1432a7b0fb053965fd8643afee26b2451ecb6a8a53a655d5fbe16e4c64ce8647225eb11e7fdcb23627471dffc5c2523bd2ae89957cba3a57a23933e5a78",
308        );
309
310        let my_private = {
311            let rng = test::rand::FixedSliceRandom { bytes: &my_private };
312            agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap()
313        };
314
315        let my_public = test::from_dirty_hex(
316            "04004e8583bbbb2ecd93f0714c332dff5ab3bc6396e62f3c560229664329baa5138c3bb1c36428abd4e23d17fcb7a2cfcc224b2e734c8941f6f121722d7b6b9415457601cf0874f204b0363f020864672fadbf87c8811eb147758b254b74b14fae742159f0f671a018212bbf25b8519e126d4cad778cfff50d288fd39ceb0cac635b175ec0",
317        );
318        let output = test::from_dirty_hex(
319            "01aaf24e5d47e4080c18c55ea35581cd8da30f1a079565045d2008d51b12d0abb4411cda7a0785b15d149ed301a3697062f42da237aa7f07e0af3fd00eb1800d9c41",
320        );
321
322        assert_eq!(my_private.algorithm(), alg);
323
324        let computed_public = my_private.compute_public_key().unwrap();
325        assert_eq!(computed_public.as_ref(), &my_public[..]);
326
327        assert_eq!(computed_public.algorithm(), alg);
328
329        let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
330            assert_eq!(key_material, &output[..]);
331            Ok(())
332        });
333        assert_eq!(result, Ok(()));
334    }
335
336    #[test]
337    fn agreement_traits() {
338        use crate::test;
339
340        let rng = rand::SystemRandom::new();
341
342        let ephemeral_private_key =
343            agreement::EphemeralPrivateKey::generate_for_test(&agreement::ECDH_P256, &rng).unwrap();
344
345        test::compile_time_assert_send::<agreement::EphemeralPrivateKey>();
346        test::compile_time_assert_sync::<agreement::EphemeralPrivateKey>();
347
348        assert_eq!(
349            format!("{:?}", &ephemeral_private_key),
350            "EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }"
351        );
352    }
353
354    fn check_computed_public_key(
355        algorithm: &AlgorithmID,
356        expected_format: &str,
357        expected_public_key_bytes: &[u8],
358        computed_public: &PublicKey,
359    ) {
360        match (algorithm, expected_format) {
361            (_, "X509") => {
362                let der = AsDer::<PublicKeyX509Der>::as_der(computed_public)
363                    .expect("serialize to uncompressed format");
364                assert_eq!(
365                    expected_public_key_bytes,
366                    der.as_ref(),
367                    "hex: {:x?}",
368                    der.as_ref()
369                );
370            }
371            (
372                AlgorithmID::ECDH_P256 | AlgorithmID::ECDH_P384 | AlgorithmID::ECDH_P521,
373                "COMPRESSED",
374            ) => {
375                let bin = AsBigEndian::<EcPublicKeyCompressedBin>::as_be_bytes(computed_public)
376                    .expect("serialize to compressed format");
377                assert_eq!(expected_public_key_bytes, bin.as_ref());
378            }
379            (
380                AlgorithmID::ECDH_P256 | AlgorithmID::ECDH_P384 | AlgorithmID::ECDH_P521,
381                "UNCOMPRESSED" | "",
382            ) => {
383                let bin = AsBigEndian::<EcPublicKeyUncompressedBin>::as_be_bytes(computed_public)
384                    .expect("serialize to uncompressed format");
385                assert_eq!(expected_public_key_bytes, bin.as_ref());
386                assert_eq!(expected_public_key_bytes, computed_public.as_ref());
387            }
388            (AlgorithmID::X25519, "") => {
389                assert_eq!(expected_public_key_bytes, computed_public.as_ref());
390            }
391            (ai, pf) => {
392                panic!("Unexpected PeerFormat={pf:?} for {ai:?}")
393            }
394        }
395    }
396
397    #[test]
398    fn agreement_agree_ephemeral() {
399        let rng = rand::SystemRandom::new();
400
401        test::run(
402            test_file!("data/agreement_tests.txt"),
403            |section, test_case| {
404                assert_eq!(section, "");
405
406                let curve_name = test_case.consume_string("Curve");
407                let alg = alg_from_curve_name(&curve_name);
408                let peer_public =
409                    agreement::UnparsedPublicKey::new(alg, test_case.consume_bytes("PeerQ"));
410
411                let myq_format = test_case
412                    .consume_optional_string("MyQFormat")
413                    .unwrap_or_default();
414
415                if test_case.consume_optional_string("Error").is_none() {
416                    let my_private_bytes = test_case.consume_bytes("D");
417                    let my_private = {
418                        let rng = test::rand::FixedSliceRandom {
419                            bytes: &my_private_bytes,
420                        };
421                        agreement::EphemeralPrivateKey::generate_for_test(alg, &rng)?
422                    };
423                    let my_public = test_case.consume_bytes("MyQ");
424                    let output = test_case.consume_bytes("Output");
425
426                    assert_eq!(my_private.algorithm(), alg);
427
428                    let computed_public = my_private.compute_public_key().unwrap();
429
430                    check_computed_public_key(&alg.id, &myq_format, &my_public, &computed_public);
431
432                    assert_eq!(my_private.algorithm(), alg);
433
434                    let result =
435                        agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
436                            assert_eq!(key_material, &output[..]);
437                            Ok(())
438                        });
439                    assert_eq!(
440                        result,
441                        Ok(()),
442                        "Failed on private key: {:?}",
443                        test::to_hex(my_private_bytes)
444                    );
445                } else {
446                    fn kdf_not_called(_: &[u8]) -> Result<(), ()> {
447                        panic!(
448                            "The KDF was called during ECDH when the peer's \
449                         public key is invalid."
450                        );
451                    }
452                    let dummy_private_key = agreement::EphemeralPrivateKey::generate(alg, &rng)?;
453                    assert!(agreement::agree_ephemeral(
454                        dummy_private_key,
455                        &peer_public,
456                        (),
457                        kdf_not_called
458                    )
459                    .is_err());
460                }
461
462                Ok(())
463            },
464        );
465    }
466
467    fn from_hex(s: &str) -> Vec<u8> {
468        match test::from_hex(s) {
469            Ok(v) => v,
470            Err(msg) => {
471                panic!("{msg} in {s}");
472            }
473        }
474    }
475
476    fn alg_from_curve_name(curve_name: &str) -> &'static agreement::Algorithm {
477        if curve_name == "P-256" {
478            &agreement::ECDH_P256
479        } else if curve_name == "P-384" {
480            &agreement::ECDH_P384
481        } else if curve_name == "P-521" {
482            &agreement::ECDH_P521
483        } else if curve_name == "X25519" {
484            &agreement::X25519
485        } else {
486            panic!("Unsupported curve: {curve_name}");
487        }
488    }
489
490    fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec<u8> {
491        try_x25519(private_key, public_key).unwrap()
492    }
493
494    fn try_x25519(private_key: &[u8], public_key: &[u8]) -> Result<Vec<u8>, Unspecified> {
495        let rng = test::rand::FixedSliceRandom { bytes: private_key };
496        let private_key =
497            agreement::EphemeralPrivateKey::generate_for_test(&agreement::X25519, &rng)?;
498        let public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, public_key);
499        agreement::agree_ephemeral(private_key, &public_key, Unspecified, |agreed_value| {
500            Ok(Vec::from(agreed_value))
501        })
502    }
503}