1#![forbid(unsafe_code)]
2
3use std::collections::HashMap;
9use std::fmt;
10use std::sync::{Arc, Mutex};
11
12use sha2::{Digest, Sha256};
13use uselesskey_core::Factory;
14
15pub const DOMAIN_PKCS11_MOCK: &str = "uselesskey:pkcs11:mock:v1";
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct SlotTokenInfo {
21 pub slot_id: u64,
22 pub token_label: String,
23 pub manufacturer_id: String,
24 pub model: String,
25 pub serial_number: String,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub struct KeyHandle(pub u64);
31
32#[derive(Clone)]
33pub struct MockPkcs11Provider {
34 inner: Arc<Inner>,
35}
36
37impl fmt::Debug for MockPkcs11Provider {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.debug_struct("MockPkcs11Provider")
40 .field("slot", &self.inner.slot)
41 .field("key_count", &self.inner.keys.len())
42 .finish()
43 }
44}
45
46struct Inner {
47 slot: SlotTokenInfo,
48 certificates: HashMap<KeyHandle, Vec<u8>>,
49 keys: HashMap<KeyHandle, KeyRecord>,
50 next_sign_count: Mutex<u64>,
51}
52
53struct KeyRecord {
54 label: String,
55 algorithm: String,
56 secret: [u8; 32],
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct Pkcs11MockSpec {
62 pub token_label: String,
63 pub manufacturer_id: String,
64 pub model: String,
65 pub key_labels: Vec<String>,
66}
67
68impl Pkcs11MockSpec {
69 pub fn basic(token_label: impl Into<String>) -> Self {
70 Self {
71 token_label: token_label.into(),
72 manufacturer_id: "uselesskey".to_string(),
73 model: "UK-PKCS11-MOCK".to_string(),
74 key_labels: vec!["signing-key".to_string()],
75 }
76 }
77
78 pub fn stable_bytes(&self) -> Vec<u8> {
79 let mut out = Vec::new();
80 write_field(&mut out, "token_label", self.token_label.as_bytes());
81 write_field(&mut out, "manufacturer_id", self.manufacturer_id.as_bytes());
82 write_field(&mut out, "model", self.model.as_bytes());
83 for label in self.effective_key_labels() {
84 write_field(&mut out, "key_label", label.as_bytes());
85 }
86 out
87 }
88
89 fn effective_key_labels(&self) -> impl Iterator<Item = &str> {
90 self.key_labels
91 .iter()
92 .map(String::as_str)
93 .chain((self.key_labels.is_empty()).then_some("signing-key"))
94 }
95}
96
97pub trait Pkcs11MockFactoryExt {
99 fn pkcs11_mock(&self, label: impl AsRef<str>, spec: Pkcs11MockSpec) -> MockPkcs11Provider;
100}
101
102impl Pkcs11MockFactoryExt for Factory {
103 fn pkcs11_mock(&self, label: impl AsRef<str>, spec: Pkcs11MockSpec) -> MockPkcs11Provider {
104 let spec_bytes = spec.stable_bytes();
105 self.get_or_init(
106 DOMAIN_PKCS11_MOCK,
107 label.as_ref(),
108 &spec_bytes,
109 "good",
110 move |seed| build_provider(spec, *seed.bytes()),
111 )
112 .as_ref()
113 .clone()
114 }
115}
116
117impl MockPkcs11Provider {
118 pub fn slot_info(&self) -> SlotTokenInfo {
119 self.inner.slot.clone()
120 }
121
122 pub fn key_handles(&self) -> Vec<KeyHandle> {
123 let mut handles: Vec<KeyHandle> = self.inner.keys.keys().copied().collect();
124 handles.sort_by_key(|h| h.0);
125 handles
126 }
127
128 pub fn sign(&self, handle: KeyHandle, message: &[u8]) -> Option<Vec<u8>> {
129 let key = self.inner.keys.get(&handle)?;
130 let mut hasher = Sha256::new();
131 hasher.update(key.secret);
132 hasher.update(key.algorithm.as_bytes());
133 hasher.update(message);
134 Some(hasher.finalize().to_vec())
135 }
136
137 pub fn verify(&self, handle: KeyHandle, message: &[u8], signature: &[u8]) -> bool {
138 self.sign(handle, message)
139 .is_some_and(|expected| expected == signature)
140 }
141
142 pub fn certificate_der(&self, handle: KeyHandle) -> Option<&[u8]> {
143 self.inner.certificates.get(&handle).map(Vec::as_slice)
144 }
145
146 pub fn key_label(&self, handle: KeyHandle) -> Option<&str> {
147 self.inner.keys.get(&handle).map(|key| key.label.as_str())
148 }
149
150 pub fn next_sign_count(&self) -> u64 {
151 let mut guard = self.inner.next_sign_count.lock().expect("sign_count mutex");
152 *guard += 1;
153 *guard
154 }
155}
156
157fn build_provider(spec: Pkcs11MockSpec, seed: [u8; 32]) -> MockPkcs11Provider {
158 let slot_id = u64::from_le_bytes(seed[0..8].try_into().expect("seed slice for slot id"));
159 let serial_hex = hex8(&seed[8..16]);
160 let mut keys = HashMap::new();
161 let mut certs = HashMap::new();
162
163 let key_labels: Vec<&str> = spec.effective_key_labels().collect();
164 for (idx, key_label) in key_labels.iter().enumerate() {
165 let mut key_hasher = Sha256::new();
166 key_hasher.update(seed);
167 key_hasher.update((idx as u32).to_le_bytes());
168 key_hasher.update(key_label.as_bytes());
169 let key_seed = key_hasher.finalize();
170
171 let mut secret = [0u8; 32];
172 secret.copy_from_slice(&key_seed[..32]);
173
174 let handle = KeyHandle((idx as u64) + 1);
175 let cert = mock_certificate_der(
176 &spec.token_label,
177 key_label,
178 &spec.manufacturer_id,
179 &spec.model,
180 &secret,
181 );
182 certs.insert(handle, cert);
183 keys.insert(
184 handle,
185 KeyRecord {
186 label: (*key_label).to_string(),
187 algorithm: "MOCK-SHA256".to_string(),
188 secret,
189 },
190 );
191 }
192
193 MockPkcs11Provider {
194 inner: Arc::new(Inner {
195 slot: SlotTokenInfo {
196 slot_id,
197 token_label: spec.token_label,
198 manufacturer_id: spec.manufacturer_id,
199 model: spec.model,
200 serial_number: serial_hex,
201 },
202 certificates: certs,
203 keys,
204 next_sign_count: Mutex::new(0),
205 }),
206 }
207}
208
209fn mock_certificate_der(
210 token_label: &str,
211 key_label: &str,
212 manufacturer_id: &str,
213 model: &str,
214 secret: &[u8; 32],
215) -> Vec<u8> {
216 let mut out = Vec::with_capacity(128);
217 out.extend_from_slice(&[0x30, 0x82]); out.extend_from_slice(&[0x00, 0x00]);
219 write_field(&mut out, "token", token_label.as_bytes());
220 write_field(&mut out, "key", key_label.as_bytes());
221 write_field(&mut out, "mfr", manufacturer_id.as_bytes());
222 write_field(&mut out, "model", model.as_bytes());
223 write_field(&mut out, "fingerprint", &Sha256::digest(secret));
224 let body_len = u16_len("certificate_der_body", out.len() - 4);
225 out[2..4].copy_from_slice(&body_len.to_be_bytes());
226 out
227}
228
229fn write_field(out: &mut Vec<u8>, name: &str, value: &[u8]) {
230 out.extend_from_slice(name.as_bytes());
231 out.push(b'=');
232 out.extend_from_slice(&u16_len(name, value.len()).to_be_bytes());
233 out.extend_from_slice(value);
234 out.push(0);
235}
236
237fn u16_len(field_name: &str, len: usize) -> u16 {
238 u16::try_from(len).unwrap_or_else(|_| {
239 panic!(
240 "{field_name} length {len} exceeds u16::MAX; fixture encoding requires <= {} bytes",
241 u16::MAX
242 )
243 })
244}
245
246fn hex8(bytes: &[u8]) -> String {
247 bytes.iter().map(|b| format!("{b:02X}")).collect::<String>()
248}
249
250#[cfg(test)]
251mod tests {
252 use std::collections::{HashMap, HashSet};
253
254 use uselesskey_core::Seed;
255 use uselesskey_test_support::{TestResult, require_ok, require_some};
256
257 use super::*;
258
259 #[test]
260 fn deterministic_provider_stable() {
261 let fx = Factory::deterministic(Seed::from_env_value("pkcs11-seed").unwrap());
262 let spec = Pkcs11MockSpec::basic("HSM-A");
263
264 let a = fx.pkcs11_mock("issuer", spec.clone());
265 let b = fx.pkcs11_mock("issuer", spec);
266
267 assert_eq!(a.slot_info(), b.slot_info());
268 assert_eq!(a.key_handles(), b.key_handles());
269 }
270
271 #[test]
272 fn sign_verify_round_trip() {
273 let fx = Factory::random();
274 let provider = fx.pkcs11_mock("rt", Pkcs11MockSpec::basic("HSM-RT"));
275 let handle = provider.key_handles()[0];
276 let msg = b"hello from fixture";
277
278 let sig = provider.sign(handle, msg).expect("signature");
279 assert!(provider.verify(handle, msg, &sig));
280 assert!(!provider.verify(handle, b"other", &sig));
281 }
282
283 #[test]
284 fn sign_count_increments_from_one() {
285 let fx = Factory::random();
286 let provider = fx.pkcs11_mock("count", Pkcs11MockSpec::basic("HSM-COUNT"));
287
288 assert_eq!(provider.next_sign_count(), 1);
289 assert_eq!(provider.next_sign_count(), 2);
290 }
291
292 #[test]
293 fn debug_summary_names_slot_and_key_count() {
294 let fx = Factory::random();
295 let provider = fx.pkcs11_mock("debug", Pkcs11MockSpec::basic("HSM-DEBUG"));
296 let debug = format!("{provider:?}");
297
298 assert!(debug.contains("MockPkcs11Provider"));
299 assert!(debug.contains("slot"));
300 assert!(debug.contains("key_count"));
301 }
302
303 #[test]
304 fn multiple_keys_get_one_based_sequential_handles() {
305 let fx = Factory::deterministic(Seed::from_env_value("pkcs11-handles").unwrap());
306 let mut spec = Pkcs11MockSpec::basic("HSM-HANDLES");
307 spec.key_labels = vec!["signing-key".to_string(), "verification-key".to_string()];
308
309 let provider = fx.pkcs11_mock("handles", spec);
310 let handles = provider.key_handles();
311
312 assert_eq!(handles, vec![KeyHandle(1), KeyHandle(2)]);
313 assert_eq!(provider.key_label(KeyHandle(1)), Some("signing-key"));
314 assert_eq!(provider.key_label(KeyHandle(2)), Some("verification-key"));
315 }
316
317 #[test]
318 fn cert_lookup_returns_der_like_bytes() {
319 let fx = Factory::random();
320 let provider = fx.pkcs11_mock("der", Pkcs11MockSpec::basic("HSM-DER"));
321 let handle = provider.key_handles()[0];
322 let der = provider.certificate_der(handle).expect("certificate");
323 assert_eq!(&der[0..2], &[0x30, 0x82]);
324 let body_len = u16::from_be_bytes(der[2..4].try_into().expect("DER body length"));
325 assert_eq!(usize::from(body_len), der.len() - 4);
326 }
327
328 #[test]
329 fn slot_serial_is_uppercase_hex() {
330 let fx = Factory::deterministic(Seed::from_env_value("pkcs11-serial").unwrap());
331 let provider = fx.pkcs11_mock("serial", Pkcs11MockSpec::basic("HSM-SERIAL"));
332 let serial = provider.slot_info().serial_number;
333
334 assert_eq!(serial.len(), 16);
335 assert!(
336 serial
337 .bytes()
338 .all(|byte| byte.is_ascii_digit() || (b'A'..=b'F').contains(&byte)),
339 "expected uppercase hex serial, got {serial}"
340 );
341 }
342
343 #[test]
344 fn empty_key_labels_falls_back_to_default_key() {
345 let fx = Factory::deterministic(Seed::from_env_value("pkcs11-empty-keys").unwrap());
346 let mut spec = Pkcs11MockSpec::basic("HSM-EMPTY");
347 spec.key_labels.clear();
348
349 let provider = fx.pkcs11_mock("empty", spec);
350 let handles = provider.key_handles();
351 assert_eq!(handles.len(), 1);
352 assert_eq!(provider.key_label(handles[0]), Some("signing-key"));
353 }
354
355 #[test]
356 fn empty_key_labels_and_explicit_default_share_stable_identity() {
357 let mut empty = Pkcs11MockSpec::basic("HSM-EMPTY");
358 empty.key_labels.clear();
359 let explicit = Pkcs11MockSpec::basic("HSM-EMPTY");
360
361 assert_eq!(empty.stable_bytes(), explicit.stable_bytes());
362 }
363
364 #[test]
365 fn key_labels_participate_in_stable_identity() {
366 let explicit = Pkcs11MockSpec::basic("HSM-IDENTITY");
367 let mut alternate = Pkcs11MockSpec::basic("HSM-IDENTITY");
368 alternate.key_labels = vec!["verification-key".to_string()];
369
370 let stable = explicit.stable_bytes();
371 assert_contains_bytes(&stable, b"key_label");
372 assert_contains_bytes(&stable, b"signing-key");
373 assert_ne!(stable, alternate.stable_bytes());
374 }
375
376 fn assert_contains_bytes(haystack: &[u8], needle: &[u8]) {
377 assert!(
378 haystack
379 .windows(needle.len())
380 .any(|window| window == needle),
381 "expected stable identity to contain {}",
382 String::from_utf8_lossy(needle)
383 );
384 }
385
386 #[test]
387 #[should_panic(expected = "token_label length")]
388 fn rejects_oversized_field_length() {
389 let fx = Factory::random();
390 let spec = Pkcs11MockSpec::basic("a".repeat((u16::MAX as usize) + 1));
391 let _ = fx.pkcs11_mock("oversized-field", spec);
392 }
393
394 #[test]
395 #[should_panic(expected = "certificate_der_body length")]
396 fn rejects_oversized_der_body() {
397 let fx = Factory::random();
398 let mut spec = Pkcs11MockSpec::basic("token");
399 let long = "z".repeat(22_000);
400 spec.manufacturer_id = long.clone();
401 spec.model = long;
402 spec.key_labels = vec!["k".repeat(22_000)];
403 let _ = fx.pkcs11_mock("oversized-der", spec);
404 }
405
406 #[test]
407 fn domain_constant_is_stable_for_the_lifetime_of_v1() {
408 assert_eq!(DOMAIN_PKCS11_MOCK, "uselesskey:pkcs11:mock:v1");
412 }
413
414 #[test]
415 fn sign_with_unknown_handle_returns_none() {
416 let fx = Factory::random();
417 let provider = fx.pkcs11_mock("err-sign", Pkcs11MockSpec::basic("HSM-ERR-SIGN"));
418
419 assert!(provider.sign(KeyHandle(0), b"msg").is_none());
422 assert!(provider.sign(KeyHandle(999), b"msg").is_none());
423 }
424
425 #[test]
426 fn verify_with_unknown_handle_returns_false() -> TestResult<()> {
427 let fx = Factory::random();
428 let provider = fx.pkcs11_mock("err-verify", Pkcs11MockSpec::basic("HSM-ERR-VERIFY"));
429 let valid = *require_some(provider.key_handles().first(), "default key handle present")?;
430 let real_sig = require_some(
431 provider.sign(valid, b"msg"),
432 "signing succeeds for valid handle",
433 )?;
434
435 assert!(!provider.verify(KeyHandle(0), b"msg", &real_sig));
438 assert!(!provider.verify(KeyHandle(999), b"msg", &real_sig));
439 assert!(!provider.verify(KeyHandle(999), b"msg", &[]));
440 Ok(())
441 }
442
443 #[test]
444 fn certificate_der_with_unknown_handle_returns_none() {
445 let fx = Factory::random();
446 let provider = fx.pkcs11_mock("err-cert", Pkcs11MockSpec::basic("HSM-ERR-CERT"));
447
448 assert!(provider.certificate_der(KeyHandle(0)).is_none());
449 assert!(provider.certificate_der(KeyHandle(999)).is_none());
450 }
451
452 #[test]
453 fn key_label_with_unknown_handle_returns_none() {
454 let fx = Factory::random();
455 let provider = fx.pkcs11_mock("err-label", Pkcs11MockSpec::basic("HSM-ERR-LABEL"));
456
457 assert!(provider.key_label(KeyHandle(0)).is_none());
458 assert!(provider.key_label(KeyHandle(999)).is_none());
459 }
460
461 #[test]
462 fn key_handle_is_copy_and_usable_in_hash_collections() {
463 let original = KeyHandle(42);
464 let copied = original; #[allow(clippy::clone_on_copy)]
468 let cloned = original.clone();
469
470 assert_eq!(original, copied);
472 assert_eq!(original, cloned);
473 assert_eq!(original.0, 42);
474
475 let mut map: HashMap<KeyHandle, &'static str> = HashMap::new();
477 map.insert(original, "first");
478 let prior = map.insert(copied, "second");
480 assert_eq!(prior, Some("first"));
481 assert_eq!(map.len(), 1);
482 assert_eq!(map.get(&KeyHandle(42)), Some(&"second"));
483
484 let mut set: HashSet<KeyHandle> = HashSet::new();
486 set.insert(original);
487 set.insert(copied);
488 set.insert(KeyHandle(42));
489 assert_eq!(set.len(), 1);
490 }
491
492 #[test]
493 fn slot_token_info_round_trips_clone_debug_and_equality() {
494 let info = SlotTokenInfo {
495 slot_id: 7,
496 token_label: "HSM-RT".to_string(),
497 manufacturer_id: "uselesskey".to_string(),
498 model: "UK-PKCS11-MOCK".to_string(),
499 serial_number: "ABCDEF0123456789".to_string(),
500 };
501
502 let cloned = info.clone();
503 assert_eq!(info, cloned);
504
505 let debug = format!("{info:?}");
508 for needle in [
509 "SlotTokenInfo",
510 "slot_id",
511 "token_label",
512 "manufacturer_id",
513 "model",
514 "serial_number",
515 ] {
516 assert!(debug.contains(needle), "debug missing {needle}: {debug}");
517 }
518
519 let mut diverged = info.clone();
522 diverged.slot_id = info.slot_id + 1;
523 assert_ne!(info, diverged);
524 assert_eq!(info.slot_id, 7);
525 }
526
527 #[test]
528 fn next_sign_count_increments_monotonically() -> TestResult<()> {
529 let fx = Factory::deterministic(require_ok(
530 Seed::from_env_value("pkcs11-monotonic"),
531 "must parse seed",
532 )?);
533 let provider = fx.pkcs11_mock("monotonic", Pkcs11MockSpec::basic("HSM-MONO"));
534
535 let observed: Vec<u64> = (0..5).map(|_| provider.next_sign_count()).collect();
536
537 assert_eq!(observed, vec![1, 2, 3, 4, 5]);
539 Ok(())
540 }
541}