ring/ec/suite_b/
ecdh.rs

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 AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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
17use super::{ops::*, private_key::*, public_key::*};
18use crate::{agreement, cpu, ec, error};
19
20/// A key agreement algorithm.
21macro_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
64ecdh!(
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
73ecdh!(
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
82fn 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)]
148mod tests {
149    use super::super::ops;
150    use crate::testutil as test;
151    use crate::{agreement, ec, limb};
152
153    static SUPPORTED_SUITE_B_ALGS: [(&str, &agreement::Algorithm, &ec::Curve, &ops::CommonOps); 2] = [
154        (
155            "P-256",
156            &agreement::ECDH_P256,
157            &super::super::curve::P256,
158            &ops::p256::COMMON_OPS,
159        ),
160        (
161            "P-384",
162            &agreement::ECDH_P384,
163            &super::super::curve::P384,
164            &ops::p384::COMMON_OPS,
165        ),
166    ];
167
168    #[test]
169    fn test_agreement_suite_b_ecdh_generate() {
170        // Generates a string of bytes 0x00...00, which will always result in
171        // a scalar value of zero.
172        let random_00 = test::rand::FixedByteRandom { byte: 0x00 };
173
174        // Generates a string of bytes 0xFF...FF, which will be larger than the
175        // group order of any curve that is supported.
176        let random_ff = test::rand::FixedByteRandom { byte: 0xff };
177
178        for &(_, alg, curve, ops) in SUPPORTED_SUITE_B_ALGS.iter() {
179            // Test that the private key value zero is rejected and that
180            // `generate` gives up after a while of only getting zeros.
181            assert!(agreement::EphemeralPrivateKey::generate(alg, &random_00).is_err());
182
183            // Test that the private key value larger than the group order is
184            // rejected and that `generate` gives up after a while of only
185            // getting values larger than the group order.
186            assert!(agreement::EphemeralPrivateKey::generate(alg, &random_ff).is_err());
187
188            // Test that a private key value exactly equal to the group order
189            // is rejected and that `generate` gives up after a while of only
190            // getting that value from the PRNG.
191            let mut n_bytes = [0u8; ec::SCALAR_MAX_BYTES];
192            let num_bytes = curve.elem_scalar_seed_len;
193            limb::big_endian_from_limbs(ops.n_limbs(), &mut n_bytes[..num_bytes]);
194            {
195                let n_bytes = &mut n_bytes[..num_bytes];
196                let rng = test::rand::FixedSliceRandom { bytes: n_bytes };
197                assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err());
198            }
199
200            // Test that a private key value exactly equal to the group order
201            // minus 1 is accepted.
202            let mut n_minus_1_bytes = n_bytes;
203            {
204                let n_minus_1_bytes = &mut n_minus_1_bytes[..num_bytes];
205                n_minus_1_bytes[num_bytes - 1] -= 1;
206                let rng = test::rand::FixedSliceRandom {
207                    bytes: n_minus_1_bytes,
208                };
209                let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
210                assert_eq!(n_minus_1_bytes, key.bytes_for_test());
211            }
212
213            // Test that n + 1 also fails.
214            let mut n_plus_1_bytes = n_bytes;
215            {
216                let n_plus_1_bytes = &mut n_plus_1_bytes[..num_bytes];
217                n_plus_1_bytes[num_bytes - 1] += 1;
218                let rng = test::rand::FixedSliceRandom {
219                    bytes: n_plus_1_bytes,
220                };
221                assert!(agreement::EphemeralPrivateKey::generate(alg, &rng).is_err());
222            }
223
224            // Test recovery from initial RNG failure. The first value will be
225            // n, then n + 1, then zero, the next value will be n - 1, which
226            // will be accepted.
227            {
228                let bytes = [
229                    &n_bytes[..num_bytes],
230                    &n_plus_1_bytes[..num_bytes],
231                    &[0u8; ec::SCALAR_MAX_BYTES][..num_bytes],
232                    &n_minus_1_bytes[..num_bytes],
233                ];
234                let rng = test::rand::FixedSliceSequenceRandom {
235                    bytes: &bytes,
236                    current: core::cell::UnsafeCell::new(0),
237                };
238                let key = agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
239                assert_eq!(&n_minus_1_bytes[..num_bytes], key.bytes_for_test());
240            }
241        }
242    }
243}