Skip to main content

cosmian_pkcs11_verify/
lib.rs

1//! Shared PKCS#11 helper functions used by both the `cosmian_pkcs11_verify`
2//! binary and the integration test suite.
3//!
4//! The binary (`main.rs`) imports these via `use cosmian_pkcs11_verify::*`.
5//! The integration tests live in `tests.rs` and reference them via `crate::`.
6
7#![allow(
8    unsafe_code,
9    clippy::print_stdout,          // diagnostic binary — stdout output is intentional
10    clippy::multiple_crate_versions,
11    clippy::cargo_common_metadata,
12    clippy::exhaustive_structs,    // clap derive structs are internal only
13    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
27// ---------------------------------------------------------------------------
28// Step C helper
29// ---------------------------------------------------------------------------
30
31pub 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    // Safety: the symbol lookup and the call are both unsafe; we ensure the
35    // library is live for the duration of the returned pointer's use in `run()`.
36    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    // &raw mut avoids creating a reference to a potentially-uninitialised pointer.
41    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
62// ---------------------------------------------------------------------------
63// Step E helper
64// ---------------------------------------------------------------------------
65
66pub 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    // First call: query the slot count.
72    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    // Second call: fill the slot buffer.
86    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
96// ---------------------------------------------------------------------------
97// Step F helper
98// ---------------------------------------------------------------------------
99
100pub 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>(), // pApplication — not used
116            None,                      // Notify callback — not used
117            &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
132// ---------------------------------------------------------------------------
133// Step F½ helper — C_Login
134// ---------------------------------------------------------------------------
135
136pub 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    // CKU_USER = 1 per PKCS#11 spec.
150    // Safety: token_bytes is valid for the duration of this call; the provider
151    // treats the pPin pointer as read-only.
152    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
166// ---------------------------------------------------------------------------
167// Steps G–I helpers
168// ---------------------------------------------------------------------------
169
170/// PKCS#11 object classes to enumerate in order.
171///
172/// The Cosmian provider requires a non-null attribute template for `C_FindObjectsInit`;
173/// passing a null pointer ("find all") returns `CKR_GENERAL_ERROR`. We therefore run
174/// one `C_FindObjectsInit` → `C_FindObjects`* → `C_FindObjectsFinal` cycle per class and sum the results.
175pub 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
183/// Maximum number of PKCS#11 object handles requested in a single `C_FindObjects` call.
184pub 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            // Per-class errors are non-fatal: the provider may reject certain classes
198            // (e.g. CKO_CERTIFICATE when the KMS contains objects whose attributes
199            // cannot be exported in the requested format). Print a diagnostic message
200            // and continue with the remaining classes. The session state is unchanged
201            // after a failed C_FindObjectsInit (PKCS#11 spec §5.13).
202            Err(e) => {
203                println!("  {class_name}: unavailable — {e}");
204            }
205        }
206    }
207
208    grand_total
209}
210
211/// Run one `C_FindObjectsInit` → `C_FindObjects`* → `C_FindObjectsFinal` cycle for a
212/// single object class and return the total number of matching handles.
213///
214/// PKCS#11 spec §5.13: `C_FindObjects` returns at most `ulMaxObjectCount` handles per
215/// call. The loop continues until the batch is smaller than the requested maximum.
216pub 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    // Safety: `object_class` is a stack variable that outlives every PKCS#11 call below.
223    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
272// ---------------------------------------------------------------------------
273// Utilities
274// ---------------------------------------------------------------------------
275
276pub 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/// Return a human-readable name for a `CK_RV` return value.
288#[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
351// ---------------------------------------------------------------------------
352// Oracle TDE migration helpers
353// ---------------------------------------------------------------------------
354
355/// Generate an AES-256 key in the token with the given label.
356///
357/// Calls `C_GenerateKey` with a `CKM_AES_KEY_GEN` mechanism and a minimal
358/// attribute template (`CKA_KEY_TYPE`, `CKA_LABEL`, `CKA_SENSITIVE`,
359/// `CKA_VALUE_LEN=32`, `CKA_EXTRACTABLE=CK_TRUE`).
360/// This mirrors what Oracle TDE does during `ADMINISTER KEY MANAGEMENT SET
361/// ENCRYPTION KEY ... MIGRATE`.
362pub 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; // AES-256
381    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
434/// Encrypt `plaintext` using the AES-CBC-PAD mechanism with a zero 16-byte IV.
435///
436/// Calls `C_EncryptInit` followed by `C_Encrypt` using the supplied `key_handle`.
437/// This mirrors the DEK-wrapping step Oracle performs during wallet migration.
438pub 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    // Allocate a generous output buffer (input + 32 bytes covers any PKCS#7 padding).
462    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
485/// Decrypt `ciphertext` using the AES-CBC-PAD mechanism with a zero 16-byte IV.
486///
487/// Calls `C_DecryptInit` followed by `C_Decrypt` using the supplied `key_handle`.
488/// This mirrors the DEK-unwrapping step Oracle performs during reverse wallet migration.
489pub 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    // Allocate output buffer equal to ciphertext length; plaintext is always shorter.
513    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;