@transmission-dynamics/pkcs11js
Version:
A Node.js implementation of the PKCS#11 2.40 interface
434 lines (339 loc) • 13.9 kB
Markdown
# 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/)