Skip to main content

dryoc/
kx.rs

1//! # Key exchange functions
2//!
3//! [`Session`] implements libsodium's key exchange functions, which use a
4//! combination of Curve25519, Diffie-Hellman, and Blake2b to generate shared
5//! session keys between two parties who know each other's public keys.
6//!
7//! You should use [`Session`] when you want to:
8//!
9//! * derive shared secrets between two parties
10//! * use public-key cryptography, but do so with another cipher that only
11//!   supports pre-shared secrets
12//! * create a session key or token that can't be used to derive the original
13//!   inputs should it become compromised
14//!
15//! # Rustaceous API example
16//!
17//! ```
18//! use dryoc::kx::*;
19//!
20//! // Generate random client/server keypairs
21//! let client_keypair = KeyPair::generate();
22//! let server_keypair = KeyPair::generate();
23//!
24//! // Compute client session keys, into default stack-allocated byte array
25//! let client_session_keys =
26//!     Session::new_client_with_defaults(&client_keypair, &server_keypair.public_key)
27//!         .expect("compute client failed");
28//!
29//! // Compute server session keys, into default stack-allocated byte array
30//! let server_session_keys =
31//!     Session::new_server_with_defaults(&server_keypair, &client_keypair.public_key)
32//!         .expect("compute client failed");
33//!
34//! let (client_rx, client_tx) = client_session_keys.into_parts();
35//! let (server_rx, server_tx) = server_session_keys.into_parts();
36//!
37//! // Client Rx should match server Tx keys
38//! assert_eq!(client_rx, server_tx);
39//! // Client Tx should match server Rx keys
40//! assert_eq!(client_tx, server_rx);
41//! ```
42//!
43//! ## Additional resources
44//!
45//! * See <https://doc.libsodium.org/key_exchange> for additional details on key
46//!   exchange
47
48use std::fmt;
49
50#[cfg(feature = "serde")]
51use serde::{Deserialize, Serialize};
52use zeroize::{Zeroize, ZeroizeOnDrop};
53
54use crate::classic::crypto_kx::{crypto_kx_client_session_keys, crypto_kx_server_session_keys};
55use crate::constants::{
56    CRYPTO_KX_PUBLICKEYBYTES, CRYPTO_KX_SECRETKEYBYTES, CRYPTO_KX_SESSIONKEYBYTES,
57};
58use crate::error::Error;
59use crate::types::*;
60
61/// Stack-allocated session key type alias
62pub type SessionKey = StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>;
63/// Stack-allocated public key type alias
64pub type PublicKey = StackByteArray<CRYPTO_KX_PUBLICKEYBYTES>;
65/// Stack-allocated secret key type alias
66pub type SecretKey = StackByteArray<CRYPTO_KX_SECRETKEYBYTES>;
67/// Stack-allocated keypair type alias
68pub type KeyPair = crate::keypair::KeyPair<PublicKey, SecretKey>;
69
70#[cfg_attr(feature = "serde", derive(Zeroize, Clone, Serialize, Deserialize))]
71#[cfg_attr(not(feature = "serde"), derive(Zeroize, Clone))]
72/// Key derivation implementation based on Curve25519, Diffie-Hellman, and
73/// Blake2b. Compatible with libsodium's `crypto_kx_*` functions.
74///
75/// The session-key type must implement [`ZeroizeOnDrop`] so keys remain
76/// self-wiping after [`Session::into_parts`] transfers ownership to the caller.
77pub struct Session<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop> {
78    rx_key: SessionKey,
79    tx_key: SessionKey,
80}
81
82impl<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop> fmt::Debug
83    for Session<SessionKey>
84{
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.debug_struct("Session")
87            .field("rx_key", &"[REDACTED]")
88            .field("tx_key", &"[REDACTED]")
89            .finish()
90    }
91}
92
93/// Stack-allocated type alias for [`Session`]. Provided for convenience.
94pub type StackSession = Session<SessionKey>;
95
96#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
97#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
98pub mod protected {
99    //! # Protected memory type aliases for [`Session`]
100    //!
101    //! Protected-memory aliases for key exchange.
102    //!
103    //! ## Example
104    //!
105    //! ```
106    //! use dryoc::kx::Session;
107    //! use dryoc::kx::protected::*;
108    //!
109    //! // Generate random client/server keypairs
110    //! let client_keypair = LockedROKeyPair::generate_readonly_locked_keypair()
111    //!     .expect("couldn't generate client keypair");
112    //! let server_keypair = LockedROKeyPair::generate_readonly_locked_keypair()
113    //!     .expect("couldn't generate server keypair");
114    //!
115    //! // Compute client session keys, into default stack-allocated byte array
116    //! let client_session_keys: LockedSession =
117    //!     Session::new_client(&client_keypair, &server_keypair.public_key)
118    //!         .expect("compute client failed");
119    //!
120    //! // Compute server session keys, into default stack-allocated byte array
121    //! let server_session_keys: LockedSession =
122    //!     Session::new_server(&server_keypair, &client_keypair.public_key)
123    //!         .expect("compute client failed");
124    //!
125    //! let (client_rx, client_tx) = client_session_keys.into_parts();
126    //! let (server_rx, server_tx) = server_session_keys.into_parts();
127    //!
128    //! // Client Rx should match server Tx keys
129    //! assert_eq!(client_rx.as_slice(), server_tx.as_slice());
130    //! // Client Tx should match server Rx keys
131    //! assert_eq!(client_tx.as_slice(), server_rx.as_slice());
132    //! ```
133    use super::*;
134    pub use crate::keypair::protected::*;
135
136    /// Heap-allocated, page-aligned session key type alias for use with
137    /// protected memory
138    pub type SessionKey = HeapByteArray<CRYPTO_KX_SESSIONKEYBYTES>;
139    /// Heap-allocated, page-aligned public key type alias for use with
140    /// protected memory
141    pub type PublicKey = HeapByteArray<CRYPTO_KX_PUBLICKEYBYTES>;
142    /// Heap-allocated, page-aligned secret key type alias for use with
143    /// protected memory
144    pub type SecretKey = HeapByteArray<CRYPTO_KX_SECRETKEYBYTES>;
145
146    /// Heap-allocated, page-aligned keypair type alias for use with
147    /// protected memory
148    pub type LockedKeyPair = crate::keypair::KeyPair<Locked<PublicKey>, Locked<SecretKey>>;
149    /// Heap-allocated, page-aligned keypair type alias for use with
150    /// protected memory
151    pub type LockedROKeyPair = crate::keypair::KeyPair<LockedRO<PublicKey>, LockedRO<SecretKey>>;
152    /// Locked session keys type alias, for use with protected memory
153    pub type LockedSession = Session<Locked<SessionKey>>;
154}
155
156impl<SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop>
157    Session<SessionKey>
158{
159    /// Computes client session keys, given `client_keypair` and
160    /// `server_public_key`, returning a new session upon success.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if `server_public_key` is unacceptable, including a
165    /// low-order point that would produce an all-zero shared secret.
166    pub fn new_client<
167        PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
168        SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
169    >(
170        client_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
171        server_public_key: &PublicKey,
172    ) -> Result<Self, Error> {
173        let mut rx_key = SessionKey::new_byte_array();
174        let mut tx_key = SessionKey::new_byte_array();
175
176        crypto_kx_client_session_keys(
177            rx_key.as_mut_array(),
178            tx_key.as_mut_array(),
179            client_keypair.public_key.as_array(),
180            client_keypair.secret_key.as_array(),
181            server_public_key.as_array(),
182        )?;
183
184        Ok(Self { rx_key, tx_key })
185    }
186
187    /// Computes server session keys, given `server_keypair` and
188    /// `client_public_key`, returning a new session upon success.
189    ///
190    /// # Errors
191    ///
192    /// Returns an error if `client_public_key` is unacceptable, including a
193    /// low-order point that would produce an all-zero shared secret.
194    pub fn new_server<
195        PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
196        SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
197    >(
198        server_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
199        client_public_key: &PublicKey,
200    ) -> Result<Self, Error> {
201        let mut rx_key = SessionKey::new_byte_array();
202        let mut tx_key = SessionKey::new_byte_array();
203
204        crypto_kx_server_session_keys(
205            rx_key.as_mut_array(),
206            tx_key.as_mut_array(),
207            server_keypair.public_key.as_array(),
208            server_keypair.secret_key.as_array(),
209            client_public_key.as_array(),
210        )?;
211
212        Ok(Self { rx_key, tx_key })
213    }
214}
215
216impl Session<SessionKey> {
217    /// Returns a new client session upon success using the default types for
218    /// the given `client_keypair` and `server_public_key`. Wraps
219    /// [`Session::new_client`], provided for convenience.
220    ///
221    /// # Errors
222    ///
223    /// Returns an error if `server_public_key` is unacceptable. See
224    /// [`Session::new_client`].
225    pub fn new_client_with_defaults<
226        PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
227        SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
228    >(
229        client_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
230        server_public_key: &PublicKey,
231    ) -> Result<Self, Error> {
232        Self::new_client(client_keypair, server_public_key)
233    }
234
235    /// Returns a new server session upon success using the default types for
236    /// the given `server_keypair` and `client_public_key`. Wraps
237    /// [`Session::new_server`], provided for convenience.
238    ///
239    /// # Errors
240    ///
241    /// Returns an error if `client_public_key` is unacceptable. See
242    /// [`Session::new_server`].
243    pub fn new_server_with_defaults<
244        PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
245        SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
246    >(
247        server_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
248        client_public_key: &PublicKey,
249    ) -> Result<Self, Error> {
250        Self::new_server(server_keypair, client_public_key)
251    }
252}
253
254impl<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop>
255    Session<SessionKey>
256{
257    /// Moves the rx_key and tx_key out of this instance, returning them as a
258    /// tuple with `(rx_key, tx_key)`.
259    pub fn into_parts(self) -> (SessionKey, SessionKey) {
260        (self.rx_key, self.tx_key)
261    }
262
263    /// Returns a reference to a slice of the Rx session key.
264    #[inline]
265    pub fn rx_as_slice(&self) -> &[u8] {
266        self.rx_key.as_slice()
267    }
268
269    /// Returns a reference to a slice of the Tx session key.
270    #[inline]
271    pub fn tx_as_slice(&self) -> &[u8] {
272        self.tx_key.as_slice()
273    }
274
275    /// Returns a reference to an array of the Rx session key.
276    #[inline]
277    pub fn rx_as_array(&self) -> &[u8; CRYPTO_KX_SESSIONKEYBYTES] {
278        self.rx_key.as_array()
279    }
280
281    /// Returns a reference to an array of the Tx session key.
282    #[inline]
283    pub fn tx_as_array(&self) -> &[u8; CRYPTO_KX_SESSIONKEYBYTES] {
284        self.tx_key.as_array()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn session_debug_redacts_keys() {
294        let session = StackSession {
295            rx_key: SessionKey::from([1u8; CRYPTO_KX_SESSIONKEYBYTES]),
296            tx_key: SessionKey::from([2u8; CRYPTO_KX_SESSIONKEYBYTES]),
297        };
298
299        assert_eq!(
300            format!("{session:?}"),
301            "Session { rx_key: \"[REDACTED]\", tx_key: \"[REDACTED]\" }"
302        );
303    }
304
305    #[test]
306    fn test_kx() {
307        let client_keypair = KeyPair::generate();
308        let server_keypair = KeyPair::generate();
309
310        let client_session_keys =
311            Session::new_client_with_defaults(&client_keypair, &server_keypair.public_key)
312                .expect("compute client failed");
313
314        let server_session_keys =
315            Session::new_server_with_defaults(&server_keypair, &client_keypair.public_key)
316                .expect("compute client failed");
317
318        let (client_rx, client_tx) = client_session_keys.into_parts();
319        let (server_rx, server_tx) = server_session_keys.into_parts();
320
321        assert_eq!(client_rx, server_tx);
322        assert_eq!(client_tx, server_rx);
323    }
324
325    #[test]
326    fn test_kx_rejects_low_order_public_key() {
327        let client_keypair = KeyPair::generate();
328        let low_order_public_key = PublicKey::default();
329
330        assert!(Session::new_client_with_defaults(&client_keypair, &low_order_public_key).is_err());
331    }
332}