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}