| // Copyright 2023 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| //! Diffie-Hellman over curve25519. | |
| //! | |
| //! X25519 is the Diffie-Hellman primitive built from curve25519. It is sometimes referred to as | |
| //! “curve25519”, but “X25519” is a more precise name. See <http://cr.yp.to/ecdh.html> and | |
| //! <https://tools.ietf.org/html/rfc7748>. | |
| //! | |
| //! ``` | |
| //! use bssl_crypto::x25519; | |
| //! | |
| //! // Alice generates her key pair. | |
| //! let (alice_public_key, alice_private_key) = x25519::PrivateKey::generate(); | |
| //! // Bob generates his key pair. | |
| //! let (bob_public_key, bob_private_key) = x25519::PrivateKey::generate(); | |
| //! | |
| //! // If Alice obtains Bob's public key somehow, she can compute their | |
| //! // shared key: | |
| //! let shared_key = alice_private_key.compute_shared_key(&bob_public_key); | |
| //! | |
| //! // Alice can then derive a key (e.g. by using HKDF), which should include | |
| //! // at least the two public keys. Then shen can send a message to Bob | |
| //! // including her public key and an AEAD-protected blob. Bob can compute the | |
| //! // same shared key given Alice's public key: | |
| //! let shared_key2 = bob_private_key.compute_shared_key(&alice_public_key); | |
| //! assert_eq!(shared_key, shared_key2); | |
| //! | |
| //! // This is an _unauthenticated_ exchange which is vulnerable to an | |
| //! // active attacker. See, for example, | |
| //! // http://www.noiseprotocol.org/noise.html for an example of building | |
| //! // real protocols from a Diffie-Hellman primitive. | |
| //! ``` | |
| use crate::{with_output_array, with_output_array_fallible, FfiSlice}; | |
| /// Number of bytes in a private key in X25519 | |
| pub const PRIVATE_KEY_LEN: usize = bssl_sys::X25519_PRIVATE_KEY_LEN as usize; | |
| /// Number of bytes in a public key in X25519 | |
| pub const PUBLIC_KEY_LEN: usize = bssl_sys::X25519_PUBLIC_VALUE_LEN as usize; | |
| /// Number of bytes in a shared secret derived with X25519 | |
| pub const SHARED_KEY_LEN: usize = bssl_sys::X25519_SHARED_KEY_LEN as usize; | |
| /// X25519 public keys are simply 32-byte strings. | |
| pub type PublicKey = [u8; PUBLIC_KEY_LEN]; | |
| /// An X25519 private key (a 32-byte string). | |
| pub struct PrivateKey(pub [u8; PRIVATE_KEY_LEN]); | |
| impl AsRef<[u8]> for PrivateKey { | |
| fn as_ref(&self) -> &[u8] { | |
| &self.0 | |
| } | |
| } | |
| impl PrivateKey { | |
| /// Derive the shared key between this private key and a peer's public key. | |
| /// Don't use the shared key directly, rather use a KDF and also include | |
| /// the two public values as inputs. | |
| /// | |
| /// Will fail and produce `None` if the peer's public key is a point of | |
| /// small order. It is safe to react to this in non-constant time. | |
| pub fn compute_shared_key(&self, other_public_key: &PublicKey) -> Option<[u8; SHARED_KEY_LEN]> { | |
| // Safety: `X25519` indeed writes `SHARED_KEY_LEN` bytes. | |
| unsafe { | |
| with_output_array_fallible(|out, _| { | |
| bssl_sys::X25519(out, self.0.as_ffi_ptr(), other_public_key.as_ffi_ptr()) == 1 | |
| }) | |
| } | |
| } | |
| /// Generate a new key pair. | |
| pub fn generate() -> (PublicKey, PrivateKey) { | |
| let mut public_key_uninit = core::mem::MaybeUninit::<[u8; PUBLIC_KEY_LEN]>::uninit(); | |
| let mut private_key_uninit = core::mem::MaybeUninit::<[u8; PRIVATE_KEY_LEN]>::uninit(); | |
| // Safety: | |
| // - private_key_uninit and public_key_uninit are the correct length. | |
| unsafe { | |
| bssl_sys::X25519_keypair( | |
| public_key_uninit.as_mut_ptr() as *mut u8, | |
| private_key_uninit.as_mut_ptr() as *mut u8, | |
| ); | |
| // Safety: Initialized by `X25519_keypair` just above. | |
| ( | |
| public_key_uninit.assume_init(), | |
| PrivateKey(private_key_uninit.assume_init()), | |
| ) | |
| } | |
| } | |
| /// Compute the public key corresponding to this private key. | |
| pub fn to_public(&self) -> PublicKey { | |
| // Safety: `X25519_public_from_private` indeed fills an entire [`PublicKey`]. | |
| unsafe { | |
| with_output_array(|out, _| { | |
| bssl_sys::X25519_public_from_private(out, self.0.as_ffi_ptr()); | |
| }) | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| #[allow(clippy::unwrap_used)] | |
| mod tests { | |
| use crate::{test_helpers::decode_hex, x25519::PrivateKey}; | |
| #[test] | |
| fn known_vector() { | |
| // wycheproof/testvectors/x25519_test.json tcId 1 | |
| let public_key: [u8; 32] = | |
| decode_hex("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); | |
| let private_key = PrivateKey(decode_hex( | |
| "c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475", | |
| )); | |
| let expected_shared_secret: [u8; 32] = | |
| decode_hex("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320"); | |
| let shared_secret = private_key.compute_shared_key(&public_key).unwrap(); | |
| assert_eq!(expected_shared_secret, shared_secret); | |
| } | |
| #[test] | |
| fn all_zero_public_key() { | |
| assert!(PrivateKey::generate() | |
| .1 | |
| .compute_shared_key(&[0u8; 32]) | |
| .is_none()); | |
| } | |
| #[test] | |
| fn to_public() { | |
| // Taken from https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1 | |
| let public_key_bytes = | |
| decode_hex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); | |
| let private_key = PrivateKey(decode_hex( | |
| "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", | |
| )); | |
| assert_eq!(public_key_bytes, private_key.to_public()); | |
| } | |
| } |