Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 125 additions & 6 deletions src/agreement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, error::Unspecified> {
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<Self, error::Unspecified> {
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<PublicKey, error::Unspecified> {
// 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 {
Expand Down Expand Up @@ -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_<F, R, E>(
my_private_key: EphemeralPrivateKey,
fn agree_<F, R, E>(
my_private_key_alg: &Algorithm,
my_private_key: &ec::Seed,
peer_public_key: UnparsedPublicKey<&[u8]>,
error_value: E,
kdf: F,
Expand All @@ -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.
Expand All @@ -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)?;
Expand All @@ -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<B: AsRef<[u8]>, F, R, E>(
my_private_key: &ReusablePrivateKey,
peer_public_key: &UnparsedPublicKey<B>,
error_value: E,
kdf: F,
) -> Result<R, E>
where
F: FnOnce(&[u8]) -> Result<R, E>,
{
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,
)
}
99 changes: 95 additions & 4 deletions tests/agreement_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<agreement::ReusablePrivateKey>();
test::compile_time_assert_sync::<agreement::ReusablePrivateKey>();

assert_eq!(
format!("{:?}", &private_key),
"ReusablePrivateKey { algorithm: Algorithm { curve: P256 } }"
);

let public_key = private_key.compute_public_key().unwrap();

test::compile_time_assert_clone::<agreement::PublicKey>();
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -196,11 +288,10 @@ fn x25519(private_key: &[u8], public_key: &[u8]) -> Vec<u8> {
}

fn x25519_(private_key: &[u8], public_key: &[u8]) -> Result<Vec<u8>, 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)),
Expand Down