@transmission-dynamics/pkcs11js
Version:
A Node.js implementation of the PKCS#11 2.40 interface
296 lines (262 loc) • 7.09 kB
JavaScript
const pkcs11 = require("./build/Release/pkcs11.node");
const util = require("node:util");
class NativeError extends Error {
constructor(message, method) {
super(message || "");
this.name = NativeError.name;
this.method = method || "";
this.nativeStack = "";
const messages = this.message.split("\n");
const matches = /(\w+):(\d+)/.exec(this.message);
if (matches) {
this.message = matches[1];
this.code = +matches[2];
if (messages.length > 1) {
const stackMatch = /at PKCS11\.(\w+) \(/.exec(messages[1]);
if (stackMatch) {
this.method = stackMatch[1];
}
this.nativeStack = messages.slice(1).join("\n");
}
} else {
this.message = messages[0];
this.code = 0;
}
}
}
pkcs11.NativeError = NativeError;
class Pkcs11Error extends NativeError {
constructor(message, code, method) {
super(message, method);
this.name = Pkcs11Error.name;
this.code = code || 0;
const matches = new RegExp(Pkcs11Error.messageReg).exec(message);
if (matches) {
this.message = matches[1];
this.code = +matches[2];
}
}
}
Pkcs11Error.messageReg = /(CKR_[^:]+):(\d+)/;
Pkcs11Error.isPkcs11 = function isPkcs11(message) {
return new RegExp(Pkcs11Error.messageReg).test(message);
}
pkcs11.Pkcs11Error = Pkcs11Error;
function prepareError(e) {
if (Pkcs11Error.isPkcs11(e.stack)) {
return new Pkcs11Error(e.stack);
}
if (e instanceof TypeError) {
return e;
}
return new NativeError(e.message);
}
function handleError(e) {
throw prepareError(e);
}
/**
* Catches and wraps PKCS#11 errors to Pkcs11Error
* @param fn
*/
function catchError(fn) {
return function (...args) {
try {
const res = fn.apply(this, args);
if (res instanceof Promise) {
return res.catch((e) => {
handleError(e);
});
}
return res;
} catch (e) {
handleError(e);
}
};
}
function modifyCallback(cb, data, useSubarray) {
if (typeof cb === "function") {
return function (err, dataOrLength) {
if (err) {
return cb(prepareError(err), null);
}
if (useSubarray) {
// Call the callback with subarray
cb(null, data.subarray(0, dataOrLength));
} else {
// Call the original callback
cb(null, dataOrLength);
}
};
}
}
function modifyMethod(key, obj, config) {
const oldMethod = obj[key];
const callbackMethodName = key + "Callback";
// Modified method
obj[key] = function (...args) {
// Handle callback logic if callbackIndex is defined
if (config.callbackIndex !== undefined && args.length > config.callbackIndex) {
return this[callbackMethodName].apply(this, args);
}
// Execute the original method
const result = oldMethod.apply(this, args);
// If outputIndex is defined, modify the output
if (config.outputIndex !== undefined) {
return args[config.outputIndex].subarray(0, result);
}
// Return the original result if outputIndex is not defined
return result;
};
// If callbackIndex is defined, update the callback method and promisify it
if (config.callbackIndex !== undefined) {
const oldCallbackMethod = obj[callbackMethodName];
obj[callbackMethodName] = function (...args) {
if (typeof args[config.callbackIndex] === "function") {
args[config.callbackIndex] = modifyCallback(args[config.callbackIndex], args[config.outputIndex], config.outputIndex !== undefined);
}
return oldCallbackMethod.apply(this, args);
};
// Promisify the callback method
const asyncMethodName = key + "Async";
obj[asyncMethodName] = util.promisify(obj[callbackMethodName]);
}
}
function modify(obj) {
for (const key in obj) {
const method = obj[key];
if (typeof method !== "function" || !key.startsWith("C_")) {
continue;
}
obj[key] = catchError(method);
switch (key) {
case "C_FindObjects": {
obj[key] = function (...args) {
if (args.length < 2) {
args.push(1);
return method.apply(obj, args)[0] || null;
}
return method.apply(obj, args);
};
break;
}
case "C_Digest":
case "C_Sign":
case "C_Encrypt":
case "C_Decrypt":
{
modifyMethod(key, obj, {
outputIndex: 2,
callbackIndex: 3,
});
break;
}
case "C_DigestFinal":
case "C_SignFinal":
case "C_EncryptFinal":
case "C_DecryptFinal":
{
modifyMethod(key, obj, {
outputIndex: 1,
callbackIndex: 2,
});
break;
}
case "C_DecryptUpdate":
case "C_EncryptUpdate":
case "C_SignRecover":
case "C_VerifyRecover":
{
modifyMethod(key, obj, {
outputIndex: 2,
});
break;
}
case "C_Verify":
case "C_GenerateKey":
{
modifyMethod(key, obj, {
callbackIndex: 3,
});
break;
}
case "C_VerifyFinal":
{
modifyMethod(key, obj, {
callbackIndex: 2,
});
break;
}
case "C_GenerateKeyPair":
case "C_DeriveKey":
{
modifyMethod(key, obj, {
callbackIndex: 4,
});
break;
}
case "C_WrapKey":
{
modifyMethod(key, obj, {
outputIndex: 4,
callbackIndex: 5,
});
break;
}
case "C_UnwrapKey":
{
modifyMethod(key, obj, {
callbackIndex: 5,
});
break;
}
case "C_DigestEncryptUpdate":
case "C_DecryptDigestUpdate":
case "C_SignEncryptUpdate":
case "C_DecryptVerifyUpdate":
{
modifyMethod(key, obj, {
outputIndex: 2,
callbackIndex: 3,
});
break;
}
}
}
}
function processAttributes(attrs) {
if (attrs && Array.isArray(attrs)) {
for (const attr of attrs) {
if (attr.type === pkcs11.CKA_START_DATE || attr.type === pkcs11.CKA_END_DATE) {
if (attr.value instanceof Date) {
attr.value = Buffer.from(attr.value.toISOString().slice(0, 10).replace(/-/g, ""));
}
}
}
}
}
class PKCS11 extends pkcs11.PKCS11 {
constructor(library) {
super();
modify(this);
if (library) {
this.load(library);
}
}
load(library) {
super.load(library);
this.libPath = library;
}
C_SetAttributeValue(session, object, attrs) {
processAttributes(attrs);
return super.C_SetAttributeValue(session, object, attrs);
}
C_CreateObject(session, attrs) {
processAttributes(attrs);
return super.C_CreateObject(session, attrs);
}
C_CopyObject(session, object, attrs) {
processAttributes(attrs);
return super.C_CopyObject(session, object, attrs);
}
}
module.exports = { ...pkcs11, PKCS11 };