| 1 | // Copyright 2015-2017 Brian Smith. |
| 2 | // |
| 3 | // Permission to use, copy, modify, and/or distribute this software for any |
| 4 | // purpose with or without fee is hereby granted, provided that the above |
| 5 | // copyright notice and this permission notice appear in all copies. |
| 6 | // |
| 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
| 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
| 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 14 | |
| 15 | //! ECDH key agreement using the P-256 and P-384 curves. |
| 16 | |
| 17 | use super::{ops::*, private_key::*, public_key::*}; |
| 18 | use crate::{agreement, cpu, ec, error}; |
| 19 | |
| 20 | /// A key agreement algorithm. |
| 21 | macro_rules! ecdh { |
| 22 | ( $NAME:ident, $curve:expr, $name_str:expr, $private_key_ops:expr, |
| 23 | $public_key_ops:expr, $ecdh:ident ) => { |
| 24 | #[doc = "ECDH using the NSA Suite B" ] |
| 25 | #[doc=$name_str] |
| 26 | #[doc = "curve." ] |
| 27 | /// |
| 28 | /// Public keys are encoding in uncompressed form using the |
| 29 | /// Octet-String-to-Elliptic-Curve-Point algorithm in |
| 30 | /// [SEC 1: Elliptic Curve Cryptography, Version 2.0]. Public keys are |
| 31 | /// validated during key agreement according to |
| 32 | /// [NIST Special Publication 800-56A, revision 2] and Appendix B.3 of |
| 33 | /// the NSA's [Suite B Implementer's Guide to NIST SP 800-56A]. |
| 34 | /// |
| 35 | /// [SEC 1: Elliptic Curve Cryptography, Version 2.0]: |
| 36 | /// http://www.secg.org/sec1-v2.pdf |
| 37 | /// [NIST Special Publication 800-56A, revision 2]: |
| 38 | /// http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf |
| 39 | /// [Suite B Implementer's Guide to NIST SP 800-56A]: |
| 40 | /// https://github.com/briansmith/ring/blob/main/doc/ecdh.pdf |
| 41 | pub static $NAME: agreement::Algorithm = agreement::Algorithm { |
| 42 | curve: $curve, |
| 43 | ecdh: $ecdh, |
| 44 | }; |
| 45 | |
| 46 | fn $ecdh( |
| 47 | out: &mut [u8], |
| 48 | my_private_key: &ec::Seed, |
| 49 | peer_public_key: untrusted::Input, |
| 50 | cpu: cpu::Features, |
| 51 | ) -> Result<(), error::Unspecified> { |
| 52 | ecdh( |
| 53 | $private_key_ops, |
| 54 | $public_key_ops, |
| 55 | out, |
| 56 | my_private_key, |
| 57 | peer_public_key, |
| 58 | cpu, |
| 59 | ) |
| 60 | } |
| 61 | }; |
| 62 | } |
| 63 | |
| 64 | ecdh!( |
| 65 | ECDH_P256, |
| 66 | &ec::suite_b::curve::P256, |
| 67 | "P-256 (secp256r1)" , |
| 68 | &p256::PRIVATE_KEY_OPS, |
| 69 | &p256::PUBLIC_KEY_OPS, |
| 70 | p256_ecdh |
| 71 | ); |
| 72 | |
| 73 | ecdh!( |
| 74 | ECDH_P384, |
| 75 | &ec::suite_b::curve::P384, |
| 76 | "P-384 (secp384r1)" , |
| 77 | &p384::PRIVATE_KEY_OPS, |
| 78 | &p384::PUBLIC_KEY_OPS, |
| 79 | p384_ecdh |
| 80 | ); |
| 81 | |
| 82 | fn ecdh( |
| 83 | private_key_ops: &PrivateKeyOps, |
| 84 | public_key_ops: &PublicKeyOps, |
| 85 | out: &mut [u8], |
| 86 | my_private_key: &ec::Seed, |
| 87 | peer_public_key: untrusted::Input, |
| 88 | cpu: cpu::Features, |
| 89 | ) -> Result<(), error::Unspecified> { |
| 90 | // The NIST SP 800-56Ar2 steps are from section 5.7.1.2 Elliptic Curve |
| 91 | // Cryptography Cofactor Diffie-Hellman (ECC CDH) Primitive. |
| 92 | // |
| 93 | // The "NSA Guide" steps are from section 3.1 of the NSA guide, "Ephemeral |
| 94 | // Unified Model." |
| 95 | |
| 96 | let q = &public_key_ops.common.elem_modulus(cpu); |
| 97 | |
| 98 | // NSA Guide Step 1 is handled separately. |
| 99 | |
| 100 | // NIST SP 800-56Ar2 5.6.2.2.2. |
| 101 | // NSA Guide Step 2. |
| 102 | // |
| 103 | // `parse_uncompressed_point` verifies that the point is not at infinity |
| 104 | // and that it is on the curve, using the Partial Public-Key Validation |
| 105 | // Routine. |
| 106 | let peer_public_key = parse_uncompressed_point(public_key_ops, q, peer_public_key)?; |
| 107 | |
| 108 | // NIST SP 800-56Ar2 Step 1. |
| 109 | // NSA Guide Step 3 (except point at infinity check). |
| 110 | // |
| 111 | // Note that the cofactor (h) is one since we only support prime-order |
| 112 | // curves, so we can safely ignore the cofactor. |
| 113 | // |
| 114 | // It is impossible for the result to be the point at infinity because our |
| 115 | // private key is in the range [1, n) and the curve has prime order and |
| 116 | // `parse_uncompressed_point` verified that the peer public key is on the |
| 117 | // curve and not at infinity. However, since the standards require the |
| 118 | // check, we do it using `assert!`. |
| 119 | // |
| 120 | // NIST SP 800-56Ar2 defines "Destroy" thusly: "In this Recommendation, to |
| 121 | // destroy is an action applied to a key or a piece of secret data. After |
| 122 | // a key or a piece of secret data is destroyed, no information about its |
| 123 | // value can be recovered." We interpret "destroy" somewhat liberally: we |
| 124 | // assume that since we throw away the values to be destroyed, no |
| 125 | // information about their values can be recovered. This doesn't meet the |
| 126 | // NSA guide's explicit requirement to "zeroize" them though. |
| 127 | // TODO: this only needs common scalar ops |
| 128 | let n = &private_key_ops.common.scalar_modulus(cpu); |
| 129 | let my_private_key = private_key_as_scalar(n, my_private_key); |
| 130 | let product = private_key_ops.point_mul(&my_private_key, &peer_public_key, cpu); |
| 131 | |
| 132 | // NIST SP 800-56Ar2 Steps 2, 3, 4, and 5. |
| 133 | // NSA Guide Steps 3 (point at infinity check) and 4. |
| 134 | // |
| 135 | // Again, we have a pretty liberal interpretation of the NIST's spec's |
| 136 | // "Destroy" that doesn't meet the NSA requirement to "zeroize." |
| 137 | // `big_endian_affine_from_jacobian` verifies that the result is not at |
| 138 | // infinity and also does an extra check to verify that the point is on |
| 139 | // the curve. |
| 140 | big_endian_affine_from_jacobian(private_key_ops, q, out, None, &product) |
| 141 | |
| 142 | // NSA Guide Step 5 & 6 are deferred to the caller. Again, we have a |
| 143 | // pretty liberal interpretation of the NIST's spec's "Destroy" that |
| 144 | // doesn't meet the NSA requirement to "zeroize." |
| 145 | } |
| 146 | |
| 147 | #[cfg (test)] |
| 148 | mod tests { |
| 149 | use super::super::ops; |
| 150 | use crate::{agreement, ec, limb, test}; |
| 151 | |
| 152 | static SUPPORTED_SUITE_B_ALGS: [(&str, &agreement::Algorithm, &ec::Curve, &ops::CommonOps); 2] = [ |
| 153 | ( |
| 154 | "P-256" , |
| 155 | &agreement::ECDH_P256, |
| 156 | &super::super::curve::P256, |
| 157 | &ops::p256::COMMON_OPS, |
| 158 | ), |
| 159 | ( |
| 160 | "P-384" , |
| 161 | &agreement::ECDH_P384, |
| 162 | &super::super::curve::P384, |
| 163 | &ops::p384::COMMON_OPS, |
| 164 | ), |
| 165 | ]; |
| 166 | |
| 167 | #[test ] |
| 168 | fn test_agreement_suite_b_ecdh_generate() { |
| 169 | // Generates a string of bytes 0x00...00, which will always result in |
| 170 | // a scalar value of zero. |
| 171 | let random_00 = test::rand::FixedByteRandom { byte: 0x00 }; |
| 172 | |
| 173 | // Generates a string of bytes 0xFF...FF, which will be larger than the |
| 174 | // group order of any curve that is supported. |
| 175 | let random_ff = test::rand::FixedByteRandom { byte: 0xff }; |
| 176 | |
| 177 | for &(_, alg, curve, ops) in SUPPORTED_SUITE_B_ALGS.iter() { |
| 178 | // Test that the private key value zero is rejected and that |
| 179 | // `generate` gives up after a while of only getting zeros. |
| 180 | assert!(agreement::EphemeralPrivateKey::generate(alg, &random_00).is_err()); |
| 181 | |
| 182 | // Test that the private key value larger than the group order is |
| 183 | // rejected and that `generate` gives up after a while of only |
| 184 | // getting values larger than the group order. |
| 185 | assert!(agreement::EphemeralPrivateKey::generate(alg, &random_ff).is_err()); |
| 186 | |
| 187 | // Test that a private key value exactly equal to the group order |
| 188 | // is rejected and that `generate` gives up after a while of only |
| 189 | // getting that value from the PRNG. |
| 190 | let mut n_bytes = [0u8; ec::SCALAR_MAX_BYTES]; |
| 191 | let num_bytes = curve.elem_scalar_seed_len; |
| 192 | limb::big_endian_from_limbs(ops.n_limbs(), &mut n_bytes[..num_bytes]); |
| 193 | { |
| 194 | let n_bytes = &mut n_bytes[..num_bytes]; |
| 195 | let rng = test::rand::FixedSliceRandom { bytes: n_bytes }; |
| 196 | assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err()); |
| 197 | } |
| 198 | |
| 199 | // Test that a private key value exactly equal to the group order |
| 200 | // minus 1 is accepted. |
| 201 | let mut n_minus_1_bytes = n_bytes; |
| 202 | { |
| 203 | let n_minus_1_bytes = &mut n_minus_1_bytes[..num_bytes]; |
| 204 | n_minus_1_bytes[num_bytes - 1] -= 1; |
| 205 | let rng = test::rand::FixedSliceRandom { |
| 206 | bytes: n_minus_1_bytes, |
| 207 | }; |
| 208 | let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap(); |
| 209 | assert_eq!(n_minus_1_bytes, key.bytes_for_test()); |
| 210 | } |
| 211 | |
| 212 | // Test that n + 1 also fails. |
| 213 | let mut n_plus_1_bytes = n_bytes; |
| 214 | { |
| 215 | let n_plus_1_bytes = &mut n_plus_1_bytes[..num_bytes]; |
| 216 | n_plus_1_bytes[num_bytes - 1] += 1; |
| 217 | let rng = test::rand::FixedSliceRandom { |
| 218 | bytes: n_plus_1_bytes, |
| 219 | }; |
| 220 | assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err()); |
| 221 | } |
| 222 | |
| 223 | // Test recovery from initial RNG failure. The first value will be |
| 224 | // n, then n + 1, then zero, the next value will be n - 1, which |
| 225 | // will be accepted. |
| 226 | { |
| 227 | let bytes = [ |
| 228 | &n_bytes[..num_bytes], |
| 229 | &n_plus_1_bytes[..num_bytes], |
| 230 | &[0u8; ec::SCALAR_MAX_BYTES][..num_bytes], |
| 231 | &n_minus_1_bytes[..num_bytes], |
| 232 | ]; |
| 233 | let rng = test::rand::FixedSliceSequenceRandom { |
| 234 | bytes: &bytes, |
| 235 | current: core::cell::UnsafeCell::new(0), |
| 236 | }; |
| 237 | let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap(); |
| 238 | assert_eq!(&n_minus_1_bytes[..num_bytes], key.bytes_for_test()); |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | |