feat(hsm-emulator): M9 key management — wrap/unwrap, derive, digest-key

- C_WrapKey/C_UnwrapKey: CKM_AES_KEY_WRAP (RFC 3394) + CKM_RSA_PKCS_OAEP, secret keys only
- new AES-KEY-WRAP primitive in cipher.zig (RFC 3394 KAT-verified, fail-closed, zeroized temps)
- C_DeriveKey: factor shared secret-key-object builder; accept raw SEC1 or DER peer point
- C_DigestKey: secret-key class + sealed-guard (CKR_KEY_INDIGESTIBLE / CKR_USER_NOT_LOGGED_IN)
- advertise CKM_ECDH1_DERIVE + CKM_AES_KEY_WRAP; add WRAP/UNWRAP to RSA-PKCS-OAEP
- 9 OASIS-exact CKR_* wrap/unwrap/digest error codes
- 70/70 tests; proven via in-process smoke + cross-process pkcs11-tool (OpenSSL + RFC 3394 oracles)
This commit is contained in:
CarterPerez-dev 2026-06-01 23:03:53 -04:00
parent ccb1b40a15
commit 0b8f8df8a3
9 changed files with 677 additions and 9 deletions

View File

@ -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 = &paramsA, .ulParameterLen = @sizeOf(ck.CK_ECDH1_DERIVE_PARAMS) };
var ecdh_mechB = ck.CK_MECHANISM{ .mechanism = ck.CKM_ECDH1_DERIVE, .pParameter = &paramsB, .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 {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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,
};

View File

@ -56,6 +56,7 @@ pub const CKR_FUNCTION_NOT_SUPPORTED: CK_RV = 0x00000054;
pub const CKR_KEY_HANDLE_INVALID: CK_RV = 0x00000060;
pub const CKR_KEY_SIZE_RANGE: CK_RV = 0x00000062;
pub const CKR_KEY_TYPE_INCONSISTENT: CK_RV = 0x00000063;
pub const CKR_KEY_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,

View File

@ -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,
};

View File

@ -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));
}

View File

@ -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).?);

View File

@ -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);
}