diff --git a/PROJECTS/advanced/hsm-emulator/examples/smoke.zig b/PROJECTS/advanced/hsm-emulator/examples/smoke.zig index fc12a248..876eee58 100644 --- a/PROJECTS/advanced/hsm-emulator/examples/smoke.zig +++ b/PROJECTS/advanced/hsm-emulator/examples/smoke.zig @@ -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 { diff --git a/PROJECTS/advanced/hsm-emulator/learn/CONFORMANCE.md b/PROJECTS/advanced/hsm-emulator/learn/CONFORMANCE.md new file mode 100644 index 00000000..1e49e7c5 --- /dev/null +++ b/PROJECTS/advanced/hsm-emulator/learn/CONFORMANCE.md @@ -0,0 +1,301 @@ + + + +# 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 | 2048–4096 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. diff --git a/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig b/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig index 3d829f15..29f581d5 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig @@ -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; } diff --git a/PROJECTS/advanced/hsm-emulator/src/api/general.zig b/PROJECTS/advanced/hsm-emulator/src/api/general.zig index a67d6a0d..145b2081 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/general.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/general.zig @@ -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; } diff --git a/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig b/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig index 2e35b344..9b531ad5 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/api/object.zig b/PROJECTS/advanced/hsm-emulator/src/api/object.zig index 71516987..88b8ecf6 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/object.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/object.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/api/random.zig b/PROJECTS/advanced/hsm-emulator/src/api/random.zig index 686838ec..e2b224a2 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/random.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/random.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/api/session.zig b/PROJECTS/advanced/hsm-emulator/src/api/session.zig index cd08fb23..8176b298 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/session.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/session.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig b/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig index e14777c1..0af7a11f 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig @@ -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(); diff --git a/PROJECTS/advanced/hsm-emulator/src/ck.zig b/PROJECTS/advanced/hsm-emulator/src/ck.zig index f0bf9cbd..13300329 100644 --- a/PROJECTS/advanced/hsm-emulator/src/ck.zig +++ b/PROJECTS/advanced/hsm-emulator/src/ck.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/config.zig b/PROJECTS/advanced/hsm-emulator/src/config.zig index 15a26fb2..b6cb9830 100644 --- a/PROJECTS/advanced/hsm-emulator/src/config.zig +++ b/PROJECTS/advanced/hsm-emulator/src/config.zig @@ -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, diff --git a/PROJECTS/advanced/hsm-emulator/src/core/object_store.zig b/PROJECTS/advanced/hsm-emulator/src/core/object_store.zig index d9ae4e51..92af6490 100644 --- a/PROJECTS/advanced/hsm-emulator/src/core/object_store.zig +++ b/PROJECTS/advanced/hsm-emulator/src/core/object_store.zig @@ -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 = .{}; diff --git a/PROJECTS/advanced/hsm-emulator/src/core/session.zig b/PROJECTS/advanced/hsm-emulator/src/core/session.zig index 37c7c86e..b9cfda82 100644 --- a/PROJECTS/advanced/hsm-emulator/src/core/session.zig +++ b/PROJECTS/advanced/hsm-emulator/src/core/session.zig @@ -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); } diff --git a/PROJECTS/advanced/hsm-emulator/src/core/state.zig b/PROJECTS/advanced/hsm-emulator/src/core/state.zig index 5a13c0cf..5f53692e 100644 --- a/PROJECTS/advanced/hsm-emulator/src/core/state.zig +++ b/PROJECTS/advanced/hsm-emulator/src/core/state.zig @@ -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; } diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig index 1e01c864..edfc68bf 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig @@ -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]; diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/digest.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/digest.zig index f16a96c1..e2d848a9 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/digest.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/digest.zig @@ -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); +} diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/openssl.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/openssl.zig index 62586958..0efcb01a 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/openssl.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/openssl.zig @@ -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; diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/rsa.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/rsa.zig index c48a706c..825c8741 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/rsa.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/rsa.zig @@ -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(); diff --git a/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig b/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig index 8f2c6c91..b398507f 100644 --- a/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig +++ b/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig @@ -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);