diff --git a/src/agreement.rs b/src/agreement.rs index 4c1e803430..0678c8c695 100644 --- a/src/agreement.rs +++ b/src/agreement.rs @@ -150,6 +150,76 @@ impl EphemeralPrivateKey { } } +/// A reusable private key for use (only) with `agree_reusable`. +pub struct ReusablePrivateKey { + private_key: ec::Seed, + algorithm: &'static Algorithm, +} + +derive_debug_via_field!( + ReusablePrivateKey, + stringify!(ReusablePrivateKey), + algorithm +); + +impl ReusablePrivateKey { + /// Generate a new reusable private key for the given algorithm. + pub fn generate( + alg: &'static Algorithm, + rng: &dyn rand::SecureRandom, + ) -> Result { + let cpu_features = cpu::features(); + + // NSA Guide Step 1. + // + // This only handles the key generation part of step 1. The rest of + // step one is done by `compute_public_key()`. + let private_key = ec::Seed::generate(&alg.curve, rng, cpu_features)?; + Ok(Self { + private_key, + algorithm: alg, + }) + } + + /// Load a reusable private key from some bytes. + pub fn from_bytes(alg: &'static Algorithm, bytes: &[u8]) -> Result { + let cpu_features = cpu::features(); + + let private_key = ec::Seed::from_bytes(&alg.curve, bytes.into(), cpu_features)?; + Ok(Self { + private_key, + algorithm: alg, + }) + } + + /// Computes the public key from the private key. + #[inline(always)] + pub fn compute_public_key(&self) -> Result { + // NSA Guide Step 1. + // + // Obviously, this only handles the part of Step 1 between the private + // key generation and the sending of the public key to the peer. `out` + // is what should be sent to the peer. + self.private_key + .compute_public_key() + .map(|public_key| PublicKey { + algorithm: self.algorithm, + bytes: public_key, + }) + } + + /// The algorithm for the private key. + #[inline] + pub fn algorithm(&self) -> &'static Algorithm { + self.algorithm + } + + /// Get raw bytes of this private key. + pub fn bytes(&self) -> &[u8] { + self.private_key.bytes_less_safe() + } +} + /// A public key for key agreement. #[derive(Clone)] pub struct PublicKey { @@ -266,11 +336,18 @@ where algorithm: peer_public_key.algorithm, bytes: peer_public_key.bytes.as_ref(), }; - agree_ephemeral_(my_private_key, peer_public_key, error_value, kdf) + agree_( + &my_private_key.algorithm, + &my_private_key.private_key, + peer_public_key, + error_value, + kdf, + ) } -fn agree_ephemeral_( - my_private_key: EphemeralPrivateKey, +fn agree_( + my_private_key_alg: &Algorithm, + my_private_key: &ec::Seed, peer_public_key: UnparsedPublicKey<&[u8]>, error_value: E, kdf: F, @@ -283,11 +360,11 @@ where // The domain parameters are hard-coded. This check verifies that the // peer's public key's domain parameters match the domain parameters of // this private key. - if peer_public_key.algorithm != my_private_key.algorithm { + if peer_public_key.algorithm != my_private_key_alg { return Err(error_value); } - let alg = &my_private_key.algorithm; + let alg = my_private_key_alg; // NSA Guide Prerequisite 2, regarding which KDFs are allowed, is delegated // to the caller. @@ -308,7 +385,7 @@ where // that doesn't meet the NSA requirement to "zeroize." (alg.ecdh)( shared_key, - &my_private_key.private_key, + my_private_key, untrusted::Input::from(peer_public_key.bytes), ) .map_err(|_| error_value)?; @@ -319,3 +396,45 @@ where // "Destroy" that doesn't meet the NSA requirement to "zeroize." kdf(shared_key) } + +/// Performs a key agreement with a reusable private key and the given public +/// key. +/// +/// `my_private_key` is the reusable private key to use. +/// +/// `peer_public_key` is the peer's public key. `agree_reusable` will return +/// `Err(error_value)` if it does not match `my_private_key's` algorithm/curve. +/// `agree_reusable` verifies that it is encoded in the standard form for the +/// algorithm and that the key is *valid*; see the algorithm's documentation for +/// details on how keys are to be encoded and what constitutes a valid key for +/// that algorithm. +/// +/// `error_value` is the value to return if an error occurs before `kdf` is +/// called, e.g. when decoding of the peer's public key fails or when the public +/// key is otherwise invalid. +/// +/// After the key agreement is done, `agree_reusable` calls `kdf` with the raw key +/// material from the key agreement operation and then returns what `kdf` +/// returns. +#[inline] +pub fn agree_reusable, F, R, E>( + my_private_key: &ReusablePrivateKey, + peer_public_key: &UnparsedPublicKey, + error_value: E, + kdf: F, +) -> Result +where + F: FnOnce(&[u8]) -> Result, +{ + let peer_public_key = UnparsedPublicKey { + algorithm: peer_public_key.algorithm, + bytes: peer_public_key.bytes.as_ref(), + }; + agree_( + &my_private_key.algorithm, + &my_private_key.private_key, + peer_public_key, + error_value, + kdf, + ) +} diff --git a/tests/agreement_tests.rs b/tests/agreement_tests.rs index 0c6cf5ec2e..da4d5ee315 100644 --- a/tests/agreement_tests.rs +++ b/tests/agreement_tests.rs @@ -51,6 +51,16 @@ fn agreement_traits<'a>() { "EphemeralPrivateKey { algorithm: Algorithm { curve: P256 } }" ); + let private_key = agreement::ReusablePrivateKey::generate(&agreement::ECDH_P256, &rng).unwrap(); + + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); + + assert_eq!( + format!("{:?}", &private_key), + "ReusablePrivateKey { algorithm: Algorithm { curve: P256 } }" + ); + let public_key = private_key.compute_public_key().unwrap(); test::compile_time_assert_clone::(); @@ -139,6 +149,88 @@ fn agreement_agree_ephemeral() { }); } +#[test] +fn agreement_agree_reusable() { + let rng = rand::SystemRandom::new(); + + test::run(test_file!("agreement_tests.txt"), |section, test_case| { + assert_eq!(section, ""); + + let curve_name = test_case.consume_string("Curve"); + let alg = alg_from_curve_name(&curve_name); + let peer_public = agreement::UnparsedPublicKey::new(alg, test_case.consume_bytes("PeerQ")); + + match test_case.consume_optional_string("Error") { + None => { + let my_private = test_case.consume_bytes("D"); + let my_private = agreement::ReusablePrivateKey::from_bytes(alg, &my_private)?; + let my_public = test_case.consume_bytes("MyQ"); + let output = test_case.consume_bytes("Output"); + + assert_eq!(my_private.algorithm(), alg); + + let computed_public = my_private.compute_public_key().unwrap(); + assert_eq!(computed_public.as_ref(), &my_public[..]); + + assert_eq!(my_private.algorithm(), alg); + + assert!( + agreement::agree_reusable(&my_private, &peer_public, (), |key_material| { + assert_eq!(key_material, &output[..]); + Ok(()) + }) + .is_ok() + ); + } + + Some(_) => { + // In the no-heap mode, some algorithms aren't supported so + // we have to skip those algorithms' test cases. + let dummy_private_key = agreement::ReusablePrivateKey::generate(alg, &rng)?; + fn kdf_not_called(_: &[u8]) -> Result<(), ()> { + panic!( + "The KDF was called during ECDH when the peer's \ + public key is invalid." + ); + } + assert!(agreement::agree_reusable( + &dummy_private_key, + &peer_public, + (), + kdf_not_called + ) + .is_err()); + } + } + + return Ok(()); + }); +} + +#[test] +fn test_reusable_private_key_to_and_from_bytes() { + use ring::rand::SecureRandom; + let mut rng = rand::SystemRandom::new(); + + let k = agreement::ReusablePrivateKey::generate(&agreement::X25519, &mut rng).unwrap(); + let bytes = k.bytes(); + let k1 = agreement::ReusablePrivateKey::from_bytes(&agreement::X25519, bytes).unwrap(); + assert_eq!(bytes, k1.bytes()); + + let key_len = bytes.len(); + + let mut bytes = vec![0u8; 2 * key_len]; + rng.fill(&mut bytes).unwrap(); + + for i in 0..=bytes.len() { + if i != key_len { + assert!( + agreement::ReusablePrivateKey::from_bytes(&agreement::X25519, &bytes[..i]).is_err() + ); + } + } +} + #[test] fn test_agreement_ecdh_x25519_rfc_iterated() { let mut k = h("0900000000000000000000000000000000000000000000000000000000000000"); @@ -196,11 +288,10 @@ fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec { } fn x25519_(private_key: &[u8], public_key: &[u8]) -> Result, error::Unspecified> { - let rng = test::rand::FixedSliceRandom { bytes: private_key }; - let private_key = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng)?; + let private_key = agreement::ReusablePrivateKey::from_bytes(&agreement::X25519, private_key)?; let public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, public_key); - agreement::agree_ephemeral( - private_key, + agreement::agree_reusable( + &private_key, &public_key, error::Unspecified, |agreed_value| Ok(Vec::from(agreed_value)),