Skip to main content

bssl_crypto/
x25519.rs

1/* Copyright 2023 The BoringSSL Authors
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
16//! Diffie-Hellman over curve25519.
17//!
18//! X25519 is the Diffie-Hellman primitive built from curve25519. It is sometimes referred to as
19//! “curve25519”, but “X25519” is a more precise name. See <http://cr.yp.to/ecdh.html> and
20//! <https://tools.ietf.org/html/rfc7748>.
21//!
22//! ```
23//! use bssl_crypto::x25519;
24//!
25//! // Alice generates her key pair.
26//! let (alice_public_key, alice_private_key) = x25519::PrivateKey::generate();
27//! // Bob generates his key pair.
28//! let (bob_public_key, bob_private_key) = x25519::PrivateKey::generate();
29//!
30//! // If Alice obtains Bob's public key somehow, she can compute their
31//! // shared key:
32//! let shared_key = alice_private_key.compute_shared_key(&bob_public_key);
33//!
34//! // Alice can then derive a key (e.g. by using HKDF), which should include
35//! // at least the two public keys. Then shen can send a message to Bob
36//! // including her public key and an AEAD-protected blob. Bob can compute the
37//! // same shared key given Alice's public key:
38//! let shared_key2 = bob_private_key.compute_shared_key(&alice_public_key);
39//! assert_eq!(shared_key, shared_key2);
40//!
41//! // This is an _unauthenticated_ exchange which is vulnerable to an
42//! // active attacker. See, for example,
43//! // http://www.noiseprotocol.org/noise.html for an example of building
44//! // real protocols from a Diffie-Hellman primitive.
45//! ```
46
47use crate::{with_output_array, with_output_array_fallible, FfiSlice};
48
49/// Number of bytes in a private key in X25519
50pub const PRIVATE_KEY_LEN: usize = bssl_sys::X25519_PRIVATE_KEY_LEN as usize;
51/// Number of bytes in a public key in X25519
52pub const PUBLIC_KEY_LEN: usize = bssl_sys::X25519_PUBLIC_VALUE_LEN as usize;
53/// Number of bytes in a shared secret derived with X25519
54pub const SHARED_KEY_LEN: usize = bssl_sys::X25519_SHARED_KEY_LEN as usize;
55
56/// X25519 public keys are simply 32-byte strings.
57pub type PublicKey = [u8; PUBLIC_KEY_LEN];
58
59/// An X25519 private key (a 32-byte string).
60pub struct PrivateKey(pub [u8; PRIVATE_KEY_LEN]);
61
62impl AsRef<[u8]> for PrivateKey {
63    fn as_ref(&self) -> &[u8] {
64        &self.0
65    }
66}
67
68impl PrivateKey {
69    /// Derive the shared key between this private key and a peer's public key.
70    /// Don't use the shared key directly, rather use a KDF and also include
71    /// the two public values as inputs.
72    ///
73    /// Will fail and produce `None` if the peer's public key is a point of
74    /// small order. It is safe to react to this in non-constant time.
75    pub fn compute_shared_key(&self, other_public_key: &PublicKey) -> Option<[u8; SHARED_KEY_LEN]> {
76        // Safety: `X25519` indeed writes `SHARED_KEY_LEN` bytes.
77        unsafe {
78            with_output_array_fallible(|out, _| {
79                bssl_sys::X25519(out, self.0.as_ffi_ptr(), other_public_key.as_ffi_ptr()) == 1
80            })
81        }
82    }
83
84    /// Generate a new key pair.
85    pub fn generate() -> (PublicKey, PrivateKey) {
86        let mut public_key_uninit = core::mem::MaybeUninit::<[u8; PUBLIC_KEY_LEN]>::uninit();
87        let mut private_key_uninit = core::mem::MaybeUninit::<[u8; PRIVATE_KEY_LEN]>::uninit();
88        // Safety:
89        // - private_key_uninit and public_key_uninit are the correct length.
90        unsafe {
91            bssl_sys::X25519_keypair(
92                public_key_uninit.as_mut_ptr() as *mut u8,
93                private_key_uninit.as_mut_ptr() as *mut u8,
94            );
95            // Safety: Initialized by `X25519_keypair` just above.
96            (
97                public_key_uninit.assume_init(),
98                PrivateKey(private_key_uninit.assume_init()),
99            )
100        }
101    }
102
103    /// Compute the public key corresponding to this private key.
104    pub fn to_public(&self) -> PublicKey {
105        // Safety: `X25519_public_from_private` indeed fills an entire [`PublicKey`].
106        unsafe {
107            with_output_array(|out, _| {
108                bssl_sys::X25519_public_from_private(out, self.0.as_ffi_ptr());
109            })
110        }
111    }
112}
113
114#[cfg(test)]
115#[allow(clippy::unwrap_used)]
116mod tests {
117    use crate::{test_helpers::decode_hex, x25519::PrivateKey};
118
119    #[test]
120    fn known_vector() {
121        // wycheproof/testvectors/x25519_test.json tcId 1
122        let public_key: [u8; 32] =
123            decode_hex("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
124        let private_key = PrivateKey(decode_hex(
125            "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475",
126        ));
127        let expected_shared_secret: [u8; 32] =
128            decode_hex("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
129        let shared_secret = private_key.compute_shared_key(&public_key).unwrap();
130        assert_eq!(expected_shared_secret, shared_secret);
131    }
132
133    #[test]
134    fn all_zero_public_key() {
135        assert!(PrivateKey::generate()
136            .1
137            .compute_shared_key(&[0u8; 32])
138            .is_none());
139    }
140
141    #[test]
142    fn to_public() {
143        // Taken from https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1
144        let public_key_bytes =
145            decode_hex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a");
146        let private_key = PrivateKey(decode_hex(
147            "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
148        ));
149        assert_eq!(public_key_bytes, private_key.to_public());
150    }
151}