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