1#![allow(
8 unsafe_code,
9 clippy::print_stdout, clippy::multiple_crate_versions,
11 clippy::cargo_common_metadata,
12 clippy::exhaustive_structs, clippy::std_instead_of_core,
14)]
15
16use std::{ffi::c_void, ptr};
17
18use libloading::{Library, Symbol};
19use pkcs11_sys::{
20 CK_ATTRIBUTE, CK_BBOOL, CK_FLAGS, CK_FUNCTION_LIST, CK_FUNCTION_LIST_PTR_PTR, CK_KEY_TYPE,
21 CK_MECHANISM, CK_OBJECT_CLASS, CK_OBJECT_HANDLE, CK_RV, CK_SESSION_HANDLE, CK_SLOT_ID, CK_TRUE,
22 CK_ULONG, CKA_CLASS, CKA_EXTRACTABLE, CKA_KEY_TYPE, CKA_LABEL, CKA_SENSITIVE, CKA_VALUE_LEN,
23 CKF_RW_SESSION, CKF_SERIAL_SESSION, CKK_AES, CKM_AES_CBC_PAD, CKM_AES_KEY_GEN, CKO_CERTIFICATE,
24 CKO_DATA, CKO_PRIVATE_KEY, CKO_PUBLIC_KEY, CKO_SECRET_KEY, CKR_OK, CKU_USER,
25};
26
27pub type GetFunctionListFn = unsafe extern "C" fn(CK_FUNCTION_LIST_PTR_PTR) -> CK_RV;
32
33pub fn call_get_function_list(lib: &Library) -> Result<*mut CK_FUNCTION_LIST, String> {
34 let get_func_list: Symbol<GetFunctionListFn> = unsafe { lib.get(b"C_GetFunctionList\0") }
37 .map_err(|e| format!("FAIL [C_GetFunctionList]: symbol not found in .so: {e}"))?;
38
39 let mut func_list_ptr: *mut CK_FUNCTION_LIST = ptr::null_mut();
40 let rv = unsafe { get_func_list(&raw mut func_list_ptr) };
42
43 if rv != CKR_OK {
44 return Err(format!(
45 "FAIL [C_GetFunctionList]: returned {} (0x{rv:08X})\n \
46 Hint: verify that ckms.toml exists, is valid TOML, and contains a \
47 reachable [http_config].server_url.",
48 ckr_name(rv)
49 ));
50 }
51
52 if func_list_ptr.is_null() {
53 return Err(
54 "FAIL [C_GetFunctionList]: returned CKR_OK but function list pointer is null"
55 .to_owned(),
56 );
57 }
58
59 Ok(func_list_ptr)
60}
61
62pub fn call_get_slot_list(func_list: &CK_FUNCTION_LIST) -> Result<CK_SLOT_ID, String> {
67 let c_get_slot_list = func_list
68 .C_GetSlotList
69 .ok_or_else(|| "FAIL [C_GetSlotList]: not present in function list".to_owned())?;
70
71 let token_present: CK_BBOOL = CK_TRUE;
73 let mut count: CK_ULONG = 0;
74 let rv = unsafe { c_get_slot_list(token_present, ptr::null_mut(), &raw mut count) };
75 check_rv(rv, "C_GetSlotList (count)")?;
76
77 if count == 0 {
78 return Err("FAIL [C_GetSlotList]: provider reports zero slots".to_owned());
79 }
80
81 let slot_count = usize::try_from(count).map_err(|e| {
82 format!("FAIL [C_GetSlotList]: slot count value out of range for usize: {e}")
83 })?;
84
85 let mut slots: Vec<CK_SLOT_ID> = vec![0; slot_count];
87 let rv = unsafe { c_get_slot_list(token_present, slots.as_mut_ptr(), &raw mut count) };
88 check_rv(rv, "C_GetSlotList (fill)")?;
89
90 slots
91 .first()
92 .copied()
93 .ok_or_else(|| "FAIL [C_GetSlotList]: slot list is empty after fill".to_owned())
94}
95
96pub fn call_open_session(
101 func_list: &CK_FUNCTION_LIST,
102 slot_id: CK_SLOT_ID,
103) -> Result<CK_SESSION_HANDLE, String> {
104 let c_open_session = func_list
105 .C_OpenSession
106 .ok_or_else(|| "FAIL [C_OpenSession]: not present in function list".to_owned())?;
107
108 let flags: CK_FLAGS = CKF_SERIAL_SESSION | CKF_RW_SESSION;
109 let mut session: CK_SESSION_HANDLE = 0;
110
111 let rv = unsafe {
112 c_open_session(
113 slot_id,
114 flags,
115 ptr::null_mut::<c_void>(), None, &raw mut session,
118 )
119 };
120
121 if rv != CKR_OK {
122 return Err(format!(
123 "FAIL [C_OpenSession]: returned {} (0x{rv:08X})\n \
124 Hint: the KMS server at the URL in ckms.toml may not be running or reachable.",
125 ckr_name(rv)
126 ));
127 }
128
129 Ok(session)
130}
131
132pub fn call_login(
137 func_list: &CK_FUNCTION_LIST,
138 session: CK_SESSION_HANDLE,
139 token: &str,
140) -> Result<(), String> {
141 let c_login = func_list
142 .C_Login
143 .ok_or_else(|| "FAIL [C_Login]: not present in function list".to_owned())?;
144
145 let token_bytes = token.as_bytes();
146 let pin_len = CK_ULONG::try_from(token_bytes.len())
147 .map_err(|e| format!("FAIL [C_Login]: token length out of CK_ULONG range: {e}"))?;
148
149 let rv = unsafe { c_login(session, CKU_USER, token_bytes.as_ptr().cast_mut(), pin_len) };
153
154 if rv != CKR_OK {
155 return Err(format!(
156 "FAIL [C_Login]: returned {} (0x{rv:08X})\n \
157 Hint: verify the JWT is valid and not expired. \
158 The KMS server must accept it as a bearer token.",
159 ckr_name(rv)
160 ));
161 }
162
163 Ok(())
164}
165
166pub const OBJECT_CLASSES: &[(CK_OBJECT_CLASS, &str)] = &[
176 (CKO_DATA, "CKO_DATA"),
177 (CKO_CERTIFICATE, "CKO_CERTIFICATE"),
178 (CKO_PUBLIC_KEY, "CKO_PUBLIC_KEY"),
179 (CKO_PRIVATE_KEY, "CKO_PRIVATE_KEY"),
180 (CKO_SECRET_KEY, "CKO_SECRET_KEY"),
181];
182
183pub const MAX_OBJECTS: usize = 64;
185
186#[must_use]
187pub fn call_find_objects(func_list: &CK_FUNCTION_LIST, session: CK_SESSION_HANDLE) -> usize {
188 println!("[C_FindObjects] Enumerating objects by class:");
189 let mut grand_total: usize = 0;
190
191 for &(class, class_name) in OBJECT_CLASSES {
192 match count_objects_by_class(func_list, session, class, class_name) {
193 Ok(count) => {
194 println!(" {class_name}: {count}");
195 grand_total += count;
196 }
197 Err(e) => {
203 println!(" {class_name}: unavailable — {e}");
204 }
205 }
206 }
207
208 grand_total
209}
210
211pub fn count_objects_by_class(
217 func_list: &CK_FUNCTION_LIST,
218 session: CK_SESSION_HANDLE,
219 class: CK_OBJECT_CLASS,
220 class_name: &str,
221) -> Result<usize, String> {
222 let mut object_class: CK_OBJECT_CLASS = class;
224 let object_class_len =
225 CK_ULONG::try_from(std::mem::size_of::<CK_OBJECT_CLASS>()).map_err(|e| {
226 format!("FAIL [C_FindObjectsInit({class_name})]: size_of overflows CK_ULONG: {e}")
227 })?;
228 let mut template = CK_ATTRIBUTE {
229 type_: CKA_CLASS,
230 pValue: (&raw mut object_class).cast::<std::ffi::c_void>(),
231 ulValueLen: object_class_len,
232 };
233
234 let c_find_objects_init = func_list
235 .C_FindObjectsInit
236 .ok_or_else(|| "FAIL [C_FindObjectsInit]: not present in function list".to_owned())?;
237 let rv = unsafe { c_find_objects_init(session, &raw mut template, 1) };
238 check_rv(rv, &format!("C_FindObjectsInit({class_name})"))?;
239
240 let c_find_objects = func_list
241 .C_FindObjects
242 .ok_or_else(|| "FAIL [C_FindObjects]: not present in function list".to_owned())?;
243 let max_ck = CK_ULONG::try_from(MAX_OBJECTS)
244 .map_err(|e| format!("FAIL [C_FindObjects]: MAX_OBJECTS out of CK_ULONG range: {e}"))?;
245
246 let mut total: usize = 0;
247 loop {
248 let mut handles: Vec<CK_OBJECT_HANDLE> = vec![0; MAX_OBJECTS];
249 let mut found: CK_ULONG = 0;
250 let rv = unsafe { c_find_objects(session, handles.as_mut_ptr(), max_ck, &raw mut found) };
251 check_rv(rv, &format!("C_FindObjects({class_name})"))?;
252
253 let batch = usize::try_from(found).map_err(|e| {
254 format!("FAIL [C_FindObjects({class_name})]: found-count out of usize range: {e}")
255 })?;
256 total += batch;
257
258 if batch < MAX_OBJECTS {
259 break;
260 }
261 }
262
263 let c_find_objects_final = func_list
264 .C_FindObjectsFinal
265 .ok_or_else(|| "FAIL [C_FindObjectsFinal]: not present in function list".to_owned())?;
266 let rv = unsafe { c_find_objects_final(session) };
267 check_rv(rv, &format!("C_FindObjectsFinal({class_name})"))?;
268
269 Ok(total)
270}
271
272pub fn check_rv(rv: CK_RV, step: &str) -> Result<(), String> {
277 if rv == CKR_OK {
278 Ok(())
279 } else {
280 Err(format!(
281 "FAIL [{step}]: returned {} (0x{rv:08X})",
282 ckr_name(rv)
283 ))
284 }
285}
286
287#[must_use]
289pub const fn ckr_name(rv: CK_RV) -> &'static str {
290 match rv {
291 0 => "CKR_OK",
292 1 => "CKR_CANCEL",
293 2 => "CKR_HOST_MEMORY",
294 3 => "CKR_SLOT_ID_INVALID",
295 5 => "CKR_GENERAL_ERROR",
296 6 => "CKR_FUNCTION_FAILED",
297 7 => "CKR_ARGUMENTS_BAD",
298 10 => "CKR_NO_EVENT",
299 11 => "CKR_NEED_TO_CREATE_THREADS",
300 12 => "CKR_CANT_LOCK",
301 16 => "CKR_ATTRIBUTE_READ_ONLY",
302 17 => "CKR_ATTRIBUTE_SENSITIVE",
303 18 => "CKR_ATTRIBUTE_TYPE_INVALID",
304 19 => "CKR_ATTRIBUTE_VALUE_INVALID",
305 32 => "CKR_DATA_INVALID",
306 33 => "CKR_DATA_LEN_RANGE",
307 48 => "CKR_DEVICE_ERROR",
308 49 => "CKR_DEVICE_MEMORY",
309 50 => "CKR_DEVICE_REMOVED",
310 64 => "CKR_ENCRYPTED_DATA_INVALID",
311 65 => "CKR_ENCRYPTED_DATA_LEN_RANGE",
312 80 => "CKR_FUNCTION_CANCELED",
313 81 => "CKR_FUNCTION_NOT_PARALLEL",
314 84 => "CKR_FUNCTION_NOT_SUPPORTED",
315 96 => "CKR_KEY_HANDLE_INVALID",
316 98 => "CKR_KEY_SIZE_RANGE",
317 99 => "CKR_KEY_TYPE_INCONSISTENT",
318 160 => "CKR_PIN_INCORRECT",
319 161 => "CKR_PIN_LOCKED",
320 176 => "CKR_OBJECT_HANDLE_INVALID",
321 208 => "CKR_SESSION_CLOSED",
322 209 => "CKR_SESSION_COUNT",
323 211 => "CKR_SESSION_HANDLE_INVALID",
324 213 => "CKR_SESSION_PARALLEL_NOT_SUPPORTED",
325 214 => "CKR_SESSION_READ_ONLY",
326 215 => "CKR_SESSION_EXISTS",
327 224 => "CKR_TOKEN_NOT_PRESENT",
328 225 => "CKR_TOKEN_NOT_RECOGNIZED",
329 226 => "CKR_TOKEN_WRITE_PROTECTED",
330 240 => "CKR_UNWRAPPING_KEY_HANDLE_INVALID",
331 241 => "CKR_UNWRAPPING_KEY_SIZE_RANGE",
332 242 => "CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT",
333 256 => "CKR_USER_ALREADY_LOGGED_IN",
334 257 => "CKR_USER_NOT_LOGGED_IN",
335 258 => "CKR_USER_PIN_NOT_INITIALIZED",
336 259 => "CKR_USER_TYPE_INVALID",
337 272 => "CKR_WRAPPED_KEY_INVALID",
338 274 => "CKR_WRAPPED_KEY_LEN_RANGE",
339 288 => "CKR_WRAPPING_KEY_HANDLE_INVALID",
340 289 => "CKR_WRAPPING_KEY_SIZE_RANGE",
341 290 => "CKR_WRAPPING_KEY_TYPE_INCONSISTENT",
342 304 => "CKR_RANDOM_SEED_NOT_SUPPORTED",
343 305 => "CKR_RANDOM_NO_RNG",
344 320 => "CKR_DOMAIN_PARAMS_INVALID",
345 400 => "CKR_CRYPTOKI_NOT_INITIALIZED",
346 401 => "CKR_CRYPTOKI_ALREADY_INITIALIZED",
347 _ => "CKR_UNKNOWN",
348 }
349}
350
351pub fn call_generate_aes_key(
363 func_list: &CK_FUNCTION_LIST,
364 session: CK_SESSION_HANDLE,
365 label: &str,
366) -> Result<CK_OBJECT_HANDLE, String> {
367 let c_generate_key = func_list
368 .C_GenerateKey
369 .ok_or_else(|| "FAIL [C_GenerateKey]: not present in function list".to_owned())?;
370
371 let mut mechanism = CK_MECHANISM {
372 mechanism: CKM_AES_KEY_GEN,
373 pParameter: ptr::null_mut(),
374 ulParameterLen: 0,
375 };
376
377 let key_type: CK_KEY_TYPE = CKK_AES;
378 let sensitive: CK_BBOOL = CK_TRUE;
379 let extractable: CK_BBOOL = CK_TRUE;
380 let value_len: CK_ULONG = 32; let label_len = CK_ULONG::try_from(label.len())
382 .map_err(|e| format!("FAIL [C_GenerateKey]: label length out of range: {e}"))?;
383 let key_type_len = CK_ULONG::try_from(std::mem::size_of::<CK_KEY_TYPE>())
384 .map_err(|e| format!("FAIL [C_GenerateKey]: size_of::<CK_KEY_TYPE> overflow: {e}"))?;
385 let bbool_len = CK_ULONG::try_from(std::mem::size_of::<CK_BBOOL>())
386 .map_err(|e| format!("FAIL [C_GenerateKey]: size_of::<CK_BBOOL> overflow: {e}"))?;
387 let ulong_len = CK_ULONG::try_from(std::mem::size_of::<CK_ULONG>())
388 .map_err(|e| format!("FAIL [C_GenerateKey]: size_of::<CK_ULONG> overflow: {e}"))?;
389
390 let mut template = [
391 CK_ATTRIBUTE {
392 type_: CKA_KEY_TYPE,
393 pValue: std::ptr::from_ref(&key_type).cast::<c_void>().cast_mut(),
394 ulValueLen: key_type_len,
395 },
396 CK_ATTRIBUTE {
397 type_: CKA_LABEL,
398 pValue: label.as_ptr().cast::<c_void>().cast_mut(),
399 ulValueLen: label_len,
400 },
401 CK_ATTRIBUTE {
402 type_: CKA_SENSITIVE,
403 pValue: std::ptr::from_ref(&sensitive).cast::<c_void>().cast_mut(),
404 ulValueLen: bbool_len,
405 },
406 CK_ATTRIBUTE {
407 type_: CKA_VALUE_LEN,
408 pValue: std::ptr::from_ref(&value_len).cast::<c_void>().cast_mut(),
409 ulValueLen: ulong_len,
410 },
411 CK_ATTRIBUTE {
412 type_: CKA_EXTRACTABLE,
413 pValue: std::ptr::from_ref(&extractable).cast::<c_void>().cast_mut(),
414 ulValueLen: bbool_len,
415 },
416 ];
417 let template_len = CK_ULONG::try_from(template.len())
418 .map_err(|e| format!("FAIL [C_GenerateKey]: template length overflow: {e}"))?;
419
420 let mut key_handle: CK_OBJECT_HANDLE = 0;
421 let rv = unsafe {
422 c_generate_key(
423 session,
424 &raw mut mechanism,
425 template.as_mut_ptr(),
426 template_len,
427 &raw mut key_handle,
428 )
429 };
430 check_rv(rv, "C_GenerateKey")?;
431 Ok(key_handle)
432}
433
434pub fn call_encrypt_aes_cbc_pad(
439 func_list: &CK_FUNCTION_LIST,
440 session: CK_SESSION_HANDLE,
441 key_handle: CK_OBJECT_HANDLE,
442 plaintext: &[u8],
443) -> Result<Vec<u8>, String> {
444 let c_encrypt_init = func_list
445 .C_EncryptInit
446 .ok_or_else(|| "FAIL [C_EncryptInit]: not present in function list".to_owned())?;
447 let c_encrypt = func_list
448 .C_Encrypt
449 .ok_or_else(|| "FAIL [C_Encrypt]: not present in function list".to_owned())?;
450
451 let mut iv = [0_u8; 16];
452 let mut mechanism = CK_MECHANISM {
453 mechanism: CKM_AES_CBC_PAD,
454 pParameter: iv.as_mut_ptr().cast::<c_void>(),
455 ulParameterLen: 16,
456 };
457
458 let rv = unsafe { c_encrypt_init(session, &raw mut mechanism, key_handle) };
459 check_rv(rv, "C_EncryptInit")?;
460
461 let mut out_buf: Vec<u8> = vec![0_u8; plaintext.len() + 32];
463 let mut out_len = CK_ULONG::try_from(out_buf.len())
464 .map_err(|e| format!("FAIL [C_Encrypt]: output buffer length overflow: {e}"))?;
465 let plain_len = CK_ULONG::try_from(plaintext.len())
466 .map_err(|e| format!("FAIL [C_Encrypt]: plaintext length overflow: {e}"))?;
467
468 let rv = unsafe {
469 c_encrypt(
470 session,
471 plaintext.as_ptr().cast_mut(),
472 plain_len,
473 out_buf.as_mut_ptr(),
474 &raw mut out_len,
475 )
476 };
477 check_rv(rv, "C_Encrypt")?;
478
479 let final_len = usize::try_from(out_len)
480 .map_err(|e| format!("FAIL [C_Encrypt]: returned length overflow: {e}"))?;
481 out_buf.truncate(final_len);
482 Ok(out_buf)
483}
484
485pub fn call_decrypt_aes_cbc_pad(
490 func_list: &CK_FUNCTION_LIST,
491 session: CK_SESSION_HANDLE,
492 key_handle: CK_OBJECT_HANDLE,
493 ciphertext: &[u8],
494) -> Result<Vec<u8>, String> {
495 let c_decrypt_init = func_list
496 .C_DecryptInit
497 .ok_or_else(|| "FAIL [C_DecryptInit]: not present in function list".to_owned())?;
498 let c_decrypt = func_list
499 .C_Decrypt
500 .ok_or_else(|| "FAIL [C_Decrypt]: not present in function list".to_owned())?;
501
502 let mut iv = [0_u8; 16];
503 let mut mechanism = CK_MECHANISM {
504 mechanism: CKM_AES_CBC_PAD,
505 pParameter: iv.as_mut_ptr().cast::<c_void>(),
506 ulParameterLen: 16,
507 };
508
509 let rv = unsafe { c_decrypt_init(session, &raw mut mechanism, key_handle) };
510 check_rv(rv, "C_DecryptInit")?;
511
512 let mut out_buf: Vec<u8> = vec![0_u8; ciphertext.len()];
514 let mut out_len = CK_ULONG::try_from(out_buf.len())
515 .map_err(|e| format!("FAIL [C_Decrypt]: output buffer length overflow: {e}"))?;
516 let cipher_len = CK_ULONG::try_from(ciphertext.len())
517 .map_err(|e| format!("FAIL [C_Decrypt]: ciphertext length overflow: {e}"))?;
518
519 let rv = unsafe {
520 c_decrypt(
521 session,
522 ciphertext.as_ptr().cast_mut(),
523 cipher_len,
524 out_buf.as_mut_ptr(),
525 &raw mut out_len,
526 )
527 };
528 check_rv(rv, "C_Decrypt")?;
529
530 let final_len = usize::try_from(out_len)
531 .map_err(|e| format!("FAIL [C_Decrypt]: returned length overflow: {e}"))?;
532 out_buf.truncate(final_len);
533 Ok(out_buf)
534}
535
536#[cfg(test)]
537mod tests;