Skip to content

Commit 0853ae1

Browse files
committed
Persistent private keys for agreement
1 parent c9168bf commit 0853ae1

File tree

2 files changed

+166
-39
lines changed

2 files changed

+166
-39
lines changed

aws-lc-rs/src/agreement.rs

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,16 @@
4949
//!
5050
//! # Ok::<(), aws_lc_rs::error::Unspecified>(())
5151
//! ```
52+
mod ephemeral;
53+
54+
pub use ephemeral::{agree_ephemeral, EphemeralPrivateKey};
55+
5256
use crate::ec::{
5357
ec_group_from_nid, ec_point_from_bytes, evp_key_generate, evp_pkey_from_public_point,
5458
};
5559
use crate::error::Unspecified;
5660
use crate::fips::indicator_check;
5761
use crate::ptr::LcPtr;
58-
use crate::rand::SecureRandom;
5962
use crate::{ec, hex};
6063
use aws_lc::{
6164
EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, EVP_PKEY_derive_init,
@@ -165,10 +168,10 @@ enum KeyInner {
165168
X25519(LcPtr<EVP_PKEY>),
166169
}
167170

168-
/// An ephemeral private key for use (only) with `agree_ephemeral`. The
169-
/// signature of `agree_ephemeral` ensures that an `EphemeralPrivateKey` can be
170-
/// used for at most one key agreement.
171-
pub struct EphemeralPrivateKey {
171+
/// A private key for use (only) with `agree`. The
172+
/// signature of `agree` allows `PrivateKey` to be
173+
/// used for more than one key agreement.
174+
pub struct PrivateKey {
172175
inner_key: KeyInner,
173176
}
174177

@@ -184,31 +187,28 @@ impl KeyInner {
184187
}
185188
}
186189

187-
unsafe impl Send for EphemeralPrivateKey {}
190+
unsafe impl Send for PrivateKey {}
188191

189192
// https://github.com/awslabs/aws-lc/blob/main/include/openssl/ec_key.h#L88
190193
// An |EC_KEY| object represents a public or private EC key. A given object may
191194
// be used concurrently on multiple threads by non-mutating functions, provided
192195
// no other thread is concurrently calling a mutating function. Unless otherwise
193196
// documented, functions which take a |const| pointer are non-mutating and
194197
// functions which take a non-|const| pointer are mutating.
195-
unsafe impl Sync for EphemeralPrivateKey {}
198+
unsafe impl Sync for PrivateKey {}
196199

197-
impl Debug for EphemeralPrivateKey {
200+
impl Debug for PrivateKey {
198201
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
199202
f.write_str(&format!(
200-
"EphemeralPrivateKey {{ algorithm: {:?} }}",
203+
"PrivateKey {{ algorithm: {:?} }}",
201204
self.inner_key.algorithm()
202205
))
203206
}
204207
}
205208

206-
impl EphemeralPrivateKey {
209+
impl PrivateKey {
207210
#[inline]
208-
/// Generate a new ephemeral private key for the given algorithm.
209-
///
210-
/// # *ring* Compatibility
211-
/// Our implementation ignores the `SecureRandom` parameter.
211+
/// Generate a new private key for the given algorithm.
212212
///
213213
// # FIPS
214214
// Use this function with one of the following algorithms:
@@ -218,29 +218,29 @@ impl EphemeralPrivateKey {
218218
//
219219
/// # Errors
220220
/// `error::Unspecified` when operation fails due to internal error.
221-
pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result<Self, Unspecified> {
221+
pub fn generate(alg: &'static Algorithm) -> Result<Self, Unspecified> {
222222
match alg.id {
223223
AlgorithmID::X25519 => {
224224
let priv_key = generate_x25519()?;
225-
Ok(EphemeralPrivateKey {
225+
Ok(PrivateKey {
226226
inner_key: KeyInner::X25519(priv_key),
227227
})
228228
}
229229
AlgorithmID::ECDH_P256 => {
230230
let ec_key = evp_key_generate(ECDH_P256.id.nid())?;
231-
Ok(EphemeralPrivateKey {
231+
Ok(PrivateKey {
232232
inner_key: KeyInner::ECDH_P256(ec_key),
233233
})
234234
}
235235
AlgorithmID::ECDH_P384 => {
236236
let ec_key = evp_key_generate(ECDH_P384.id.nid())?;
237-
Ok(EphemeralPrivateKey {
237+
Ok(PrivateKey {
238238
inner_key: KeyInner::ECDH_P384(ec_key),
239239
})
240240
}
241241
AlgorithmID::ECDH_P521 => {
242242
let ec_key = evp_key_generate(ECDH_P521.id.nid())?;
243-
Ok(EphemeralPrivateKey {
243+
Ok(PrivateKey {
244244
inner_key: KeyInner::ECDH_P521(ec_key),
245245
})
246246
}
@@ -251,7 +251,7 @@ impl EphemeralPrivateKey {
251251
#[allow(clippy::missing_errors_doc)]
252252
pub fn generate_for_test(
253253
alg: &'static Algorithm,
254-
rng: &dyn SecureRandom,
254+
rng: &dyn crate::rand::SecureRandom,
255255
) -> Result<Self, Unspecified> {
256256
match alg.id {
257257
AlgorithmID::X25519 => {
@@ -292,31 +292,31 @@ impl EphemeralPrivateKey {
292292
)
293293
})?;
294294

295-
Ok(EphemeralPrivateKey {
295+
Ok(PrivateKey {
296296
inner_key: KeyInner::X25519(pkey),
297297
})
298298
}
299299

300300
#[cfg(test)]
301301
fn from_p256_private_key(priv_key: &[u8]) -> Result<Self, Unspecified> {
302302
let pkey = from_ec_private_key(priv_key, ECDH_P256.id.nid())?;
303-
Ok(EphemeralPrivateKey {
303+
Ok(PrivateKey {
304304
inner_key: KeyInner::ECDH_P256(pkey),
305305
})
306306
}
307307

308308
#[cfg(test)]
309309
fn from_p384_private_key(priv_key: &[u8]) -> Result<Self, Unspecified> {
310310
let pkey = from_ec_private_key(priv_key, ECDH_P384.id.nid())?;
311-
Ok(EphemeralPrivateKey {
311+
Ok(PrivateKey {
312312
inner_key: KeyInner::ECDH_P384(pkey),
313313
})
314314
}
315315

316316
#[cfg(test)]
317317
fn from_p521_private_key(priv_key: &[u8]) -> Result<Self, Unspecified> {
318318
let pkey = from_ec_private_key(priv_key, ECDH_P521.id.nid())?;
319-
Ok(EphemeralPrivateKey {
319+
Ok(PrivateKey {
320320
inner_key: KeyInner::ECDH_P521(pkey),
321321
})
322322
}
@@ -480,16 +480,14 @@ impl<B: AsRef<[u8]>> UnparsedPublicKey<B> {
480480
}
481481
}
482482

483-
/// Performs a key agreement with an ephemeral private key and the given public
484-
/// key.
483+
/// Performs a key agreement with a private key and the given public key.
485484
///
486-
/// `my_private_key` is the ephemeral private key to use. Since it is moved, it
487-
/// will not be usable after calling `agree_ephemeral`, thus guaranteeing that
488-
/// the key is used for only one key agreement.
485+
/// `my_private_key` is the private key to use. Only a reference to the key
486+
/// is required, allowing the key to continue to be used.
489487
///
490-
/// `peer_public_key` is the peer's public key. `agree_ephemeral` will return
488+
/// `peer_public_key` is the peer's public key. `agree` will return
491489
/// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve.
492-
/// `agree_ephemeral` verifies that it is encoded in the standard form for the
490+
/// `agree` verifies that it is encoded in the standard form for the
493491
/// algorithm and that the key is *valid*; see the algorithm's documentation for
494492
/// details on how keys are to be encoded and what constitutes a valid key for
495493
/// that algorithm.
@@ -498,7 +496,7 @@ impl<B: AsRef<[u8]>> UnparsedPublicKey<B> {
498496
/// called, e.g. when decoding of the peer's public key fails or when the public
499497
/// key is otherwise invalid.
500498
///
501-
/// After the key agreement is done, `agree_ephemeral` calls `kdf` with the raw
499+
/// After the key agreement is done, `agree` calls `kdf` with the raw
502500
/// key material from the key agreement operation and then returns what `kdf`
503501
/// returns.
504502
///
@@ -511,10 +509,9 @@ impl<B: AsRef<[u8]>> UnparsedPublicKey<B> {
511509
/// # Errors
512510
/// `error_value` on internal failure.
513511
#[inline]
514-
#[allow(clippy::needless_pass_by_value)]
515512
#[allow(clippy::missing_panics_doc)]
516-
pub fn agree_ephemeral<B: AsRef<[u8]>, F, R, E>(
517-
my_private_key: EphemeralPrivateKey,
513+
pub fn agree<B: AsRef<[u8]>, F, R, E>(
514+
my_private_key: &PrivateKey,
518515
peer_public_key: &UnparsedPublicKey<B>,
519516
error_value: E,
520517
kdf: F,
@@ -703,7 +700,7 @@ mod tests {
703700

704701
let my_private = {
705702
let rng = test::rand::FixedSliceRandom { bytes: &my_private };
706-
agreement::EphemeralPrivateKey::generate_for_test(alg, &rng).unwrap()
703+
agreement::PrivateKey::generate_for_test(alg, &rng).unwrap()
707704
};
708705

709706
let my_public = test::from_dirty_hex(
@@ -720,11 +717,17 @@ mod tests {
720717

721718
assert_eq!(computed_public.algorithm(), alg);
722719

723-
let result = agreement::agree_ephemeral(my_private, &peer_public, (), |key_material| {
720+
let result = agreement::agree(&my_private, &peer_public, (), |key_material| {
724721
assert_eq!(key_material, &output[..]);
725722
Ok(())
726723
});
727724
assert_eq!(result, Ok(()));
725+
726+
let result2 = agreement::agree(&my_private, &peer_public, (), |key_material| {
727+
assert_eq!(key_material, &output[..]);
728+
Ok(())
729+
});
730+
assert_eq!(result2, Ok(()));
728731
}
729732

730733
#[test]
@@ -857,13 +860,24 @@ mod tests {
857860

858861
let rng = rand::SystemRandom::new();
859862
let private_key =
863+
agreement::PrivateKey::generate_for_test(&agreement::ECDH_P256, &rng).unwrap();
864+
865+
test::compile_time_assert_send::<agreement::PrivateKey>();
866+
test::compile_time_assert_sync::<agreement::PrivateKey>();
867+
868+
assert_eq!(
869+
format!("{:?}", &private_key),
870+
"PrivateKey { algorithm: Algorithm { curve: P256 } }"
871+
);
872+
873+
let ephemeral_private_key =
860874
agreement::EphemeralPrivateKey::generate_for_test(&agreement::ECDH_P256, &rng).unwrap();
861875

862876
test::compile_time_assert_send::<agreement::EphemeralPrivateKey>();
863-
//test::compile_time_assert_sync::<agreement::EphemeralPrivateKey>();
877+
test::compile_time_assert_sync::<agreement::EphemeralPrivateKey>();
864878

865879
assert_eq!(
866-
format!("{:?}", &private_key),
880+
format!("{:?}", &ephemeral_private_key),
867881
"EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }"
868882
);
869883

aws-lc-rs/src/agreement/ephemeral.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR ISC
3+
4+
use crate::agreement::{agree, Algorithm, PrivateKey, PublicKey, UnparsedPublicKey};
5+
use crate::error::Unspecified;
6+
use crate::rand::SecureRandom;
7+
use core::fmt;
8+
use std::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)]
14+
pub struct EphemeralPrivateKey(PrivateKey);
15+
16+
impl 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+
25+
impl 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+
///
32+
// # FIPS
33+
// Use this function with one of the following algorithms:
34+
// * `ECDH_P256`
35+
// * `ECDH_P384`
36+
// * `ECDH_P521`
37+
//
38+
/// # Errors
39+
/// `error::Unspecified` when operation fails due to internal error.
40+
pub fn generate(alg: &'static Algorithm, _rng: &dyn SecureRandom) -> Result<Self, Unspecified> {
41+
Ok(Self(PrivateKey::generate(alg)?))
42+
}
43+
44+
#[cfg(test)]
45+
#[allow(clippy::missing_errors_doc)]
46+
pub fn generate_for_test(
47+
alg: &'static Algorithm,
48+
rng: &dyn SecureRandom,
49+
) -> Result<Self, Unspecified> {
50+
Ok(Self(PrivateKey::generate_for_test(alg, rng)?))
51+
}
52+
53+
/// Computes the public key from the private key.
54+
///
55+
/// # Errors
56+
/// `error::Unspecified` when operation fails due to internal error.
57+
pub fn compute_public_key(&self) -> Result<PublicKey, Unspecified> {
58+
self.0.compute_public_key()
59+
}
60+
61+
/// The algorithm for the private key.
62+
#[inline]
63+
#[must_use]
64+
pub fn algorithm(&self) -> &'static Algorithm {
65+
self.0.algorithm()
66+
}
67+
}
68+
69+
/// Performs a key agreement with an ephemeral private key and the given public
70+
/// key.
71+
///
72+
/// `my_private_key` is the ephemeral private key to use. Since it is moved, it
73+
/// will not be usable after calling `agree_ephemeral`, thus guaranteeing that
74+
/// the key is used for only one key agreement.
75+
///
76+
/// `peer_public_key` is the peer's public key. `agree_ephemeral` will return
77+
/// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve.
78+
/// `agree_ephemeral` verifies that it is encoded in the standard form for the
79+
/// algorithm and that the key is *valid*; see the algorithm's documentation for
80+
/// details on how keys are to be encoded and what constitutes a valid key for
81+
/// that algorithm.
82+
///
83+
/// `error_value` is the value to return if an error occurs before `kdf` is
84+
/// called, e.g. when decoding of the peer's public key fails or when the public
85+
/// key is otherwise invalid.
86+
///
87+
/// After the key agreement is done, `agree_ephemeral` calls `kdf` with the raw
88+
/// key material from the key agreement operation and then returns what `kdf`
89+
/// returns.
90+
///
91+
// # FIPS
92+
// Use this function with one of the following key algorithms:
93+
// * `ECDH_P256`
94+
// * `ECDH_P384`
95+
// * `ECDH_P521`
96+
//
97+
/// # Errors
98+
/// `error_value` on internal failure.
99+
#[inline]
100+
#[allow(clippy::needless_pass_by_value)]
101+
#[allow(clippy::missing_panics_doc)]
102+
#[allow(clippy::module_name_repetitions)]
103+
pub fn agree_ephemeral<B: AsRef<[u8]>, F, R, E>(
104+
my_private_key: EphemeralPrivateKey,
105+
peer_public_key: &UnparsedPublicKey<B>,
106+
error_value: E,
107+
kdf: F,
108+
) -> Result<R, E>
109+
where
110+
F: FnOnce(&[u8]) -> Result<R, E>,
111+
{
112+
agree(&my_private_key.0, peer_public_key, error_value, kdf)
113+
}

0 commit comments

Comments
 (0)