PKCS11js is a package for direct interaction with the PKCS#11 API, the standard interface for interacting with hardware crypto devices such as Smart Cards and Hardware Security Modules (HSMs). It was developed to the PKCS#11 2.40 specification and has been tested with a variety of devices.
Versioning Note:
nan
module, which allowed the package to be built for older versions of Node.js.napi
. As a result, the minimum required Node.js version is now v18.For most use cases, we recommend our package Graphene, which provides a simplistic Object Oriented interface for interacting with PKCS#11 devices.
This was developed to the PKCS#11 2.40 specification. It should be easy enough to extend it for any new versions at a later date.
It has been tested with :
NOTE: For testing purposes it may be easier to work with SoftHSM2 which is a software implementation of PKCS#11 based on OpenSSL or Botan.
$ npm install pkcs11js
https://peculiarventures.github.io/pkcs11js/
var pkcs11js = require("pkcs11js");
var pkcs11 = new pkcs11js.PKCS11();
pkcs11.load("/usr/local/lib/softhsm/libsofthsm2.so");
pkcs11.C_Initialize();
try {
// Getting info about PKCS11 Module
var module_info = pkcs11.C_GetInfo();
// Getting list of slots
var slots = pkcs11.C_GetSlotList(true);
var slot = slots[0];
// Getting info about slot
var slot_info = pkcs11.C_GetSlotInfo(slot);
// Getting info about token
var token_info = pkcs11.C_GetTokenInfo(slot);
// Getting info about Mechanism
var mechs = pkcs11.C_GetMechanismList(slot);
var mech_info = pkcs11.C_GetMechanismInfo(slot, mechs[0]);
var session = pkcs11.C_OpenSession(slot, pkcs11js.CKF_RW_SESSION | pkcs11js.CKF_SERIAL_SESSION);
// Getting info about Session
var info = pkcs11.C_GetSessionInfo(session);
pkcs11.C_Login(session, 1, "password");
/**
* Your app code here
*/
pkcs11.C_Logout(session);
pkcs11.C_CloseSession(session);
}
catch(e){
console.error(e);
}
finally {
pkcs11.C_Finalize();
}
Generating secret key using AES mechanism
var template = [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My AES Key" },
{ type: pkcs11js.CKA_VALUE_LEN, value: 256 / 8 },
{ type: pkcs11js.CKA_ENCRYPT, value: true },
{ type: pkcs11js.CKA_DECRYPT, value: true },
];
var key = pkcs11.C_GenerateKey(session, { mechanism: pkcs11js.CKM_AES_KEY_GEN }, template);
Generating key pair using RSA-PKCS1 mechanism
var publicKeyTemplate = [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PUBLIC_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My RSA Public Key" },
{ type: pkcs11js.CKA_PUBLIC_EXPONENT, value: new Buffer([1, 0, 1]) },
{ type: pkcs11js.CKA_MODULUS_BITS, value: 2048 },
{ type: pkcs11js.CKA_VERIFY, value: true }
];
var privateKeyTemplate = [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PRIVATE_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My RSA Private Key" },
{ type: pkcs11js.CKA_SIGN, value: true },
];
var keys = pkcs11.C_GenerateKeyPair(session, { mechanism: pkcs11js.CKM_RSA_PKCS_KEY_PAIR_GEN }, publicKeyTemplate, privateKeyTemplate);
Generating key pair using ECDSA mechanism
var publicKeyTemplate = [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PUBLIC_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My EC Public Key" },
{ type: pkcs11js.CKA_EC_PARAMS, value: new Buffer("06082A8648CE3D030107", "hex") }, // secp256r1
];
var privateKeyTemplate = [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PRIVATE_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My EC Private Key" },
{ type: pkcs11js.CKA_DERIVE, value: true },
];
var keys = pkcs11.C_GenerateKeyPair(session, { mechanism: pkcs11js.CKM_EC_KEY_PAIR_GEN }, publicKeyTemplate, privateKeyTemplate);
Working with Object
var nObject = pkcs11.C_CreateObject(session, [
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_DATA },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_PRIVATE, value: false },
{ type: pkcs11js.CKA_LABEL, value: "My custom data" },
]);
// Updating label of Object
pkcs11.C_SetAttributeValue(session, nObject, [{ type: pkcs11js.CKA_LABEL, value: "My custom data!!!" }]);
// Getting attribute value
var label = pkcs11.C_GetAttributeValue(session, nObject, [
{ type: pkcs11js.CKA_LABEL },
{ type: pkcs11js.CKA_TOKEN }
]);
console.log(label[0].value.toString()); // My custom data!!!
console.log(!!label[1].value[0]); // false
// Copying Object
var cObject = pkcs11.C_CopyObject(session, nObject, [
{ type: pkcs11js.CKA_CLASS},
{ type: pkcs11js.CKA_TOKEN},
{ type: pkcs11js.CKA_PRIVATE},
{ type: pkcs11js.CKA_LABEL},
]);
// Removing Object
pkcs11.C_DestroyObject(session, cObject);
Searching objects
NOTE: If template is not set for C_FindObjectsInit, then C_FindObjects returns all objects from slot
pkcs11.C_FindObjectsInit(session, [{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_DATA }]);
var hObject = pkcs11.C_FindObjects(session);
while (hObject) {
var attrs = pkcs11.C_GetAttributeValue(session, hObject, [
{ type: pkcs11js.CKA_CLASS },
{ type: pkcs11js.CKA_TOKEN },
{ type: pkcs11js.CKA_LABEL }
]);
// Output info for objects from token only
if (attrs[1].value[0]){
console.log(`Object #${hObject}: ${attrs[2].value.toString()}`);
}
hObject = pkcs11.C_FindObjects(session);
}
pkcs11.C_FindObjectsFinal(session);
Generating random values
var random = pkcs11.C_GenerateRandom(session, new Buffer(20));
console.log(random.toString("hex"));
or
var random = new Buffer(20);
pkcs11.C_GenerateRandom(session, random);
console.log(random.toString("hex"));
Digest
pkcs11.C_DigestInit(_session, { mechanism: pkcs11js.CKM_SHA256 });
pkcs11.C_DigestUpdate(session, new Buffer("Incoming message 1"));
pkcs11.C_DigestUpdate(session, new Buffer("Incoming message N"));
var digest = pkcs11.C_DigestFinal(_session, Buffer(256 / 8));
console.log(digest.toString("hex"));
Signing data
pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_SHA256_RSA_PKCS }, keys.privateKey);
pkcs11.C_SignUpdate(session, new Buffer("Incoming message 1"));
pkcs11.C_SignUpdate(session, new Buffer("Incoming message N"));
var signature = pkcs11.C_SignFinal(session, Buffer(256));
Verifying data
pkcs11.C_VerifyInit(session, { mechanism: pkcs11js.CKM_SHA256_RSA_PKCS }, keys.publicKey);
pkcs11.C_VerifyUpdate(session, new Buffer("Incoming message 1"));
pkcs11.C_VerifyUpdate(session, new Buffer("Incoming message N"));
var verify = pkcs11.C_VerifyFinal(session, signature);
Encrypting data with AES-CBC mechanism
var cbc_param = pkcs11.C_GenerateRandom(new Buffer(16));
pkcs11.C_EncryptInit(
session,
{
mechanism: pkcs11js.CKM_AES_CBC,
parameter: cbc_param
},
secretKey
);
var enc = new Buffer(0);
enc = Buffer.concat([enc, pkcs11.C_EncryptUpdate(session, new Buffer("Incoming data 1"), new Buffer(16))]);
enc = Buffer.concat([enc, pkcs11.C_EncryptUpdate(session, new Buffer("Incoming data N"), new Buffer(16))]);
enc = Buffer.concat([enc, pkcs11.C_EncryptFinal(session, new Buffer(16))]);
console.log(enc.toString("hex"));
Decrypting data with AES-CBC mechanism
pkcs11.C_DecryptInit(
session,
{
mechanism: pkcs11js.CKM_AES_CBC,
parameter: cbc_param
},
secretKey
);
var dec = new Buffer(0);
dec = Buffer.concat([dec, pkcs11.C_DecryptUpdate(session, enc, new Buffer(32))]);
dec = Buffer.concat([dec, pkcs11.C_DecryptFinal(session, new Buffer(16))]);
console.log(dec.toString());
Deriving key with ECDH mechanism
// Receive public data from EC public key
var attrs = pkcs11.C_GetAttributeValue(session, publicKeyEC, [{ type: pkcs11js.CKA_EC_POINT }])
var ec = attrs[0].value;
var derivedKey = pkcs11.C_DeriveKey(
session,
{
mechanism: pkcs11js.CKM_ECDH1_DERIVE,
parameter: {
type: pkcs11js.CK_PARAMS_EC_DH,
kdf: 2,
publicData: ec
}
},
privateKeyEC,
[
{ type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY },
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_AES },
{ type: pkcs11js.CKA_LABEL, value: "Derived AES key" },
{ type: pkcs11js.CKA_ENCRYPT, value: true },
{ type: pkcs11js.CKA_VALUE_LEN, value: 256 / 8 }
]
);
Initializing NSS crypto library
Use options
parameter for C_Initialize
function.
Type
interface InitializationOptions {
/**
* NSS library parameters
*/
libraryParameters?: string;
/**
* bit flags specifying options for `C_Initialize`
* - CKF_LIBRARY_CANT_CREATE_OS_THREADS. True if application threads which are executing calls to the library
* may not use native operating system calls to spawn new threads; false if they may
* - CKF_OS_LOCKING_OK. True if the library can use the native operation system threading model for locking;
* false otherwise
*/
flags?: number;
}
/**
* Initializes the Cryptoki library
* @param options Initialization options
* Supports implementation of standard `CK_C_INITIALIZE_ARGS` and extended NSS format.
* - if `options` is null or empty, it calls native `C_Initialize` with `NULL`
* - if `options` doesn't have `libraryParameters`, it uses `CK_C_INITIALIZE_ARGS` structure
* - if `options` has `libraryParameters`, it uses extended NSS structure
*/
C_Initialize(options?: InitializationOptions): void;
Code
const mod = new pkcs11.PKCS11();
mod.load("/usr/local/opt/nss/lib/libsoftokn3.dylib");
mod.C_Initialize({
libraryParameters: "configdir='' certPrefix='' keyPrefix='' secmod='' flags=readOnly,noCertDB,noModDB,forceOpen,optimizeSpace",
});
// Your code here
mod.C_Finalize();
More info about NSS params for C_Initialize
Detect a slot event
var pkcs11js = require("pkcs11js");
var pkcs11 = new pkcs11js.PKCS11();
// Need a compliant Cryptoki Version 2.01 or later
pkcs11.load("/usr/local/lib/softhsm/libsofthsm2.so");
pkcs11.C_Initialize();
try {
const slotId = pkcs11.C_WaitForSlotEvent(pkcs11js.CKF_DONT_BLOCK);
if (slotId) {
console.log(`Slot ${slotId} has been inserted`);
} else {
console.log(`No slot event`);
}
} catch (e) {
console.error(e);
} finally {
pkcs11.C_Finalize();
}
At this time this solution should be considered suitable for research and experimentation, further code and security review is needed before utilization in a production application.
Please report bugs either as pull requests or as issues in the issue tracker. Graphene has a full disclosure vulnerability policy. Please do NOT attempt to report any security vulnerability in this code privately to anybody.