-
Notifications
You must be signed in to change notification settings - Fork 134
/
x25519.rs
377 lines (339 loc) · 13.8 KB
/
x25519.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// -*- mode: rust; -*-
//
// This file is part of x25519-dalek.
// Copyright (c) 2017-2021 isis lovecruft
// Copyright (c) 2019-2021 DebugSteven
// See LICENSE for licensing information.
//
// Authors:
// - isis agora lovecruft <[email protected]>
// - DebugSteven <[email protected]>
//! x25519 Diffie-Hellman key exchange
//!
//! This implements x25519 key exchange as specified by Mike Hamburg
//! and Adam Langley in [RFC7748](https://tools.ietf.org/html/rfc7748).
use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint, traits::IsIdentity};
use rand_core::CryptoRng;
use rand_core::RngCore;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
/// A Diffie-Hellman public key
///
/// We implement `Zeroize` so that downstream consumers may derive it for `Drop`
/// should they wish to erase public keys from memory. Note that this erasure
/// (in this crate) does *not* automatically happen, but either must be derived
/// for Drop or explicitly called.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct PublicKey(pub(crate) MontgomeryPoint);
impl From<[u8; 32]> for PublicKey {
/// Given a byte array, construct a x25519 `PublicKey`.
fn from(bytes: [u8; 32]) -> PublicKey {
PublicKey(MontgomeryPoint(bytes))
}
}
impl PublicKey {
/// Convert this public key to a byte array.
#[inline]
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
/// View this public key as a byte array.
#[inline]
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
}
impl AsRef<[u8]> for PublicKey {
/// View this public key as a byte array.
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
/// A short-lived Diffie-Hellman secret key that can only be used to compute a single
/// [`SharedSecret`].
///
/// This type is identical to the `StaticSecret` type, except that the
/// [`EphemeralSecret::diffie_hellman`] method consumes and then wipes the secret key, and there
/// are no serialization methods defined. This means that [`EphemeralSecret`]s can only be
/// generated from fresh randomness where the compiler statically checks that the resulting
/// secret is used at most once.
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[cfg_attr(feature = "zeroize", zeroize(drop))]
pub struct EphemeralSecret(pub(crate) [u8; 32]);
impl EphemeralSecret {
/// Perform a Diffie-Hellman key agreement between `self` and
/// `their_public` key to produce a [`SharedSecret`].
pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {
SharedSecret(their_public.0.mul_clamped(self.0))
}
/// Generate a new [`EphemeralSecret`] with the supplied RNG.
#[deprecated(
since = "2.0.0",
note = "Renamed to `random_from_rng`. This will be removed in 2.1.0"
)]
pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
Self::random_from_rng(&mut csprng)
}
/// Generate a new [`EphemeralSecret`] with the supplied RNG.
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
// The secret key is random bytes. Clamping is done later.
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
EphemeralSecret(bytes)
}
/// Generate a new [`EphemeralSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
}
}
impl<'a> From<&'a EphemeralSecret> for PublicKey {
/// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicKey`].
fn from(secret: &'a EphemeralSecret) -> PublicKey {
PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
}
}
/// A Diffie-Hellman secret key which may be used more than once, but is
/// purposefully not serialiseable in order to discourage key-reuse. This is
/// implemented to facilitate protocols such as Noise (e.g. Noise IK key usage,
/// etc.) and X3DH which require an "ephemeral" key to conduct the
/// Diffie-Hellman operation multiple times throughout the protocol, while the
/// protocol run at a higher level is only conducted once per key.
///
/// Similarly to [`EphemeralSecret`], this type does _not_ have serialisation
/// methods, in order to discourage long-term usage of secret key material. (For
/// long-term secret keys, see `StaticSecret`.)
///
/// # Warning
///
/// If you're uncertain about whether you should use this, then you likely
/// should not be using this. Our strongly recommended advice is to use
/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that
/// secret keys are never reused, which can have very serious security
/// implications for many protocols.
#[cfg(feature = "reusable_secrets")]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[cfg_attr(feature = "zeroize", zeroize(drop))]
#[derive(Clone)]
pub struct ReusableSecret(pub(crate) [u8; 32]);
#[cfg(feature = "reusable_secrets")]
impl ReusableSecret {
/// Perform a Diffie-Hellman key agreement between `self` and
/// `their_public` key to produce a [`SharedSecret`].
pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
SharedSecret(their_public.0.mul_clamped(self.0))
}
/// Generate a new [`ReusableSecret`] with the supplied RNG.
#[deprecated(
since = "2.0.0",
note = "Renamed to `random_from_rng`. This will be removed in 2.1.0."
)]
pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
Self::random_from_rng(&mut csprng)
}
/// Generate a new [`ReusableSecret`] with the supplied RNG.
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
// The secret key is random bytes. Clamping is done later.
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
ReusableSecret(bytes)
}
/// Generate a new [`ReusableSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
}
}
#[cfg(feature = "reusable_secrets")]
impl<'a> From<&'a ReusableSecret> for PublicKey {
/// Given an x25519 [`ReusableSecret`] key, compute its corresponding [`PublicKey`].
fn from(secret: &'a ReusableSecret) -> PublicKey {
PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
}
}
/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s.
///
/// This type is identical to the [`EphemeralSecret`] type, except that the
/// [`StaticSecret::diffie_hellman`] method does not consume the secret key, and the type provides
/// serialization methods to save and load key material. This means that the secret may be used
/// multiple times (but does not *have to be*).
///
/// # Warning
///
/// If you're uncertain about whether you should use this, then you likely
/// should not be using this. Our strongly recommended advice is to use
/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that
/// secret keys are never reused, which can have very serious security
/// implications for many protocols.
#[cfg(feature = "static_secrets")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[cfg_attr(feature = "zeroize", zeroize(drop))]
#[derive(Clone)]
pub struct StaticSecret([u8; 32]);
#[cfg(feature = "static_secrets")]
impl StaticSecret {
/// Perform a Diffie-Hellman key agreement between `self` and
/// `their_public` key to produce a `SharedSecret`.
pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
SharedSecret(their_public.0.mul_clamped(self.0))
}
/// Generate a new [`StaticSecret`] with the supplied RNG.
#[deprecated(
since = "2.0.0",
note = "Renamed to `random_from_rng`. This will be removed in 2.1.0"
)]
pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
Self::random_from_rng(&mut csprng)
}
/// Generate a new [`StaticSecret`] with the supplied RNG.
pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
// The secret key is random bytes. Clamping is done later.
let mut bytes = [0u8; 32];
csprng.fill_bytes(&mut bytes);
StaticSecret(bytes)
}
/// Generate a new [`StaticSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
}
/// Extract this key's bytes for serialization.
#[inline]
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
/// View this key as a byte array.
#[inline]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(feature = "static_secrets")]
impl From<[u8; 32]> for StaticSecret {
/// Load a secret key from a byte array.
fn from(bytes: [u8; 32]) -> StaticSecret {
StaticSecret(bytes)
}
}
#[cfg(feature = "static_secrets")]
impl<'a> From<&'a StaticSecret> for PublicKey {
/// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicKey`].
fn from(secret: &'a StaticSecret) -> PublicKey {
PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
}
}
#[cfg(feature = "static_secrets")]
impl AsRef<[u8]> for StaticSecret {
/// View this key as a byte array.
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
/// The result of a Diffie-Hellman key exchange.
///
/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their
/// counterparty's [`PublicKey`].
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[cfg_attr(feature = "zeroize", zeroize(drop))]
pub struct SharedSecret(pub(crate) MontgomeryPoint);
impl SharedSecret {
/// Convert this shared secret to a byte array.
#[inline]
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
/// View this shared secret key as a byte array.
#[inline]
pub fn as_bytes(&self) -> &[u8; 32] {
self.0.as_bytes()
}
/// Ensure in constant-time that this shared secret did not result from a
/// key exchange with non-contributory behaviour.
///
/// In some more exotic protocols which need to guarantee "contributory"
/// behaviour for both parties, that is, that each party contributed a public
/// value which increased the security of the resulting shared secret.
/// To take an example protocol attack where this could lead to undesirable
/// results [from Thái "thaidn" Dương](https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html):
///
/// > If Mallory replaces Alice's and Bob's public keys with zero, which is
/// > a valid Curve25519 public key, he would be able to force the ECDH
/// > shared value to be zero, which is the encoding of the point at infinity,
/// > and thus get to dictate some publicly known values as the shared
/// > keys. It still requires an active man-in-the-middle attack to pull the
/// > trick, after which, however, not only Mallory can decode Alice's data,
/// > but everyone too! It is also impossible for Alice and Bob to detect the
/// > intrusion, as they still share the same keys, and can communicate with
/// > each other as normal.
///
/// The original Curve25519 specification argues that checks for
/// non-contributory behaviour are "unnecessary for Diffie-Hellman".
/// Whether this check is necessary for any particular given protocol is
/// often a matter of debate, which we will not re-hash here, but simply
/// cite some of the [relevant] [public] [discussions].
///
/// # Returns
///
/// Returns `true` if the key exchange was contributory (good), and `false`
/// otherwise (can be bad for some protocols).
///
/// [relevant]: https://tools.ietf.org/html/rfc7748#page-15
/// [public]: https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html
/// [discussions]: https://vnhacker.blogspot.com/2016/08/the-internet-of-broken-protocols.html
#[must_use]
pub fn was_contributory(&self) -> bool {
!self.0.is_identity()
}
}
impl AsRef<[u8]> for SharedSecret {
/// View this shared secret key as a byte array.
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
/// The bare, byte-oriented x25519 function, exactly as specified in RFC7748.
///
/// This can be used with [`X25519_BASEPOINT_BYTES`] for people who
/// cannot use the better, safer, and faster ephemeral DH API.
///
/// # Example
#[cfg_attr(feature = "static_secrets", doc = "```")]
#[cfg_attr(not(feature = "static_secrets"), doc = "```ignore")]
/// use rand_core::OsRng;
/// use rand_core::RngCore;
///
/// use x25519_dalek::x25519;
/// use x25519_dalek::StaticSecret;
/// use x25519_dalek::PublicKey;
///
/// // Generate Alice's key pair.
/// let alice_secret = StaticSecret::random_from_rng(&mut OsRng);
/// let alice_public = PublicKey::from(&alice_secret);
///
/// // Generate Bob's key pair.
/// let bob_secret = StaticSecret::random_from_rng(&mut OsRng);
/// let bob_public = PublicKey::from(&bob_secret);
///
/// // Alice and Bob should now exchange their public keys.
///
/// // Once they've done so, they may generate a shared secret.
/// let alice_shared = x25519(alice_secret.to_bytes(), bob_public.to_bytes());
/// let bob_shared = x25519(bob_secret.to_bytes(), alice_public.to_bytes());
///
/// assert_eq!(alice_shared, bob_shared);
/// ```
pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] {
MontgomeryPoint(u).mul_clamped(k).to_bytes()
}
/// The X25519 basepoint, for use with the bare, byte-oriented x25519
/// function. This is provided for people who cannot use the typed
/// DH API for some reason.
pub const X25519_BASEPOINT_BYTES: [u8; 32] = [
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, 0, 0, 0,
];