feat(hsm-emulator): M10 crypto-surface, M11 conformance, deep-audit hardening

M10 (crypto-surface): RUP-safe buffered AES-GCM streaming (16 MiB cap),
DigestEncrypt/SignEncrypt/DecryptDigest/DecryptVerify dual functions
(decrypt-side CBC-only), RSA C_SignRecover/C_VerifyRecover (CKM_RSA_PKCS),
digest-only Get/SetOperationState.

M11 (conformance): spec-correct C_WaitForSlotEvent (DONT_BLOCK->CKR_NO_EVENT,
blocking->FUNCTION_NOT_SUPPORTED, pReserved->ARGUMENTS_BAD) + CKF_DONT_BLOCK;
public learn/CONFORMANCE.md documenting every deliberate N/A with its OASIS
v2.40 section and exact return code.

Deep project audit (5 read-only dimension agents, 0 Critical/High):
- fix: C_Finalize-vs-fast-path use-after-free race -- replace lockless
  state.current() with state.acquire() (lock + verify-live atomically)
  across all ~57 entry points
- fix: enforce CKR_PIN_LEN_RANGE on the new PIN in InitToken/InitPIN/SetPIN
- fix: clear a stale sealed flag on Object.set replace (login-unseal wedge)
- harden: zeroize CBC plaintext stack temporaries; Debug leak-assert at
  C_Finalize turns smoke into an API-layer leak gate
- test: ABI layout-check PSS/OAEP param structs; +sealed-flag regression

Proven: 76/76 unit (Debug + ReleaseSafe), in-process smoke (with PIN-length
asserts), cross-process pkcs11-tool (init/pin/RSA keygen+sign/wrong-pin).
.so exports only C_GetFunctionList.
This commit is contained in:
CarterPerez-dev 2026-06-02 06:48:25 -04:00
parent 0b8f8df8a3
commit f70ca3f32c
19 changed files with 1320 additions and 244 deletions

View File

