ring/ec/curve25519/
x25519.rs

1// Copyright 2015-2016 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//! X25519 Key agreement.
16
17use super::{ops, scalar::SCALAR_LEN};
18use crate::{agreement, bb, cpu, ec, error, rand};
19use core::ffi::c_int;
20
21static CURVE25519: ec::Curve = ec::Curve {
22    public_key_len: PUBLIC_KEY_LEN,
23    elem_scalar_seed_len: ELEM_AND_SCALAR_LEN,
24    id: ec::CurveID::Curve25519,
25    check_private_key_bytes: x25519_check_private_key_bytes,
26    generate_private_key: x25519_generate_private_key,
27    public_from_private: x25519_public_from_private,
28};
29
30/// X25519 (ECDH using Curve25519) as described in [RFC 7748].
31///
32/// Everything is as described in RFC 7748. Key agreement will fail if the
33/// result of the X25519 operation is zero; see the notes on the
34/// "all-zero value" in [RFC 7748 section 6.1].
35///
36/// [RFC 7748]: https://tools.ietf.org/html/rfc7748
37/// [RFC 7748 section 6.1]: https://tools.ietf.org/html/rfc7748#section-6.1
38pub static X25519: agreement::Algorithm = agreement::Algorithm {
39    curve: &CURVE25519,
40    ecdh: x25519_ecdh,
41};
42
43#[allow(clippy::unnecessary_wraps)]
44fn x25519_check_private_key_bytes(
45    bytes: &[u8],
46    _: cpu::Features,
47) -> Result<(), error::Unspecified> {
48    debug_assert_eq!(bytes.len(), PRIVATE_KEY_LEN);
49    Ok(())
50}
51
52fn x25519_generate_private_key(
53    rng: &dyn rand::SecureRandom,
54    out: &mut [u8],
55    _: cpu::Features,
56) -> Result<(), error::Unspecified> {
57    rng.fill(out)
58}
59
60fn x25519_public_from_private(
61    public_out: &mut [u8],
62    private_key: &ec::Seed,
63    cpu_features: cpu::Features,
64) -> Result<(), error::Unspecified> {
65    let public_out = public_out.try_into()?;
66
67    let private_key: &[u8; SCALAR_LEN] = private_key.bytes_less_safe().try_into()?;
68    let private_key = ops::MaskedScalar::from_bytes_masked(*private_key);
69
70    #[cfg(all(
71        all(target_arch = "arm", target_endian = "little"),
72        any(target_os = "android", target_os = "linux")
73    ))]
74    if let Some(cpu) = <cpu::Features as cpu::GetFeature<_>>::get_feature(&cpu_features) {
75        static MONTGOMERY_BASE_POINT: [u8; 32] = [
76            9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77            0, 0, 0,
78        ];
79        x25519_neon(public_out, &private_key, &MONTGOMERY_BASE_POINT, cpu);
80        return Ok(());
81    }
82
83    prefixed_extern! {
84        fn x25519_public_from_private_generic_masked(
85            public_key_out: &mut PublicKey,
86            private_key: &PrivateKey,
87            use_adx: c_int,
88        );
89    }
90    unsafe {
91        x25519_public_from_private_generic_masked(
92            public_out,
93            &private_key,
94            ops::has_fe25519_adx(cpu_features).into(),
95        );
96    }
97
98    Ok(())
99}
100
101fn x25519_ecdh(
102    out: &mut [u8],
103    my_private_key: &ec::Seed,
104    peer_public_key: untrusted::Input,
105    cpu_features: cpu::Features,
106) -> Result<(), error::Unspecified> {
107    let my_private_key: &[u8; SCALAR_LEN] = my_private_key.bytes_less_safe().try_into()?;
108    let my_private_key = ops::MaskedScalar::from_bytes_masked(*my_private_key);
109    let peer_public_key: &[u8; PUBLIC_KEY_LEN] = peer_public_key.as_slice_less_safe().try_into()?;
110
111    fn scalar_mult(
112        out: &mut ops::EncodedPoint,
113        scalar: &ops::MaskedScalar,
114        point: &ops::EncodedPoint,
115        #[allow(unused_variables)] cpu_features: cpu::Features,
116    ) {
117        #[cfg(all(
118            all(target_arch = "arm", target_endian = "little"),
119            any(target_os = "android", target_os = "linux")
120        ))]
121        if let Some(cpu) = <cpu::Features as cpu::GetFeature<_>>::get_feature(&cpu_features) {
122            return x25519_neon(out, scalar, point, cpu);
123        }
124
125        #[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
126        {
127            if ops::has_fe25519_adx(cpu_features) {
128                prefixed_extern! {
129                    fn x25519_scalar_mult_adx(
130                        out: &mut ops::EncodedPoint,
131                        scalar: &ops::MaskedScalar,
132                        point: &ops::EncodedPoint,
133                    );
134                }
135                return unsafe { x25519_scalar_mult_adx(out, scalar, point) };
136            }
137        }
138
139        prefixed_extern! {
140            fn x25519_scalar_mult_generic_masked(
141                out: &mut ops::EncodedPoint,
142                scalar: &ops::MaskedScalar,
143                point: &ops::EncodedPoint,
144            );
145        }
146        unsafe {
147            x25519_scalar_mult_generic_masked(out, scalar, point);
148        }
149    }
150
151    scalar_mult(
152        out.try_into()?,
153        &my_private_key,
154        peer_public_key,
155        cpu_features,
156    );
157
158    let zeros: SharedSecret = [0; SHARED_SECRET_LEN];
159    if bb::verify_slices_are_equal(out, &zeros).is_ok() {
160        // All-zero output results when the input is a point of small order.
161        return Err(error::Unspecified);
162    }
163
164    Ok(())
165}
166
167// BoringSSL uses `!defined(OPENSSL_APPLE)`.
168#[cfg(all(
169    all(target_arch = "arm", target_endian = "little"),
170    any(target_os = "android", target_os = "linux")
171))]
172fn x25519_neon(
173    out: &mut ops::EncodedPoint,
174    scalar: &ops::MaskedScalar,
175    point: &ops::EncodedPoint,
176    _cpu: cpu::arm::Neon,
177) {
178    prefixed_extern! {
179        fn x25519_NEON(
180            out: &mut ops::EncodedPoint,
181            scalar: &ops::MaskedScalar,
182            point: &ops::EncodedPoint,
183        );
184    }
185    unsafe { x25519_NEON(out, scalar, point) }
186}
187
188const ELEM_AND_SCALAR_LEN: usize = ops::ELEM_LEN;
189
190type PrivateKey = ops::MaskedScalar;
191const PRIVATE_KEY_LEN: usize = ELEM_AND_SCALAR_LEN;
192
193// An X25519 public key as an encoded Curve25519 point.
194type PublicKey = [u8; PUBLIC_KEY_LEN];
195const PUBLIC_KEY_LEN: usize = ELEM_AND_SCALAR_LEN;
196
197// An X25519 shared secret as an encoded Curve25519 point.
198type SharedSecret = [u8; SHARED_SECRET_LEN];
199const SHARED_SECRET_LEN: usize = ELEM_AND_SCALAR_LEN;
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::ec;
205    use untrusted::Input;
206
207    #[test]
208    fn test_x25519_public_from_private() {
209        struct TestVector {
210            private: [u8; 32],
211            public: [u8; 32],
212        }
213        static TEST_CASES: &[TestVector] = &[
214            TestVector {
215                private: [
216                    0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51,
217                    0xb2, 0x66, 0x45, 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77,
218                    0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a,
219                ],
220                public: [
221                    0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, 0x74, 0x8b, 0x7d, 0xdc, 0xb4,
222                    0x3e, 0xf7, 0x5a, 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4, 0xeb, 0xa4,
223                    0xa9, 0x8e, 0xaa, 0x9b, 0x4e, 0x6a,
224                ],
225            },
226            TestVector {
227                private: [
228                    0x5d, 0xab, 0x08, 0x7e, 0x62, 0x4a, 0x8a, 0x4b, 0x79, 0xe1, 0x7f, 0x8b, 0x83,
229                    0x80, 0x0e, 0xe6, 0x6f, 0x3b, 0xb1, 0x29, 0x26, 0x18, 0xb6, 0xfd, 0x1c, 0x2f,
230                    0x8b, 0x27, 0xff, 0x88, 0xe0, 0xeb,
231                ],
232                public: [
233                    0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4, 0xd3, 0x5b, 0x61, 0xc2, 0xec,
234                    0xe4, 0x35, 0x37, 0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d, 0xad, 0xfc,
235                    0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f,
236                ],
237            },
238        ];
239        let cpu_features = cpu::features();
240        for test_case in TEST_CASES {
241            let seed =
242                ec::Seed::from_bytes(&CURVE25519, Input::from(&test_case.private), cpu_features)
243                    .unwrap();
244            let mut output = [0u8; 32];
245            x25519_public_from_private(&mut output, &seed, cpu_features).unwrap();
246            assert_eq!(output, test_case.public);
247        }
248    }
249}