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