@ -24,6 +24,7 @@ pub fn main() !void {
var user_pin = "1234".*;
var new_user_pin = "5678".*;
var wrong_pin = "0000".*;
var short_pin = "12".*;
var label: [32]u8 = @splat(' ');
@memcpy(label[0..11], "smoke-token");
@ -65,6 +66,7 @@ pub fn main() !void {
try check("C_GetMechanismList(size)", f.C_GetMechanismList.?(slot, null, &mech_count));
if (mech_count == 0) return error.NoMechanisms;
if (f.C_InitToken.?(slot, &short_pin, short_pin.len, &label) != ck.CKR_PIN_LEN_RANGE) return error.ShortSoPinNotRejected;
try check("C_InitToken", f.C_InitToken.?(slot, &so_pin, so_pin.len, &label));
try check("C_GetTokenInfo(post-init)", f.C_GetTokenInfo.?(slot, &token_info));
if (token_info.flags & ck.CKF_TOKEN_INITIALIZED == 0) return error.InitTokenDidNotInitialize;
@ -80,6 +82,7 @@ pub fn main() !void {
try check("C_GetSessionInfo(SO)", f.C_GetSessionInfo.?(h, &si));
if (si.state != ck.CKS_RW_SO_FUNCTIONS) return error.UnexpectedSoState;
if (f.C_InitPIN.?(h, &short_pin, short_pin.len) != ck.CKR_PIN_LEN_RANGE) return error.ShortUserPinNotRejected;
try check("C_InitPIN", f.C_InitPIN.?(h, &user_pin, user_pin.len));
try check("C_Logout(SO)", f.C_Logout.?(h));
@ -90,6 +93,7 @@ pub fn main() !void {
try check("C_GetSessionInfo(USER)", f.C_GetSessionInfo.?(h, &si));
if (si.state != ck.CKS_RW_USER_FUNCTIONS) return error.UnexpectedUserState;
if (f.C_SetPIN.?(h, &user_pin, user_pin.len, &short_pin, short_pin.len) != ck.CKR_PIN_LEN_RANGE) return error.ShortNewPinNotRejected;
try check("C_SetPIN", f.C_SetPIN.?(h, &user_pin, user_pin.len, &new_user_pin, new_user_pin.len));
try check("C_Logout(USER)", f.C_Logout.?(h));
@ -492,9 +496,197 @@ pub fn main() !void {
try check("C_Digest(value)", f.C_Digest.?(h, &dk_val, dk_val.len, &dk_d2, &dk_d2len));
if (dk_d1len != 32 or !std.mem.eql(u8, dk_d1[0..32], dk_d2[0..32])) return error.DigestKeyMismatch;
var gcm_iv = [_]u8{ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB };
var gcm_aad = "gcm-associated-data".*;
var gcm_params = ck.CK_GCM_PARAMS{
.pIv = &gcm_iv,
.ulIvLen = gcm_iv.len,
.ulIvBits = 96,
.pAAD = &gcm_aad,
.ulAADLen = gcm_aad.len,
.ulTagBits = 128,
};
var gcm_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_GCM, .pParameter = &gcm_params, .ulParameterLen = @sizeOf(ck.CK_GCM_PARAMS) };
var gcm_pt = "GCM streaming over multiple update calls exceeds one block".*;
var gcm_ct1: [128]u8 = undefined;
var gcm_ct1len: ck.CK_ULONG = gcm_ct1.len;
try check("C_EncryptInit(GCM one-shot)", f.C_EncryptInit.?(h, &gcm_mech, h_aes));
try check("C_Encrypt(GCM one-shot)", f.C_Encrypt.?(h, &gcm_pt, gcm_pt.len, &gcm_ct1, &gcm_ct1len));
var gcm_ct2: [128]u8 = undefined;
var part_out: [128]u8 = undefined;
var part_outlen: ck.CK_ULONG = part_out.len;
try check("C_EncryptInit(GCM stream)", f.C_EncryptInit.?(h, &gcm_mech, h_aes));
var off: usize = 0;
while (off < gcm_pt.len) {
const end = @min(off + 17, gcm_pt.len);
part_outlen = part_out.len;
try check("C_EncryptUpdate(GCM)", f.C_EncryptUpdate.?(h, gcm_pt[off..].ptr, @intCast(end - off), &part_out, &part_outlen));
if (part_outlen != 0) return error.GcmUpdateEmittedEarly;
off = end;
}
var gcm_ct2len: ck.CK_ULONG = gcm_ct2.len;
try check("C_EncryptFinal(GCM)", f.C_EncryptFinal.?(h, &gcm_ct2, &gcm_ct2len));
if (gcm_ct1len != gcm_ct2len or !std.mem.eql(u8, gcm_ct1[0..gcm_ct1len], gcm_ct2[0..gcm_ct2len])) return error.GcmStreamMismatch;
var gcm_back: [128]u8 = undefined;
try check("C_DecryptInit(GCM stream)", f.C_DecryptInit.?(h, &gcm_mech, h_aes));
off = 0;
while (off < gcm_ct1len) {
const end = @min(off + 19, gcm_ct1len);
part_outlen = part_out.len;
try check("C_DecryptUpdate(GCM)", f.C_DecryptUpdate.?(h, gcm_ct1[off..].ptr, @intCast(end - off), &part_out, &part_outlen));
if (part_outlen != 0) return error.GcmDecryptEmittedEarly;
off = end;
}
var gcm_backlen: ck.CK_ULONG = gcm_back.len;
try check("C_DecryptFinal(GCM)", f.C_DecryptFinal.?(h, &gcm_back, &gcm_backlen));
if (gcm_backlen != gcm_pt.len or !std.mem.eql(u8, gcm_back[0..gcm_backlen], &gcm_pt)) return error.GcmStreamRoundTripFailed;
var gcm_bad: [128]u8 = undefined;
@memcpy(gcm_bad[0..gcm_ct1len], gcm_ct1[0..gcm_ct1len]);
gcm_bad[0] ^= 0xff;
var gcm_badlen: ck.CK_ULONG = gcm_back.len;
try check("C_DecryptInit(GCM tamper)", f.C_DecryptInit.?(h, &gcm_mech, h_aes));
if (f.C_Decrypt.?(h, &gcm_bad, gcm_ct1len, &gcm_back, &gcm_badlen) != ck.CKR_ENCRYPTED_DATA_INVALID) return error.GcmTamperNotDetected;
var de_sha = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256, .pParameter = null, .ulParameterLen = 0 };
var dual_pt = "dual-function plaintext spanning blocks".*;
var de_iv = [_]u8{1} ** 16;
var de_cbcpad = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_CBC_PAD, .pParameter = &de_iv, .ulParameterLen = de_iv.len };
try check("C_DigestInit(DigestEncrypt)", f.C_DigestInit.?(h, &de_sha));
try check("C_EncryptInit(DigestEncrypt)", f.C_EncryptInit.?(h, &de_cbcpad, h_aes));
var de_ct: [80]u8 = undefined;
var de_ctlen: usize = 0;
off = 0;
while (off < dual_pt.len) {
const end = @min(off + 16, dual_pt.len);
var seg: ck.CK_ULONG = @intCast(de_ct.len - de_ctlen);
try check("C_DigestEncryptUpdate", f.C_DigestEncryptUpdate.?(h, dual_pt[off..].ptr, @intCast(end - off), de_ct[de_ctlen..].ptr, &seg));
de_ctlen += @intCast(seg);
off = end;
}
var de_finlen: ck.CK_ULONG = @intCast(de_ct.len - de_ctlen);
try check("C_EncryptFinal(DigestEncrypt)", f.C_EncryptFinal.?(h, de_ct[de_ctlen..].ptr, &de_finlen));
de_ctlen += @intCast(de_finlen);
var de_dig: [32]u8 = undefined;
var de_diglen: ck.CK_ULONG = de_dig.len;
try check("C_DigestFinal(DigestEncrypt)", f.C_DigestFinal.?(h, &de_dig, &de_diglen));
var de_ref: [32]u8 = undefined;
var de_reflen: ck.CK_ULONG = de_ref.len;
try check("C_DigestInit(DigestEncrypt ref)", f.C_DigestInit.?(h, &de_sha));
try check("C_Digest(DigestEncrypt ref)", f.C_Digest.?(h, &dual_pt, dual_pt.len, &de_ref, &de_reflen));
if (!std.mem.eql(u8, de_dig[0..32], de_ref[0..32])) return error.DualDigestMismatch;
var de_back: [80]u8 = undefined;
var de_backlen: ck.CK_ULONG = de_back.len;
try check("C_DecryptInit(DigestEncrypt verify)", f.C_DecryptInit.?(h, &de_cbcpad, h_aes));
try check("C_Decrypt(DigestEncrypt verify)", f.C_Decrypt.?(h, &de_ct, @intCast(de_ctlen), &de_back, &de_backlen));
if (de_backlen != dual_pt.len or !std.mem.eql(u8, de_back[0..de_backlen], &dual_pt)) return error.DualEncryptRoundTrip;
var dd_pt = "thirty-two-byte aligned message!".*;
var dd_iv = [_]u8{2} ** 16;
var dd_cbc = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_CBC, .pParameter = &dd_iv, .ulParameterLen = dd_iv.len };
var dd_ct: [48]u8 = undefined;
var dd_ctlen: ck.CK_ULONG = dd_ct.len;
try check("C_EncryptInit(DecryptDigest setup)", f.C_EncryptInit.?(h, &dd_cbc, h_aes));
try check("C_Encrypt(DecryptDigest setup)", f.C_Encrypt.?(h, &dd_pt, dd_pt.len, &dd_ct, &dd_ctlen));
try check("C_DecryptInit(DecryptDigest)", f.C_DecryptInit.?(h, &dd_cbc, h_aes));
try check("C_DigestInit(DecryptDigest)", f.C_DigestInit.?(h, &de_sha));
var dd_back: [48]u8 = undefined;
var dd_seg: ck.CK_ULONG = dd_back.len;
try check("C_DecryptDigestUpdate", f.C_DecryptDigestUpdate.?(h, &dd_ct, dd_ctlen, &dd_back, &dd_seg));
var dd_backlen: usize = @intCast(dd_seg);
var dd_finlen: ck.CK_ULONG = @intCast(dd_back.len - dd_backlen);
try check("C_DecryptFinal(DecryptDigest)", f.C_DecryptFinal.?(h, dd_back[dd_backlen..].ptr, &dd_finlen));
dd_backlen += @intCast(dd_finlen);
var dd_dig: [32]u8 = undefined;
var dd_diglen: ck.CK_ULONG = dd_dig.len;
try check("C_DigestFinal(DecryptDigest)", f.C_DigestFinal.?(h, &dd_dig, &dd_diglen));
if (dd_backlen != dd_pt.len or !std.mem.eql(u8, dd_back[0..dd_backlen], &dd_pt)) return error.DualDecryptRoundTrip;
var dd_ref: [32]u8 = undefined;
var dd_reflen: ck.CK_ULONG = dd_ref.len;
try check("C_DigestInit(DecryptDigest ref)", f.C_DigestInit.?(h, &de_sha));
try check("C_Digest(DecryptDigest ref)", f.C_Digest.?(h, &dd_pt, dd_pt.len, &dd_ref, &dd_reflen));
if (!std.mem.eql(u8, dd_dig[0..32], dd_ref[0..32])) return error.DualDecryptDigestMismatch;
var sv_pt = "verify-after-decrypt aligned!!!!".*;
var sv_iv = [_]u8{3} ** 16;
var sv_cbc = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_CBC, .pParameter = &sv_iv, .ulParameterLen = sv_iv.len };
try check("C_SignInit(SignEncrypt)", f.C_SignInit.?(h, &hmac_mech, h_hmac));
try check("C_EncryptInit(SignEncrypt)", f.C_EncryptInit.?(h, &sv_cbc, h_aes));
var sv_ct: [48]u8 = undefined;
var sv_seg: ck.CK_ULONG = sv_ct.len;
try check("C_SignEncryptUpdate", f.C_SignEncryptUpdate.?(h, &sv_pt, sv_pt.len, &sv_ct, &sv_seg));
var sv_ctlen: usize = @intCast(sv_seg);
var sv_finlen: ck.CK_ULONG = @intCast(sv_ct.len - sv_ctlen);
try check("C_EncryptFinal(SignEncrypt)", f.C_EncryptFinal.?(h, sv_ct[sv_ctlen..].ptr, &sv_finlen));
sv_ctlen += @intCast(sv_finlen);
var sv_mac: [32]u8 = undefined;
var sv_maclen: ck.CK_ULONG = sv_mac.len;
try check("C_SignFinal(SignEncrypt)", f.C_SignFinal.?(h, &sv_mac, &sv_maclen));
try check("C_DecryptInit(DecryptVerify)", f.C_DecryptInit.?(h, &sv_cbc, h_aes));
try check("C_VerifyInit(DecryptVerify)", f.C_VerifyInit.?(h, &hmac_mech, h_hmac));
var sv_back: [48]u8 = undefined;
var sv_bseg: ck.CK_ULONG = sv_back.len;
try check("C_DecryptVerifyUpdate", f.C_DecryptVerifyUpdate.?(h, sv_ct[0..sv_ctlen].ptr, @intCast(sv_ctlen), &sv_back, &sv_bseg));
var sv_backlen: usize = @intCast(sv_bseg);
var sv_bfinlen: ck.CK_ULONG = @intCast(sv_back.len - sv_backlen);
try check("C_DecryptFinal(DecryptVerify)", f.C_DecryptFinal.?(h, sv_back[sv_backlen..].ptr, &sv_bfinlen));
sv_backlen += @intCast(sv_bfinlen);
try check("C_VerifyFinal(DecryptVerify)", f.C_VerifyFinal.?(h, &sv_mac, sv_maclen));
if (sv_backlen != sv_pt.len or !std.mem.eql(u8, sv_back[0..sv_backlen], &sv_pt)) return error.DualSignVerifyRoundTrip;
var rec_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_RSA_PKCS, .pParameter = null, .ulParameterLen = 0 };
var rec_msg = "recover me via RSA".*;
var rec_sig: [256]u8 = undefined;
var rec_siglen: ck.CK_ULONG = rec_sig.len;
try check("C_SignRecoverInit", f.C_SignRecoverInit.?(h, &rec_mech, h_rsapriv));
try check("C_SignRecover", f.C_SignRecover.?(h, &rec_msg, rec_msg.len, &rec_sig, &rec_siglen));
if (rec_siglen != 256) return error.SignRecoverLenWrong;
var rec_out: [256]u8 = undefined;
var rec_outlen: ck.CK_ULONG = rec_out.len;
try check("C_VerifyRecoverInit", f.C_VerifyRecoverInit.?(h, &rec_mech, h_rsapub));
try check("C_VerifyRecover", f.C_VerifyRecover.?(h, &rec_sig, rec_siglen, &rec_out, &rec_outlen));
if (rec_outlen != rec_msg.len or !std.mem.eql(u8, rec_out[0..rec_outlen], &rec_msg)) return error.VerifyRecoverMismatch;
var os_sha = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256, .pParameter = null, .ulParameterLen = 0 };
var os_p1 = "operation-".*;
var os_p2 = "state".*;
try check("C_DigestInit(opstate)", f.C_DigestInit.?(h, &os_sha));
try check("C_DigestUpdate(opstate p1)", f.C_DigestUpdate.?(h, &os_p1, os_p1.len));
var os_blob: [256]u8 = undefined;
var os_bloblen: ck.CK_ULONG = 0;
try check("C_GetOperationState(size)", f.C_GetOperationState.?(h, null, &os_bloblen));
if (os_bloblen == 0 or os_bloblen > os_blob.len) return error.OpStateSizeWrong;
try check("C_GetOperationState(fill)", f.C_GetOperationState.?(h, &os_blob, &os_bloblen));
try check("C_DigestUpdate(opstate p2)", f.C_DigestUpdate.?(h, &os_p2, os_p2.len));
var os_dA: [32]u8 = undefined;
var os_dAlen: ck.CK_ULONG = os_dA.len;
try check("C_DigestFinal(opstate A)", f.C_DigestFinal.?(h, &os_dA, &os_dAlen));
try check("C_SetOperationState", f.C_SetOperationState.?(h, &os_blob, os_bloblen, ck.CK_INVALID_HANDLE, ck.CK_INVALID_HANDLE));
try check("C_DigestUpdate(opstate p2 restored)", f.C_DigestUpdate.?(h, &os_p2, os_p2.len));
var os_dB: [32]u8 = undefined;
var os_dBlen: ck.CK_ULONG = os_dB.len;
try check("C_DigestFinal(opstate B)", f.C_DigestFinal.?(h, &os_dB, &os_dBlen));
if (!std.mem.eql(u8, os_dA[0..32], os_dB[0..32])) return error.OpStateRestoreMismatch;
var slot_evt: ck.CK_SLOT_ID = 0;
var reserved_probe: u8 = 0;
const reserved_ptr: ?*anyopaque = &reserved_probe;
if (f.C_WaitForSlotEvent.?(ck.CKF_DONT_BLOCK, &slot_evt, null) != ck.CKR_NO_EVENT) return error.WaitSlotEventNotNoEvent;
if (f.C_WaitForSlotEvent.?(0, &slot_evt, null) != ck.CKR_FUNCTION_NOT_SUPPORTED) return error.WaitSlotEventBlockingNotRefused;
if (f.C_WaitForSlotEvent.?(ck.CKF_DONT_BLOCK, &slot_evt, reserved_ptr) != ck.CKR_ARGUMENTS_BAD) return error.WaitSlotEventReservedNotChecked;
if (f.C_GetFunctionStatus.?(h) != ck.CKR_FUNCTION_NOT_PARALLEL) return error.GetFunctionStatusNotParallel;
if (f.C_CancelFunction.?(h) != ck.CKR_FUNCTION_NOT_PARALLEL) return error.CancelFunctionNotParallel;
var seed_probe = "seed-material".*;
if (f.C_SeedRandom.?(h, &seed_probe, seed_probe.len) != ck.CKR_RANDOM_SEED_NOT_SUPPORTED) return error.SeedRandomNotRefused;
try check("C_CloseSession", f.C_CloseSession.?(h));
try check("C_Finalize", f.C_Finalize.?(null));
if (f.C_WaitForSlotEvent.?(ck.CKF_DONT_BLOCK, &slot_evt, null) != ck.CKR_CRYPTOKI_NOT_INITIALIZED) return error.WaitSlotEventUninitNotRejected;
std.debug.print("smoke: OK\n", .{});
std.debug.print(" cryptokiVersion = {d}.{d}\n", .{ info.cryptokiVersion.major, info.cryptokiVersion.minor });
std.debug.print(" slots = {d}\n", .{count});
@ -510,6 +702,11 @@ pub fn main() !void {
std.debug.print(" derive = C_DeriveKey ECDH1 P-256: both parties agree (raw + DER-wrapped peer point)\n", .{});
std.debug.print(" keywrap = C_WrapKey/C_UnwrapKey AES-KEY-WRAP + RSA-OAEP round-trips; unextractable target refused\n", .{});
std.debug.print(" digestkey = C_DigestKey digest equals C_Digest of the same key bytes\n", .{});
std.debug.print(" gcmstream = AES-GCM multipart enc/dec == one-shot (chunked); tamper -> ENCRYPTED_DATA_INVALID\n", .{});
std.debug.print(" dual = DigestEncrypt/SignEncrypt + DecryptDigest/DecryptVerify lock-step round-trips\n", .{});
std.debug.print(" recover = RSA C_SignRecover -> C_VerifyRecover recovers the exact message\n", .{});
std.debug.print(" opstate = C_GetOperationState -> C_SetOperationState resumes the digest identically\n", .{});
std.debug.print(" conformance = WaitForSlotEvent(DONT_BLOCK)->NO_EVENT, blocking->NOT_SUPPORTED, pReserved->ARGS_BAD; Get/CancelFunction->NOT_PARALLEL; SeedRandom->SEED_NOT_SUPPORTED\n", .{});
}
fn check(name: []const u8, rv: ck.CK_RV) !void {

View File

@ -0,0 +1,301 @@
<!-- ©AngelaMos | 2026 -->
<!-- CONFORMANCE.md -->
# PKCS#11 v2.40 Conformance Statement
The AngelaMos HSM Emulator implements the full Cryptoki (PKCS#11) v2.40 C ABI: all
68 functions in the canonical `CK_FUNCTION_LIST`, machine-checked against the
vendored OASIS headers at build time. This document records every place where the
module narrows behavior: the exact return code it gives, the spec clause that
permits it, and why. Every narrowing here is a documented decision with a defined
return value.
A function that is not applicable to a fixed software token returns the specific
code the spec defines for that situation. The `CKR_FUNCTION_NOT_SUPPORTED` results
that remain mark the boundary between single-shot and multi-part operation surfaces,
covered in section 2.
**Specifications**
- PKCS#11 Base Specification v2.40 (OASIS, errata 01) — function semantics and return codes.
- PKCS#11 Current Mechanisms v2.40 (OASIS, errata 01) — per-mechanism parameters.
Section numbers below refer to the Base specification unless a line names the
Mechanisms document.
---
## 1. Function-level conformance
### 1.1 `C_WaitForSlotEvent` — §5.5 (slot and token management)
The module exposes a single fixed slot (ID 0) whose token is always present. No
insertion or removal event can ever occur, so the function reports that fact
precisely rather than pretending to support hardware slot events.
| Call | Return | Basis |
|------|--------|-------|
| `flags` has `CKF_DONT_BLOCK`, no event pending | `CKR_NO_EVENT` | §5.5: a non-blocking poll with no pending event returns `CKR_NO_EVENT`. For a fixed slot, no event is ever pending, so this is always the answer. |
| `flags` clears `CKF_DONT_BLOCK` (blocking) | `CKR_FUNCTION_NOT_SUPPORTED` | A blocking wait must not return until an event occurs. For a fixed software slot no event can occur, so blocking would hang the caller forever. The module declines the blocking mode instead. |
| `pReserved != NULL_PTR` | `CKR_ARGUMENTS_BAD` | §5.5: `pReserved` is reserved and must be `NULL_PTR` in v2.40. |
| called before `C_Initialize` | `CKR_CRYPTOKI_NOT_INITIALIZED` | §5.4 general semantics. |
Interop note: `pkcs11-tool --wait` calls this in blocking mode and therefore
receives `CKR_FUNCTION_NOT_SUPPORTED` immediately rather than blocking. Hosts that
poll with `CKF_DONT_BLOCK` (the common case for slot enumeration) get the correct
`CKR_NO_EVENT`.
### 1.2 `C_GetFunctionStatus`, `C_CancelFunction` — §5.15 (parallel function management)
Both are legacy functions from the era of parallel (asynchronous) Cryptoki calls.
v2.40 has no parallel execution model, and the spec defines the canonical answer
for a serial implementation:
| Call | Return | Basis |
|------|--------|-------|
| `C_GetFunctionStatus` | `CKR_FUNCTION_NOT_PARALLEL` | §5.15: the only meaningful return for a library that does not run functions in parallel. |
| `C_CancelFunction` | `CKR_FUNCTION_NOT_PARALLEL` | §5.15: same. |
### 1.3 `C_SeedRandom` — §5.14 (random number generation)
| Call | Return | Basis |
|------|--------|-------|
| `C_SeedRandom` | `CKR_RANDOM_SEED_NOT_SUPPORTED` | The RNG is the operating-system CSPRNG, drawn through `std.Io.randomSecure` (`getrandom(2)`, `arc4random_buf`, or `/dev/urandom` depending on platform and libc). Caller-supplied seed material cannot meaningfully reseed it, so the module declines rather than silently discarding the seed (which would mislead the caller). |
| `C_GenerateRandom` | fully supported | — |
---
## 2. Operation-surface boundaries
These are the deliberate edges of the multi-part operation surface. Each returns a
specific code so a caller can distinguish "wrong call for this mechanism" from a
runtime failure.
### 2.1 AES-GCM is RUP-safe buffered — §5.8, §5.9
`CKM_AES_GCM` multi-part encryption and decryption buffer the entire message and run
the authenticated operation **once at `*Final`**:
- `C_EncryptUpdate` / `C_DecryptUpdate` append the part to an internal buffer and
emit **0 bytes**. §5.8/§5.9 permit an Update to produce fewer output bytes than it
consumes (block buffering); producing the whole result at `*Final` is conformant.
- `C_EncryptFinal` emits ciphertext + 128-bit tag; `C_DecryptFinal` verifies the tag
and only then releases plaintext.
This is a security decision. A streaming GCM *decrypt* built on incremental
release would hand back **unverified plaintext** before the tag is checked (release
of unverified plaintext, "RUP") — an anti-pattern for an HSM. Buffering until the
tag verifies makes RUP impossible by construction.
Because the buffer holds the whole message, a single GCM message is bounded:
| Condition | Return |
|-----------|--------|
| buffered length would exceed 16 MiB (`max_gcm_stream_len`), encrypt | `CKR_DATA_LEN_RANGE` |
| buffered length would exceed 16 MiB, decrypt | `CKR_ENCRYPTED_DATA_LEN_RANGE` |
Strict parameter validation (`CK_GCM_PARAMS`):
| Parameter | Accepted | Else |
|-----------|----------|------|
| `ulIvLen` | exactly 12 bytes | `CKR_MECHANISM_PARAM_INVALID` |
| `ulIvBits` | `0` or `96` | `CKR_MECHANISM_PARAM_INVALID` |
| `ulTagBits` | exactly `128` | `CKR_MECHANISM_PARAM_INVALID` |
| `pIv` | non-NULL | `CKR_MECHANISM_PARAM_INVALID` |
| `ulAADLen` | ≤ 256 bytes | `CKR_ARGUMENTS_BAD` |
Interop note: a host MUST request a 128-bit tag and supply a 12-byte IV. With
`pkcs11-tool` that means `--iv <24 hex chars> --tag-bits-len 128`; omitting either
trips `CKR_MECHANISM_PARAM_INVALID`.
### 2.2 Dual-function operations — §5.12
| Function | Supported modes | Else |
|----------|-----------------|------|
| `C_DigestEncryptUpdate`, `C_SignEncryptUpdate` (encrypt side) | AES-CBC, AES-CBC-PAD, AES-GCM | — |
| `C_DecryptDigestUpdate`, `C_DecryptVerifyUpdate` (decrypt side) | AES-CBC only | non-CBC → `CKR_FUNCTION_NOT_SUPPORTED` |
| any dual-function leg using an RSA sign/verify operation | — | `CKR_FUNCTION_NOT_SUPPORTED` |
The decrypt side couples the **recovered plaintext** of each `C_Decrypt*Update` into
the digest/verify operation. That coupling is only exact when the cipher releases
exactly the decrypted bytes on every call:
- **AES-CBC** releases each decrypted block immediately, so bytes-out equals
bytes-to-digest per call. Supported.
- **AES-CBC-PAD** holds back the final block until `C_DecryptFinal` (it cannot know
the padding until the end), so the digest would miss the last block.
- **AES-GCM** buffers everything and releases at `C_DecryptFinal` (§2.1), so the
digest would receive nothing incrementally.
Coupling either of the latter would desynchronize the pair, so the module returns
`CKR_FUNCTION_NOT_SUPPORTED` rather than producing a silently wrong digest. The
encrypt side has no such constraint — it digests/signs the **input** plaintext,
which is fully available on each call, so all three modes work.
### 2.3 RSA is single-shot — §5.8, §5.9, §5.11
Every RSA operation is a single modular exponentiation over the whole input, so RSA
has no multi-part form:
| Call on an RSA operation | Return |
|--------------------------|--------|
| `C_EncryptUpdate` / `C_EncryptFinal` | `CKR_FUNCTION_NOT_SUPPORTED` |
| `C_DecryptUpdate` / `C_DecryptFinal` | `CKR_FUNCTION_NOT_SUPPORTED` |
| `C_SignUpdate` / `C_SignFinal` | `CKR_FUNCTION_NOT_SUPPORTED` |
| `C_VerifyUpdate` / `C_VerifyFinal` | `CKR_FUNCTION_NOT_SUPPORTED` |
Use the one-shot `C_Encrypt` / `C_Decrypt` / `C_Sign` / `C_Verify`. The hash-then-sign
mechanisms (`CKM_SHA256_RSA_PKCS`, the PSS variants) still need the full message
before the single RSA operation, so they too are one-shot.
### 2.4 Sign / Verify-Recover — §5.11
| Mechanism | Supported | Else |
|-----------|-----------|------|
| `CKM_RSA_PKCS` (EMSA-PKCS1-v1.5 type 1, message recoverable from the signature) | yes | other mechanism → `CKR_MECHANISM_INVALID` |
`C_SignRecover` rejects input that cannot fit the modulus with PKCS#1 v1.5 overhead
(data length + 11 bytes > modulus) with `CKR_DATA_LEN_RANGE`. `C_VerifyRecover`
returns the recovered message; a signature whose length is not the modulus length
returns `CKR_SIGNATURE_LEN_RANGE`, and a malformed encoding returns
`CKR_SIGNATURE_INVALID`. Raw recover (`CKM_RSA_X_509`) is not offered.
### 2.5 Get / SetOperationState — §5.6
Operation state is **digest-only**:
| Call | Return | Basis |
|------|--------|-------|
| `C_GetOperationState` with an active sign/verify/encrypt/decrypt/sign-recover/verify-recover operation | `CKR_STATE_UNSAVEABLE` | §5.6: a library may decline to save state it cannot serialize. Only digest state is saveable here. |
| `C_GetOperationState` with no operation active | `CKR_OPERATION_NOT_INITIALIZED` | §5.6. |
| `C_SetOperationState` with non-zero `hEncryptionKey` or `hAuthenticationKey` | `CKR_KEY_NOT_NEEDED` | §5.6: a digest needs no key, so passing one is an error. |
| `C_SetOperationState` with a malformed blob (wrong version byte, unknown hasher tag, wrong length) | `CKR_SAVED_STATE_INVALID` | §5.6. |
The saved blob is `[version][hasher tag][raw hasher state]`, validated on restore.
It is **opaque and same-build only**: it carries the raw standard-library hasher
state, whose layout is not stable across builds. PKCS#11 does not promise
operation-state portability across implementations or builds (§5.6). Restore is
byte-exact within the same binary; any other input fails closed via the version,
tag, and exact-length checks above.
---
## 3. Mechanism constraints
### 3.1 ECDH key derivation — §5.13, Mechanisms (CKM_ECDH1_DERIVE)
| Aspect | Value | Else |
|--------|-------|------|
| KDF | `CKD_NULL` only | other KDF → `CKR_MECHANISM_PARAM_INVALID` |
| peer public point | raw SEC1 uncompressed **or** DER `OCTET STRING`-wrapped | malformed → `CKR_MECHANISM_PARAM_INVALID` |
| curves | P-256, P-384 | — |
| shared data | none (`CKD_NULL` carries no shared data) | — |
### 3.2 RSA — Mechanisms (CKM_RSA_PKCS, _PSS, _OAEP)
| Aspect | Value | Else |
|--------|-------|------|
| key size | 20484096 bits | outside range → `CKR_KEY_SIZE_RANGE` |
| public exponent | fixed at 65537 (F4) | not selectable at keygen |
| PSS / OAEP hash | SHA-256, SHA-384, SHA-512 | other → `CKR_MECHANISM_PARAM_INVALID` |
| MGF hash | must equal the content hash | mismatch → `CKR_MECHANISM_PARAM_INVALID` |
| OAEP label (source) | not supported | `ulSourceDataLen != 0``CKR_MECHANISM_PARAM_INVALID` |
Dedicated `CKM_SHA384_RSA_PKCS` / `CKM_SHA512_RSA_PKCS` mechanisms are not
advertised; SHA-384 and SHA-512 are reachable through the `CKM_RSA_PKCS_PSS` and
`CKM_RSA_PKCS_OAEP` parameter `hashAlg`.
### 3.3 AES — Mechanisms (CKM_AES_*)
AES-128 and AES-256 only. AES-192 is not implemented (the Zig standard library
exposes no 192-bit AES). A key length outside {16, 32} bytes → `CKR_KEY_SIZE_RANGE`.
### 3.4 ECDSA — Mechanisms (CKM_ECDSA*)
Curves P-256 and P-384. Mechanisms `CKM_ECDSA` (pre-hashed input) and
`CKM_ECDSA_SHA256`. `CKM_ECDSA_SHA384` and `CKM_ECDSA_SHA512` are out of scope.
### 3.5 Key wrap — §5.13
| Aspect | Value | Else |
|--------|-------|------|
| wrappable target | secret keys (`CKO_SECRET_KEY`) only | asymmetric target → `CKR_KEY_NOT_WRAPPABLE` |
| wrapping mechanisms | `CKM_AES_KEY_WRAP` (RFC 3394), `CKM_RSA_PKCS_OAEP` | other → `CKR_MECHANISM_INVALID` |
| unextractable target | refused | `CKR_KEY_UNEXTRACTABLE` |
| tampered wrapped blob on unwrap | refused | `CKR_WRAPPED_KEY_INVALID` |
---
## 4. Object and token model (informative)
- **One fixed slot** (ID 0), always present, hosting **one token**. Login is required
to see or use private objects.
- **Encrypted at rest.** Token objects persist to a file under a selective envelope:
only sensitive attribute *values* are sealed with AES-256-GCM under a per-token
master key. The master key is wrapped under a single **User-PIN keyslot**
(Argon2id-derived KEK). There is no SO keyslot for user secrets by design — the
Security Officer must not be able to read user key material.
- **Public objects and attributes stay in plaintext** and are visible before login,
which is spec-correct: only private/sensitive material is gated by login.
- In memory, sensitive attributes are plaintext only while the User is logged in;
logout and session teardown re-seal them and zeroize the master key.
---
## 5. Advertised mechanism list
`C_GetMechanismList` returns these 21 mechanisms. Key-size units are
mechanism-dependent per the spec (bits for RSA and EC, bytes for AES and HMAC).
| Mechanism | Min | Max | Flags |
|-----------|-----|-----|-------|
| `CKM_SHA256` | 0 | 0 | DIGEST |
| `CKM_SHA384` | 0 | 0 | DIGEST |
| `CKM_SHA512` | 0 | 0 | DIGEST |
| `CKM_SHA256_HMAC` | 32 | 64 | SIGN, VERIFY |
| `CKM_SHA384_HMAC` | 32 | 64 | SIGN, VERIFY |
| `CKM_SHA512_HMAC` | 32 | 64 | SIGN, VERIFY |
| `CKM_AES_KEY_GEN` | 16 | 32 | GENERATE |
| `CKM_AES_CBC` | 16 | 32 | ENCRYPT, DECRYPT |
| `CKM_AES_CBC_PAD` | 16 | 32 | ENCRYPT, DECRYPT |
| `CKM_AES_GCM` | 16 | 32 | ENCRYPT, DECRYPT |
| `CKM_EC_KEY_PAIR_GEN` | 256 | 384 | GENERATE_KEY_PAIR, EC_NAMEDCURVE |
| `CKM_ECDSA` | 256 | 384 | SIGN, VERIFY, EC_NAMEDCURVE |
| `CKM_ECDSA_SHA256` | 256 | 384 | SIGN, VERIFY, EC_NAMEDCURVE |
| `CKM_ECDH1_DERIVE` | 256 | 384 | DERIVE, EC_NAMEDCURVE |
| `CKM_RSA_PKCS_KEY_PAIR_GEN` | 2048 | 4096 | GENERATE_KEY_PAIR |
| `CKM_RSA_PKCS` | 2048 | 4096 | SIGN, VERIFY, ENCRYPT, DECRYPT, SIGN_RECOVER, VERIFY_RECOVER |
| `CKM_SHA256_RSA_PKCS` | 2048 | 4096 | SIGN, VERIFY |
| `CKM_RSA_PKCS_PSS` | 2048 | 4096 | SIGN, VERIFY |
| `CKM_SHA256_RSA_PKCS_PSS` | 2048 | 4096 | SIGN, VERIFY |
| `CKM_RSA_PKCS_OAEP` | 2048 | 4096 | ENCRYPT, DECRYPT, WRAP, UNWRAP |
| `CKM_AES_KEY_WRAP` | 16 | 32 | WRAP, UNWRAP |
---
## 6. Summary: deliberate return codes
| Boundary | Return code |
|----------|-------------|
| `C_WaitForSlotEvent`, non-blocking poll | `CKR_NO_EVENT` |
| `C_WaitForSlotEvent`, blocking mode | `CKR_FUNCTION_NOT_SUPPORTED` |
| `C_WaitForSlotEvent`, `pReserved != NULL` | `CKR_ARGUMENTS_BAD` |
| `C_GetFunctionStatus`, `C_CancelFunction` | `CKR_FUNCTION_NOT_PARALLEL` |
| `C_SeedRandom` | `CKR_RANDOM_SEED_NOT_SUPPORTED` |
| GCM message over 16 MiB | `CKR_DATA_LEN_RANGE` / `CKR_ENCRYPTED_DATA_LEN_RANGE` |
| GCM bad parameters | `CKR_MECHANISM_PARAM_INVALID` / `CKR_ARGUMENTS_BAD` |
| dual-function decrypt side, non-CBC | `CKR_FUNCTION_NOT_SUPPORTED` |
| RSA multi-part (`*Update` / `*Final`) | `CKR_FUNCTION_NOT_SUPPORTED` |
| Sign/Verify-Recover, non-`CKM_RSA_PKCS` | `CKR_MECHANISM_INVALID` |
| Get/SetOperationState, non-digest operation | `CKR_STATE_UNSAVEABLE` |
| SetOperationState with a key handle | `CKR_KEY_NOT_NEEDED` |
| SetOperationState, malformed blob | `CKR_SAVED_STATE_INVALID` |
| ECDH non-`CKD_NULL` KDF | `CKR_MECHANISM_PARAM_INVALID` |
| OAEP with a label | `CKR_MECHANISM_PARAM_INVALID` |
| AES key length not 16/32 bytes | `CKR_KEY_SIZE_RANGE` |
| wrap of an asymmetric target | `CKR_KEY_NOT_WRAPPABLE` |
| wrap of an unextractable key | `CKR_KEY_UNEXTRACTABLE` |
Every entry above is exercised by the unit tests, the in-process smoke harness, or a
cross-process `pkcs11-tool` run; the corrected slot/parallel/RNG codes are asserted
in `examples/smoke.zig` against the built shared object.

View File

@ -373,6 +373,60 @@ fn updateOutLen(op: *const cipher.Cipher, in_len: usize) ck.CK_ULONG {
return @intCast(((op.partial_len + in_len) / config.aes_block_len) * config.aes_block_len);
}
const NeedResult = union(enum) { ok: ck.CK_ULONG, err: ck.CK_RV };
const EmitResult = union(enum) { ok: usize, err: ck.CK_RV };
fn encUpdateNeed(op: *const session.EncryptOp, in_len: usize) NeedResult {
return switch (op.*) {
.rsa => .{ .err = ck.CKR_FUNCTION_NOT_SUPPORTED },
.aes => |*c| .{ .ok = updateOutLen(c, in_len) },
.gcm => .{ .ok = 0 },
};
}
fn encUpdateEmit(inst: *state.Instance, op: *session.EncryptOp, in: []const u8, out: []u8) EmitResult {
switch (op.*) {
.rsa => return .{ .err = ck.CKR_FUNCTION_NOT_SUPPORTED },
.aes => |*c| return .{ .ok = c.encryptUpdate(in, out) },
.gcm => |*g| {
g.append(inst.allocator(), in) catch |e| return .{ .err = switch (e) {
error.OutOfMemory => ck.CKR_HOST_MEMORY,
error.TooLarge => ck.CKR_DATA_LEN_RANGE,
} };
return .{ .ok = 0 };
},
}
}
fn decUpdateNeed(op: *const session.DecryptOp, in_len: usize) NeedResult {
return switch (op.*) {
.rsa => .{ .err = ck.CKR_FUNCTION_NOT_SUPPORTED },
.aes => |*c| .{ .ok = updateOutLen(c, in_len) },
.gcm => .{ .ok = 0 },
};
}
fn decUpdateEmit(inst: *state.Instance, op: *session.DecryptOp, in: []const u8, out: []u8) EmitResult {
switch (op.*) {
.rsa => return .{ .err = ck.CKR_FUNCTION_NOT_SUPPORTED },
.aes => |*c| return .{ .ok = c.decryptUpdate(in, out) },
.gcm => |*g| {
g.append(inst.allocator(), in) catch |e| return .{ .err = switch (e) {
error.OutOfMemory => ck.CKR_HOST_MEMORY,
error.TooLarge => ck.CKR_ENCRYPTED_DATA_LEN_RANGE,
} };
return .{ .ok = 0 };
},
}
}
fn decryptSideDualOk(op: *const session.DecryptOp) bool {
return switch (op.*) {
.aes => |*c| c.mode == .cbc,
else => false,
};
}
fn emitDigest(sess: *session.Session, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) ck.CK_RV {
const op = &sess.digest_op.?;
const dlen: ck.CK_ULONG = @intCast(op.digestLen());
@ -415,16 +469,16 @@ fn rsaCryptInit(inst: *state.Instance, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK
}
pub fn C_EncryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.encrypt_op != null) return ck.CKR_OPERATION_ACTIVE;
if (cipher.modeOf(pMechanism.mechanism) != null) {
sess.encrypt_op = .{ .aes = switch (buildCipher(inst, pMechanism, hKey, true, ck.CKA_ENCRYPT)) {
const c = switch (buildCipher(inst, pMechanism, hKey, true, ck.CKA_ENCRYPT)) {
.err => |rv| return rv,
.ok => |c| c,
} };
.ok => |built| built,
};
sess.encrypt_op = if (c.mode == .gcm) .{ .gcm = .{ .cipher = c } } else .{ .aes = c };
return ck.CKR_OK;
}
if (isRsaCryptMech(pMechanism.mechanism)) {
@ -438,8 +492,7 @@ pub fn C_EncryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANIS
}
pub fn C_Encrypt(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pEncryptedData: ?[*]ck.CK_BYTE, pulEncryptedDataLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -457,19 +510,28 @@ pub fn C_Encrypt(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pEncryptedData.?[0..@intCast(need)];
var n: usize = 0;
switch (c.mode) {
.gcm => n = c.gcmEncrypt(in, out),
.cbc, .cbc_pad => {
n = c.encryptUpdate(in, out);
n += c.encryptFinal(out[n..]) catch |e| {
sess.endEncrypt();
return mapCipherErr(e);
};
},
}
var n = c.encryptUpdate(in, out);
n += c.encryptFinal(out[n..]) catch |e| {
sess.endEncrypt(inst.allocator());
return mapCipherErr(e);
};
pulEncryptedDataLen.* = @intCast(n);
sess.endEncrypt();
sess.endEncrypt(inst.allocator());
return ck.CKR_OK;
},
.gcm => |*g| {
const need: ck.CK_ULONG = @intCast(cipher.encryptOutLen(.gcm, in.len));
if (pEncryptedData == null) {
pulEncryptedDataLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedDataLen.* < need) {
pulEncryptedDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = g.cipher.gcmEncrypt(in, pEncryptedData.?[0..@intCast(need)]);
pulEncryptedDataLen.* = @intCast(n);
sess.endEncrypt(inst.allocator());
return ck.CKR_OK;
},
.rsa => |*r| {
@ -484,36 +546,32 @@ pub fn C_Encrypt(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen
}
const pc = switch (rsaPublicComponents(inst, r.key, ck.CKA_ENCRYPT)) {
.err => |rv| {
sess.endEncrypt();
sess.endEncrypt(inst.allocator());
return rv;
},
.ok => |c| c,
};
const n = rsa.encrypt(pc, r.params, in, pEncryptedData.?[0..@intCast(need)]) catch {
sess.endEncrypt();
sess.endEncrypt(inst.allocator());
return ck.CKR_DATA_LEN_RANGE;
};
pulEncryptedDataLen.* = @intCast(n);
sess.endEncrypt();
sess.endEncrypt(inst.allocator());
return ck.CKR_OK;
},
}
}
pub fn C_EncryptUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG, pEncryptedPart: ?[*]ck.CK_BYTE, pulEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const in = part(pPart, ulPartLen);
const need = updateOutLen(c, in.len);
const need = switch (encUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pEncryptedPart == null) {
pulEncryptedPartLen.* = need;
return ck.CKR_OK;
@ -522,51 +580,71 @@ pub fn C_EncryptUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulP
pulEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
pulEncryptedPartLen.* = @intCast(c.encryptUpdate(in, pEncryptedPart.?[0..@intCast(need)]));
const wrote = switch (encUpdateEmit(inst, op, in, pEncryptedPart.?[0..@intCast(need)])) {
.err => |rv| {
sess.endEncrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
pulEncryptedPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}
pub fn C_EncryptFinal(hSession: ck.CK_SESSION_HANDLE, pLastEncryptedPart: ?[*]ck.CK_BYTE, pulLastEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastEncryptedPart == null) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_OK;
.aes => |*c| {
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastEncryptedPart == null) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastEncryptedPartLen.* < need) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.encryptFinal(pLastEncryptedPart.?[0..@intCast(need)]) catch |e| {
sess.endEncrypt(inst.allocator());
return mapCipherErr(e);
};
pulLastEncryptedPartLen.* = @intCast(n);
sess.endEncrypt(inst.allocator());
return ck.CKR_OK;
},
.gcm => |*g| {
const need: ck.CK_ULONG = @intCast(g.len + config.gcm_tag_len);
if (pLastEncryptedPart == null) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastEncryptedPartLen.* < need) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = g.cipher.gcmEncrypt(g.data(), pLastEncryptedPart.?[0..@intCast(need)]);
pulLastEncryptedPartLen.* = @intCast(n);
sess.endEncrypt(inst.allocator());
return ck.CKR_OK;
},
}
if (pulLastEncryptedPartLen.* < need) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.encryptFinal(pLastEncryptedPart.?[0..@intCast(need)]) catch |e| {
sess.endEncrypt();
return mapCipherErr(e);
};
pulLastEncryptedPartLen.* = @intCast(n);
sess.endEncrypt();
return ck.CKR_OK;
}
pub fn C_DecryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.decrypt_op != null) return ck.CKR_OPERATION_ACTIVE;
if (cipher.modeOf(pMechanism.mechanism) != null) {
sess.decrypt_op = .{ .aes = switch (buildCipher(inst, pMechanism, hKey, false, ck.CKA_DECRYPT)) {
const c = switch (buildCipher(inst, pMechanism, hKey, false, ck.CKA_DECRYPT)) {
.err => |rv| return rv,
.ok => |c| c,
} };
.ok => |built| built,
};
sess.decrypt_op = if (c.mode == .gcm) .{ .gcm = .{ .cipher = c } } else .{ .aes = c };
return ck.CKR_OK;
}
if (isRsaCryptMech(pMechanism.mechanism)) {
@ -580,8 +658,7 @@ pub fn C_DecryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANIS
}
pub fn C_Decrypt(hSession: ck.CK_SESSION_HANDLE, pEncryptedData: [*]ck.CK_BYTE, ulEncryptedDataLen: ck.CK_ULONG, pData: ?[*]ck.CK_BYTE, pulDataLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -599,22 +676,31 @@ pub fn C_Decrypt(hSession: ck.CK_SESSION_HANDLE, pEncryptedData: [*]ck.CK_BYTE,
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pData.?[0..@intCast(need)];
var n: usize = 0;
switch (c.mode) {
.gcm => n = c.gcmDecrypt(in, out) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
},
.cbc, .cbc_pad => {
n = c.decryptUpdate(in, out);
n += c.decryptFinal(out[n..]) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
};
},
}
var n = c.decryptUpdate(in, out);
n += c.decryptFinal(out[n..]) catch |e| {
sess.endDecrypt(inst.allocator());
return mapCipherErr(e);
};
pulDataLen.* = @intCast(n);
sess.endDecrypt();
sess.endDecrypt(inst.allocator());
return ck.CKR_OK;
},
.gcm => |*g| {
const need: ck.CK_ULONG = @intCast(cipher.decryptOutLen(.gcm, in.len));
if (pData == null) {
pulDataLen.* = need;
return ck.CKR_OK;
}
if (pulDataLen.* < need) {
pulDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = g.cipher.gcmDecrypt(in, pData.?[0..@intCast(need)]) catch |e| {
sess.endDecrypt(inst.allocator());
return mapCipherErr(e);
};
pulDataLen.* = @intCast(n);
sess.endDecrypt(inst.allocator());
return ck.CKR_OK;
},
.rsa => |*r| {
@ -629,36 +715,32 @@ pub fn C_Decrypt(hSession: ck.CK_SESSION_HANDLE, pEncryptedData: [*]ck.CK_BYTE,
}
const sc = switch (rsaPrivateComponents(inst, r.key, ck.CKA_DECRYPT)) {
.err => |rv| {
sess.endDecrypt();
sess.endDecrypt(inst.allocator());
return rv;
},
.ok => |c| c,
};
const n = rsa.decrypt(sc, r.params, in, pData.?[0..@intCast(need)]) catch {
sess.endDecrypt();
sess.endDecrypt(inst.allocator());
return ck.CKR_ENCRYPTED_DATA_INVALID;
};
pulDataLen.* = @intCast(n);
sess.endDecrypt();
sess.endDecrypt(inst.allocator());
return ck.CKR_OK;
},
}
}
pub fn C_DecryptUpdate(hSession: ck.CK_SESSION_HANDLE, pEncryptedPart: [*]ck.CK_BYTE, ulEncryptedPartLen: ck.CK_ULONG, pPart: ?[*]ck.CK_BYTE, pulPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const in = part(pEncryptedPart, ulEncryptedPartLen);
const need = updateOutLen(c, in.len);
const need = switch (decUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pPart == null) {
pulPartLen.* = need;
return ck.CKR_OK;
@ -667,43 +749,65 @@ pub fn C_DecryptUpdate(hSession: ck.CK_SESSION_HANDLE, pEncryptedPart: [*]ck.CK_
pulPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
pulPartLen.* = @intCast(c.decryptUpdate(in, pPart.?[0..@intCast(need)]));
const wrote = switch (decUpdateEmit(inst, op, in, pPart.?[0..@intCast(need)])) {
.err => |rv| {
sess.endDecrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
pulPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}
pub fn C_DecryptFinal(hSession: ck.CK_SESSION_HANDLE, pLastPart: ?[*]ck.CK_BYTE, pulLastPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastPart == null) {
pulLastPartLen.* = need;
return ck.CKR_OK;
.aes => |*c| {
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastPart == null) {
pulLastPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastPartLen.* < need) {
pulLastPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.decryptFinal(pLastPart.?[0..@intCast(need)]) catch |e| {
sess.endDecrypt(inst.allocator());
return mapCipherErr(e);
};
pulLastPartLen.* = @intCast(n);
sess.endDecrypt(inst.allocator());
return ck.CKR_OK;
},
.gcm => |*g| {
const need: ck.CK_ULONG = @intCast(cipher.decryptOutLen(.gcm, g.len));
if (pLastPart == null) {
pulLastPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastPartLen.* < need) {
pulLastPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = g.cipher.gcmDecrypt(g.data(), pLastPart.?[0..@intCast(need)]) catch |e| {
sess.endDecrypt(inst.allocator());
return mapCipherErr(e);
};
pulLastPartLen.* = @intCast(n);
sess.endDecrypt(inst.allocator());
return ck.CKR_OK;
},
}
if (pulLastPartLen.* < need) {
pulLastPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.decryptFinal(pLastPart.?[0..@intCast(need)]) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
};
pulLastPartLen.* = @intCast(n);
sess.endDecrypt();
return ck.CKR_OK;
}
pub fn C_DigestInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op != null) return ck.CKR_OPERATION_ACTIVE;
@ -712,8 +816,7 @@ pub fn C_DigestInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM
}
pub fn C_Digest(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -731,8 +834,7 @@ pub fn C_Digest(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen:
}
pub fn C_DigestUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -741,8 +843,7 @@ pub fn C_DigestUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPa
}
pub fn C_DigestKey(hSession: ck.CK_SESSION_HANDLE, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -756,8 +857,7 @@ pub fn C_DigestKey(hSession: ck.CK_SESSION_HANDLE, hKey: ck.CK_OBJECT_HANDLE) ca
}
pub fn C_DigestFinal(hSession: ck.CK_SESSION_HANDLE, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -765,8 +865,7 @@ pub fn C_DigestFinal(hSession: ck.CK_SESSION_HANDLE, pDigest: ?[*]ck.CK_BYTE, pu
}
pub fn C_SignInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op != null) return ck.CKR_OPERATION_ACTIVE;
@ -778,8 +877,7 @@ pub fn C_SignInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM,
}
pub fn C_Sign(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -818,8 +916,7 @@ pub fn C_Sign(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: c
}
pub fn C_SignUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -832,8 +929,7 @@ pub fn C_SignUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPart
}
pub fn C_SignFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -844,17 +940,57 @@ pub fn C_SignFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: ?[*]ck.CK_BYTE, p
return emitSign(inst, sess, pSignature, pulSignatureLen);
}
pub fn C_SignRecoverInit(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_SignRecoverInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_recover_op != null) return ck.CKR_OPERATION_ACTIVE;
if (pMechanism.mechanism != ck.CKM_RSA_PKCS) return ck.CKR_MECHANISM_INVALID;
const pc = switch (rsaPrivateComponents(inst, hKey, ck.CKA_SIGN_RECOVER)) {
.err => |rv| return rv,
.ok => |c| c,
};
sess.sign_recover_op = .{ .key = hKey, .out_len = pc.n.len };
return ck.CKR_OK;
}
pub fn C_SignRecover(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_SignRecover(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.sign_recover_op) |o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const need: ck.CK_ULONG = @intCast(op.out_len);
if (pSignature == null) {
pulSignatureLen.* = need;
return ck.CKR_OK;
}
if (pulSignatureLen.* < need) {
pulSignatureLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const in = part(pData, ulDataLen);
if (in.len + rsa.pkcs1_v15_min_overhead > op.out_len) {
sess.endSignRecover();
return ck.CKR_DATA_LEN_RANGE;
}
const sc = switch (rsaPrivateComponents(inst, op.key, ck.CKA_SIGN_RECOVER)) {
.err => |rv| {
sess.endSignRecover();
return rv;
},
.ok => |c| c,
};
const n = rsa.sign(sc, .{ .scheme = .pkcs1, .digest = .none }, in, pSignature.?[0..@intCast(need)]) catch {
sess.endSignRecover();
return ck.CKR_FUNCTION_FAILED;
};
pulSignatureLen.* = @intCast(n);
sess.endSignRecover();
return ck.CKR_OK;
}
pub fn C_VerifyInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op != null) return ck.CKR_OPERATION_ACTIVE;
@ -866,8 +1002,7 @@ pub fn C_VerifyInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM
}
pub fn C_Verify(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -894,8 +1029,7 @@ pub fn C_Verify(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen:
}
pub fn C_VerifyUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -908,8 +1042,7 @@ pub fn C_VerifyUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPa
}
pub fn C_VerifyFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
@ -920,26 +1053,188 @@ pub fn C_VerifyFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: [*]ck.CK_BYTE,
return finalizeVerify(sess, pSignature, ulSignatureLen);
}
pub fn C_VerifyRecoverInit(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_VerifyRecoverInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_recover_op != null) return ck.CKR_OPERATION_ACTIVE;
if (pMechanism.mechanism != ck.CKM_RSA_PKCS) return ck.CKR_MECHANISM_INVALID;
const pc = switch (rsaPublicComponents(inst, hKey, ck.CKA_VERIFY_RECOVER)) {
.err => |rv| return rv,
.ok => |c| c,
};
sess.verify_recover_op = .{ .key = hKey, .out_len = pc.n.len };
return ck.CKR_OK;
}
pub fn C_VerifyRecover(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_VerifyRecover(hSession: ck.CK_SESSION_HANDLE, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG, pData: ?[*]ck.CK_BYTE, pulDataLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.verify_recover_op) |o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
if (pData == null) {
pulDataLen.* = @intCast(op.out_len);
return ck.CKR_OK;
}
if (ulSignatureLen != op.out_len) {
sess.endVerifyRecover();
return ck.CKR_SIGNATURE_LEN_RANGE;
}
const pc = switch (rsaPublicComponents(inst, op.key, ck.CKA_VERIFY_RECOVER)) {
.err => |rv| {
sess.endVerifyRecover();
return rv;
},
.ok => |c| c,
};
var tmp: [rsa.max_modulus_bytes]u8 = undefined;
defer std.crypto.secureZero(u8, &tmp);
const m = rsa.recover(pc, part(pSignature, ulSignatureLen), &tmp) catch {
sess.endVerifyRecover();
return ck.CKR_SIGNATURE_INVALID;
};
if (pulDataLen.* < m) {
pulDataLen.* = @intCast(m);
return ck.CKR_BUFFER_TOO_SMALL;
}
@memcpy(pData.?[0..m], tmp[0..m]);
pulDataLen.* = @intCast(m);
sess.endVerifyRecover();
return ck.CKR_OK;
}
pub fn C_DigestEncryptUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_DigestEncryptUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG, pEncryptedPart: ?[*]ck.CK_BYTE, pulEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null or sess.encrypt_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const op = &sess.encrypt_op.?;
const in = part(pPart, ulPartLen);
const need = switch (encUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pEncryptedPart == null) {
pulEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedPartLen.* < need) {
pulEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
sess.digest_op.?.update(in);
const wrote = switch (encUpdateEmit(inst, op, in, pEncryptedPart.?[0..@intCast(need)])) {
.err => |rv| {
sess.endEncrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
pulEncryptedPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}
pub fn C_DecryptDigestUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_DecryptDigestUpdate(hSession: ck.CK_SESSION_HANDLE, pEncryptedPart: [*]ck.CK_BYTE, ulEncryptedPartLen: ck.CK_ULONG, pPart: ?[*]ck.CK_BYTE, pulPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.decrypt_op == null or sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const op = &sess.decrypt_op.?;
if (!decryptSideDualOk(op)) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const in = part(pEncryptedPart, ulEncryptedPartLen);
const need = switch (decUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pPart == null) {
pulPartLen.* = need;
return ck.CKR_OK;
}
if (pulPartLen.* < need) {
pulPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pPart.?[0..@intCast(need)];
const wrote = switch (decUpdateEmit(inst, op, in, out)) {
.err => |rv| {
sess.endDecrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
sess.digest_op.?.update(out[0..wrote]);
pulPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}
pub fn C_SignEncryptUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_SignEncryptUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG, pEncryptedPart: ?[*]ck.CK_BYTE, pulEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null or sess.encrypt_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.sign_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
const op = &sess.encrypt_op.?;
const in = part(pPart, ulPartLen);
const need = switch (encUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pEncryptedPart == null) {
pulEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedPartLen.* < need) {
pulEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
sess.sign_op.?.update(in);
const wrote = switch (encUpdateEmit(inst, op, in, pEncryptedPart.?[0..@intCast(need)])) {
.err => |rv| {
sess.endEncrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
pulEncryptedPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}
pub fn C_DecryptVerifyUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_DecryptVerifyUpdate(hSession: ck.CK_SESSION_HANDLE, pEncryptedPart: [*]ck.CK_BYTE, ulEncryptedPartLen: ck.CK_ULONG, pPart: ?[*]ck.CK_BYTE, pulPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.decrypt_op == null or sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const op = &sess.decrypt_op.?;
if (!decryptSideDualOk(op)) return ck.CKR_FUNCTION_NOT_SUPPORTED;
switch (sess.verify_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
const in = part(pEncryptedPart, ulEncryptedPartLen);
const need = switch (decUpdateNeed(op, in.len)) {
.err => |rv| return rv,
.ok => |n| n,
};
if (pPart == null) {
pulPartLen.* = need;
return ck.CKR_OK;
}
if (pulPartLen.* < need) {
pulPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pPart.?[0..@intCast(need)];
const wrote = switch (decUpdateEmit(inst, op, in, out)) {
.err => |rv| {
sess.endDecrypt(inst.allocator());
return rv;
},
.ok => |n| n,
};
sess.verify_op.?.update(out[0..wrote]);
pulPartLen.* = @intCast(wrote);
return ck.CKR_OK;
}

View File

@ -44,6 +44,10 @@ pub fn C_CancelFunction(_: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_PARALLEL;
}
pub fn C_WaitForSlotEvent(_: ck.CK_FLAGS, _: *ck.CK_SLOT_ID, _: ?*anyopaque) callconv(.c) ck.CK_RV {
pub fn C_WaitForSlotEvent(flags: ck.CK_FLAGS, pSlot: *ck.CK_SLOT_ID, pReserved: ?*anyopaque) callconv(.c) ck.CK_RV {
_ = pSlot;
if (!state.isInitialized()) return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (pReserved != null) return ck.CKR_ARGUMENTS_BAD;
if ((flags & ck.CKF_DONT_BLOCK) != 0) return ck.CKR_NO_EVENT;
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}

View File

@ -25,8 +25,7 @@ fn ulongFrom(bytes: []const u8) ?ck.CK_ULONG {
}
pub fn C_GenerateKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (pMechanism.mechanism != ck.CKM_AES_KEY_GEN) return ck.CKR_MECHANISM_INVALID;
@ -168,8 +167,7 @@ fn buildRsaPrivate(obj: *Object, allocator: std.mem.Allocator, template: []const
}
pub fn C_GenerateKeyPair(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, pPublicKeyTemplate: [*]ck.CK_ATTRIBUTE, ulPublicKeyAttributeCount: ck.CK_ULONG, pPrivateKeyTemplate: [*]ck.CK_ATTRIBUTE, ulPrivateKeyAttributeCount: ck.CK_ULONG, phPublicKey: *ck.CK_OBJECT_HANDLE, phPrivateKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -354,8 +352,7 @@ fn oaepParams(pMechanism: *ck.CK_MECHANISM) OaepVal {
}
pub fn C_WrapKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hWrappingKey: ck.CK_OBJECT_HANDLE, hKey: ck.CK_OBJECT_HANDLE, pWrappedKey: ?[*]ck.CK_BYTE, pulWrappedKeyLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
_ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -411,8 +408,7 @@ pub fn C_WrapKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, h
}
pub fn C_UnwrapKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hUnwrappingKey: ck.CK_OBJECT_HANDLE, pWrappedKey: [*]ck.CK_BYTE, ulWrappedKeyLen: ck.CK_ULONG, pTemplate: [*]ck.CK_ATTRIBUTE, ulAttributeCount: ck.CK_ULONG, phKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -513,8 +509,7 @@ fn peerPointSec1(curve: ecdsa.Curve, data: []const u8) ?[]const u8 {
}
pub fn C_DeriveKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hBaseKey: ck.CK_OBJECT_HANDLE, pTemplate: ?[*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (pMechanism.mechanism != ck.CKM_ECDH1_DERIVE) return ck.CKR_MECHANISM_INVALID;

View File

@ -125,8 +125,7 @@ fn persistIfToken(inst: *state.Instance, is_token: bool) void {
}
pub fn C_CreateObject(hSession: ck.CK_SESSION_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phObject: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -150,8 +149,7 @@ pub fn C_CreateObject(hSession: ck.CK_SESSION_HANDLE, pTemplate: [*]ck.CK_ATTRIB
}
pub fn C_CopyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phNewObject: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -183,8 +181,7 @@ pub fn C_CopyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE
}
pub fn C_DestroyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -201,8 +198,7 @@ pub fn C_DestroyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HAN
}
pub fn C_GetObjectSize(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pulSize: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
_ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -214,8 +210,7 @@ pub fn C_GetObjectSize(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HAN
}
pub fn C_GetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
_ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -251,8 +246,7 @@ pub fn C_GetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT
}
pub fn C_SetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -281,8 +275,7 @@ pub fn C_SetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT
}
pub fn C_FindObjectsInit(hSession: ck.CK_SESSION_HANDLE, pTemplate: ?[*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -306,8 +299,7 @@ pub fn C_FindObjectsInit(hSession: ck.CK_SESSION_HANDLE, pTemplate: ?[*]ck.CK_AT
}
pub fn C_FindObjects(hSession: ck.CK_SESSION_HANDLE, phObject: [*]ck.CK_OBJECT_HANDLE, ulMaxObjectCount: ck.CK_ULONG, pulObjectCount: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
@ -324,8 +316,7 @@ pub fn C_FindObjects(hSession: ck.CK_SESSION_HANDLE, phObject: [*]ck.CK_OBJECT_H
}
pub fn C_FindObjectsFinal(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;

View File

@ -5,16 +5,14 @@ const ck = @import("../ck.zig");
const state = @import("../core/state.zig");
pub fn C_SeedRandom(hSession: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
return ck.CKR_RANDOM_SEED_NOT_SUPPORTED;
}
pub fn C_GenerateRandom(hSession: ck.CK_SESSION_HANDLE, pRandomData: [*]ck.CK_BYTE, ulRandomLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
if (ulRandomLen == 0) return ck.CKR_OK;

View File

@ -9,6 +9,7 @@ const pin = @import("../crypto/pin.zig");
const keystore = @import("../crypto/keystore.zig");
const token = @import("../core/token.zig");
const object_store = @import("../core/object_store.zig");
const digest = @import("../crypto/digest.zig");
fn pinSlice(p: ?[*]ck.CK_UTF8CHAR, len: ck.CK_ULONG) []const u8 {
return if (p) |ptr| ptr[0..@intCast(len)] else &.{};
@ -26,12 +27,10 @@ fn sessionState(flags: ck.CK_FLAGS, logged_in: ?ck.CK_USER_TYPE) ck.CK_STATE {
pub fn C_OpenSession(slotID: ck.CK_SLOT_ID, flags: ck.CK_FLAGS, pApplication: ?*anyopaque, notify: ck.CK_NOTIFY, phSession: *ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
_ = pApplication;
_ = notify;
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
if ((flags & ck.CKF_SERIAL_SESSION) == 0) return ck.CKR_SESSION_PARALLEL_NOT_SUPPORTED;
state.mutex.lock();
defer state.mutex.unlock();
if ((flags & ck.CKF_RW_SESSION) == 0 and inst.logged_in == ck.CKU_SO) {
return ck.CKR_SESSION_READ_WRITE_SO_EXISTS;
}
@ -41,10 +40,9 @@ pub fn C_OpenSession(slotID: ck.CK_SLOT_ID, flags: ck.CK_FLAGS, pApplication: ?*
}
pub fn C_CloseSession(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (!inst.sessions.close(hSession)) return ck.CKR_SESSION_HANDLE_INVALID;
if (!inst.sessions.close(inst.allocator(), hSession)) return ck.CKR_SESSION_HANDLE_INVALID;
if (!inst.sessions.anyOpen()) {
inst.relock();
inst.logged_in = null;
@ -53,19 +51,17 @@ pub fn C_CloseSession(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
}
pub fn C_CloseAllSessions(slotID: ck.CK_SLOT_ID) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
inst.sessions.closeAll(slotID);
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
inst.sessions.closeAll(inst.allocator(), slotID);
inst.relock();
inst.logged_in = null;
return ck.CKR_OK;
}
pub fn C_GetSessionInfo(hSession: ck.CK_SESSION_HANDLE, pInfo: *ck.CK_SESSION_INFO) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const s = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
pInfo.* = .{
@ -77,19 +73,56 @@ pub fn C_GetSessionInfo(hSession: ck.CK_SESSION_HANDLE, pInfo: *ck.CK_SESSION_IN
return ck.CKR_OK;
}
pub fn C_GetOperationState(_: ck.CK_SESSION_HANDLE, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_GetOperationState(hSession: ck.CK_SESSION_HANDLE, pOperationState: ?[*]ck.CK_BYTE, pulOperationStateLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op != null or sess.verify_op != null or sess.encrypt_op != null or
sess.decrypt_op != null or sess.sign_recover_op != null or sess.verify_recover_op != null)
{
return ck.CKR_STATE_UNSAVEABLE;
}
const d = if (sess.digest_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const total: ck.CK_ULONG = @intCast(config.op_state_header_len + d.stateLen());
if (pOperationState == null) {
pulOperationStateLen.* = total;
return ck.CKR_OK;
}
if (pulOperationStateLen.* < total) {
pulOperationStateLen.* = total;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pOperationState.?[0..@intCast(total)];
out[0] = config.op_state_version;
out[1] = d.stateTag();
d.writeState(out[config.op_state_header_len..]);
pulOperationStateLen.* = total;
return ck.CKR_OK;
}
pub fn C_SetOperationState(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ck.CK_OBJECT_HANDLE, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
pub fn C_SetOperationState(hSession: ck.CK_SESSION_HANDLE, pOperationState: [*]ck.CK_BYTE, ulOperationStateLen: ck.CK_ULONG, hEncryptionKey: ck.CK_OBJECT_HANDLE, hAuthenticationKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (hEncryptionKey != ck.CK_INVALID_HANDLE or hAuthenticationKey != ck.CK_INVALID_HANDLE) {
return ck.CKR_KEY_NOT_NEEDED;
}
const blob = pOperationState[0..@intCast(ulOperationStateLen)];
if (blob.len < config.op_state_header_len) return ck.CKR_SAVED_STATE_INVALID;
if (blob[0] != config.op_state_version) return ck.CKR_SAVED_STATE_INVALID;
const restored = digest.Hasher.fromState(blob[1], blob[config.op_state_header_len..]) orelse return ck.CKR_SAVED_STATE_INVALID;
sess.endDigest();
sess.digest_op = restored;
return ck.CKR_OK;
}
pub fn C_Login(hSession: ck.CK_SESSION_HANDLE, userType: ck.CK_USER_TYPE, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (userType != ck.CKU_SO and userType != ck.CKU_USER) return ck.CKR_USER_TYPE_INVALID;
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (userType != ck.CKU_SO and userType != ck.CKU_USER) {
state.mutex.unlock();
return ck.CKR_USER_TYPE_INVALID;
}
state.mutex.lock();
if (inst.sessions.get(hSession) == null) {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
@ -182,8 +215,7 @@ pub fn C_Login(hSession: ck.CK_SESSION_HANDLE, userType: ck.CK_USER_TYPE, pPin:
}
pub fn C_Logout(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
if (inst.logged_in == null) return ck.CKR_USER_NOT_LOGGED_IN;

View File

@ -19,6 +19,10 @@ fn pinSlice(p: ?[*]ck.CK_UTF8CHAR, len: ck.CK_ULONG) []const u8 {
return if (p) |ptr| ptr[0..@intCast(len)] else &.{};
}
fn pinLenOk(len: ck.CK_ULONG) bool {
return len >= config.min_pin_len and len <= config.max_pin_len;
}
fn labelFrom(p: ?[*]ck.CK_UTF8CHAR) [config.label_len]u8 {
var out: [config.label_len]u8 = @splat(' ');
if (p) |lp| @memcpy(&out, lp[0..config.label_len]);
@ -63,11 +67,9 @@ pub fn C_GetSlotInfo(slotID: ck.CK_SLOT_ID, pInfo: *ck.CK_SLOT_INFO) callconv(.c
}
pub fn C_GetTokenInfo(slotID: ck.CK_SLOT_ID, pInfo: *ck.CK_TOKEN_INFO) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
state.mutex.lock();
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
defer state.mutex.unlock();
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
var flags: ck.CK_FLAGS = ck.CKF_RNG | ck.CKF_LOGIN_REQUIRED;
if (inst.token.initialized) flags |= ck.CKF_TOKEN_INITIALIZED;
@ -167,7 +169,7 @@ pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE,
ck.CKM_RSA_PKCS => .{
.ulMinKeySize = config.rsa_min_key_bits,
.ulMaxKeySize = config.rsa_max_key_bits,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY | ck.CKF_ENCRYPT | ck.CKF_DECRYPT,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY | ck.CKF_ENCRYPT | ck.CKF_DECRYPT | ck.CKF_SIGN_RECOVER | ck.CKF_VERIFY_RECOVER,
},
ck.CKM_SHA256_RSA_PKCS, ck.CKM_RSA_PKCS_PSS, ck.CKM_SHA256_RSA_PKCS_PSS => .{
.ulMinKeySize = config.rsa_min_key_bits,
@ -185,15 +187,21 @@ pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE,
}
pub fn C_InitToken(slotID: ck.CK_SLOT_ID, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG, pLabel: ?[*]ck.CK_UTF8CHAR) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) {
state.mutex.unlock();
return ck.CKR_SLOT_ID_INVALID;
}
state.mutex.lock();
if (inst.sessions.anyOpen()) {
state.mutex.unlock();
return ck.CKR_SESSION_EXISTS;
}
const was_init = inst.token.initialized;
if (!was_init and !pinLenOk(ulPinLen)) {
state.mutex.unlock();
return ck.CKR_PIN_LEN_RANGE;
}
if (was_init and inst.token.so_fail >= config.login_max_attempts) {
state.mutex.unlock();
return ck.CKR_PIN_LOCKED;
@ -267,9 +275,8 @@ pub fn C_InitToken(slotID: ck.CK_SLOT_ID, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck
}
pub fn C_InitPIN(hSession: ck.CK_SESSION_HANDLE, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
if (inst.sessions.get(hSession) == null) {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
@ -278,6 +285,10 @@ pub fn C_InitPIN(hSession: ck.CK_SESSION_HANDLE, pPin: ?[*]ck.CK_UTF8CHAR, ulPin
state.mutex.unlock();
return ck.CKR_USER_NOT_LOGGED_IN;
}
if (!pinLenOk(ulPinLen)) {
state.mutex.unlock();
return ck.CKR_PIN_LEN_RANGE;
}
_ = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();
@ -318,9 +329,8 @@ pub fn C_InitPIN(hSession: ck.CK_SESSION_HANDLE, pPin: ?[*]ck.CK_UTF8CHAR, ulPin
}
pub fn C_SetPIN(hSession: ck.CK_SESSION_HANDLE, pOldPin: ?[*]ck.CK_UTF8CHAR, ulOldLen: ck.CK_ULONG, pNewPin: ?[*]ck.CK_UTF8CHAR, ulNewLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
const inst = state.acquire() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const sess = inst.sessions.get(hSession) orelse {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
@ -349,6 +359,10 @@ pub fn C_SetPIN(hSession: ck.CK_SESSION_HANDLE, pOldPin: ?[*]ck.CK_UTF8CHAR, ulO
hash = u.hash;
wrapped_mk = inst.token.user_mk;
}
if (!pinLenOk(ulNewLen)) {
state.mutex.unlock();
return ck.CKR_PIN_LEN_RANGE;
}
const gen = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();

View File

@ -56,6 +56,7 @@ pub const CKR_FUNCTION_NOT_SUPPORTED: CK_RV = 0x00000054;
pub const CKR_KEY_HANDLE_INVALID: CK_RV = 0x00000060;
pub const CKR_KEY_SIZE_RANGE: CK_RV = 0x00000062;
pub const CKR_KEY_TYPE_INCONSISTENT: CK_RV = 0x00000063;
pub const CKR_KEY_NOT_NEEDED: CK_RV = 0x00000064;
pub const CKR_KEY_INDIGESTIBLE: CK_RV = 0x00000067;
pub const CKR_KEY_FUNCTION_NOT_PERMITTED: CK_RV = 0x00000068;
pub const CKR_KEY_NOT_WRAPPABLE: CK_RV = 0x00000069;
@ -253,6 +254,8 @@ pub const CKF_EC_COMPRESS: CK_FLAGS = 0x02000000;
pub const CKF_LIBRARY_CANT_CREATE_OS_THREADS: CK_FLAGS = 0x00000001;
pub const CKF_OS_LOCKING_OK: CK_FLAGS = 0x00000002;
pub const CKF_DONT_BLOCK: CK_FLAGS = 0x00000001;
pub const CKG_MGF1_SHA1: CK_RSA_PKCS_MGF_TYPE = 0x00000001;
pub const CKG_MGF1_SHA256: CK_RSA_PKCS_MGF_TYPE = 0x00000002;
pub const CKG_MGF1_SHA384: CK_RSA_PKCS_MGF_TYPE = 0x00000003;

View File

@ -63,9 +63,13 @@ pub const gcm_iv_bits: ck.CK_ULONG = 96;
pub const gcm_tag_len: usize = 16;
pub const gcm_tag_bits: ck.CK_ULONG = 128;
pub const max_gcm_aad_len: usize = 256;
pub const max_gcm_stream_len: usize = 16 * 1024 * 1024;
pub const master_key_len: usize = 32;
pub const op_state_version: u8 = 1;
pub const op_state_header_len: usize = 2;
pub const supported_mechanisms = [_]ck.CK_MECHANISM_TYPE{
ck.CKM_SHA256,
ck.CKM_SHA384,

View File

@ -91,6 +91,7 @@ pub const Object = struct {
const dup = try allocator.dupe(u8, bytes);
secureFree(allocator, a.value);
a.value = dup;
a.sealed = false;
return;
}
if (self.attrs.items.len >= config.max_attributes_per_object) return error.TooManyAttributes;
@ -385,6 +386,18 @@ test "set replaces an existing attribute and reports size" {
try std.testing.expectEqual(@as(ck.CK_ULONG, "second-value".len), obj.sizeBytes());
}
test "set on an existing attribute clears a stale sealed flag" {
const a = std.testing.allocator;
var obj: Object = .{};
defer obj.deinit(a);
try obj.set(a, ck.CKA_VALUE, "ciphertext-placeholder");
obj.findPtr(ck.CKA_VALUE).?.sealed = true;
try obj.set(a, ck.CKA_VALUE, "fresh-plaintext");
try std.testing.expect(!obj.findPtr(ck.CKA_VALUE).?.sealed);
try std.testing.expectEqualSlices(u8, "fresh-plaintext", obj.get(ck.CKA_VALUE).?);
}
test "bool and class helpers read CK_BBOOL semantics" {
const a = std.testing.allocator;
var obj: Object = .{};

View File

@ -29,6 +29,50 @@ pub const RsaCrypt = struct {
out_len: usize,
};
pub const RsaRecover = struct {
key: ck.CK_OBJECT_HANDLE,
out_len: usize,
};
pub const GcmStream = struct {
cipher: cipher.Cipher,
buf: ?[]u8 = null,
len: usize = 0,
pub fn append(self: *GcmStream, allocator: std.mem.Allocator, bytes: []const u8) error{ OutOfMemory, TooLarge }!void {
if (bytes.len == 0) return;
const needed = self.len + bytes.len;
if (needed > config.max_gcm_stream_len) return error.TooLarge;
if (self.buf == null or self.buf.?.len < needed) {
var new_cap: usize = if (self.buf) |b| b.len else 256;
while (new_cap < needed) new_cap *|= 2;
if (new_cap > config.max_gcm_stream_len) new_cap = config.max_gcm_stream_len;
const fresh = try allocator.alloc(u8, new_cap);
if (self.buf) |old| {
@memcpy(fresh[0..self.len], old[0..self.len]);
std.crypto.secureZero(u8, old);
allocator.free(old);
}
self.buf = fresh;
}
@memcpy(self.buf.?[self.len..][0..bytes.len], bytes);
self.len += bytes.len;
}
pub fn data(self: *const GcmStream) []const u8 {
return if (self.buf) |b| b[0..self.len] else &.{};
}
pub fn deinit(self: *GcmStream, allocator: std.mem.Allocator) void {
if (self.buf) |b| {
std.crypto.secureZero(u8, b);
allocator.free(b);
}
self.buf = null;
self.len = 0;
}
};
pub const SignOp = union(enum) {
mac: mac.Mac,
ec: ecdsa.SignState,
@ -67,18 +111,28 @@ pub const VerifyOp = union(enum) {
pub const EncryptOp = union(enum) {
aes: cipher.Cipher,
gcm: GcmStream,
rsa: RsaCrypt,
pub fn zeroize(self: *EncryptOp) void {
pub fn deinit(self: *EncryptOp, allocator: std.mem.Allocator) void {
switch (self.*) {
.gcm => |*g| g.deinit(allocator),
else => {},
}
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
pub const DecryptOp = union(enum) {
aes: cipher.Cipher,
gcm: GcmStream,
rsa: RsaCrypt,
pub fn zeroize(self: *DecryptOp) void {
pub fn deinit(self: *DecryptOp, allocator: std.mem.Allocator) void {
switch (self.*) {
.gcm => |*g| g.deinit(allocator),
else => {},
}
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
@ -92,6 +146,8 @@ pub const Session = struct {
verify_op: ?VerifyOp = null,
encrypt_op: ?EncryptOp = null,
decrypt_op: ?DecryptOp = null,
sign_recover_op: ?RsaRecover = null,
verify_recover_op: ?RsaRecover = null,
pub fn endDigest(self: *Session) void {
if (self.digest_op) |*o| std.crypto.secureZero(u8, std.mem.asBytes(o));
@ -108,15 +164,36 @@ pub const Session = struct {
self.verify_op = null;
}
pub fn endEncrypt(self: *Session) void {
if (self.encrypt_op) |*o| o.zeroize();
pub fn endEncrypt(self: *Session, allocator: std.mem.Allocator) void {
if (self.encrypt_op) |*o| o.deinit(allocator);
self.encrypt_op = null;
}
pub fn endDecrypt(self: *Session) void {
if (self.decrypt_op) |*o| o.zeroize();
pub fn endDecrypt(self: *Session, allocator: std.mem.Allocator) void {
if (self.decrypt_op) |*o| o.deinit(allocator);
self.decrypt_op = null;
}
pub fn endSignRecover(self: *Session) void {
if (self.sign_recover_op) |*o| std.crypto.secureZero(u8, std.mem.asBytes(o));
self.sign_recover_op = null;
}
pub fn endVerifyRecover(self: *Session) void {
if (self.verify_recover_op) |*o| std.crypto.secureZero(u8, std.mem.asBytes(o));
self.verify_recover_op = null;
}
pub fn freeHeap(self: *Session, allocator: std.mem.Allocator) void {
if (self.encrypt_op) |*o| switch (o.*) {
.gcm => |*g| g.deinit(allocator),
else => {},
};
if (self.decrypt_op) |*o| switch (o.*) {
.gcm => |*g| g.deinit(allocator),
else => {},
};
}
};
pub const Table = struct {
@ -139,18 +216,20 @@ pub const Table = struct {
return null;
}
pub fn close(self: *Table, h: ck.CK_SESSION_HANDLE) bool {
pub fn close(self: *Table, allocator: std.mem.Allocator, h: ck.CK_SESSION_HANDLE) bool {
if (h == 0 or h > config.max_sessions) return false;
if (self.slots[h - 1] == null) return false;
if (self.slots[h - 1]) |*s| s.freeHeap(allocator);
std.crypto.secureZero(u8, std.mem.asBytes(&self.slots[h - 1]));
self.slots[h - 1] = null;
return true;
}
pub fn closeAll(self: *Table, slot: ck.CK_SLOT_ID) void {
pub fn closeAll(self: *Table, allocator: std.mem.Allocator, slot: ck.CK_SLOT_ID) void {
for (&self.slots) |*s| {
if (s.*) |*sp| {
if (sp.slot == slot) {
sp.freeHeap(allocator);
std.crypto.secureZero(u8, std.mem.asBytes(s));
s.* = null;
}
@ -158,7 +237,10 @@ pub const Table = struct {
}
}
pub fn wipeAll(self: *Table) void {
pub fn wipeAll(self: *Table, allocator: std.mem.Allocator) void {
for (&self.slots) |*s| {
if (s.*) |*sp| sp.freeHeap(allocator);
}
std.crypto.secureZero(u8, std.mem.asBytes(&self.slots));
}
@ -200,13 +282,14 @@ test "open returns nonzero handles and get resolves them" {
}
test "close frees the slot and closeAll empties the table" {
const a = std.testing.allocator;
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
try std.testing.expect(t.close(h));
try std.testing.expect(!t.close(h));
try std.testing.expect(t.close(a, h));
try std.testing.expect(!t.close(a, h));
try std.testing.expect(!t.anyOpen());
_ = t.open(0, ck.CKF_SERIAL_SESSION);
t.closeAll(0);
t.closeAll(a, 0);
try std.testing.expect(!t.anyOpen());
}
@ -214,22 +297,45 @@ fn expectAllZero(bytes: []const u8) !void {
for (bytes) |b| try std.testing.expectEqual(@as(u8, 0), b);
}
test "EncryptOp.zeroize zeros the AES key material" {
test "EncryptOp.deinit zeros the AES key material" {
var op: EncryptOp = .{ .aes = .{ .mode = .cbc, .encrypt = true, .key_len = 32 } };
const key: []u8 = &op.aes.key_buf;
@memset(key, 0xAA);
op.zeroize();
op.deinit(std.testing.allocator);
try expectAllZero(key);
}
test "DecryptOp.zeroize zeros the AES key material" {
test "DecryptOp.deinit zeros the AES key material" {
var op: DecryptOp = .{ .aes = .{ .mode = .cbc, .encrypt = false, .key_len = 16 } };
const key: []u8 = &op.aes.key_buf;
@memset(key, 0xAA);
op.zeroize();
op.deinit(std.testing.allocator);
try expectAllZero(key);
}
test "GcmStream secure-grows across a realloc and deinit clears it" {
const a = std.testing.allocator;
var g: GcmStream = .{ .cipher = .{ .mode = .gcm, .encrypt = true, .key_len = 32 } };
var part: [200]u8 = undefined;
for (&part, 0..) |*b, i| b.* = @intCast(i & 0xff);
try g.append(a, &part);
try g.append(a, &part);
try std.testing.expectEqual(@as(usize, 400), g.len);
try std.testing.expectEqualSlices(u8, &part, g.data()[0..200]);
try std.testing.expectEqualSlices(u8, &part, g.data()[200..400]);
g.deinit(a);
try std.testing.expect(g.buf == null);
try std.testing.expectEqual(@as(usize, 0), g.len);
}
test "GcmStream enforces the DoS bound" {
const a = std.testing.allocator;
var g: GcmStream = .{ .cipher = .{ .mode = .gcm, .encrypt = true, .key_len = 16 } };
defer g.deinit(a);
g.len = config.max_gcm_stream_len;
try std.testing.expectError(error.TooLarge, g.append(a, "x"));
}
test "SignOp.zeroize zeros the EC private scalar" {
const scalar = [_]u8{0xAB} ** 32;
var op: SignOp = .{ .ec = ecdsa.SignState.init(.p256, ck.CKM_ECDSA, &scalar).? };
@ -247,35 +353,49 @@ test "VerifyOp.zeroize zeros HMAC key state" {
}
test "endEncrypt clears the op and removes the secret from the slot" {
const a = std.testing.allocator;
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.encrypt_op = .{ .aes = .{ .mode = .gcm, .encrypt = true, .key_len = 32 } };
sess.encrypt_op = .{ .aes = .{ .mode = .cbc, .encrypt = true, .key_len = 32 } };
const key: []u8 = &sess.encrypt_op.?.aes.key_buf;
@memset(key, 0x5C);
sess.endEncrypt();
sess.endEncrypt(a);
try std.testing.expect(sess.encrypt_op == null);
try std.testing.expect(std.mem.indexOfScalar(u8, key, 0x5C) == null);
}
test "close removes an active op's secret from the slot" {
const a = std.testing.allocator;
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.decrypt_op = .{ .aes = .{ .mode = .cbc, .encrypt = false, .key_len = 32 } };
const key: []u8 = &sess.decrypt_op.?.aes.key_buf;
@memset(key, 0x5C);
try std.testing.expect(t.close(h));
try std.testing.expect(t.close(a, h));
try std.testing.expect(std.mem.indexOfScalar(u8, key, 0x5C) == null);
}
test "close frees an active GCM stream's heap buffer" {
const a = std.testing.allocator;
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.encrypt_op = .{ .gcm = .{ .cipher = .{ .mode = .gcm, .encrypt = true, .key_len = 32 } } };
try sess.encrypt_op.?.gcm.append(a, "buffered-plaintext-awaiting-final");
try std.testing.expect(t.close(a, h));
try std.testing.expect(!t.anyOpen());
}
test "wipeAll zeros secret material in every slot" {
const a = std.testing.allocator;
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.sign_op = .{ .mac = undefined };
const st: []u8 = std.mem.asBytes(&sess.sign_op.?.mac);
@memset(st, 0xEF);
t.wipeAll();
t.wipeAll(a);
try expectAllZero(st);
}

View File

@ -49,8 +49,13 @@ var present: bool = false;
var inflight: usize = 0;
var generation: u64 = 0;
pub fn current() ?*Instance {
return if (@atomicLoad(bool, &present, .acquire)) &storage else null;
pub fn acquire() ?*Instance {
mutex.lock();
if (!@atomicLoad(bool, &present, .acquire)) {
mutex.unlock();
return null;
}
return &storage;
}
pub fn isInitialized() bool {
@ -87,11 +92,12 @@ pub fn finalize() ck.CK_RV {
mutex.lock();
defer mutex.unlock();
storage.sessions.wipeAll();
storage.sessions.wipeAll(storage.allocator());
storage.wipeMasterKey();
storage.objects.deinit(storage.allocator());
storage.threaded.deinit();
_ = storage.debug_alloc.deinit();
const leak = storage.debug_alloc.deinit();
if (builtin.mode == .Debug and leak == .leak) @panic("hsm: allocator leak detected at C_Finalize");
return ck.CKR_OK;
}

View File

@ -70,6 +70,7 @@ pub const Cipher = struct {
fn cbcEncStep(self: *Cipher, in16: *const [block]u8, out16: *[block]u8) void {
var x: [block]u8 = undefined;
defer std.crypto.secureZero(u8, &x);
for (0..block) |j| x[j] = in16[j] ^ self.chain[j];
encBlockRaw(self.key(), &x, out16);
self.chain = out16.*;
@ -77,6 +78,7 @@ pub const Cipher = struct {
fn cbcDecStep(self: *Cipher, in16: *const [block]u8, out16: *[block]u8) void {
var d: [block]u8 = undefined;
defer std.crypto.secureZero(u8, &d);
decBlockRaw(self.key(), in16, &d);
for (0..block) |j| out16[j] = d[j] ^ self.chain[j];
self.chain = in16.*;
@ -142,6 +144,7 @@ pub const Cipher = struct {
if (self.mode == .cbc) return 0;
if (!self.has_held) return Error.EncryptedDataLenRange;
var pt: [block]u8 = undefined;
defer std.crypto.secureZero(u8, &pt);
self.cbcDecStep(&self.held, &pt);
self.has_held = false;
const padlen = pt[block - 1];

View File

@ -8,6 +8,10 @@ const sha2 = std.crypto.hash.sha2;
pub const max_digest_len = sha2.Sha512.digest_length;
pub const state_tag_sha256: u8 = 1;
pub const state_tag_sha384: u8 = 2;
pub const state_tag_sha512: u8 = 3;
pub const Hasher = union(enum) {
sha256: sha2.Sha256,
sha384: sha2.Sha384,
@ -42,6 +46,50 @@ pub const Hasher = union(enum) {
},
}
}
pub fn stateTag(self: *const Hasher) u8 {
return switch (self.*) {
.sha256 => state_tag_sha256,
.sha384 => state_tag_sha384,
.sha512 => state_tag_sha512,
};
}
pub fn stateLen(self: *const Hasher) usize {
return switch (self.*) {
inline else => |*h| @sizeOf(@TypeOf(h.*)),
};
}
pub fn writeState(self: *const Hasher, out: []u8) void {
switch (self.*) {
inline else => |*h| @memcpy(out[0..@sizeOf(@TypeOf(h.*))], std.mem.asBytes(h)),
}
}
pub fn fromState(tag: u8, bytes: []const u8) ?Hasher {
switch (tag) {
state_tag_sha256 => {
if (bytes.len != @sizeOf(sha2.Sha256)) return null;
var h: Hasher = .{ .sha256 = undefined };
@memcpy(std.mem.asBytes(&h.sha256), bytes);
return h;
},
state_tag_sha384 => {
if (bytes.len != @sizeOf(sha2.Sha384)) return null;
var h: Hasher = .{ .sha384 = undefined };
@memcpy(std.mem.asBytes(&h.sha384), bytes);
return h;
},
state_tag_sha512 => {
if (bytes.len != @sizeOf(sha2.Sha512)) return null;
var h: Hasher = .{ .sha512 = undefined };
@memcpy(std.mem.asBytes(&h.sha512), bytes);
return h;
},
else => return null,
}
}
};
pub fn digestLenOf(mech: ck.CK_MECHANISM_TYPE) ?usize {
@ -87,3 +135,26 @@ test "unknown mechanism yields null" {
try std.testing.expect(Hasher.init(ck.CKM_AES_CBC) == null);
try std.testing.expect(digestLenOf(ck.CKM_SHA384).? == 48);
}
test "op-state serialize then restore continues the same digest" {
var a = Hasher.init(ck.CKM_SHA256).?;
a.update("first part ");
var blob: [1 + @sizeOf(sha2.Sha512)]u8 = undefined;
const tag = a.stateTag();
const len = a.stateLen();
a.writeState(blob[0..len]);
var b = Hasher.fromState(tag, blob[0..len]).?;
a.update("second part");
b.update("second part");
var oa: [max_digest_len]u8 = undefined;
var ob: [max_digest_len]u8 = undefined;
a.finalInto(&oa);
b.finalInto(&ob);
try std.testing.expectEqualSlices(u8, oa[0..32], ob[0..32]);
try std.testing.expect(Hasher.fromState(99, blob[0..len]) == null);
try std.testing.expect(Hasher.fromState(tag, blob[0 .. len - 1]) == null);
}

View File

@ -72,6 +72,8 @@ pub extern fn EVP_PKEY_sign_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_sign(ctx: ?*EVP_PKEY_CTX, sig: ?[*]u8, siglen: *usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_PKEY_verify_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_verify(ctx: ?*EVP_PKEY_CTX, sig: [*]const u8, siglen: usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_PKEY_verify_recover_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_verify_recover(ctx: ?*EVP_PKEY_CTX, rout: ?[*]u8, routlen: *usize, sig: [*]const u8, siglen: usize) c_int;
pub extern fn EVP_PKEY_encrypt_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_encrypt(ctx: ?*EVP_PKEY_CTX, out: ?[*]u8, outlen: *usize, in: [*]const u8, inlen: usize) c_int;

View File

@ -8,6 +8,7 @@ const ossl = @import("openssl.zig");
pub const max_modulus_bytes: usize = config.rsa_max_key_bits / 8;
pub const max_sig_bytes: usize = max_modulus_bytes;
pub const pkcs1_v15_min_overhead: usize = 11;
const component_count = 8;
pub const Error = error{Crypto};
@ -235,6 +236,18 @@ pub fn verify(pc: PublicComponents, p: SignParams, data: []const u8, sig: []cons
return if (ossl.EVP_PKEY_verify(ctx, sig.ptr, sig.len, data.ptr, data.len) == 1) .ok else .invalid;
}
pub fn recover(pc: PublicComponents, sig: []const u8, out: []u8) Error!usize {
const pkey = try buildPublic(pc);
defer ossl.EVP_PKEY_free(pkey);
const ctx = ossl.EVP_PKEY_CTX_new(pkey, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_verify_recover_init(ctx) <= 0) return Error.Crypto;
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_pkcs1) <= 0) return Error.Crypto;
var outlen: usize = out.len;
if (ossl.EVP_PKEY_verify_recover(ctx, out.ptr, &outlen, sig.ptr, sig.len) <= 0) return Error.Crypto;
return outlen;
}
fn applyCryptPadding(ctx: ?*ossl.EVP_PKEY_CTX, p: CryptParams) Error!void {
if (p.scheme == .oaep) {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_oaep) <= 0) return Error.Crypto;
@ -341,6 +354,18 @@ test "PKCS#1 v1.5 encrypt/decrypt round-trips" {
try std.testing.expectEqualSlices(u8, msg, pt[0..pn]);
}
test "RSA sign-recover then verify-recover returns the original message" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: SignParams = .{ .scheme = .pkcs1, .digest = .none };
const msg = "recoverable enterprise payload";
var sig: [max_sig_bytes]u8 = undefined;
const n = try sign(testPriv(&g), params, msg, &sig);
var rec: [max_modulus_bytes]u8 = undefined;
const m = try recover(testPub(&g), sig[0..n], &rec);
try std.testing.expectEqualSlices(u8, msg, rec[0..m]);
}
test "OAEP-SHA256 encrypt/decrypt round-trips" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();

View File

@ -82,6 +82,8 @@ test "hand-coded structs match OASIS-translated layout byte-for-byte" {
try expectSameLayout(ck.CK_ATTRIBUTE, p11c.CK_ATTRIBUTE);
try expectSameLayout(ck.CK_MECHANISM, p11c.CK_MECHANISM);
try expectSameLayout(ck.CK_GCM_PARAMS, p11c.CK_GCM_PARAMS);
try expectSameLayout(ck.CK_RSA_PKCS_PSS_PARAMS, p11c.CK_RSA_PKCS_PSS_PARAMS);
try expectSameLayout(ck.CK_RSA_PKCS_OAEP_PARAMS, p11c.CK_RSA_PKCS_OAEP_PARAMS);
try expectSameLayout(ck.CK_ECDH1_DERIVE_PARAMS, p11c.CK_ECDH1_DERIVE_PARAMS);
try expectSameLayout(ck.CK_DATE, p11c.CK_DATE);
try expectSameLayout(ck.CK_C_INITIALIZE_ARGS, p11c.CK_C_INITIALIZE_ARGS);