dryoc/classic/
crypto_kx.rs

1//! # Key exchange
2//!
3//! This module implements libsodium's key exchange functions, which uses a
4//! combination of Curve25519, Diffie-Hellman, and Blake2b to generate shared
5//! session keys.
6//!
7//! ## Classic API example
8//!
9//! ```
10//! use dryoc::classic::crypto_kx::*;
11//!
12//! // Generate random client & server keypairs
13//! let (client_pk, client_sk) = crypto_kx_keypair();
14//! let (server_pk, server_sk) = crypto_kx_keypair();
15//!
16//! // Variables for client & server rx/tx session keys
17//! let (mut crx, mut ctx, mut srx, mut stx) = (
18//!     SessionKey::default(),
19//!     SessionKey::default(),
20//!     SessionKey::default(),
21//!     SessionKey::default(),
22//! );
23//!
24//! // Calculate the client Rx & Tx keys
25//! crypto_kx_client_session_keys(&mut crx, &mut ctx, &client_pk, &client_sk, &server_pk)
26//!     .expect("client kx failed");
27//!
28//! // Calculate the server Rx & Tx keys
29//! crypto_kx_server_session_keys(&mut srx, &mut stx, &server_pk, &server_sk, &client_pk)
30//!     .expect("server kx failed");
31//!
32//! assert_eq!(crx, stx);
33//! assert_eq!(ctx, srx);
34//! ```
35
36use zeroize::Zeroize;
37
38use super::crypto_core::{crypto_scalarmult, crypto_scalarmult_base};
39use super::crypto_generichash::{
40    crypto_generichash, crypto_generichash_final, crypto_generichash_init,
41    crypto_generichash_update,
42};
43use crate::constants::{
44    CRYPTO_KX_PUBLICKEYBYTES, CRYPTO_KX_SECRETKEYBYTES, CRYPTO_KX_SEEDBYTES,
45    CRYPTO_KX_SESSIONKEYBYTES, CRYPTO_SCALARMULT_BYTES,
46};
47use crate::error::Error;
48use crate::types::*;
49
50/// Public key type for key exchange
51pub type PublicKey = [u8; CRYPTO_KX_PUBLICKEYBYTES];
52/// Secret key type for key exchange
53pub type SecretKey = [u8; CRYPTO_KX_SECRETKEYBYTES];
54/// Session data type for key exchange
55pub type SessionKey = [u8; CRYPTO_KX_SESSIONKEYBYTES];
56
57/// Computes and returns a keypair of `(PublicKey, SecretKey)` based on `seed`
58/// upon success. Uses the Blake2b function to derive a secret from `seed`.
59///
60/// Compatible with libsodium's `crypto_kx_seed_keypair`.
61pub fn crypto_kx_seed_keypair(
62    seed: &[u8; CRYPTO_KX_SEEDBYTES],
63) -> Result<(PublicKey, SecretKey), Error> {
64    let mut sk = SecretKey::default();
65    let mut pk = PublicKey::default();
66
67    crypto_generichash(&mut sk, seed, None)?;
68
69    crypto_scalarmult_base(&mut pk, &sk);
70
71    Ok((pk, sk))
72}
73
74/// Returns a randomly generated keypair, suitable for use with key exchange.
75///
76/// Equivalent to libsodium's `crypto_kx_keypair`.
77pub fn crypto_kx_keypair() -> (PublicKey, SecretKey) {
78    let sk = SecretKey::gen();
79    let mut pk = PublicKey::default();
80
81    crypto_scalarmult_base(&mut pk, &sk);
82
83    (pk, sk)
84}
85
86fn crypto_kx(
87    x1: &mut SessionKey,
88    x2: &mut SessionKey,
89    client_pk: &PublicKey,
90    server_pk: &PublicKey,
91    mut shared_secret: [u8; CRYPTO_SCALARMULT_BYTES],
92) -> Result<(), Error> {
93    let mut keys = [0u8; 2 * CRYPTO_KX_SESSIONKEYBYTES];
94
95    let mut hasher = crypto_generichash_init(None, 2 * CRYPTO_KX_SESSIONKEYBYTES)?;
96    crypto_generichash_update(&mut hasher, &shared_secret);
97    shared_secret.zeroize();
98    crypto_generichash_update(&mut hasher, client_pk);
99    crypto_generichash_update(&mut hasher, server_pk);
100    crypto_generichash_final(hasher, &mut keys)?;
101
102    x1.copy_from_slice(&keys[..CRYPTO_KX_SESSIONKEYBYTES]);
103    x2.copy_from_slice(&keys[CRYPTO_KX_SESSIONKEYBYTES..]);
104
105    keys.zeroize();
106
107    Ok(())
108}
109
110/// Computes client session keys for `rx` and `tx`, using `client_pk`,
111/// `client_sk`, and `server_pk`. Returns unit `()` upon success.
112///
113/// Compatible with libsodium's `crypto_kx_client_session_keys`.
114pub fn crypto_kx_client_session_keys(
115    rx: &mut SessionKey,
116    tx: &mut SessionKey,
117    client_pk: &PublicKey,
118    client_sk: &SecretKey,
119    server_pk: &PublicKey,
120) -> Result<(), Error> {
121    let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
122
123    crypto_scalarmult(&mut shared_secret, client_sk, server_pk);
124
125    crypto_kx(rx, tx, client_pk, server_pk, shared_secret)
126}
127
128/// Computes server session keys for `rx` and `tx`, using `client_pk`,
129/// `client_sk`, and `server_pk`. Returns unit `()` upon success.
130///
131/// Compatible with libsodium's `crypto_kx_server_session_keys`.
132pub fn crypto_kx_server_session_keys(
133    rx: &mut SessionKey,
134    tx: &mut SessionKey,
135    server_pk: &PublicKey,
136    server_sk: &SecretKey,
137    client_pk: &PublicKey,
138) -> Result<(), Error> {
139    let mut shared_secret = [0u8; CRYPTO_SCALARMULT_BYTES];
140
141    crypto_scalarmult(&mut shared_secret, server_sk, client_pk);
142
143    crypto_kx(tx, rx, client_pk, server_pk, shared_secret)
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_kx() {
152        for _ in 0..20 {
153            let (client_pk, client_sk) = crypto_kx_keypair();
154            let (server_pk, server_sk) = crypto_kx_keypair();
155
156            let (mut crx, mut ctx, mut srx, mut stx) = (
157                SessionKey::default(),
158                SessionKey::default(),
159                SessionKey::default(),
160                SessionKey::default(),
161            );
162
163            crypto_kx_client_session_keys(&mut crx, &mut ctx, &client_pk, &client_sk, &server_pk)
164                .expect("client kx failed");
165
166            crypto_kx_server_session_keys(&mut srx, &mut stx, &server_pk, &server_sk, &client_pk)
167                .expect("server kx failed");
168
169            assert_eq!(crx, stx);
170            assert_eq!(ctx, srx);
171
172            use sodiumoxide::crypto::kx;
173
174            let client_pk = kx::PublicKey::from_slice(&client_pk).expect("client pk failed");
175            let client_sk = kx::SecretKey::from_slice(&client_sk).expect("client sk failed");
176            let server_pk = kx::PublicKey::from_slice(&server_pk).expect("server pk failed");
177            let server_sk = kx::SecretKey::from_slice(&server_sk).expect("server sk failed");
178
179            let (rx1, tx1) = match kx::client_session_keys(&client_pk, &client_sk, &server_pk) {
180                Ok((rx, tx)) => (rx, tx),
181                Err(()) => panic!("bad server signature"),
182            };
183
184            // server performs the same operation
185            let (rx2, tx2) = match kx::server_session_keys(&server_pk, &server_sk, &client_pk) {
186                Ok((rx, tx)) => (rx, tx),
187                Err(()) => panic!("bad client signature"),
188            };
189
190            assert_eq!(rx1.as_ref(), crx);
191            assert_eq!(rx2.as_ref(), srx);
192            assert_eq!(tx1.as_ref(), ctx);
193            assert_eq!(tx2.as_ref(), stx);
194        }
195    }
196}