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
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
// Copyright 2025 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0
//! Wycheproof-based cryptographic tests
//!
//! This module uses test vectors from the Wycheproof project to verify
//! that cryptographic operations work correctly.
mod common;
use crate::common::{get_pkcs11, init_pins, SO_PIN, USER_PIN};
use cryptoki::context::{CInitializeArgs, CInitializeFlags, Function};
use cryptoki::mechanism::aead::{GcmMessageParams, GeneratorFunction};
use cryptoki::mechanism::{Mechanism, MessageParam};
use cryptoki::object::Attribute;
use cryptoki::session::UserType;
use cryptoki::types::AuthPin;
use serial_test::serial;
use testresult::TestResult;
/// Test AES-GCM encryption/decryption using Wycheproof test vectors
#[test]
#[serial]
fn aes_gcm_wycheproof() -> TestResult {
let (pkcs11, slot) = init_pins();
let session = pkcs11.open_rw_session(slot)?;
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
// Determine PKCS#11 version to apply appropriate limits
// PKCS#11 2.40: max nonce size is 256 bytes (ulIvBits is CK_ULONG = 32 bits, max value 2^32-1 *bits* = 2^29 *bytes*)
// PKCS#11 3.x: max nonce size is 2^32-1 bytes (ulIvLen is CK_ULONG in bytes)
// See: PKCS#11 v2.40 section 5.16.3 and PKCS#11 v3.2 section 5.15.3
let info = pkcs11.get_library_info()?;
let cryptoki_version = info.cryptoki_version();
let max_nonce_bytes = if cryptoki_version.major() >= 3 {
u32::MAX as usize // PKCS#11 3.x allows up to 2^32-1 bytes
} else {
256 // PKCS#11 2.40 limits to 256 bytes
};
// Load Wycheproof AES-GCM test vectors
let test_set = wycheproof::aead::TestSet::load(wycheproof::aead::TestName::AesGcm)?;
let mut passed = 0;
let mut failed = 0;
let mut skipped = 0;
for test_group in &test_set.test_groups {
let key_size = test_group.key_size;
// Only test key sizes we support (128, 192, 256 bits)
if ![128, 192, 256].contains(&key_size) {
skipped += test_group.tests.len();
continue;
}
for test in &test_group.tests {
// Skip tests with nonce sizes that exceed PKCS#11 version-specific limits
if test.nonce.len() > max_nonce_bytes {
skipped += 1;
continue;
}
// Skip tests with tag sizes that exceed PKCS#11 limits (max 128 bits)
if test.tag.len() * 8 > 128 {
skipped += 1;
continue;
}
// Import the test key
let key_template = vec![
Attribute::Class(cryptoki::object::ObjectClass::SECRET_KEY),
Attribute::KeyType(cryptoki::object::KeyType::AES),
Attribute::Token(false),
Attribute::Sensitive(false),
Attribute::Extractable(true),
Attribute::Encrypt(true),
Attribute::Decrypt(true),
Attribute::Value(test.key.to_vec()),
];
let key = match session.create_object(&key_template) {
Ok(k) => k,
Err(e) => {
eprintln!("Test {}: Failed to create key: {:?}", test.tc_id, e);
failed += 1;
continue;
}
};
// Prepare GCM parameters (need mutable nonce for GcmParams)
let mut nonce = test.nonce.to_vec();
let tag_bits = match (test.tag.len() * 8).try_into() {
Ok(bits) => bits,
Err(e) => {
eprintln!("Test {}: Failed to convert tag length: {:?}", test.tc_id, e);
failed += 1;
continue;
}
};
let gcm_params =
match cryptoki::mechanism::aead::GcmParams::new(&mut nonce, &test.aad, tag_bits) {
Ok(params) => params,
Err(e) => {
eprintln!("Test {}: Failed to create GCM params: {:?}", test.tc_id, e);
failed += 1;
continue;
}
};
// Test encryption
let encrypt_result = session.encrypt(&Mechanism::AesGcm(gcm_params), key, &test.pt);
match (&test.result, encrypt_result) {
// Valid test should succeed
(wycheproof::TestResult::Valid, Ok(ciphertext)) => {
let expected = [&test.ct[..], &test.tag[..]].concat();
if ciphertext == expected {
println!(
"✓ Test {}: {:?} - Key: {}-bit, Nonce: {}, Tag: {}, AAD: {}, PT: {}",
test.tc_id,
test.result,
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
passed += 1;
} else {
eprintln!(
"✗ Test {}: Encryption output mismatch (expected valid)",
test.tc_id
);
eprintln!(
" Key size: {}, Nonce len: {}, Tag len: {}, AAD len: {}, PT len: {}",
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
eprintln!(" Expected: {}", hex::encode(&expected));
eprintln!(" Got: {}", hex::encode(&ciphertext));
failed += 1;
}
}
// Invalid/Acceptable tests may fail - this is good
(wycheproof::TestResult::Invalid | wycheproof::TestResult::Acceptable, Err(_)) => {
println!(
"✓ Test {}: {:?} (expected failure) - Key: {}-bit, Nonce: {}, Tag: {}, AAD: {}, PT: {}",
test.tc_id,
test.result,
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
passed += 1;
}
// Invalid test that succeeded - Note: SoftHSM may not catch all invalid cases
// This is an HSM implementation detail, not a wrapper issue
(wycheproof::TestResult::Invalid, Ok(_)) => {
println!(
"✓ Test {}: {:?} (HSM accepted, which is OK) - Key: {}-bit, Nonce: {}, Tag: {}, AAD: {}, PT: {}",
test.tc_id,
test.result,
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
passed += 1;
}
// Valid test that failed - this shouldn't happen and indicates an issue
(wycheproof::TestResult::Valid, Err(e)) => {
use cryptoki::error::Error;
// Some providers may not support very large nonces even if spec allows it
if matches!(e, Error::Pkcs11(_, _)) && test.nonce.len() > 256 {
eprintln!(
"Note: Test {}: Provider doesn't support {}-byte nonce ({})",
test.tc_id,
test.nonce.len(),
e
);
passed += 1; // Accept as provider limitation
} else {
eprintln!("✗ Test {}: Valid test FAILED: {:?}", test.tc_id, e);
eprintln!(
" Key size: {}, Nonce len: {}, Tag len: {}, AAD len: {}, PT len: {}",
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
failed += 1;
}
}
// Acceptable tests can go either way
(wycheproof::TestResult::Acceptable, Ok(_)) => {
println!(
"✓ Test {}: {:?} (HSM accepted) - Key: {}-bit, Nonce: {}, Tag: {}, AAD: {}, PT: {}",
test.tc_id,
test.result,
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
passed += 1;
}
}
// Clean up
let _ = session.destroy_object(key);
}
}
println!(
"AES-GCM Wycheproof results: {} passed, {} failed, {} skipped",
passed, failed, skipped
);
// The main requirement is that Valid tests pass
// Invalid tests may or may not be caught by the HSM implementation
assert_eq!(failed, 0, "Some valid Wycheproof tests failed");
session.close()?;
pkcs11.finalize()?;
Ok(())
}
/// Test AES-GCM message-based encryption/decryption using Wycheproof test vectors
/// Message-based encryption is a PKCS#11 3.0+ feature for processing data in multiple parts
#[test]
#[serial]
fn aes_gcm_message_wycheproof() -> TestResult {
// Get PKCS#11 context - may already be initialized from previous test
let pkcs11 = get_pkcs11();
// Try to initialize, but ignore if already initialized
let _ = pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK));
// Find slot
let slot = pkcs11.get_slots_with_token()?.remove(0);
// Initialize token and set PINs (may already be done)
let so_pin = AuthPin::new(SO_PIN.into());
let _ = pkcs11.init_token(slot, &so_pin, "Test Token");
{
// Set user PIN
let session = pkcs11.open_rw_session(slot)?;
let _ = session.login(UserType::So, Some(&so_pin));
let _ = session.init_pin(&AuthPin::new(USER_PIN.into()));
}
// PKCS#11 3.0 API is not supported by this token. Skip
if !pkcs11.is_fn_supported(Function::MessageEncryptInit) {
println!("SKIP: The PKCS#11 module does not support message-based encryption");
pkcs11.finalize()?;
return Ok(());
}
let session = pkcs11.open_rw_session(slot)?;
session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
// Load Wycheproof AES-GCM test vectors
let test_set = wycheproof::aead::TestSet::load(wycheproof::aead::TestName::AesGcm)?;
let mut passed = 0;
let mut failed = 0;
let mut skipped = 0;
for test_group in &test_set.test_groups {
let key_size = test_group.key_size;
// Only test key sizes we support (128, 192, 256 bits)
if ![128, 192, 256].contains(&key_size) {
skipped += test_group.tests.len();
continue;
}
for test in &test_group.tests {
// Skip tests with tag sizes that exceed PKCS#11 limits (max 128 bits)
if test.tag.len() * 8 > 128 {
skipped += 1;
continue;
}
// Import the test key
let key_template = vec![
Attribute::Class(cryptoki::object::ObjectClass::SECRET_KEY),
Attribute::KeyType(cryptoki::object::KeyType::AES),
Attribute::Token(false),
Attribute::Sensitive(false),
Attribute::Extractable(true),
Attribute::Encrypt(true),
Attribute::Decrypt(true),
Attribute::Value(test.key.to_vec()),
];
let key = match session.create_object(&key_template) {
Ok(k) => k,
Err(e) => {
eprintln!(
"Test {}: Failed to create key (message API): {:?}",
test.tc_id, e
);
failed += 1;
continue;
}
};
// Prepare GCM message parameters
let mut nonce = test.nonce.to_vec();
// For message-based encryption, iv_fixed_bits is used for IV generation.
// Since we're not generating IVs (using NoGenerate), we set it to the full IV length in bits.
let iv_bits = match (test.nonce.len() * 8).try_into() {
Ok(bits) => bits,
Err(e) => {
eprintln!(
"Test {}: Failed to convert nonce length to bits (message API): {:?}",
test.tc_id, e
);
failed += 1;
continue;
}
};
// Allocate tag buffer
let mut tag = vec![0u8; test.tag.len()];
let gcm_params = match GcmMessageParams::new(
&mut nonce,
iv_bits,
GeneratorFunction::NoGenerate,
&mut tag,
) {
Ok(params) => params,
Err(e) => {
eprintln!(
"Test {}: Failed to create GCM message params: {:?}",
test.tc_id, e
);
failed += 1;
continue;
}
};
// Test encryption with message-based API
let mechanism = Mechanism::AesGcmMessage(gcm_params);
let encrypt_result = (|| -> Result<Vec<u8>, cryptoki::error::Error> {
session.message_encrypt_init(&mechanism, key)?;
let param = MessageParam::AesGcmMessage(gcm_params);
let ciphertext = session.encrypt_message(¶m, &test.aad, &test.pt)?;
session.message_encrypt_final()?;
Ok(ciphertext)
})();
// Always try to finalize to clean up state, even if encryption failed.
if encrypt_result.is_err() {
let _ = session.message_encrypt_final();
}
match (&test.result, encrypt_result) {
// Valid test should succeed
(wycheproof::TestResult::Valid, Ok(ciphertext)) => {
// Verify ciphertext matches expected
if ciphertext == test.ct.to_vec() && tag == test.tag.to_vec() {
println!(
"✓ Test {}: PASS [key={}b, nonce={}b, tag={}b, aad={}b, pt={}b]",
test.tc_id,
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
passed += 1;
} else {
eprintln!(
"✗ Test {}: Message encryption output mismatch (expected valid)",
test.tc_id
);
eprintln!(
" Key size: {}, Nonce len: {}, Tag len: {}, AAD len: {}, PT len: {}",
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
failed += 1;
}
}
// Invalid/Acceptable tests may fail - this is good
(wycheproof::TestResult::Invalid | wycheproof::TestResult::Acceptable, Err(_)) => {
println!(
"✓ Test {}: PASS (expected to fail, did fail) [key={}b, nonce={}b]",
test.tc_id,
key_size,
test.nonce.len()
);
passed += 1;
}
// Invalid test that succeeded - Note: HSM may not catch all invalid cases
(wycheproof::TestResult::Invalid, Ok(_)) => {
println!(
"✓ Test {}: PASS (invalid but HSM accepted) [key={}b, nonce={}b]",
test.tc_id,
key_size,
test.nonce.len()
);
passed += 1;
}
// Valid test that failed - this shouldn't happen for standard cases
(wycheproof::TestResult::Valid, Err(e)) => {
use cryptoki::error::Error;
match e {
// Some PKCS#11 providers may not support zero-length plaintext
// or unusual nonce sizes. These are acceptable limitations.
Error::Pkcs11(_, _) if test.pt.is_empty() => {
// Zero-length plaintext edge case
println!(
"✓ Test {}: PASS (provider limitation: zero-length plaintext not supported) [key={}b, nonce={}b, aad={}b]",
test.tc_id, key_size, test.nonce.len(), test.aad.len()
);
passed += 1; // Accept as provider limitation
}
Error::Pkcs11(_, _) if test.nonce.len() < 12 || test.nonce.len() > 16 => {
// Unusual nonce size that may not be supported
println!(
"✓ Test {}: PASS (provider limitation: {}-byte nonce not supported) [key={}b, tag={}b, pt={}b]",
test.tc_id, test.nonce.len(), key_size, test.tag.len(), test.pt.len()
);
passed += 1; // Accept as provider limitation
}
_ => {
// Genuine failure for a standard case
eprintln!("✗ Test {}: Valid message test FAILED: {:?}", test.tc_id, e);
eprintln!(
" Key size: {}, Nonce len: {}, Tag len: {}, AAD len: {}, PT len: {}",
key_size,
test.nonce.len(),
test.tag.len(),
test.aad.len(),
test.pt.len()
);
failed += 1;
}
}
}
// Acceptable tests can go either way
(wycheproof::TestResult::Acceptable, Ok(_)) => {
println!(
"✓ Test {}: PASS (acceptable test) [key={}b, nonce={}b]",
test.tc_id,
key_size,
test.nonce.len()
);
passed += 1;
}
}
// Clean up
let _ = session.destroy_object(key);
}
}
println!(
"AES-GCM Message Wycheproof results: {} passed, {} failed, {} skipped",
passed, failed, skipped
);
// The main requirement is that Valid tests pass
assert_eq!(failed, 0, "Some valid Wycheproof message tests failed");
session.close()?;
pkcs11.finalize()?;
Ok(())
}