blob: aae31d23696ea25c663a6cb096a998ca1eccb8d0 [file] [log] [blame]
// Copyright 2015-2017 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//! ECDH key agreement using the P-256 and P-384 curves.
use super::{ops::*, private_key::*, public_key::*};
use crate::{agreement, ec, error};
/// A key agreement algorithm.
macro_rules! ecdh {
( $NAME:ident, $curve:expr, $name_str:expr, $private_key_ops:expr,
$public_key_ops:expr, $ecdh:ident ) => {
#[doc = "ECDH using the NSA Suite B"]
#[doc=$name_str]
#[doc = "curve."]
///
/// Public keys are encoding in uncompressed form using the
/// Octet-String-to-Elliptic-Curve-Point algorithm in
/// [SEC 1: Elliptic Curve Cryptography, Version 2.0]. Public keys are
/// validated during key agreement according to
/// [NIST Special Publication 800-56A, revision 2] and Appendix B.3 of
/// the NSA's [Suite B Implementer's Guide to NIST SP 800-56A].
///
/// [SEC 1: Elliptic Curve Cryptography, Version 2.0]:
/// http://www.secg.org/sec1-v2.pdf
/// [NIST Special Publication 800-56A, revision 2]:
/// http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
/// [Suite B Implementer's Guide to NIST SP 800-56A]:
/// https://github.com/briansmith/ring/blob/main/doc/ecdh.pdf
pub static $NAME: agreement::Algorithm = agreement::Algorithm {
curve: $curve,
ecdh: $ecdh,
};
fn $ecdh(
out: &mut [u8],
my_private_key: &ec::Seed,
peer_public_key: untrusted::Input,
) -> Result<(), error::Unspecified> {
ecdh(
$private_key_ops,
$public_key_ops,
out,
my_private_key,
peer_public_key,
)
}
};
}
ecdh!(
ECDH_P256,
&ec::suite_b::curve::P256,
"P-256 (secp256r1)",
&p256::PRIVATE_KEY_OPS,
&p256::PUBLIC_KEY_OPS,
p256_ecdh
);
ecdh!(
ECDH_P384,
&ec::suite_b::curve::P384,
"P-384 (secp384r1)",
&p384::PRIVATE_KEY_OPS,
&p384::PUBLIC_KEY_OPS,
p384_ecdh
);
fn ecdh(
private_key_ops: &PrivateKeyOps,
public_key_ops: &PublicKeyOps,
out: &mut [u8],
my_private_key: &ec::Seed,
peer_public_key: untrusted::Input,
) -> Result<(), error::Unspecified> {
// The NIST SP 800-56Ar2 steps are from section 5.7.1.2 Elliptic Curve
// Cryptography Cofactor Diffie-Hellman (ECC CDH) Primitive.
//
// The "NSA Guide" steps are from section 3.1 of the NSA guide, "Ephemeral
// Unified Model."
// NSA Guide Step 1 is handled separately.
// NIST SP 800-56Ar2 5.6.2.2.2.
// NSA Guide Step 2.
//
// `parse_uncompressed_point` verifies that the point is not at infinity
// and that it is on the curve, using the Partial Public-Key Validation
// Routine.
let peer_public_key = parse_uncompressed_point(public_key_ops, peer_public_key)?;
// NIST SP 800-56Ar2 Step 1.
// NSA Guide Step 3 (except point at infinity check).
//
// Note that the cofactor (h) is one since we only support prime-order
// curves, so we can safely ignore the cofactor.
//
// It is impossible for the result to be the point at infinity because our
// private key is in the range [1, n) and the curve has prime order and
// `parse_uncompressed_point` verified that the peer public key is on the
// curve and not at infinity. However, since the standards require the
// check, we do it using `assert!`.
//
// NIST SP 800-56Ar2 defines "Destroy" thusly: "In this Recommendation, to
// destroy is an action applied to a key or a piece of secret data. After
// a key or a piece of secret data is destroyed, no information about its
// value can be recovered." We interpret "destroy" somewhat liberally: we
// assume that since we throw away the values to be destroyed, no
// information about their values can be recovered. This doesn't meet the
// NSA guide's explicit requirement to "zeroize" them though.
// TODO: this only needs common scalar ops
let my_private_key = private_key_as_scalar(private_key_ops, my_private_key);
let product = private_key_ops.point_mul(&my_private_key, &peer_public_key);
// NIST SP 800-56Ar2 Steps 2, 3, 4, and 5.
// NSA Guide Steps 3 (point at infinity check) and 4.
//
// Again, we have a pretty liberal interpretation of the NIST's spec's
// "Destroy" that doesn't meet the NSA requirement to "zeroize."
// `big_endian_affine_from_jacobian` verifies that the result is not at
// infinity and also does an extra check to verify that the point is on
// the curve.
big_endian_affine_from_jacobian(private_key_ops, Some(out), None, &product)
// NSA Guide Step 5 & 6 are deferred to the caller. Again, we have a
// pretty liberal interpretation of the NIST's spec's "Destroy" that
// doesn't meet the NSA requirement to "zeroize."
}
#[cfg(test)]
mod tests {
use super::super::ops;
use crate::{agreement, ec, limb, test};
static SUPPORTED_SUITE_B_ALGS: [(&str, &agreement::Algorithm, &ec::Curve, &ops::CommonOps); 2] = [
(
"P-256",
&agreement::ECDH_P256,
&super::super::curve::P256,
&super::super::ops::p256::COMMON_OPS,
),
(
"P-384",
&agreement::ECDH_P384,
&super::super::curve::P384,
&super::super::ops::p384::COMMON_OPS,
),
];
#[test]
fn test_agreement_suite_b_ecdh_generate() {
// Generates a string of bytes 0x00...00, which will always result in
// a scalar value of zero.
let random_00 = test::rand::FixedByteRandom { byte: 0x00 };
// Generates a string of bytes 0xFF...FF, which will be larger than the
// group order of any curve that is supported.
let random_ff = test::rand::FixedByteRandom { byte: 0xff };
for &(_, alg, curve, ops) in SUPPORTED_SUITE_B_ALGS.iter() {
// Test that the private key value zero is rejected and that
// `generate` gives up after a while of only getting zeros.
assert!(agreement::EphemeralPrivateKey::generate(alg, &random_00).is_err());
// Test that the private key value larger than the group order is
// rejected and that `generate` gives up after a while of only
// getting values larger than the group order.
assert!(agreement::EphemeralPrivateKey::generate(alg, &random_ff).is_err());
// Test that a private key value exactly equal to the group order
// is rejected and that `generate` gives up after a while of only
// getting that value from the PRNG.
let mut n_bytes = [0u8; ec::SCALAR_MAX_BYTES];
let num_bytes = curve.elem_scalar_seed_len;
limb::big_endian_from_limbs(&ops.n.limbs[..ops.num_limbs], &mut n_bytes[..num_bytes]);
{
let n_bytes = &mut n_bytes[..num_bytes];
let rng = test::rand::FixedSliceRandom { bytes: n_bytes };
assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err());
}
// Test that a private key value exactly equal to the group order
// minus 1 is accepted.
let mut n_minus_1_bytes = n_bytes;
{
let n_minus_1_bytes = &mut n_minus_1_bytes[..num_bytes];
n_minus_1_bytes[num_bytes - 1] -= 1;
let rng = test::rand::FixedSliceRandom {
bytes: n_minus_1_bytes,
};
let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
assert_eq!(&n_minus_1_bytes[..], key.bytes());
}
// Test that n + 1 also fails.
let mut n_plus_1_bytes = n_bytes;
{
let n_plus_1_bytes = &mut n_plus_1_bytes[..num_bytes];
n_plus_1_bytes[num_bytes - 1] += 1;
let rng = test::rand::FixedSliceRandom {
bytes: n_plus_1_bytes,
};
assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err());
}
// Test recovery from initial RNG failure. The first value will be
// n, then n + 1, then zero, the next value will be n - 1, which
// will be accepted.
{
let bytes = [
&n_bytes[..num_bytes],
&n_plus_1_bytes[..num_bytes],
&[0u8; ec::SCALAR_MAX_BYTES][..num_bytes],
&n_minus_1_bytes[..num_bytes],
];
let rng = test::rand::FixedSliceSequenceRandom {
bytes: &bytes,
current: core::cell::UnsafeCell::new(0),
};
let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
assert_eq!(&n_minus_1_bytes[..num_bytes], key.bytes());
}
}
}
}