UNPKG

@transmission-dynamics/pkcs11js

Version:

A Node.js implementation of the PKCS#11 2.40 interface

434 lines (339 loc) 13.9 kB
# PKCS11js 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:** - Version 1.x was implemented using the `nan` module, which allowed the package to be built for older versions of Node.js. - Starting from version 2.x, the module has been rewritten to use `napi`. As a result, the minimum required Node.js version is now v18. 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 : - [SoftHSM2](https://www.opendnssec.org/softhsm/) - [Thales NShield](https://www.thales-esecurity.com/products-and-services/products-and-services/hardware-security-modules/general-purpose-hsms/nshield-solo) - [Safenet Luna HSMs](http://www.safenet-inc.com/) - [RuToken](http://www.rutoken.ru/) **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 @transmission-dynamics/pkcs11js ``` ## Documentation [https://peculiarventures.github.io/pkcs11js/](https://peculiarventures.github.io/pkcs11js/) ### Install SoftHSM2 - For OSX see the [instructions here](https://github.com/opendnssec/SoftHSMv2/blob/develop/OSX-NOTES.md) - For linux [instructions here](https://github.com/opendnssec/SoftHSMv2/blob/develop/README.md) - For Windows [instructions here](https://github.com/disig/SoftHSM2-for-Windows#softhsm2-installer-for-ms-windows) ## Examples ### Example #1 ```javascript var pkcs11js = require("@transmission-dynamics/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 ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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 ```javascript var random = pkcs11.C_GenerateRandom(session, new Buffer(20)); console.log(random.toString("hex")); ``` or ```javascript var random = new Buffer(20); pkcs11.C_GenerateRandom(session, random); console.log(random.toString("hex")); ``` ### Example #6 Digest ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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 ```javascript // 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__ ```ts 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__ ```js 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](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/FC_Initialize) info about NSS params for `C_Initialize` ### Example #11 Detect a slot event ```javascript 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(); } ``` ## 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](http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/pkcs11-curr-v2.40.html) - [Many PKCS #11 Specifications](http://www.cryptsoft.com/pkcs11doc/) - [Attacking and Fixing PKCS#11 Security Tokens](http://www.lsv.ens-cachan.fr/Publis/PAPERS/PDF/BCFS-ccs10.pdf) - [PERL PKCS #11 binding](https://github.com/dotse/p5-crypt-pkcs11) - [.NET PKCS #11 binding](https://github.com/jariq/Pkcs11Interop) - [Ruby PKCS #11 binding](https://github.com/larskanis/pkcs11) - [OCaml PKCS #11 binding](https://github.com/ANSSI-FR/caml-crush) - [OCaml PKCS #11 CLI](https://github.com/ANSSI-FR/opkcs11-tool) - [Go PKCS #11 binding](https://github.com/miekg/pkcs11) - [PKCS #11 Admin](http://www.pkcs11admin.net) - [Node.js Foreign Function Interface](https://github.com/node-ffi/node-ffi) - [GOST PKCS#11 constants](https://github.com/romanovskiy-k/pkcs11/blob/master/rtpkcs11t.h) - [PKCS#11 logging proxy module](https://github.com/jariq/pkcs11-logger) - [PKCS#11 Proxy](https://github.com/iksaif/pkcs11-proxy) - [PKCS#11 Tests](https://github.com/google/pkcs11test) - [OpenCryptoKi](http://sourceforge.net/projects/opencryptoki/) - [SoftHSM](https://www.opendnssec.org/softhsm/) - [SofHSM2 for Windows](https://github.com/disig/SoftHSM2-for-Windows/) - [node-pcsc](https://github.com/santigimeno/node-pcsclite) - [PKCS#11 URIs](https://tools.ietf.org/html/rfc7512) - [Key Length Recommendations](http://www.keylength.com/en/compare/)