Skip to main content

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::Zeroizing;
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`.
61///
62/// # Errors
63///
64/// Returns an error if the underlying generic hash rejects the output. The
65/// fixed output length used here is valid.
66pub fn crypto_kx_seed_keypair(
67    seed: &[u8; CRYPTO_KX_SEEDBYTES],
68) -> Result<(PublicKey, SecretKey), Error> {
69    let mut sk = SecretKey::default();
70    let mut pk = PublicKey::default();
71
72    crypto_generichash(&mut sk, seed, None)?;
73
74    crypto_scalarmult_base(&mut pk, &sk);
75
76    Ok((pk, sk))
77}
78
79/// Returns a randomly generated keypair, suitable for use with key exchange.
80///
81/// Equivalent to libsodium's `crypto_kx_keypair`.
82pub fn crypto_kx_keypair() -> (PublicKey, SecretKey) {
83    let sk = SecretKey::generate();
84    let mut pk = PublicKey::default();
85
86    crypto_scalarmult_base(&mut pk, &sk);
87
88    (pk, sk)
89}
90
91fn crypto_kx(
92    x1: &mut SessionKey,
93    x2: &mut SessionKey,
94    client_pk: &PublicKey,
95    server_pk: &PublicKey,
96    shared_secret: Zeroizing<[u8; CRYPTO_SCALARMULT_BYTES]>,
97) -> Result<(), Error> {
98    let mut keys = Zeroizing::new([0u8; 2 * CRYPTO_KX_SESSIONKEYBYTES]);
99
100    let mut hasher = crypto_generichash_init(None, 2 * CRYPTO_KX_SESSIONKEYBYTES)?;
101    crypto_generichash_update(&mut hasher, &shared_secret[..]);
102    crypto_generichash_update(&mut hasher, client_pk);
103    crypto_generichash_update(&mut hasher, server_pk);
104    crypto_generichash_final(hasher, &mut keys[..])?;
105
106    x1.copy_from_slice(&keys[..CRYPTO_KX_SESSIONKEYBYTES]);
107    x2.copy_from_slice(&keys[CRYPTO_KX_SESSIONKEYBYTES..]);
108
109    Ok(())
110}
111
112/// Computes client session keys for `rx` and `tx`, using `client_pk`,
113/// `client_sk`, and `server_pk`. Returns unit `()` upon success.
114///
115/// Compatible with libsodium's `crypto_kx_client_session_keys`.
116///
117/// # Errors
118///
119/// Returns an error if `server_pk` is an unacceptable low-order public key or
120/// session-key derivation fails.
121pub fn crypto_kx_client_session_keys(
122    rx: &mut SessionKey,
123    tx: &mut SessionKey,
124    client_pk: &PublicKey,
125    client_sk: &SecretKey,
126    server_pk: &PublicKey,
127) -> Result<(), Error> {
128    let mut shared_secret = Zeroizing::new([0u8; CRYPTO_SCALARMULT_BYTES]);
129
130    crypto_scalarmult(&mut shared_secret, client_sk, server_pk)?;
131
132    crypto_kx(rx, tx, client_pk, server_pk, shared_secret)
133}
134
135/// Computes server session keys for `rx` and `tx`, using `server_pk`,
136/// `server_sk`, and `client_pk`. Returns unit `()` upon success.
137///
138/// Compatible with libsodium's `crypto_kx_server_session_keys`.
139///
140/// # Errors
141///
142/// Returns an error if `client_pk` is an unacceptable low-order public key or
143/// session-key derivation fails.
144pub fn crypto_kx_server_session_keys(
145    rx: &mut SessionKey,
146    tx: &mut SessionKey,
147    server_pk: &PublicKey,
148    server_sk: &SecretKey,
149    client_pk: &PublicKey,
150) -> Result<(), Error> {
151    let mut shared_secret = Zeroizing::new([0u8; CRYPTO_SCALARMULT_BYTES]);
152
153    crypto_scalarmult(&mut shared_secret, server_sk, client_pk)?;
154
155    crypto_kx(tx, rx, client_pk, server_pk, shared_secret)
156}
157
158#[cfg(all(test, dryoc_native_tests))]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_kx_rejects_low_order_public_keys() {
164        use sodiumoxide::crypto::kx;
165
166        let (client_pk, client_sk) = crypto_kx_keypair();
167        let mut rx = SessionKey::default();
168        let mut tx = SessionKey::default();
169        let mut one = PublicKey::default();
170        one[0] = 1;
171
172        for server_pk in [PublicKey::default(), one] {
173            assert!(
174                crypto_kx_client_session_keys(
175                    &mut rx,
176                    &mut tx,
177                    &client_pk,
178                    &client_sk,
179                    &server_pk,
180                )
181                .is_err()
182            );
183            assert!(
184                kx::client_session_keys(
185                    &kx::PublicKey::from_slice(&client_pk).unwrap(),
186                    &kx::SecretKey::from_slice(&client_sk).unwrap(),
187                    &kx::PublicKey::from_slice(&server_pk).unwrap(),
188                )
189                .is_err()
190            );
191        }
192    }
193
194    #[test]
195    fn test_kx() {
196        for _ in 0..20 {
197            let (client_pk, client_sk) = crypto_kx_keypair();
198            let (server_pk, server_sk) = crypto_kx_keypair();
199
200            let (mut crx, mut ctx, mut srx, mut stx) = (
201                SessionKey::default(),
202                SessionKey::default(),
203                SessionKey::default(),
204                SessionKey::default(),
205            );
206
207            crypto_kx_client_session_keys(&mut crx, &mut ctx, &client_pk, &client_sk, &server_pk)
208                .expect("client kx failed");
209
210            crypto_kx_server_session_keys(&mut srx, &mut stx, &server_pk, &server_sk, &client_pk)
211                .expect("server kx failed");
212
213            assert_eq!(crx, stx);
214            assert_eq!(ctx, srx);
215
216            use sodiumoxide::crypto::kx;
217
218            let client_pk = kx::PublicKey::from_slice(&client_pk).expect("client pk failed");
219            let client_sk = kx::SecretKey::from_slice(&client_sk).expect("client sk failed");
220            let server_pk = kx::PublicKey::from_slice(&server_pk).expect("server pk failed");
221            let server_sk = kx::SecretKey::from_slice(&server_sk).expect("server sk failed");
222
223            let (rx1, tx1) = match kx::client_session_keys(&client_pk, &client_sk, &server_pk) {
224                Ok((rx, tx)) => (rx, tx),
225                Err(()) => panic!("bad server signature"),
226            };
227
228            // server performs the same operation
229            let (rx2, tx2) = match kx::server_session_keys(&server_pk, &server_sk, &client_pk) {
230                Ok((rx, tx)) => (rx, tx),
231                Err(()) => panic!("bad client signature"),
232            };
233
234            assert_eq!(rx1.as_ref(), crx);
235            assert_eq!(rx2.as_ref(), srx);
236            assert_eq!(tx1.as_ref(), ctx);
237            assert_eq!(tx2.as_ref(), stx);
238        }
239    }
240}