wolfcrypt-wrapper 1.1.3

Rust wrapper for wolfssl C library cryptographic functionality
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/*
 * Copyright (C) 2006-2026 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

#![cfg(mlkem)]

mod common;

use wolfcrypt_wrapper::mlkem::MlKem;
#[cfg(random)]
use wolfcrypt_wrapper::random::RNG;

/// Verify the type constants have the correct numeric values required by
/// the wolfCrypt API.
#[test]
fn test_type_constants() {
    assert_eq!(MlKem::TYPE_512, 0);
    assert_eq!(MlKem::TYPE_768, 1);
    assert_eq!(MlKem::TYPE_1024, 2);
}

/// Verify the shared constants have the correct values.
#[test]
fn test_shared_constants() {
    assert_eq!(MlKem::SYM_SIZE, 32);
    assert_eq!(MlKem::SHARED_SECRET_SIZE, 32);
    assert_eq!(MlKem::MAKEKEY_RAND_SIZE, 64);
    assert_eq!(MlKem::ENC_RAND_SIZE, 32);
}

/// Verify that `new()` creates an initialized key for each type.
#[test]
fn test_new() {
    common::setup();
    MlKem::new(MlKem::TYPE_512).expect("Error with new() TYPE_512");
    MlKem::new(MlKem::TYPE_768).expect("Error with new() TYPE_768");
    MlKem::new(MlKem::TYPE_1024).expect("Error with new() TYPE_1024");
}

/// Verify that `new_ex()` accepts the optional heap and device ID parameters.
#[test]
fn test_new_ex() {
    common::setup();
    MlKem::new_ex(MlKem::TYPE_768, None, None).expect("Error with new_ex()");
}

/// Verify that the runtime size queries return plausible values for each key type.
#[test]
fn test_size_queries() {
    common::setup();
    for key_type in [MlKem::TYPE_512, MlKem::TYPE_768, MlKem::TYPE_1024] {
        let key = MlKem::new(key_type).expect("Error with new()");
        let pub_size = key.public_key_size().expect("Error with public_key_size()");
        let priv_size = key
            .private_key_size()
            .expect("Error with private_key_size()");
        let ct_size = key
            .cipher_text_size()
            .expect("Error with cipher_text_size()");
        let ss_size = key
            .shared_secret_size()
            .expect("Error with shared_secret_size()");
        assert!(
            pub_size > 0,
            "public_key_size must be positive for key_type {}",
            key_type
        );
        assert!(
            priv_size > 0,
            "private_key_size must be positive for key_type {}",
            key_type
        );
        assert!(
            ct_size > 0,
            "cipher_text_size must be positive for key_type {}",
            key_type
        );
        assert_eq!(
            ss_size,
            MlKem::SHARED_SECRET_SIZE,
            "shared_secret_size must equal SHARED_SECRET_SIZE for key_type {}",
            key_type
        );
    }
}

/// Encapsulate and decapsulate with ML-KEM-512, verifying that both sides
/// arrive at the same shared secret.
#[test]
#[cfg(random)]
fn test_encap_decap_type512() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut key =
        MlKem::generate(MlKem::TYPE_512, &mut rng).expect("Error with generate() TYPE_512");

    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut ct = vec![0u8; ct_size];
    let mut ss_enc = vec![0u8; ss_size];
    key.encapsulate(&mut ct, &mut ss_enc, &mut rng)
        .expect("Error with encapsulate()");

    let mut ss_dec = vec![0u8; ss_size];
    key.decapsulate(&mut ss_dec, &ct)
        .expect("Error with decapsulate()");

    assert_eq!(
        ss_enc, ss_dec,
        "Shared secrets must match after encap/decap"
    );
}

/// Encapsulate and decapsulate with ML-KEM-768, verifying that both sides
/// arrive at the same shared secret. Also verifies that a tampered cipher text
/// produces a different (implicit rejection) shared secret.
#[test]
#[cfg(random)]
fn test_encap_decap_type768() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut key =
        MlKem::generate(MlKem::TYPE_768, &mut rng).expect("Error with generate() TYPE_768");

    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut ct = vec![0u8; ct_size];
    let mut ss_enc = vec![0u8; ss_size];
    key.encapsulate(&mut ct, &mut ss_enc, &mut rng)
        .expect("Error with encapsulate()");

    let mut ss_dec = vec![0u8; ss_size];
    key.decapsulate(&mut ss_dec, &ct)
        .expect("Error with decapsulate()");

    assert_eq!(
        ss_enc, ss_dec,
        "Shared secrets must match after encap/decap"
    );

    // Tamper with the cipher text. ML-KEM uses implicit rejection, so
    // decapsulation succeeds but returns a different (pseudorandom) secret.
    let mut ct_tampered = ct.clone();
    ct_tampered[0] ^= 0xFF;
    let mut ss_tampered = vec![0u8; ss_size];
    key.decapsulate(&mut ss_tampered, &ct_tampered)
        .expect("Error with decapsulate() on tampered ct");
    assert_ne!(
        ss_enc, ss_tampered,
        "Tampered ct must yield different shared secret"
    );
}

/// Encapsulate and decapsulate with ML-KEM-1024, verifying that both sides
/// arrive at the same shared secret.
#[test]
#[cfg(random)]
fn test_encap_decap_type1024() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut key =
        MlKem::generate(MlKem::TYPE_1024, &mut rng).expect("Error with generate() TYPE_1024");

    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut ct = vec![0u8; ct_size];
    let mut ss_enc = vec![0u8; ss_size];
    key.encapsulate(&mut ct, &mut ss_enc, &mut rng)
        .expect("Error with encapsulate()");

    let mut ss_dec = vec![0u8; ss_size];
    key.decapsulate(&mut ss_dec, &ct)
        .expect("Error with decapsulate()");

    assert_eq!(
        ss_enc, ss_dec,
        "Shared secrets must match after encap/decap"
    );
}

/// Verify that `generate_with_random()` is deterministic: the same random
/// bytes produce the same key pair on repeated calls.
#[test]
fn test_generate_with_random_determinism() {
    common::setup();
    // MAKEKEY_RAND_SIZE = 64 bytes
    let rand = [0x42u8; 64];

    let key1 = MlKem::generate_with_random(MlKem::TYPE_768, &rand)
        .expect("Error with generate_with_random() first call");
    let key2 = MlKem::generate_with_random(MlKem::TYPE_768, &rand)
        .expect("Error with generate_with_random() second call");

    let pub_size = key1
        .public_key_size()
        .expect("Error with public_key_size()");
    let mut pub1 = vec![0u8; pub_size];
    let mut pub2 = vec![0u8; pub_size];
    key1.encode_public_key(&mut pub1)
        .expect("Error with encode_public_key() key1");
    key2.encode_public_key(&mut pub2)
        .expect("Error with encode_public_key() key2");
    assert_eq!(pub1, pub2, "Same random must yield same public key");

    let priv_size = key1
        .private_key_size()
        .expect("Error with private_key_size()");
    let mut priv1 = vec![0u8; priv_size];
    let mut priv2 = vec![0u8; priv_size];
    key1.encode_private_key(&mut priv1)
        .expect("Error with encode_private_key() key1");
    key2.encode_private_key(&mut priv2)
        .expect("Error with encode_private_key() key2");
    assert_eq!(priv1, priv2, "Same random must yield same private key");
}

/// Verify that `encapsulate_with_random()` is deterministic: the same public
/// key and random bytes produce the same cipher text and shared secret.
#[test]
fn test_encapsulate_with_random_determinism() {
    common::setup();
    let key_rand = [0x11u8; 64];
    let enc_rand = [0x22u8; 32];

    let mut key = MlKem::generate_with_random(MlKem::TYPE_768, &key_rand)
        .expect("Error with generate_with_random()");

    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut ct1 = vec![0u8; ct_size];
    let mut ss1 = vec![0u8; ss_size];
    key.encapsulate_with_random(&mut ct1, &mut ss1, &enc_rand)
        .expect("Error with encapsulate_with_random() first call");

    let mut ct2 = vec![0u8; ct_size];
    let mut ss2 = vec![0u8; ss_size];
    key.encapsulate_with_random(&mut ct2, &mut ss2, &enc_rand)
        .expect("Error with encapsulate_with_random() second call");

    assert_eq!(ct1, ct2, "Same inputs must yield same cipher text");
    assert_eq!(ss1, ss2, "Same inputs must yield same shared secret");

    // Decapsulate and verify the shared secrets match.
    let mut ss_dec = vec![0u8; ss_size];
    key.decapsulate(&mut ss_dec, &ct1)
        .expect("Error with decapsulate()");
    assert_eq!(
        ss1, ss_dec,
        "Shared secret from encapsulate must match decapsulate"
    );
}

/// Encode and decode the public key for ML-KEM-768, verifying the round-trip
/// preserves the key bytes and that encapsulation works with the re-imported key.
#[test]
#[cfg(random)]
fn test_encode_decode_public_key() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut key = MlKem::generate(MlKem::TYPE_768, &mut rng).expect("Error with generate()");

    let pub_size = key.public_key_size().expect("Error with public_key_size()");
    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut pub_buf = vec![0u8; pub_size];
    let written = key
        .encode_public_key(&mut pub_buf)
        .expect("Error with encode_public_key()");
    assert_eq!(written, pub_size);

    // Re-import public key and encapsulate.
    let mut pub_key = MlKem::new(MlKem::TYPE_768).expect("Error with new()");
    pub_key
        .decode_public_key(&pub_buf)
        .expect("Error with decode_public_key()");

    let mut ct = vec![0u8; ct_size];
    let mut ss_enc = vec![0u8; ss_size];
    pub_key
        .encapsulate(&mut ct, &mut ss_enc, &mut rng)
        .expect("Error with encapsulate() via imported public key");

    // Decapsulate with the original full key pair.
    let mut ss_dec = vec![0u8; ss_size];
    key.decapsulate(&mut ss_dec, &ct)
        .expect("Error with decapsulate()");

    assert_eq!(
        ss_enc, ss_dec,
        "Shared secrets must match after public key import"
    );
}

/// Encode and decode the private key for ML-KEM-768, verifying that
/// decapsulation works with the re-imported key.
#[test]
#[cfg(random)]
fn test_encode_decode_private_key() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");
    let mut key = MlKem::generate(MlKem::TYPE_768, &mut rng).expect("Error with generate()");

    let priv_size = key
        .private_key_size()
        .expect("Error with private_key_size()");
    let ct_size = key
        .cipher_text_size()
        .expect("Error with cipher_text_size()");
    let ss_size = key
        .shared_secret_size()
        .expect("Error with shared_secret_size()");

    let mut priv_buf = vec![0u8; priv_size];
    let written = key
        .encode_private_key(&mut priv_buf)
        .expect("Error with encode_private_key()");
    assert_eq!(written, priv_size);

    // Encapsulate with the original key.
    let mut ct = vec![0u8; ct_size];
    let mut ss_enc = vec![0u8; ss_size];
    key.encapsulate(&mut ct, &mut ss_enc, &mut rng)
        .expect("Error with encapsulate()");

    // Re-import private key and decapsulate.
    let mut priv_key = MlKem::new(MlKem::TYPE_768).expect("Error with new()");
    priv_key
        .decode_private_key(&priv_buf)
        .expect("Error with decode_private_key()");

    let mut ss_dec = vec![0u8; ss_size];
    priv_key
        .decapsulate(&mut ss_dec, &ct)
        .expect("Error with decapsulate() via imported private key");

    assert_eq!(
        ss_enc, ss_dec,
        "Shared secrets must match after private key import"
    );
}

/// Verify that encapsulate/decapsulate round-trips work across all three
/// security levels.
#[test]
#[cfg(random)]
fn test_encap_decap_all_types() {
    common::setup();
    let mut rng = RNG::new().expect("Error creating RNG");

    for key_type in [MlKem::TYPE_512, MlKem::TYPE_768, MlKem::TYPE_1024] {
        let mut key = MlKem::generate(key_type, &mut rng).expect("Error with generate()");

        let ct_size = key
            .cipher_text_size()
            .expect("Error with cipher_text_size()");
        let ss_size = key
            .shared_secret_size()
            .expect("Error with shared_secret_size()");

        let mut ct = vec![0u8; ct_size];
        let mut ss_enc = vec![0u8; ss_size];
        key.encapsulate(&mut ct, &mut ss_enc, &mut rng)
            .expect("Error with encapsulate()");

        let mut ss_dec = vec![0u8; ss_size];
        key.decapsulate(&mut ss_dec, &ct)
            .expect("Error with decapsulate()");

        assert_eq!(
            ss_enc, ss_dec,
            "Shared secrets must match for key_type {}",
            key_type
        );
        assert_eq!(
            ss_size,
            MlKem::SHARED_SECRET_SIZE,
            "Shared secret size must equal SHARED_SECRET_SIZE for key_type {}",
            key_type
        );
    }
}