README
PKCS11js
We make a package called Graphene, it provides a simplistic Object Oriented interface for interacting with PKCS#11 devices, for most people this is the right level to build on. In some cases you may want to interact directly with the PKCS#11 API, if so PKCS11js is the package for you.
PKCS#11 (also known as CryptoKI or PKCS11) is the standard interface for interacting with hardware crypto devices such as Smart Cards and Hardware Security Modules (HSMs).
This was developed to the PKCS#11 2.30 specification, the 2.40 headers were not available at the time we created this, it should be easy enough to extend it for the new version 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.
Installation
$ npm install pkcs11js
Documentation
https://peculiarventures.github.io/pkcs11js/
Install SoftHSM2
- For OSX see the instructions here
- For linux instructions here
- For Windows instructions here
Examples
Example #1
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();
}
Example #2
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);
Example #3
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);
Example #4
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);
Example #4
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);
Example #4
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);
Example #5
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"));
Example #6
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"));
Example #7
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);
Example #8
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());
Example #9
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 }
]
);
Example #10
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
Example #11
Detect if smartcard is removed with C_WaitForSlotEvent function
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();
var session;
var intervalId;
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];
console.log(slot);
// Getting info about slot
var slot_info = pkcs11.C_GetSlotInfo(slot);
// Getting info about token
var token_info = pkcs11.C_GetTokenInfo(slot);
console.log(slot_info);
// Getting info about Mechanism
var mechs = pkcs11.C_GetMechanismList(slot);
var mech_info = pkcs11.C_GetMechanismInfo(slot, mechs[0]);
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, 1234, "password");
intervalId = setInterval(() => {
const rv = pkcs11.C_WaitForSlotEvent(pkcs11js.CKF_DONT_BLOCK, slot);
console.log('C_WaitForSlotEvent value : ' + rv.readUInt8(0));
if (rv.readUInt8(0) !== pkcs11js.CKR_NO_EVENT) {
/**
* Your code here to handle token removal for example
*/
}
}, 1000);
/**
* Your app code here
*/
// pkcs11.C_Logout(session);
}
catch(e){
console.error(e);
process.exit(1);
}
finally {
}
function myCleanup() {
console.log('App specific cleanup code...');
clearInterval(intervalId);
try {
if (session) {
pkcs11.C_CloseSession(session);
pkcs11.C_Finalize();
}
}
catch(e){
}
console.log('Bye !');
};
process.on('SIGINT', myCleanup);
Suitability
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.
Bug Reporting
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.
Related
- PKCS #11 2.40 Specification
- Many PKCS #11 Specifications
- Attacking and Fixing PKCS#11 Security Tokens
- PERL PKCS #11 binding
- .NET PKCS #11 binding
- Ruby PKCS #11 binding
- OCaml PKCS #11 binding
- OCaml PKCS #11 CLI
- Go PKCS #11 binding
- PKCS #11 Admin
- Node.js Foreign Function Interface
- GOST PKCS#11 constants
- PKCS#11 logging proxy module
- PKCS#11 Proxy
- PKCS#11 Tests
- OpenCryptoKi
- SoftHSM
- SofHSM2 for Windows
- node-pcsc
- PKCS#11 URIs
- Key Length Recommendations