diff --git a/PROJECTS/advanced/hsm-emulator/examples/smoke.zig b/PROJECTS/advanced/hsm-emulator/examples/smoke.zig index 97bfd7db..fc12a248 100644 --- a/PROJECTS/advanced/hsm-emulator/examples/smoke.zig +++ b/PROJECTS/advanced/hsm-emulator/examples/smoke.zig @@ -357,6 +357,141 @@ pub fn main() !void { }; if (f.C_GetAttributeValue.?(h, h_rsapriv, &rsaval_q, rsaval_q.len) != ck.CKR_ATTRIBUTE_SENSITIVE) return error.RsaPrivNotSensitive; + var ecdh_kpgen = ck.CK_MECHANISM{ .mechanism = ck.CKM_EC_KEY_PAIR_GEN, .pParameter = null, .ulParameterLen = 0 }; + var derive_pub_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_EC_PARAMS, .pValue = &ec_params, .ulValueLen = ec_params.len }, + }; + var derive_priv_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_DERIVE, .pValue = &ck_yes, .ulValueLen = 1 }, + .{ .type = ck.CKA_PRIVATE, .pValue = &ck_false, .ulValueLen = 1 }, + }; + var h_pubA: ck.CK_OBJECT_HANDLE = 0; + var h_privA: ck.CK_OBJECT_HANDLE = 0; + var h_pubB: ck.CK_OBJECT_HANDLE = 0; + var h_privB: ck.CK_OBJECT_HANDLE = 0; + try check("C_GenerateKeyPair(ECDH A)", f.C_GenerateKeyPair.?(h, &ecdh_kpgen, &derive_pub_tmpl, derive_pub_tmpl.len, &derive_priv_tmpl, derive_priv_tmpl.len, &h_pubA, &h_privA)); + try check("C_GenerateKeyPair(ECDH B)", f.C_GenerateKeyPair.?(h, &ecdh_kpgen, &derive_pub_tmpl, derive_pub_tmpl.len, &derive_priv_tmpl, derive_priv_tmpl.len, &h_pubB, &h_privB)); + + var ptA: [67]u8 = undefined; + var ptB: [67]u8 = undefined; + var ptA_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_EC_POINT, .pValue = &ptA, .ulValueLen = ptA.len }}; + var ptB_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_EC_POINT, .pValue = &ptB, .ulValueLen = ptB.len }}; + try check("C_GetAttributeValue(EC_POINT A)", f.C_GetAttributeValue.?(h, h_pubA, &ptA_q, ptA_q.len)); + try check("C_GetAttributeValue(EC_POINT B)", f.C_GetAttributeValue.?(h, h_pubB, &ptB_q, ptB_q.len)); + + var derive_value_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) }, + .{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_generic, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) }, + .{ .type = ck.CKA_SENSITIVE, .pValue = &ck_false, .ulValueLen = 1 }, + .{ .type = ck.CKA_EXTRACTABLE, .pValue = &ck_yes, .ulValueLen = 1 }, + }; + const lenB: usize = @intCast(ptB_q[0].ulValueLen); + const ptB_raw = ptB[2..lenB]; + var paramsA = ck.CK_ECDH1_DERIVE_PARAMS{ .kdf = ck.CKD_NULL, .ulSharedDataLen = 0, .pSharedData = null, .ulPublicDataLen = ptB_raw.len, .pPublicData = ptB_raw.ptr }; + var paramsB = ck.CK_ECDH1_DERIVE_PARAMS{ .kdf = ck.CKD_NULL, .ulSharedDataLen = 0, .pSharedData = null, .ulPublicDataLen = ptA_q[0].ulValueLen, .pPublicData = &ptA }; + var ecdh_mechA = ck.CK_MECHANISM{ .mechanism = ck.CKM_ECDH1_DERIVE, .pParameter = ¶msA, .ulParameterLen = @sizeOf(ck.CK_ECDH1_DERIVE_PARAMS) }; + var ecdh_mechB = ck.CK_MECHANISM{ .mechanism = ck.CKM_ECDH1_DERIVE, .pParameter = ¶msB, .ulParameterLen = @sizeOf(ck.CK_ECDH1_DERIVE_PARAMS) }; + var h_secretA: ck.CK_OBJECT_HANDLE = 0; + var h_secretB: ck.CK_OBJECT_HANDLE = 0; + try check("C_DeriveKey(A uses raw peer point)", f.C_DeriveKey.?(h, &ecdh_mechA, h_privA, &derive_value_tmpl, derive_value_tmpl.len, &h_secretA)); + try check("C_DeriveKey(B uses DER peer point)", f.C_DeriveKey.?(h, &ecdh_mechB, h_privB, &derive_value_tmpl, derive_value_tmpl.len, &h_secretB)); + + var dvA: [48]u8 = undefined; + var dvB: [48]u8 = undefined; + var dvA_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_VALUE, .pValue = &dvA, .ulValueLen = dvA.len }}; + var dvB_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_VALUE, .pValue = &dvB, .ulValueLen = dvB.len }}; + try check("C_GetAttributeValue(derived A)", f.C_GetAttributeValue.?(h, h_secretA, &dvA_q, dvA_q.len)); + try check("C_GetAttributeValue(derived B)", f.C_GetAttributeValue.?(h, h_secretB, &dvB_q, dvB_q.len)); + if (dvA_q[0].ulValueLen != 32 or dvB_q[0].ulValueLen != 32) return error.DerivedLenWrong; + if (!std.mem.eql(u8, dvA[0..32], dvB[0..32])) return error.EcdhDisagree; + + var kek_val: [32]u8 = undefined; + for (0..32) |j| kek_val[j] = @intCast(0xA0 + j); + var kek_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) }, + .{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_aes, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) }, + .{ .type = ck.CKA_VALUE, .pValue = &kek_val, .ulValueLen = kek_val.len }, + .{ .type = ck.CKA_WRAP, .pValue = &ck_yes, .ulValueLen = 1 }, + .{ .type = ck.CKA_UNWRAP, .pValue = &ck_yes, .ulValueLen = 1 }, + }; + var h_kek: ck.CK_OBJECT_HANDLE = 0; + try check("C_CreateObject(KEK)", f.C_CreateObject.?(h, &kek_tmpl, kek_tmpl.len, &h_kek)); + + var target_val = "0123456789abcdef".*; + var target_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) }, + .{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_aes, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) }, + .{ .type = ck.CKA_VALUE, .pValue = &target_val, .ulValueLen = target_val.len }, + .{ .type = ck.CKA_EXTRACTABLE, .pValue = &ck_yes, .ulValueLen = 1 }, + .{ .type = ck.CKA_SENSITIVE, .pValue = &ck_false, .ulValueLen = 1 }, + }; + var h_target: ck.CK_OBJECT_HANDLE = 0; + try check("C_CreateObject(wrap target)", f.C_CreateObject.?(h, &target_tmpl, target_tmpl.len, &h_target)); + + var keywrap_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_KEY_WRAP, .pParameter = null, .ulParameterLen = 0 }; + var wsize: ck.CK_ULONG = 0; + try check("C_WrapKey(size)", f.C_WrapKey.?(h, &keywrap_mech, h_kek, h_target, null, &wsize)); + if (wsize != target_val.len + 8) return error.WrapSizeWrong; + var wrapped: [64]u8 = undefined; + var wrappedlen: ck.CK_ULONG = wrapped.len; + try check("C_WrapKey", f.C_WrapKey.?(h, &keywrap_mech, h_kek, h_target, &wrapped, &wrappedlen)); + if (wrappedlen != target_val.len + 8) return error.WrapLenWrong; + + var unwrap_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) }, + .{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_aes, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) }, + .{ .type = ck.CKA_EXTRACTABLE, .pValue = &ck_yes, .ulValueLen = 1 }, + .{ .type = ck.CKA_SENSITIVE, .pValue = &ck_false, .ulValueLen = 1 }, + }; + var h_unwrapped: ck.CK_OBJECT_HANDLE = 0; + try check("C_UnwrapKey", f.C_UnwrapKey.?(h, &keywrap_mech, h_kek, &wrapped, wrappedlen, &unwrap_tmpl, unwrap_tmpl.len, &h_unwrapped)); + + var uwval: [32]u8 = undefined; + var uwval_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_VALUE, .pValue = &uwval, .ulValueLen = uwval.len }}; + try check("C_GetAttributeValue(unwrapped)", f.C_GetAttributeValue.?(h, h_unwrapped, &uwval_q, uwval_q.len)); + const uwlen: usize = @intCast(uwval_q[0].ulValueLen); + if (uwlen != target_val.len or !std.mem.eql(u8, uwval[0..uwlen], &target_val)) return error.UnwrapMismatch; + + var wrapped2: [64]u8 = undefined; + var wrapped2len: ck.CK_ULONG = wrapped2.len; + if (f.C_WrapKey.?(h, &keywrap_mech, h_kek, h_gen, &wrapped2, &wrapped2len) != ck.CKR_KEY_UNEXTRACTABLE) return error.UnextractableWrapNotRejected; + + var oaep_params = ck.CK_RSA_PKCS_OAEP_PARAMS{ .hashAlg = ck.CKM_SHA256, .mgf = ck.CKG_MGF1_SHA256, .source = ck.CKZ_DATA_SPECIFIED, .pSourceData = null, .ulSourceDataLen = 0 }; + var oaep_wrap_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_RSA_PKCS_OAEP, .pParameter = &oaep_params, .ulParameterLen = @sizeOf(ck.CK_RSA_PKCS_OAEP_PARAMS) }; + var rsawrapped: [256]u8 = undefined; + var rsawrappedlen: ck.CK_ULONG = rsawrapped.len; + try check("C_WrapKey(RSA-OAEP)", f.C_WrapKey.?(h, &oaep_wrap_mech, h_rsapub, h_target, &rsawrapped, &rsawrappedlen)); + if (rsawrappedlen != 256) return error.RsaWrapLenWrong; + var h_rsaunwrapped: ck.CK_OBJECT_HANDLE = 0; + try check("C_UnwrapKey(RSA-OAEP)", f.C_UnwrapKey.?(h, &oaep_wrap_mech, h_rsapriv, &rsawrapped, rsawrappedlen, &unwrap_tmpl, unwrap_tmpl.len, &h_rsaunwrapped)); + var ruwval: [32]u8 = undefined; + var ruwval_q = [_]ck.CK_ATTRIBUTE{.{ .type = ck.CKA_VALUE, .pValue = &ruwval, .ulValueLen = ruwval.len }}; + try check("C_GetAttributeValue(rsa-unwrapped)", f.C_GetAttributeValue.?(h, h_rsaunwrapped, &ruwval_q, ruwval_q.len)); + const ruwlen: usize = @intCast(ruwval_q[0].ulValueLen); + if (ruwlen != target_val.len or !std.mem.eql(u8, ruwval[0..ruwlen], &target_val)) return error.RsaUnwrapMismatch; + + var dk_val = "digest-this-key!".*; + var dk_tmpl = [_]ck.CK_ATTRIBUTE{ + .{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) }, + .{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_generic, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) }, + .{ .type = ck.CKA_VALUE, .pValue = &dk_val, .ulValueLen = dk_val.len }, + }; + var h_dk: ck.CK_OBJECT_HANDLE = 0; + try check("C_CreateObject(digestkey)", f.C_CreateObject.?(h, &dk_tmpl, dk_tmpl.len, &h_dk)); + + var dk_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256, .pParameter = null, .ulParameterLen = 0 }; + var dk_d1: [32]u8 = undefined; + var dk_d1len: ck.CK_ULONG = dk_d1.len; + try check("C_DigestInit(key)", f.C_DigestInit.?(h, &dk_mech)); + try check("C_DigestKey", f.C_DigestKey.?(h, h_dk)); + try check("C_DigestFinal(key)", f.C_DigestFinal.?(h, &dk_d1, &dk_d1len)); + + var dk_d2: [32]u8 = undefined; + var dk_d2len: ck.CK_ULONG = dk_d2.len; + try check("C_DigestInit(value)", f.C_DigestInit.?(h, &dk_mech)); + 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; + try check("C_CloseSession", f.C_CloseSession.?(h)); try check("C_Finalize", f.C_Finalize.?(null)); @@ -372,6 +507,9 @@ pub fn main() !void { std.debug.print(" keygen = C_GenerateKey AES OK; generated key CKA_VALUE is sensitive (unextractable)\n", .{}); std.debug.print(" ecdsa = C_GenerateKeyPair EC P-256 OK; ECDSA-SHA256 sign/verify (+tamper) OK; priv scalar sensitive\n", .{}); std.debug.print(" rsa = C_GenerateKeyPair RSA-2048 OK; SHA256-RSA-PKCS sign/verify (+tamper) + RSA-PKCS enc/dec OK; priv sensitive\n", .{}); + 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", .{}); } fn check(name: []const u8, rv: ck.CK_RV) !void { diff --git a/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig b/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig index 06dd90a0..3d829f15 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/crypto_ops.zig @@ -748,8 +748,10 @@ pub fn C_DigestKey(hSession: ck.CK_SESSION_HANDLE, hKey: ck.CK_OBJECT_HANDLE) ca if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED; const obj = inst.objects.getPtr(hKey) orelse return ck.CKR_KEY_HANDLE_INVALID; if (!object_store.visible(obj, inst.logged_in)) return ck.CKR_KEY_HANDLE_INVALID; - const val = obj.get(ck.CKA_VALUE) orelse return ck.CKR_KEY_HANDLE_INVALID; - sess.digest_op.?.update(val); + if (objectClass(obj) != ck.CKO_SECRET_KEY) return ck.CKR_KEY_INDIGESTIBLE; + const sa = obj.findPtr(ck.CKA_VALUE) orelse return ck.CKR_KEY_INDIGESTIBLE; + if (sa.sealed) return ck.CKR_USER_NOT_LOGGED_IN; + sess.digest_op.?.update(sa.value); return ck.CKR_OK; } diff --git a/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig b/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig index 9f4cbd1d..2e35b344 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/keymgmt.zig @@ -5,10 +5,12 @@ const std = @import("std"); const ck = @import("../ck.zig"); const config = @import("../config.zig"); const state = @import("../core/state.zig"); +const session = @import("../core/session.zig"); const object_store = @import("../core/object_store.zig"); const object = @import("object.zig"); const ecdsa = @import("../crypto/ecdsa.zig"); const rsa = @import("../crypto/rsa.zig"); +const cipher = @import("../crypto/cipher.zig"); const Object = object_store.Object; @@ -217,14 +219,333 @@ pub fn C_GenerateKeyPair(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECH return ck.CKR_OK; } -pub fn C_WrapKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: ck.CK_OBJECT_HANDLE, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV { - return ck.CKR_FUNCTION_NOT_SUPPORTED; +fn buildSecretKeyObject( + inst: *state.Instance, + sess: *session.Session, + template: []const ck.CK_ATTRIBUTE, + value: []const u8, + base_always_sensitive: bool, + base_never_extractable: bool, + phKey: *ck.CK_OBJECT_HANDLE, +) ck.CK_RV { + const allocator = inst.allocator(); + var obj: Object = .{}; + var moved = false; + defer if (!moved) obj.deinit(allocator); + + for (template) |a| { + if (a.type == ck.CKA_VALUE_LEN or a.type == ck.CKA_VALUE) continue; + obj.set(allocator, a.type, attrBytes(a)) catch |e| return object_store.mapSetErr(e); + } + + if (!obj.has(ck.CKA_CLASS)) { + const cls: ck.CK_OBJECT_CLASS = ck.CKO_SECRET_KEY; + obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&cls)) catch |e| return object_store.mapSetErr(e); + } + if (!obj.has(ck.CKA_KEY_TYPE)) { + const kt: ck.CK_KEY_TYPE = ck.CKK_GENERIC_SECRET; + obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&kt)) catch |e| return object_store.mapSetErr(e); + } + obj.set(allocator, ck.CKA_VALUE, value) catch |e| return object_store.mapSetErr(e); + obj.set(allocator, ck.CKA_LOCAL, &[_]u8{ck.CK_FALSE}) catch |e| return object_store.mapSetErr(e); + + if (!obj.has(ck.CKA_SENSITIVE)) obj.set(allocator, ck.CKA_SENSITIVE, &[_]u8{ck.CK_TRUE}) catch |e| return object_store.mapSetErr(e); + if (!obj.has(ck.CKA_EXTRACTABLE)) obj.set(allocator, ck.CKA_EXTRACTABLE, &[_]u8{ck.CK_FALSE}) catch |e| return object_store.mapSetErr(e); + const always_sensitive: u8 = if (obj.getBool(ck.CKA_SENSITIVE) and base_always_sensitive) ck.CK_TRUE else ck.CK_FALSE; + const never_extractable: u8 = if (!obj.getBool(ck.CKA_EXTRACTABLE) and base_never_extractable) ck.CK_TRUE else ck.CK_FALSE; + obj.set(allocator, ck.CKA_ALWAYS_SENSITIVE, &[_]u8{always_sensitive}) catch |e| return object_store.mapSetErr(e); + obj.set(allocator, ck.CKA_NEVER_EXTRACTABLE, &[_]u8{never_extractable}) catch |e| return object_store.mapSetErr(e); + + object.materializeDefaults(&obj, allocator, ck.CKO_SECRET_KEY) catch |e| return object_store.mapSetErr(e); + + moved = true; + return object.insertNew(inst, sess, obj, phKey); } -pub fn C_UnwrapKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: [*]ck.CK_ATTRIBUTE, _: ck.CK_ULONG, _: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV { - return ck.CKR_FUNCTION_NOT_SUPPORTED; +const SecretVal = union(enum) { + ok: []const u8, + err: ck.CK_RV, +}; + +fn wrapTargetValue(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) SecretVal { + const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID }; + if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID }; + if (classOf(obj) != ck.CKO_SECRET_KEY) return .{ .err = ck.CKR_KEY_NOT_WRAPPABLE }; + if (obj.has(ck.CKA_EXTRACTABLE) and !obj.getBool(ck.CKA_EXTRACTABLE)) return .{ .err = ck.CKR_KEY_UNEXTRACTABLE }; + const sa = obj.findPtr(ck.CKA_VALUE) orelse return .{ .err = ck.CKR_KEY_NOT_WRAPPABLE }; + if (sa.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN }; + return .{ .ok = sa.value }; } -pub fn C_DeriveKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: ?[*]ck.CK_ATTRIBUTE, _: ck.CK_ULONG, _: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV { - return ck.CKR_FUNCTION_NOT_SUPPORTED; +fn aesKekValue(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, usage: ck.CK_ATTRIBUTE_TYPE, handle_err: ck.CK_RV, type_err: ck.CK_RV, size_err: ck.CK_RV) SecretVal { + const obj = inst.objects.getPtr(hKey) orelse return .{ .err = handle_err }; + if (!object_store.visible(obj, inst.logged_in)) return .{ .err = handle_err }; + if (classOf(obj) != ck.CKO_SECRET_KEY or keyTypeOf(obj) != ck.CKK_AES) return .{ .err = type_err }; + if (obj.has(usage) and !obj.getBool(usage)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED }; + const sa = obj.findPtr(ck.CKA_VALUE) orelse return .{ .err = handle_err }; + if (sa.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN }; + if (!cipher.validKeyLen(sa.value.len)) return .{ .err = size_err }; + return .{ .ok = sa.value }; +} + +const RsaPubVal = union(enum) { + ok: rsa.PublicComponents, + err: ck.CK_RV, +}; + +fn rsaWrapPublic(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) RsaPubVal { + const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_WRAPPING_KEY_HANDLE_INVALID }; + if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_WRAPPING_KEY_HANDLE_INVALID }; + if (classOf(obj) != ck.CKO_PUBLIC_KEY or keyTypeOf(obj) != ck.CKK_RSA) return .{ .err = ck.CKR_WRAPPING_KEY_TYPE_INCONSISTENT }; + if (obj.has(ck.CKA_WRAP) and !obj.getBool(ck.CKA_WRAP)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED }; + return .{ .ok = .{ + .n = obj.get(ck.CKA_MODULUS) orelse return .{ .err = ck.CKR_WRAPPING_KEY_HANDLE_INVALID }, + .e = obj.get(ck.CKA_PUBLIC_EXPONENT) orelse return .{ .err = ck.CKR_WRAPPING_KEY_HANDLE_INVALID }, + } }; +} + +const RsaPrivVal = union(enum) { + ok: rsa.PrivateComponents, + err: ck.CK_RV, +}; + +fn rsaUnwrapPrivate(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) RsaPrivVal { + const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }; + if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }; + if (classOf(obj) != ck.CKO_PRIVATE_KEY or keyTypeOf(obj) != ck.CKK_RSA) return .{ .err = ck.CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT }; + if (obj.has(ck.CKA_UNWRAP) and !obj.getBool(ck.CKA_UNWRAP)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED }; + if (obj.findPtr(ck.CKA_PRIVATE_EXPONENT)) |da| { + if (da.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN }; + } + return .{ .ok = .{ + .n = obj.get(ck.CKA_MODULUS) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .e = obj.get(ck.CKA_PUBLIC_EXPONENT) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .d = obj.get(ck.CKA_PRIVATE_EXPONENT) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .p = obj.get(ck.CKA_PRIME_1) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .q = obj.get(ck.CKA_PRIME_2) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .dmp1 = obj.get(ck.CKA_EXPONENT_1) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .dmq1 = obj.get(ck.CKA_EXPONENT_2) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + .iqmp = obj.get(ck.CKA_COEFFICIENT) orelse return .{ .err = ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID }, + } }; +} + +fn mgfHash(mgf: ck.CK_RSA_PKCS_MGF_TYPE) ?rsa.Hash { + return switch (mgf) { + ck.CKG_MGF1_SHA256 => .sha256, + ck.CKG_MGF1_SHA384 => .sha384, + ck.CKG_MGF1_SHA512 => .sha512, + else => null, + }; +} + +const OaepVal = union(enum) { + ok: rsa.CryptParams, + err: ck.CK_RV, +}; + +fn oaepParams(pMechanism: *ck.CK_MECHANISM) OaepVal { + const p = pMechanism.pParameter orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID }; + if (pMechanism.ulParameterLen != @sizeOf(ck.CK_RSA_PKCS_OAEP_PARAMS)) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID }; + const op: *const ck.CK_RSA_PKCS_OAEP_PARAMS = @ptrCast(@alignCast(p)); + const h = rsa.Hash.fromMech(op.hashAlg) orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID }; + if (mgfHash(op.mgf) != h) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID }; + if (op.ulSourceDataLen != 0) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID }; + return .{ .ok = .{ .scheme = .oaep, .oaep_hash = h } }; +} + +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(); + defer state.mutex.unlock(); + _ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID; + + const target = switch (wrapTargetValue(inst, hKey)) { + .err => |rv| return rv, + .ok => |v| v, + }; + + switch (pMechanism.mechanism) { + ck.CKM_AES_KEY_WRAP => { + const kek = switch (aesKekValue(inst, hWrappingKey, ck.CKA_WRAP, ck.CKR_WRAPPING_KEY_HANDLE_INVALID, ck.CKR_WRAPPING_KEY_TYPE_INCONSISTENT, ck.CKR_WRAPPING_KEY_SIZE_RANGE)) { + .err => |rv| return rv, + .ok => |v| v, + }; + if (target.len < 2 * cipher.key_wrap_overhead or target.len % cipher.key_wrap_overhead != 0) return ck.CKR_KEY_NOT_WRAPPABLE; + const need: ck.CK_ULONG = @intCast(target.len + cipher.key_wrap_overhead); + if (pWrappedKey == null) { + pulWrappedKeyLen.* = need; + return ck.CKR_OK; + } + if (pulWrappedKeyLen.* < need) { + pulWrappedKeyLen.* = need; + return ck.CKR_BUFFER_TOO_SMALL; + } + const n = cipher.aesKeyWrap(kek, target, pWrappedKey.?[0..@intCast(need)]) catch return ck.CKR_FUNCTION_FAILED; + pulWrappedKeyLen.* = @intCast(n); + return ck.CKR_OK; + }, + ck.CKM_RSA_PKCS_OAEP => { + const params = switch (oaepParams(pMechanism)) { + .err => |rv| return rv, + .ok => |p| p, + }; + const pc = switch (rsaWrapPublic(inst, hWrappingKey)) { + .err => |rv| return rv, + .ok => |c| c, + }; + const need: ck.CK_ULONG = @intCast(pc.n.len); + if (pWrappedKey == null) { + pulWrappedKeyLen.* = need; + return ck.CKR_OK; + } + if (pulWrappedKeyLen.* < need) { + pulWrappedKeyLen.* = need; + return ck.CKR_BUFFER_TOO_SMALL; + } + const n = rsa.encrypt(pc, params, target, pWrappedKey.?[0..@intCast(need)]) catch return ck.CKR_KEY_SIZE_RANGE; + pulWrappedKeyLen.* = @intCast(n); + return ck.CKR_OK; + }, + else => return ck.CKR_MECHANISM_INVALID, + } +} + +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(); + defer state.mutex.unlock(); + const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID; + + const wrapped = pWrappedKey[0..@intCast(ulWrappedKeyLen)]; + const template = if (ulAttributeCount == 0) &[_]ck.CK_ATTRIBUTE{} else pTemplate[0..@intCast(ulAttributeCount)]; + + var buf: [rsa.max_modulus_bytes]u8 = undefined; + defer std.crypto.secureZero(u8, &buf); + + const recovered: []const u8 = switch (pMechanism.mechanism) { + ck.CKM_AES_KEY_WRAP => blk: { + const kek = switch (aesKekValue(inst, hUnwrappingKey, ck.CKA_UNWRAP, ck.CKR_UNWRAPPING_KEY_HANDLE_INVALID, ck.CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT, ck.CKR_UNWRAPPING_KEY_SIZE_RANGE)) { + .err => |rv| return rv, + .ok => |v| v, + }; + if (wrapped.len < 3 * cipher.key_wrap_overhead or wrapped.len % cipher.key_wrap_overhead != 0) return ck.CKR_WRAPPED_KEY_LEN_RANGE; + if (wrapped.len - cipher.key_wrap_overhead > buf.len) return ck.CKR_WRAPPED_KEY_LEN_RANGE; + const n = cipher.aesKeyUnwrap(kek, wrapped, &buf) catch |e| switch (e) { + cipher.WrapError.Integrity => return ck.CKR_WRAPPED_KEY_INVALID, + else => return ck.CKR_WRAPPED_KEY_LEN_RANGE, + }; + break :blk buf[0..n]; + }, + ck.CKM_RSA_PKCS_OAEP => blk: { + const params = switch (oaepParams(pMechanism)) { + .err => |rv| return rv, + .ok => |p| p, + }; + const sc = switch (rsaUnwrapPrivate(inst, hUnwrappingKey)) { + .err => |rv| return rv, + .ok => |c| c, + }; + const n = rsa.decrypt(sc, params, wrapped, &buf) catch return ck.CKR_WRAPPED_KEY_INVALID; + break :blk buf[0..n]; + }, + else => return ck.CKR_MECHANISM_INVALID, + }; + + var final = recovered; + for (template) |a| { + if (a.type == ck.CKA_VALUE_LEN) { + const v = ulongFrom(attrBytes(a)) orelse return ck.CKR_ATTRIBUTE_VALUE_INVALID; + const vlen: usize = @intCast(v); + if (vlen == 0 or vlen > recovered.len) return ck.CKR_ATTRIBUTE_VALUE_INVALID; + final = recovered[0..vlen]; + } + } + + return buildSecretKeyObject(inst, sess, template, final, false, false, phKey); +} + +fn classOf(obj: *const Object) ?ck.CK_OBJECT_CLASS { + const v = obj.get(ck.CKA_CLASS) orelse return null; + if (v.len != @sizeOf(ck.CK_OBJECT_CLASS)) return null; + return std.mem.bytesToValue(ck.CK_OBJECT_CLASS, v[0..@sizeOf(ck.CK_OBJECT_CLASS)]); +} + +fn keyTypeOf(obj: *const Object) ?ck.CK_KEY_TYPE { + const v = obj.get(ck.CKA_KEY_TYPE) orelse return null; + if (v.len != @sizeOf(ck.CK_KEY_TYPE)) return null; + return std.mem.bytesToValue(ck.CK_KEY_TYPE, v[0..@sizeOf(ck.CK_KEY_TYPE)]); +} + +const EcDeriveBase = union(enum) { + ok: struct { + curve: ecdsa.Curve, + scalar: []const u8, + always_sensitive: bool, + never_extractable: bool, + }, + err: ck.CK_RV, +}; + +fn ecDeriveBaseKey(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) EcDeriveBase { + const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID }; + if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID }; + if (classOf(obj) != ck.CKO_PRIVATE_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT }; + if (keyTypeOf(obj) != ck.CKK_EC) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT }; + if (obj.has(ck.CKA_DERIVE) and !obj.getBool(ck.CKA_DERIVE)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED }; + const params = obj.get(ck.CKA_EC_PARAMS) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT }; + const curve = ecdsa.curveFromParams(params) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT }; + const sa = obj.findPtr(ck.CKA_VALUE) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID }; + if (sa.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN }; + if (sa.value.len != curve.scalarLen()) return .{ .err = ck.CKR_FUNCTION_FAILED }; + return .{ .ok = .{ + .curve = curve, + .scalar = sa.value, + .always_sensitive = obj.getBool(ck.CKA_ALWAYS_SENSITIVE), + .never_extractable = obj.getBool(ck.CKA_NEVER_EXTRACTABLE), + } }; +} + +fn peerPointSec1(curve: ecdsa.Curve, data: []const u8) ?[]const u8 { + if (data.len == curve.pointLen()) return data; + const inner = ecdsa.unwrapEcPoint(data) orelse return null; + if (inner.len == curve.pointLen()) return inner; + return null; +} + +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(); + 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; + + const p = pMechanism.pParameter orelse return ck.CKR_MECHANISM_PARAM_INVALID; + if (pMechanism.ulParameterLen != @sizeOf(ck.CK_ECDH1_DERIVE_PARAMS)) return ck.CKR_MECHANISM_PARAM_INVALID; + const dp: *const ck.CK_ECDH1_DERIVE_PARAMS = @ptrCast(@alignCast(p)); + if (dp.kdf != ck.CKD_NULL) return ck.CKR_MECHANISM_PARAM_INVALID; + if (dp.ulSharedDataLen != 0) return ck.CKR_MECHANISM_PARAM_INVALID; + const peer = (dp.pPublicData orelse return ck.CKR_MECHANISM_PARAM_INVALID)[0..@intCast(dp.ulPublicDataLen)]; + + const base = switch (ecDeriveBaseKey(inst, hBaseKey)) { + .err => |rv| return rv, + .ok => |b| b, + }; + + const peer_sec1 = peerPointSec1(base.curve, peer) orelse return ck.CKR_MECHANISM_PARAM_INVALID; + var secret: [ecdsa.max_scalar]u8 = undefined; + defer std.crypto.secureZero(u8, &secret); + const slen = ecdsa.ecdh(base.curve, base.scalar, peer_sec1, &secret) catch return ck.CKR_FUNCTION_FAILED; + + const template = if (ulCount == 0) &[_]ck.CK_ATTRIBUTE{} else (pTemplate orelse return ck.CKR_ARGUMENTS_BAD)[0..@intCast(ulCount)]; + + var value_len: usize = slen; + for (template) |a| { + if (a.type == ck.CKA_VALUE_LEN) { + const v = ulongFrom(attrBytes(a)) orelse return ck.CKR_ATTRIBUTE_VALUE_INVALID; + value_len = @intCast(v); + } + } + if (value_len == 0 or value_len > slen) return ck.CKR_ATTRIBUTE_VALUE_INVALID; + + return buildSecretKeyObject(inst, sess, template, secret[0..value_len], base.always_sensitive, base.never_extractable, phKey); } diff --git a/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig b/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig index e2c4545b..e14777c1 100644 --- a/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig +++ b/PROJECTS/advanced/hsm-emulator/src/api/slot_token.zig @@ -139,6 +139,11 @@ pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE, .ulMaxKeySize = config.aes_max_key_bytes, .flags = ck.CKF_ENCRYPT | ck.CKF_DECRYPT, }, + ck.CKM_AES_KEY_WRAP => .{ + .ulMinKeySize = config.aes_min_key_bytes, + .ulMaxKeySize = config.aes_max_key_bytes, + .flags = ck.CKF_WRAP | ck.CKF_UNWRAP, + }, ck.CKM_EC_KEY_PAIR_GEN => .{ .ulMinKeySize = config.ec_min_key_bits, .ulMaxKeySize = config.ec_max_key_bits, @@ -149,6 +154,11 @@ pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE, .ulMaxKeySize = config.ec_max_key_bits, .flags = ck.CKF_SIGN | ck.CKF_VERIFY | ck.CKF_EC_NAMEDCURVE, }, + ck.CKM_ECDH1_DERIVE => .{ + .ulMinKeySize = config.ec_min_key_bits, + .ulMaxKeySize = config.ec_max_key_bits, + .flags = ck.CKF_DERIVE | ck.CKF_EC_NAMEDCURVE, + }, ck.CKM_RSA_PKCS_KEY_PAIR_GEN => .{ .ulMinKeySize = config.rsa_min_key_bits, .ulMaxKeySize = config.rsa_max_key_bits, @@ -167,7 +177,7 @@ pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE, ck.CKM_RSA_PKCS_OAEP => .{ .ulMinKeySize = config.rsa_min_key_bits, .ulMaxKeySize = config.rsa_max_key_bits, - .flags = ck.CKF_ENCRYPT | ck.CKF_DECRYPT, + .flags = ck.CKF_ENCRYPT | ck.CKF_DECRYPT | ck.CKF_WRAP | ck.CKF_UNWRAP, }, else => return ck.CKR_MECHANISM_INVALID, }; diff --git a/PROJECTS/advanced/hsm-emulator/src/ck.zig b/PROJECTS/advanced/hsm-emulator/src/ck.zig index c13141a0..f0bf9cbd 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_INDIGESTIBLE: CK_RV = 0x00000067; pub const CKR_KEY_FUNCTION_NOT_PERMITTED: CK_RV = 0x00000068; pub const CKR_KEY_NOT_WRAPPABLE: CK_RV = 0x00000069; pub const CKR_KEY_UNEXTRACTABLE: CK_RV = 0x0000006A; @@ -84,12 +85,20 @@ pub const CKR_TEMPLATE_INCONSISTENT: CK_RV = 0x000000D1; pub const CKR_TOKEN_NOT_PRESENT: CK_RV = 0x000000E0; pub const CKR_TOKEN_NOT_RECOGNIZED: CK_RV = 0x000000E1; pub const CKR_TOKEN_WRITE_PROTECTED: CK_RV = 0x000000E2; +pub const CKR_UNWRAPPING_KEY_HANDLE_INVALID: CK_RV = 0x000000F0; +pub const CKR_UNWRAPPING_KEY_SIZE_RANGE: CK_RV = 0x000000F1; +pub const CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT: CK_RV = 0x000000F2; pub const CKR_USER_ALREADY_LOGGED_IN: CK_RV = 0x00000100; pub const CKR_USER_NOT_LOGGED_IN: CK_RV = 0x00000101; pub const CKR_USER_PIN_NOT_INITIALIZED: CK_RV = 0x00000102; pub const CKR_USER_TYPE_INVALID: CK_RV = 0x00000103; pub const CKR_USER_ANOTHER_ALREADY_LOGGED_IN: CK_RV = 0x00000104; pub const CKR_USER_TOO_MANY_TYPES: CK_RV = 0x00000105; +pub const CKR_WRAPPED_KEY_INVALID: CK_RV = 0x00000110; +pub const CKR_WRAPPED_KEY_LEN_RANGE: CK_RV = 0x00000112; +pub const CKR_WRAPPING_KEY_HANDLE_INVALID: CK_RV = 0x00000113; +pub const CKR_WRAPPING_KEY_SIZE_RANGE: CK_RV = 0x00000114; +pub const CKR_WRAPPING_KEY_TYPE_INCONSISTENT: CK_RV = 0x00000115; pub const CKR_RANDOM_SEED_NOT_SUPPORTED: CK_RV = 0x00000120; pub const CKR_RANDOM_NO_RNG: CK_RV = 0x00000121; pub const CKR_DOMAIN_PARAMS_INVALID: CK_RV = 0x00000130; @@ -153,10 +162,12 @@ pub const CKM_ECDSA: CK_MECHANISM_TYPE = 0x00001041; pub const CKM_ECDSA_SHA256: CK_MECHANISM_TYPE = 0x00001044; pub const CKM_ECDSA_SHA384: CK_MECHANISM_TYPE = 0x00001045; pub const CKM_ECDSA_SHA512: CK_MECHANISM_TYPE = 0x00001046; +pub const CKM_ECDH1_DERIVE: CK_MECHANISM_TYPE = 0x00001050; pub const CKM_AES_KEY_GEN: CK_MECHANISM_TYPE = 0x00001080; pub const CKM_AES_CBC: CK_MECHANISM_TYPE = 0x00001082; pub const CKM_AES_CBC_PAD: CK_MECHANISM_TYPE = 0x00001085; pub const CKM_AES_GCM: CK_MECHANISM_TYPE = 0x00001087; +pub const CKM_AES_KEY_WRAP: CK_MECHANISM_TYPE = 0x00002109; pub const CKA_CLASS: CK_ATTRIBUTE_TYPE = 0x00000000; pub const CKA_TOKEN: CK_ATTRIBUTE_TYPE = 0x00000001; @@ -339,6 +350,17 @@ pub const CK_RSA_PKCS_OAEP_PARAMS = extern struct { ulSourceDataLen: CK_ULONG, }; +pub const CK_EC_KDF_TYPE = CK_ULONG; +pub const CKD_NULL: CK_EC_KDF_TYPE = 0x00000001; + +pub const CK_ECDH1_DERIVE_PARAMS = extern struct { + kdf: CK_EC_KDF_TYPE, + ulSharedDataLen: CK_ULONG, + pSharedData: ?[*]CK_BYTE, + ulPublicDataLen: CK_ULONG, + pPublicData: ?[*]CK_BYTE, +}; + pub const CK_DATE = extern struct { year: [4]CK_CHAR, month: [2]CK_CHAR, diff --git a/PROJECTS/advanced/hsm-emulator/src/config.zig b/PROJECTS/advanced/hsm-emulator/src/config.zig index 7ee44fe9..15a26fb2 100644 --- a/PROJECTS/advanced/hsm-emulator/src/config.zig +++ b/PROJECTS/advanced/hsm-emulator/src/config.zig @@ -80,10 +80,12 @@ pub const supported_mechanisms = [_]ck.CK_MECHANISM_TYPE{ ck.CKM_EC_KEY_PAIR_GEN, ck.CKM_ECDSA, ck.CKM_ECDSA_SHA256, + ck.CKM_ECDH1_DERIVE, ck.CKM_RSA_PKCS_KEY_PAIR_GEN, ck.CKM_RSA_PKCS, ck.CKM_SHA256_RSA_PKCS, ck.CKM_RSA_PKCS_PSS, ck.CKM_SHA256_RSA_PKCS_PSS, ck.CKM_RSA_PKCS_OAEP, + ck.CKM_AES_KEY_WRAP, }; diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig index 375ade2e..1e01c864 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/cipher.zig @@ -199,6 +199,89 @@ pub fn decryptOutLen(mode: Mode, in_len: usize) usize { }; } +pub const WrapError = error{ KeySize, DataLen, Integrity }; + +pub const key_wrap_overhead = 8; + +const key_wrap_iv = [8]u8{ 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 }; + +fn xorCounter(a: *[8]u8, t: u64) void { + var tb: [8]u8 = undefined; + std.mem.writeInt(u64, &tb, t, .big); + for (a, tb) |*x, y| x.* ^= y; +} + +pub fn aesKeyWrap(kek: []const u8, plain: []const u8, out: []u8) WrapError!usize { + if (kek.len != config.aes_min_key_bytes and kek.len != config.aes_max_key_bytes) return WrapError.KeySize; + if (plain.len < 2 * key_wrap_overhead or plain.len % key_wrap_overhead != 0) return WrapError.DataLen; + std.debug.assert(out.len >= plain.len + key_wrap_overhead); + const n = plain.len / key_wrap_overhead; + @memcpy(out[key_wrap_overhead..][0..plain.len], plain); + const r = out[key_wrap_overhead..][0..plain.len]; + var a: [8]u8 = key_wrap_iv; + var blk: [block]u8 = undefined; + var enc: [block]u8 = undefined; + defer { + std.crypto.secureZero(u8, &a); + std.crypto.secureZero(u8, &blk); + std.crypto.secureZero(u8, &enc); + } + var j: usize = 0; + while (j < 6) : (j += 1) { + var i: usize = 0; + while (i < n) : (i += 1) { + @memcpy(blk[0..8], &a); + @memcpy(blk[8..16], r[i * 8 ..][0..8]); + encBlockRaw(kek, &blk, &enc); + @memcpy(&a, enc[0..8]); + xorCounter(&a, n * j + i + 1); + @memcpy(r[i * 8 ..][0..8], enc[8..16]); + } + } + @memcpy(out[0..8], &a); + return plain.len + key_wrap_overhead; +} + +pub fn aesKeyUnwrap(kek: []const u8, wrapped: []const u8, out: []u8) WrapError!usize { + if (kek.len != config.aes_min_key_bytes and kek.len != config.aes_max_key_bytes) return WrapError.KeySize; + if (wrapped.len < 3 * key_wrap_overhead or wrapped.len % key_wrap_overhead != 0) return WrapError.DataLen; + const n = wrapped.len / key_wrap_overhead - 1; + const plain_len = n * key_wrap_overhead; + std.debug.assert(out.len >= plain_len); + @memcpy(out[0..plain_len], wrapped[key_wrap_overhead..][0..plain_len]); + const r = out[0..plain_len]; + var a: [8]u8 = undefined; + @memcpy(&a, wrapped[0..8]); + var blk: [block]u8 = undefined; + var dec: [block]u8 = undefined; + defer { + std.crypto.secureZero(u8, &a); + std.crypto.secureZero(u8, &blk); + std.crypto.secureZero(u8, &dec); + } + var j: usize = 6; + while (j > 0) { + j -= 1; + var i: usize = n; + while (i > 0) { + i -= 1; + xorCounter(&a, n * j + i + 1); + @memcpy(blk[0..8], &a); + @memcpy(blk[8..16], r[i * 8 ..][0..8]); + decBlockRaw(kek, &blk, &dec); + @memcpy(&a, dec[0..8]); + @memcpy(r[i * 8 ..][0..8], dec[8..16]); + } + } + var diff: u8 = 0; + for (a, key_wrap_iv) |x, y| diff |= x ^ y; + if (diff != 0) { + std.crypto.secureZero(u8, out[0..plain_len]); + return WrapError.Integrity; + } + return plain_len; +} + fn testKey() [32]u8 { var k: [32]u8 = undefined; for (0..32) |j| k[j] = @intCast(j); @@ -300,3 +383,46 @@ test "GCM round-trips and rejects a tampered tag" { d2.iv = [_]u8{7} ** 12; try std.testing.expectError(Error.EncryptedDataInvalid, d2.gcmDecrypt(ct[0..cn], &back)); } + +test "AES-128 key wrap matches the RFC 3394 section 4.1 KAT and unwraps" { + const kek = [_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + const plain = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + const expect = [_]u8{ 0x1f, 0xa6, 0x8b, 0x0a, 0x81, 0x12, 0xb4, 0x47, 0xae, 0xf3, 0x4b, 0xd8, 0xfb, 0x5a, 0x7b, 0x82, 0x9d, 0x3e, 0x86, 0x23, 0x71, 0xd2, 0xcf, 0xe5 }; + + var out: [24]u8 = undefined; + const n = try aesKeyWrap(&kek, &plain, &out); + try std.testing.expectEqual(@as(usize, 24), n); + try std.testing.expectEqualSlices(u8, &expect, &out); + + var back: [16]u8 = undefined; + const m = try aesKeyUnwrap(&kek, &out, &back); + try std.testing.expectEqual(@as(usize, 16), m); + try std.testing.expectEqualSlices(u8, &plain, &back); +} + +test "AES-256 key wrap round-trips 256-bit key material and rejects tampering" { + const kek = testKey(); + var plain: [32]u8 = undefined; + for (0..32) |k| plain[k] = @intCast((k * 11) & 0xff); + + var wrapped: [40]u8 = undefined; + const n = try aesKeyWrap(&kek, &plain, &wrapped); + try std.testing.expectEqual(@as(usize, 40), n); + + var back: [32]u8 = undefined; + const m = try aesKeyUnwrap(&kek, &wrapped, &back); + try std.testing.expectEqual(@as(usize, 32), m); + try std.testing.expectEqualSlices(u8, &plain, &back); + + wrapped[3] ^= 0x01; + try std.testing.expectError(WrapError.Integrity, aesKeyUnwrap(&kek, &wrapped, &back)); +} + +test "AES key wrap rejects bad input and KEK lengths" { + const kek = testKey(); + var out: [64]u8 = undefined; + try std.testing.expectError(WrapError.DataLen, aesKeyWrap(&kek, &[_]u8{0} ** 8, &out)); + try std.testing.expectError(WrapError.DataLen, aesKeyWrap(&kek, &[_]u8{0} ** 20, &out)); + try std.testing.expectError(WrapError.DataLen, aesKeyUnwrap(&kek, &[_]u8{0} ** 16, &out)); + try std.testing.expectError(WrapError.KeySize, aesKeyWrap(&[_]u8{0} ** 24, &[_]u8{0} ** 16, &out)); +} diff --git a/PROJECTS/advanced/hsm-emulator/src/crypto/ecdsa.zig b/PROJECTS/advanced/hsm-emulator/src/crypto/ecdsa.zig index 882d702c..aed244c2 100644 --- a/PROJECTS/advanced/hsm-emulator/src/crypto/ecdsa.zig +++ b/PROJECTS/advanced/hsm-emulator/src/crypto/ecdsa.zig @@ -308,6 +308,25 @@ fn verifyImpl(comptime Scheme: type, point: []const u8, prehash: []const u8, sig return .ok; } +pub fn ecdh(curve: Curve, scalar: []const u8, peer_point_sec1: []const u8, out: []u8) Error!usize { + return switch (curve) { + .p256 => ecdhImpl(std.crypto.ecc.P256, 32, scalar, peer_point_sec1, out), + .p384 => ecdhImpl(std.crypto.ecc.P384, 48, scalar, peer_point_sec1, out), + }; +} + +fn ecdhImpl(comptime Pt: type, comptime n: usize, scalar: []const u8, peer_point_sec1: []const u8, out: []u8) Error!usize { + if (scalar.len != n or out.len < n) return Error.Crypto; + const peer = Pt.fromSec1(peer_point_sec1) catch return Error.Crypto; + var s: [n]u8 = undefined; + defer std.crypto.secureZero(u8, &s); + @memcpy(&s, scalar[0..n]); + const shared = peer.mul(s, .big) catch return Error.Crypto; + const xb = shared.affineCoordinates().x.toBytes(.big); + @memcpy(out[0..n], xb[0..n]); + return n; +} + fn hexToBytes(comptime hex: []const u8) [hex.len / 2]u8 { var out: [hex.len / 2]u8 = undefined; _ = std.fmt.hexToBytes(&out, hex) catch unreachable; @@ -406,6 +425,33 @@ test "wrong-length signature reports len_range" { try std.testing.expectEqual(VerifyResult.len_range, v.finalVerify(&[_]u8{0} ** 63)); } +test "ECDH P-256 shared secret agrees on both sides and rejects a bad point" { + const io = std.testing.io; + const a = try generate(io, .p256); + const b = try generate(io, .p256); + var sa: [max_scalar]u8 = undefined; + var sb: [max_scalar]u8 = undefined; + const na = try ecdh(.p256, a.scalarBytes(), b.pointBytes(), &sa); + const nb = try ecdh(.p256, b.scalarBytes(), a.pointBytes(), &sb); + try std.testing.expectEqual(@as(usize, 32), na); + try std.testing.expectEqualSlices(u8, sa[0..na], sb[0..nb]); + + const bad = [_]u8{0x04} ++ [_]u8{0xff} ** 64; + try std.testing.expectError(Error.Crypto, ecdh(.p256, a.scalarBytes(), &bad, &sa)); +} + +test "ECDH P-384 shared secret agrees on both sides" { + const io = std.testing.io; + const a = try generate(io, .p384); + const b = try generate(io, .p384); + var sa: [max_scalar]u8 = undefined; + var sb: [max_scalar]u8 = undefined; + const na = try ecdh(.p384, a.scalarBytes(), b.pointBytes(), &sa); + const nb = try ecdh(.p384, b.scalarBytes(), a.pointBytes(), &sb); + try std.testing.expectEqual(@as(usize, 48), na); + try std.testing.expectEqualSlices(u8, sa[0..na], sb[0..nb]); +} + test "curve OID mapping and EC point DER round-trip" { try std.testing.expectEqual(Curve.p256, curveFromParams(&oid_p256).?); try std.testing.expectEqual(Curve.p384, curveFromParams(&oid_p384).?); diff --git a/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig b/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig index d7a1278d..8f2c6c91 100644 --- a/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig +++ b/PROJECTS/advanced/hsm-emulator/tests/abi_test.zig @@ -82,6 +82,7 @@ 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_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); }