Graphene
A simple layer for interacting with PKCS #11 / PKCS11 / CryptoKI for Node in TypeScript
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). It wraps the library closely, but uses attempts to look like 'node.crypto' where it makes sense.
It has been tested with :
We have also created a basic CLI for interacting with PKCS#11 devices based on this library we call graphene-cli.
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.
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var mod = Module.load("/usr/local/lib/softhsm/libsofthsm2.so", "SoftHSM");
mod.initialize();
var session = mod.getSlots(0).open();
session.login("password");
console.log(session.find({class: graphene.ObjectClass.PRIVATE_KEY}).length);
session.logout();
mod.finalize();
Installation
$ npm install graphene-pk11
Documentation
https://peculiarventures.github.io/graphene/
Using the Package
Install the package
$ npm install graphene-pk11 --save
Install TypeScript definition using TSD package manager
$ tsd install graphene-pk11 --save
Load module
var graphene = require("graphene-pk11");
Install SoftHSM2
Examples
Listing capabilities
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slots = mod.getSlots(true);
if (slots.length > 0) {
for (var i = 0; i < slots.length; i++) {
var slot = slots.items(i);
console.log("Slot #" + slot.handle);
console.log("\tDescription:", slot.slotDescription);
console.log("\tSerial:", slot.getToken().serialNumber);
console.log("\tPassword(min/max): %d/%d", slot.getToken().minPinLen, slot.getToken().maxPinLen);
console.log("\tIs hardware:", !!(slot.flags & graphene.SlotFlag.HW_SLOT));
console.log("\tIs removable:", !!(slot.flags & graphene.SlotFlag.REMOVABLE_DEVICE));
console.log("\tIs initialized:", !!(slot.flags & graphene.SlotFlag.TOKEN_PRESENT));
console.log("\n\nMechanisms:");
console.log("Name h/s/v/e/d/w/u");
console.log("========================================");
function b(v) {
return v ? "+" : "-";
}
function s(v) {
v = v.toString();
for (var i_1 = v.length; i_1 < 27; i_1++) {
v += " ";
}
return v;
}
var mechs = slot.getMechanisms();
for (var j = 0; j < mechs.length; j++) {
var mech = mechs.items(j);
console.log(s(mech.name) +
b(mech.flags & graphene.MechanismFlag.DIGEST) + "/" +
b(mech.flags & graphene.MechanismFlag.SIGN) + "/" +
b(mech.flags & graphene.MechanismFlag.VERIFY) + "/" +
b(mech.flags & graphene.MechanismFlag.ENCRYPT) + "/" +
b(mech.flags & graphene.MechanismFlag.DECRYPT) + "/" +
b(mech.flags & graphene.MechanismFlag.WRAP) + "/" +
b(mech.flags & graphene.MechanismFlag.UNWRAP));
}
}
}
mod.finalize();
####Output
Slot #0
Description: SoftHSM slot 0
Serial: f89e34b310e83df2
Password(min/max): 4/255
Is hardware: false
Is removable: false
Is initialized: true
Mechanisms:
Name h/s/v/e/d/w/u
========================================
MD5 +/-/-/-/-/-/-
SHA_1 +/-/-/-/-/-/-
SHA224 +/-/-/-/-/-/-
SHA256 +/-/-/-/-/-/-
SHA384 +/-/-/-/-/-/-
SHA512 +/-/-/-/-/-/-
MD5_HMAC -/+/+/-/-/-/-
SHA_1_HMAC -/+/+/-/-/-/-
SHA224_HMAC -/+/+/-/-/-/-
SHA256_HMAC -/+/+/-/-/-/-
SHA384_HMAC -/+/+/-/-/-/-
SHA512_HMAC -/+/+/-/-/-/-
RSA_PKCS_KEY_PAIR_GEN -/-/-/-/-/-/-
RSA_PKCS -/+/+/+/+/+/+
RSA_X_509 -/+/+/+/+/-/-
MD5_RSA_PKCS -/+/+/-/-/-/-
SHA1_RSA_PKCS -/+/+/-/-/-/-
RSA_PKCS_OAEP -/-/-/+/+/+/+
Hashing
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
var digest = session.createDigest("sha1");
digest.update("simple text 1");
digest.update("simple text 2");
var hash = digest.final();
console.log("Hash SHA1:", hash.toString("hex"));
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Generating keys
AES
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var k = session.generateKey(graphene.KeyGenMechanism.AES, {
"class": graphene.ObjectClass.SECRET_KEY,
"token": false,
"valueLen": 256 / 8,
"keyType": graphene.KeyType.AES,
"label": "My AES secret key",
"private": true
});
console.log("Key.handle:", k.handle);
console.log("Key.type:", graphene.KeyType[k.type]);
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
ECC
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var keys = session.generateKeyPair(graphene.KeyGenMechanism.ECDSA, {
keyType: graphene.KeyType.ECDSA,
token: false,
verify: true,
paramsECDSA: graphene.NamedCurve.getByName("secp192r1").value
}, {
keyType: graphene.KeyType.ECDSA,
token: false,
sign: true
});
console.log("Key type:", graphene.KeyType[keys.privateKey.type]);
console.log("Object's class:", graphene.ObjectClass[keys.privateKey.class]);
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Exporting public key
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var keys = session.generateKeyPair(graphene.KeyGenMechanism.RSA, {
keyType: graphene.KeyType.RSA,
modulusBits: 1024,
publicExponent: Buffer.from([3]),
token: false,
verify: true,
encrypt: true,
wrap: true
}, {
keyType: graphene.KeyType.RSA,
token: false,
sign: true,
decrypt: true,
unwrap: true
});
var pubKey = keys.publicKey.getAttribute({
modulus: null,
publicExponent: null
});
pubKey.modulus = pubKey.modulus.toString("base64");
pubKey.publicExponent = pubKey.publicExponent.toString("base64");
console.log(JSON.stringify(pubKey, null, 4));
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Signing / Verifying
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var keys = session.generateKeyPair(graphene.KeyGenMechanism.RSA, {
keyType: graphene.KeyType.RSA,
modulusBits: 1024,
publicExponent: Buffer.from([3]),
token: false,
verify: true,
encrypt: true,
wrap: true
}, {
keyType: graphene.KeyType.RSA,
token: false,
sign: true,
decrypt: true,
unwrap: true
});
var sign = session.createSign("SHA1_RSA_PKCS", keys.privateKey);
sign.update("simple text 1");
sign.update("simple text 2");
var signature = sign.final();
console.log("Signature RSA-SHA1:", signature.toString("hex"));
var verify = session.createVerify("SHA1_RSA_PKCS", keys.publicKey);
verify.update("simple text 1");
verify.update("simple text 2");
var verify_result = verify.final(signature);
console.log("Signature RSA-SHA1 verify:", verify_result);
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Encrypting / Decrypting
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var key = session.generateKey(graphene.KeyGenMechanism.AES, {
"class": graphene.ObjectClass.SECRET_KEY,
"token": false,
"valueLen": 256 / 8,
"keyType": graphene.KeyType.AES,
"label": "My AES secret key",
"encrypt": true,
"decrypt": true
});
var alg = {
name: "AES_CBC_PAD",
params: Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6])
};
var MESSAGE = "Encrypted message";
var cipher = session.createCipher(alg, key);
var enc = cipher.update(MESSAGE);
enc = Buffer.concat([enc, cipher.final()]);
console.log("Enc:", enc.toString("hex"));
var decipher = session.createDecipher(alg, key);
var dec = decipher.update(enc);
var msg = Buffer.concat([dec, decipher.final()]).toString();
console.log("Message:", msg.toString());
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Derive key
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345");
var keys = session.generateKeyPair(graphene.KeyGenMechanism.ECDSA, {
keyType: graphene.KeyType.ECDSA,
token: false,
derive: true,
paramsECDSA: graphene.NamedCurve.getByName("secp192r1").value
}, {
keyType: graphene.KeyType.ECDSA,
token: false,
derive: true
});
var alg = {
name: "ECDH1_DERIVE",
params: new graphene.EcdhParams(
graphene.EcKdf.SHA1,
null,
keys.publicKey.getAttribute({pointEC: null}).pointEC)
};
var template = {
"class": graphene.ObjectClass.SECRET_KEY,
"token": false,
"keyType": graphene.KeyType.AES,
"valueLen": 256 / 8,
"encrypt": true,
"decrypt": true
}
var dKey = session.deriveKey(alg, keys.privateKey, template)
console.log("Derived key handle:", dKey.handle);
session.logout();
session.close();
}
else {
console.error("Slot is not initialized");
}
mod.finalize();
Change User's PIN
var graphene = require("graphene-pk11");
var Module = graphene.Module;
var lib = "/usr/local/lib/softhsm/libsofthsm2.so";
var mod = Module.load(lib, "SoftHSM");
mod.initialize();
try {
var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {
var session = slot.open();
session.login("12345", graphene.UserType.USER);
session.setPin("12345", "new pin");
session.logout();
session.close();
console.log("User's PIN was changed successfully");
}
}
catch(e) {
console.error(e);
}
mod.finalize();
Adding x509 certificate
const graphene = require("graphene-pk11");
const mod = graphene.Module.load("/usr/local/lib/softhsm/libsofthsm2.so", "SoftHSM");
mod.initialize();
try {
const slot = mod.getSlots(0);
const session = slot.open(2 | 4)
session.login("password");
const template = {
class: graphene.ObjectClass.CERTIFICATE,
certType: graphene.CertificateType.X_509,
private: false,
token: false,
id: Buffer.from([1, 2, 3, 4, 5]),
label: "My certificate",
subject: Buffer.from("3034310B300906035504...", "hex"),
value: Buffer.from("308203A830820290A003...", "hex"),
};
const objCert = session.create(template).toType();
console.log("Certificate: created\n");
console.log("Certificate info:\n===========================");
console.log("Handle:", objCert.handle.toString("hex"));
console.log("ID:", objCert.id.toString("hex"));
console.log("Label:", objCert.label);
console.log("category:", graphene.CertificateCategory[objCert.category]);
console.log("Subject:", objCert.subject.toString("hex"));
console.log("Value:", objCert.value.toString("hex"));
} catch (err) {
console.error(err);
}
mod.finalize();
Initializing NSS crypto library
Use options
parameter for Module::initialize
method.
Type
interface InitializationOptions {
libraryParameters?: string;
flags?: number;
}
Code
const mod = Module.load("/usr/local/opt/nss/lib/libsoftokn3.dylib", "NSS");
mod.initialize({
libraryParameters: "configdir='' certPrefix='' keyPrefix='' secmod='' flags=readOnly,noCertDB,noModDB,forceOpen,optimizeSpace",
});
mod.finalize();
Developing
Use npm command to publish graphene-pk11 module
> npm run pub
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.
TODO
- Add tests to the library
- Add additional capabilities to CLI (device initialization, file signing, file encryption, etc)
Related