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:
parent
ccb1b40a15
commit
0b8f8df8a3
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).?);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue