1use core::fmt;
44
45#[cfg(feature = "serde")]
46use serde::{Deserialize, Serialize};
47use zeroize::{Zeroize, ZeroizeOnDrop};
48
49use crate::classic::crypto_kx::{crypto_kx_client_session_keys, crypto_kx_server_session_keys};
50use crate::constants::{
51 CRYPTO_KX_PUBLICKEYBYTES, CRYPTO_KX_SECRETKEYBYTES, CRYPTO_KX_SESSIONKEYBYTES,
52};
53use crate::error::Error;
54use crate::types::*;
55
56pub type SessionKey = StackByteArray<CRYPTO_KX_SESSIONKEYBYTES>;
58pub type PublicKey = StackByteArray<CRYPTO_KX_PUBLICKEYBYTES>;
60pub type SecretKey = StackByteArray<CRYPTO_KX_SECRETKEYBYTES>;
62pub type StackKeyPair = crate::keypair::KeyPair<PublicKey, SecretKey>;
64
65#[derive(Zeroize, Clone)]
66#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
67pub struct Session<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop> {
73 rx_key: SessionKey,
74 tx_key: SessionKey,
75}
76
77impl<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop> fmt::Debug
78 for Session<SessionKey>
79{
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_struct("Session")
82 .field("rx_key", &"[REDACTED]")
83 .field("tx_key", &"[REDACTED]")
84 .finish()
85 }
86}
87
88pub type StackSession = Session<SessionKey>;
90
91#[cfg(any(
92 all(feature = "protected", any(unix, windows)),
93 all(doc, not(doctest), feature = "std")
94))]
95#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
96pub mod protected {
97 use super::*;
132 pub use crate::protected::*;
133
134 pub type SessionKey = HeapByteArray<CRYPTO_KX_SESSIONKEYBYTES>;
137 pub type PublicKey = HeapByteArray<CRYPTO_KX_PUBLICKEYBYTES>;
140 pub type SecretKey = HeapByteArray<CRYPTO_KX_SECRETKEYBYTES>;
143
144 pub type LockedKeyPair = crate::keypair::KeyPair<Locked<PublicKey>, Locked<SecretKey>>;
147 pub type LockedROKeyPair = crate::keypair::KeyPair<LockedRO<PublicKey>, LockedRO<SecretKey>>;
150 pub type LockedSession = Session<Locked<SessionKey>>;
152}
153
154impl<SessionKey: NewByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop>
155 Session<SessionKey>
156{
157 pub fn new_client<
165 PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
166 SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
167 >(
168 client_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
169 server_public_key: &PublicKey,
170 ) -> Result<Self, Error> {
171 let mut rx_key = SessionKey::new_byte_array();
172 let mut tx_key = SessionKey::new_byte_array();
173
174 crypto_kx_client_session_keys(
175 rx_key.as_mut_array(),
176 tx_key.as_mut_array(),
177 client_keypair.public_key.as_array(),
178 client_keypair.secret_key.as_array(),
179 server_public_key.as_array(),
180 )?;
181
182 Ok(Self { rx_key, tx_key })
183 }
184
185 pub fn new_server<
193 PublicKey: ByteArray<CRYPTO_KX_PUBLICKEYBYTES> + Zeroize,
194 SecretKey: ByteArray<CRYPTO_KX_SECRETKEYBYTES> + Zeroize,
195 >(
196 server_keypair: &crate::keypair::KeyPair<PublicKey, SecretKey>,
197 client_public_key: &PublicKey,
198 ) -> Result<Self, Error> {
199 let mut rx_key = SessionKey::new_byte_array();
200 let mut tx_key = SessionKey::new_byte_array();
201
202 crypto_kx_server_session_keys(
203 rx_key.as_mut_array(),
204 tx_key.as_mut_array(),
205 server_keypair.public_key.as_array(),
206 server_keypair.secret_key.as_array(),
207 client_public_key.as_array(),
208 )?;
209
210 Ok(Self { rx_key, tx_key })
211 }
212}
213
214impl<SessionKey: ByteArray<CRYPTO_KX_SESSIONKEYBYTES> + Zeroize + ZeroizeOnDrop>
215 Session<SessionKey>
216{
217 #[must_use]
220 pub fn into_parts(self) -> (SessionKey, SessionKey) {
221 (self.rx_key, self.tx_key)
222 }
223
224 #[inline]
226 pub fn rx_as_slice(&self) -> &[u8] {
227 self.rx_key.as_slice()
228 }
229
230 #[inline]
232 pub fn tx_as_slice(&self) -> &[u8] {
233 self.tx_key.as_slice()
234 }
235
236 #[inline]
238 pub fn rx_as_array(&self) -> &[u8; CRYPTO_KX_SESSIONKEYBYTES] {
239 self.rx_key.as_array()
240 }
241
242 #[inline]
244 pub fn tx_as_array(&self) -> &[u8; CRYPTO_KX_SESSIONKEYBYTES] {
245 self.tx_key.as_array()
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn session_debug_redacts_keys() {
255 let session = StackSession {
256 rx_key: SessionKey::from([1u8; CRYPTO_KX_SESSIONKEYBYTES]),
257 tx_key: SessionKey::from([2u8; CRYPTO_KX_SESSIONKEYBYTES]),
258 };
259
260 assert_eq!(
261 format!("{session:?}"),
262 "Session { rx_key: \"[REDACTED]\", tx_key: \"[REDACTED]\" }"
263 );
264 }
265
266 use crate::classic::crypto_kx::{crypto_kx_client_session_keys, crypto_kx_server_session_keys};
267 use crate::constants::CRYPTO_BOX_SEEDBYTES;
268 use crate::utils::test_util::XorShift64;
269
270 const CLIENT_SEED: [u8; CRYPTO_BOX_SEEDBYTES] = [1u8; CRYPTO_BOX_SEEDBYTES];
274 const SERVER_SEED: [u8; CRYPTO_BOX_SEEDBYTES] = [2u8; CRYPTO_BOX_SEEDBYTES];
275 const CLIENT_RX: &str = "4081524abf55a75021ebd5e98e08552fb2bd26315c40e563b74e64abff1be442";
276 const CLIENT_TX: &str = "2f9c2f944f504caf772db17affc91e3ba8886a806ba53ab37881d15c042f3410";
277
278 fn kat_keypairs() -> (StackKeyPair, StackKeyPair) {
279 (
280 StackKeyPair::from_seed(&CLIENT_SEED),
281 StackKeyPair::from_seed(&SERVER_SEED),
282 )
283 }
284
285 fn low_order_public_keys() -> [PublicKey; 2] {
286 let mut identity = PublicKey::default();
287 identity[0] = 1;
288 [PublicKey::default(), identity]
289 }
290
291 #[test]
292 fn seeded_sessions_match_libsodium_known_answers_with_rx_tx_crossed() {
293 let (client, server) = kat_keypairs();
294 let client_rx = hex::decode(CLIENT_RX).expect("hex");
295 let client_tx = hex::decode(CLIENT_TX).expect("hex");
296
297 let client_session = StackSession::new_client(&client, &server.public_key).expect("client");
298 let server_session = StackSession::new_server(&server, &client.public_key).expect("server");
299
300 assert_eq!(client_session.rx_as_slice(), client_rx.as_slice());
301 assert_eq!(client_session.tx_as_slice(), client_tx.as_slice());
302 assert_eq!(client_session.rx_as_array(), &client_rx[..]);
303 assert_eq!(client_session.tx_as_array(), &client_tx[..]);
304 assert_eq!(server_session.rx_as_slice(), client_tx.as_slice());
305 assert_eq!(server_session.tx_as_slice(), client_rx.as_slice());
306
307 let (rx, tx) = client_session.into_parts();
308 assert_eq!(rx.as_slice(), client_rx.as_slice());
309 assert_eq!(tx.as_slice(), client_tx.as_slice());
310 let (rx, tx) = server_session.into_parts();
311 assert_eq!(rx.as_slice(), client_tx.as_slice());
312 assert_eq!(tx.as_slice(), client_rx.as_slice());
313
314 let swapped = StackSession::new_client(&server, &client.public_key).expect("kx");
316 assert_ne!(swapped.rx_as_slice(), client_rx.as_slice());
317 assert_ne!(swapped.rx_as_slice(), client_tx.as_slice());
318 }
319
320 #[test]
321 fn sessions_match_classic_session_keys() {
322 let mut rng = XorShift64::new(0x6b78_5f73_6573_7300);
323 for _ in 0..8 {
324 let client = StackKeyPair::from_seed(&rng.next_bytes32());
325 let server = StackKeyPair::from_seed(&rng.next_bytes32());
326
327 let mut rx = [0u8; CRYPTO_KX_SESSIONKEYBYTES];
328 let mut tx = [0u8; CRYPTO_KX_SESSIONKEYBYTES];
329 crypto_kx_client_session_keys(
330 &mut rx,
331 &mut tx,
332 client.public_key.as_array(),
333 client.secret_key.as_array(),
334 server.public_key.as_array(),
335 )
336 .expect("classic client");
337 let session: Session<SessionKey> =
338 Session::new_client(&client, &server.public_key).expect("client");
339 assert_eq!(session.rx_as_array(), &rx);
340 assert_eq!(session.tx_as_array(), &tx);
341
342 crypto_kx_server_session_keys(
343 &mut rx,
344 &mut tx,
345 server.public_key.as_array(),
346 server.secret_key.as_array(),
347 client.public_key.as_array(),
348 )
349 .expect("classic server");
350 let session: Session<SessionKey> =
351 Session::new_server(&server, &client.public_key).expect("server");
352 assert_eq!(session.rx_as_array(), &rx);
353 assert_eq!(session.tx_as_array(), &tx);
354 }
355 }
356
357 #[test]
358 fn low_order_peer_keys_are_rejected_on_both_sides() {
359 let (client, server) = kat_keypairs();
360 for low_order in low_order_public_keys() {
361 assert!(StackSession::new_client(&client, &low_order).is_err());
362 assert!(StackSession::new_server(&server, &low_order).is_err());
363 }
364 }
365
366 #[cfg(all(feature = "serde", feature = "alloc"))]
367 #[test]
368 fn serde_round_trip_keeps_rx_and_tx_in_place() {
369 use crate::dryocsecretbox::{DryocSecretBox, Nonce, VecBox};
370
371 let (client, server) = kat_keypairs();
372 let client_session = StackSession::new_client(&client, &server.public_key).expect("client");
373 let server_session = StackSession::new_server(&server, &client.public_key).expect("server");
374
375 let json = serde_json::to_string(&client_session).expect("serialize");
376 let decoded: StackSession = serde_json::from_str(&json).expect("deserialize");
377 assert_eq!(decoded.rx_as_slice(), hex::decode(CLIENT_RX).expect("hex"));
378 assert_eq!(decoded.tx_as_slice(), hex::decode(CLIENT_TX).expect("hex"));
379
380 let nonce = Nonce::from([7u8; crate::constants::CRYPTO_SECRETBOX_NONCEBYTES]);
383 let (server_rx, server_tx) = server_session.into_parts();
384 let (decoded_rx, decoded_tx) = decoded.into_parts();
385 let from_server = DryocSecretBox::encrypt_to_vecbox(b"server says", &nonce, &server_tx)
386 .expect("encrypt failed");
387 assert_eq!(
388 VecBox::from_bytes(&from_server.to_vec())
389 .expect("parse")
390 .decrypt_to_vec(&nonce, &decoded_rx)
391 .expect("decrypt"),
392 b"server says"
393 );
394 let from_client = DryocSecretBox::encrypt_to_vecbox(b"client says", &nonce, &decoded_tx)
395 .expect("encrypt failed");
396 assert_eq!(
397 from_client
398 .decrypt_to_vec(&nonce, &server_rx)
399 .expect("decrypt"),
400 b"client says"
401 );
402 assert!(from_client.decrypt_to_vec(&nonce, &server_tx).is_err());
403 }
404
405 #[cfg(all(feature = "protected", any(unix, windows)))]
406 #[test]
407 fn locked_sessions_match_stack_sessions() {
408 use crate::kx::protected::*;
409
410 let (client, server) = kat_keypairs();
411 let locked_client = LockedKeyPair {
412 public_key: protected::PublicKey::from_slice_into_locked(client.public_key.as_slice())
413 .expect("lock client pk"),
414 secret_key: protected::SecretKey::from_slice_into_locked(client.secret_key.as_slice())
415 .expect("lock client sk"),
416 };
417 let locked_server_pk =
418 protected::PublicKey::from_slice_into_locked(server.public_key.as_slice())
419 .expect("lock server pk");
420
421 let session: LockedSession =
422 Session::new_client(&locked_client, &locked_server_pk).expect("client");
423 assert_eq!(session.rx_as_slice(), hex::decode(CLIENT_RX).expect("hex"));
424 assert_eq!(session.tx_as_slice(), hex::decode(CLIENT_TX).expect("hex"));
425
426 let locked_low_order = protected::PublicKey::new_locked().expect("lock low-order key");
427 let rejected: Result<LockedSession, Error> =
428 Session::new_client(&locked_client, &locked_low_order);
429 assert!(rejected.is_err());
430 }
431
432 #[cfg(dryoc_native_tests)]
433 #[test]
434 fn sessions_match_libsodium_session_keys() {
435 use crate::native_test_util::{kx_client_session_keys, kx_server_session_keys};
436
437 let mut rng = XorShift64::new(0x6c69_6273_6f64_6b78);
438 for _ in 0..8 {
439 let client = StackKeyPair::from_seed(&rng.next_bytes32());
440 let server = StackKeyPair::from_seed(&rng.next_bytes32());
441 let client_session =
442 StackSession::new_client(&client, &server.public_key).expect("client");
443 let server_session =
444 StackSession::new_server(&server, &client.public_key).expect("server");
445
446 let (rx, tx) =
447 kx_client_session_keys(&client.public_key, &client.secret_key, &server.public_key)
448 .expect("libsodium client");
449 assert_eq!(client_session.rx_as_array(), &rx);
450 assert_eq!(client_session.tx_as_array(), &tx);
451
452 let (rx, tx) =
453 kx_server_session_keys(&server.public_key, &server.secret_key, &client.public_key)
454 .expect("libsodium server");
455 assert_eq!(server_session.rx_as_array(), &rx);
456 assert_eq!(server_session.tx_as_array(), &tx);
457 }
458 }
459}