1use crate::agreement::{agree, Algorithm, ParsedPublicKey, PrivateKey, PublicKey};
5use crate::error::Unspecified;
6use crate::rand::SecureRandom;
7use core::fmt;
8use core::fmt::{Debug, Formatter};
9
10#[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 pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result<Self, Unspecified> {
40 Ok(Self(PrivateKey::generate(alg)?))
41 }
42
43 #[cfg(any(test, dev_tests_only))]
44 #[allow(missing_docs, clippy::missing_errors_doc)]
45 pub fn generate_for_test(
46 alg: &'static Algorithm,
47 rng: &mut dyn SecureRandom,
48 ) -> Result<Self, Unspecified> {
49 Ok(Self(PrivateKey::generate_for_test(alg, rng)?))
50 }
51
52 pub fn compute_public_key(&self) -> Result<PublicKey, Unspecified> {
57 self.0.compute_public_key()
58 }
59
60 #[inline]
62 #[must_use]
63 pub fn algorithm(&self) -> &'static Algorithm {
64 self.0.algorithm()
65 }
66}
67
68#[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: TryInto<ParsedPublicKey>, F, R, E>(
102 my_private_key: EphemeralPrivateKey,
103 peer_public_key: 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 #[cfg(not(disable_slow_tests))]
158 expect_iterated_x25519(
159 "2c125a20f639d504a7703d2e223c79a79de48c4ee8c23379aa19a62ecd211815",
160 1_000..10_000,
161 &mut k,
162 &mut u,
163 );
164 }
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 mut rng = test::rand::FixedSliceRandom { bytes: &my_private };
190 agreement::EphemeralPrivateKey::generate_for_test(alg, &mut 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 mut rng = test::rand::FixedSliceRandom { bytes: &my_private };
232 agreement::EphemeralPrivateKey::generate_for_test(alg, &mut 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 mut rng = test::rand::FixedSliceRandom { bytes: &my_private };
272 agreement::EphemeralPrivateKey::generate_for_test(alg, &mut 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 mut rng = test::rand::FixedSliceRandom { bytes: &my_private };
312 agreement::EphemeralPrivateKey::generate_for_test(alg, &mut 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 mut rng = rand::SystemRandom::new();
341
342 let ephemeral_private_key =
343 agreement::EphemeralPrivateKey::generate_for_test(&agreement::ECDH_P256, &mut rng)
344 .unwrap();
345
346 test::compile_time_assert_send::<agreement::EphemeralPrivateKey>();
347 test::compile_time_assert_sync::<agreement::EphemeralPrivateKey>();
348
349 assert_eq!(
350 format!("{:?}", &ephemeral_private_key),
351 "EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }"
352 );
353 }
354
355 fn check_computed_public_key(
356 algorithm: &AlgorithmID,
357 expected_format: &str,
358 expected_public_key_bytes: &[u8],
359 computed_public: &PublicKey,
360 ) {
361 match (algorithm, expected_format) {
362 (_, "X509") => {
363 let der = AsDer::<PublicKeyX509Der>::as_der(computed_public)
364 .expect("serialize to uncompressed format");
365 assert_eq!(
366 expected_public_key_bytes,
367 der.as_ref(),
368 "hex: {:x?}",
369 der.as_ref()
370 );
371 }
372 (
373 AlgorithmID::ECDH_P256 | AlgorithmID::ECDH_P384 | AlgorithmID::ECDH_P521,
374 "COMPRESSED",
375 ) => {
376 let bin = AsBigEndian::<EcPublicKeyCompressedBin>::as_be_bytes(computed_public)
377 .expect("serialize to compressed format");
378 assert_eq!(expected_public_key_bytes, bin.as_ref());
379 }
380 (
381 AlgorithmID::ECDH_P256 | AlgorithmID::ECDH_P384 | AlgorithmID::ECDH_P521,
382 "UNCOMPRESSED" | "",
383 ) => {
384 let bin = AsBigEndian::<EcPublicKeyUncompressedBin>::as_be_bytes(computed_public)
385 .expect("serialize to uncompressed format");
386 assert_eq!(expected_public_key_bytes, bin.as_ref());
387 assert_eq!(expected_public_key_bytes, computed_public.as_ref());
388 }
389 (AlgorithmID::X25519, "") => {
390 assert_eq!(expected_public_key_bytes, computed_public.as_ref());
391 }
392 (ai, pf) => {
393 panic!("Unexpected PeerFormat={pf:?} for {ai:?}")
394 }
395 }
396 }
397
398 #[test]
399 fn agreement_agree_ephemeral() {
400 let rng = rand::SystemRandom::new();
401
402 test::run(
403 test_file!("data/agreement_tests.txt"),
404 |section, test_case| {
405 assert_eq!(section, "");
406
407 let curve_name = test_case.consume_string("Curve");
408 let alg = alg_from_curve_name(&curve_name);
409 let peer_public =
410 agreement::UnparsedPublicKey::new(alg, test_case.consume_bytes("PeerQ"));
411
412 let myq_format = test_case
413 .consume_optional_string("MyQFormat")
414 .unwrap_or_default();
415
416 if test_case.consume_optional_string("Error").is_none() {
417 let my_private_bytes = test_case.consume_bytes("D");
418 let my_private = {
419 let mut rng = test::rand::FixedSliceRandom {
420 bytes: &my_private_bytes,
421 };
422 agreement::EphemeralPrivateKey::generate_for_test(alg, &mut rng)?
423 };
424 let my_public = test_case.consume_bytes("MyQ");
425 let output = test_case.consume_bytes("Output");
426
427 assert_eq!(my_private.algorithm(), alg);
428
429 let computed_public = my_private.compute_public_key().unwrap();
430
431 check_computed_public_key(&alg.id, &myq_format, &my_public, &computed_public);
432
433 assert_eq!(my_private.algorithm(), alg);
434
435 let result =
436 agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
437 assert_eq!(key_material, &output[..]);
438 Ok(())
439 });
440 assert_eq!(
441 result,
442 Ok(()),
443 "Failed on private key: {:?}",
444 test::to_hex(my_private_bytes)
445 );
446 } else {
447 fn kdf_not_called(_: &[u8]) -> Result<(), ()> {
448 panic!(
449 "The KDF was called during ECDH when the peer's \
450 public key is invalid."
451 );
452 }
453 let dummy_private_key = agreement::EphemeralPrivateKey::generate(alg, &rng)?;
454 assert!(agreement::agree_ephemeral(
455 dummy_private_key,
456 &peer_public,
457 (),
458 kdf_not_called
459 )
460 .is_err());
461 }
462
463 Ok(())
464 },
465 );
466 }
467
468 fn from_hex(s: &str) -> Vec<u8> {
469 match test::from_hex(s) {
470 Ok(v) => v,
471 Err(msg) => {
472 panic!("{msg} in {s}");
473 }
474 }
475 }
476
477 fn alg_from_curve_name(curve_name: &str) -> &'static agreement::Algorithm {
478 if curve_name == "P-256" {
479 &agreement::ECDH_P256
480 } else if curve_name == "P-384" {
481 &agreement::ECDH_P384
482 } else if curve_name == "P-521" {
483 &agreement::ECDH_P521
484 } else if curve_name == "X25519" {
485 &agreement::X25519
486 } else {
487 panic!("Unsupported curve: {curve_name}");
488 }
489 }
490
491 fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec<u8> {
492 try_x25519(private_key, public_key).unwrap()
493 }
494
495 fn try_x25519(private_key: &[u8], public_key: &[u8]) -> Result<Vec<u8>, Unspecified> {
496 let mut rng = test::rand::FixedSliceRandom { bytes: private_key };
497 let private_key =
498 agreement::EphemeralPrivateKey::generate_for_test(&agreement::X25519, &mut rng)?;
499 let public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, public_key);
500 agreement::agree_ephemeral(private_key, public_key, Unspecified, |agreed_value| {
501 Ok(Vec::from(agreed_value))
502 })
503 }
504}