pkcs11js-modified
v1.0.0
Published
A Node.js implementation of the PKCS#11 2.3 interface
Downloads
36
Maintainers
Readme
LUNA - PKCS11-JS
NOTE: npm run build, the project and add it to npm repo to be used by PKCS11 HSM Clients. This requires a SafeNet Luna HSM with BIP32 support.
Init LUNA HSM Client
LUNACM
> source setenv
> lunacm
> > slot set -slot 3
> > partition init -label default
> > role login -name partition so
> > slot set -slot 3
> > role init -name crypto officer
> > role logout
> > slot set -slot 3
> > role login -name crypto officer
> > slot set -slot 3
> > role changepw -name crypto officer
Graphene
> graphene
> > module load -l /home/opc/lunacloudhsmclient/libs/64/libCryptoki2.so -n SoftHSMv2.0
> > slot open --slot 0 -p 1234567890
> > test sign -it 1 --slot 0x3
BIP32 Master and Child Key Pair Derivation
Performing BIP32 master and child key pair derivations.
var lib = "/home/opc/lunacloudhsmclient/libs/64/libCryptoki2.so";
var pin = "12345678";
var pkcs11js = require("pkcs11js");
var sha256 = require("sha256");
var BN = require('bn.js');
var pkcs11 = new pkcs11js.PKCS11();
var slot = 0;
var secp256k1_N = new BN("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
function dbg(...args) {
let vargs = ['⏰', new Date().toLocaleString(), '→ '];
vargs.push.apply(vargs, args);
console.log.apply(null, vargs);
}
function generateSeed(session) {
var seedTemplate = [
{type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_GENERIC_SECRET},
{type: pkcs11js.CKA_TOKEN, value: false},
{type: pkcs11js.CKA_DERIVE, value: true},
{type: pkcs11js.CKA_PRIVATE, value: true},
{type: pkcs11js.CKA_EXTRACTABLE, value: false},
{type: pkcs11js.CKA_MODIFIABLE, value: false},
{type: pkcs11js.CKA_VALUE_LEN, value: 32}
];
return pkcs11.C_GenerateKey(session, { mechanism: pkcs11js.CKM_GENERIC_SECRET_KEY_GEN }, seedTemplate);
}
function deriveMaster(session, seed) {
var publicKeyTemplate = [
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_PRIVATE, value: true },
{ type: pkcs11js.CKA_VERIFY, value: true },
{ type: pkcs11js.CKA_DERIVE, value: true },
{ type: pkcs11js.CKA_MODIFIABLE, value: false },
];
var privateKeyTemplate = [
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_PRIVATE, value: true },
{ type: pkcs11js.CKA_SIGN, value: true },
{ type: pkcs11js.CKA_DERIVE, value: true },
{ type: pkcs11js.CKA_MODIFIABLE, value: false },
{ type: pkcs11js.CKA_EXTRACTABLE, value: false },
];
return pkcs11.DeriveBIP32Master(session, seed, publicKeyTemplate, privateKeyTemplate);
}
function deriveChild(session, masterPrivate, path) {
var publicKeyTemplate = [
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_PRIVATE, value: true },
{ type: pkcs11js.CKA_VERIFY, value: true },
{ type: pkcs11js.CKA_DERIVE, value: false },
{ type: pkcs11js.CKA_MODIFIABLE, value: false },
];
var privateKeyTemplate = [
{ type: pkcs11js.CKA_TOKEN, value: false },
{ type: pkcs11js.CKA_PRIVATE, value: true },
{ type: pkcs11js.CKA_SIGN, value: true },
{ type: pkcs11js.CKA_DERIVE, value: false },
{ type: pkcs11js.CKA_MODIFIABLE, value: false },
{ type: pkcs11js.CKA_EXTRACTABLE, value: false },
];
return pkcs11.DeriveBIP32Child(session, masterPrivate, publicKeyTemplate, privateKeyTemplate, path);
}
function sign(session, privateKey, data) {
var mech = {
mechanism: pkcs11js.CKM_ECDSA,
};
pkcs11.C_SignInit(session, mech, privateKey)
return pkcs11.C_Sign(session, data, Buffer.alloc(64));
}
function verify(session, publicKey, data, signature) {
var mech = {
mechanism: pkcs11js.CKM_ECDSA,
};
pkcs11.C_VerifyInit(session, mech, publicKey)
return pkcs11.C_Verify(session, data, signature)
}
function signatureLowS(sig) {
var r = new BN(sig.slice(0, sig.length / 2).toString('hex'), 16);
var s = new BN(sig.slice(sig.length / 2).toString('hex'), 16);
var halfOrder = secp256k1_N.shrn(1);
if (s.cmp(halfOrder) == 1) {
s = secp256k1_N.sub(s);
}
return Buffer.concat([r.toBuffer("be", 32), s.toBuffer("be", 32)]);
}
pkcs11.load(lib);
pkcs11.C_Initialize();
dbg('C_Initialize ✅');
var session;
try {
var slots = pkcs11.C_GetSlotList(true);
slot = slots[slot];
dbg('C_GetSlotList ✅');
session = pkcs11.C_OpenSession(slot, pkcs11js.CKF_RW_SESSION | pkcs11js.CKF_SERIAL_SESSION);
dbg('C_OpenSession ✅');
pkcs11.C_Login(session, 1, pin);
dbg('C_Login ✅');
var seed = generateSeed(session);
dbg('generateSeed ✅');
var master = deriveMaster(session, seed);
dbg('deriveMaster ✅');
/*
https://thalesdocs.com/gphsm/luna/7/docs/network/Content/sdk/extensions/BIP32.htm#
The path generates a key pair that follows the BIP44 convention and can be used to receive BTC.
This mechanism can be used to generate keys that are several levels deep in the key hierarchy.
The path of the key is specified with pulPath and ulPathLen.
The path is an array of 32-bit unsigned integers (key indices).
The highest bit (0x80000000) is used to indicate a hardened key.
So a path value for a normal key must be <= 0x7FFFFFFF (decimal 2147483647).
For a hardened key, 0x80000000 <= path value <= 0xFFFFFFFF.
The path is relative to the input key.
BIP32: If the key index is greater than or equal to 2^31 (0x80000000 in hexadecimal), the derived key is considered hardened.
For example, if the path is [5, 1, 4] and the path of the input key is m/0 then the resulting path is m/0/5/1/4.
CK_ULONG path[] = {
CKF_BIP32_HARDENED | CKG_BIP44_PURPOSE,
CKF_BIP32_HARDENED | CKG_BIP44_COIN_TYPE_BTC,
CKF_BIP32_HARDENED | 1,
CKG_BIP32_EXTERNAL_CHAIN,
0
};
BIP32 Serialization Format
Extended public and private keys are serialized as follows:
>4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
>1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
>4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
>4 bytes: child number (index) – 32-bit unsigned integer with most significant byte first (0x00000000 if master key)
>32 bytes: the chain code
>33 bytes: the public key or private key data
This 78 byte structure is encoded like other Bitcoin data in Base58,
by first adding 32 checksum bits (derived from the double SHA-256 checksum),
and then converting to the Base58 representation.
This results in a Base58-encoded string of up to CKG_BIP32_MAX_SERIALIZED_LEN characters.
Because of the choice of the version bytes:
The Base58 representation will start with "xprv" or "xpub" on mainnet, "tprv" or "tpub" on testnet.
CKF_BIP32_HARDENED: 0x80000000
CKG_BIP44_PURPOSE: 0x0000002C (44)
CKG_BIP44_COIN_TYPE_BTC: 0x00000000 (0)
CKG_BIP44_COIN_TYPE_ETC: 0x0000003C (60)
*/
// m / purpose' / coin_type' / account' / change / address_index
// Bitcoin has a 0' coin type, Ethereum has a 60
var path = [0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0];
var child = deriveChild(session, master['privateKey'], path);
dbg('deriveChild ✅');
var hash = sha256("BIP32 Message")
var data = new Buffer(hash, "hex");
dbg({hash});
var signature = sign(session, master['privateKey'], data);
signature = signatureLowS(signature);
dbg({signature});
verify(session, master['publicKey'], data, signature);
dbg('verification done ✅');
}
catch(e){
console.error(' ❌ ERROR ❌ \n', e);
}
finally {
if(session) {
pkcs11.C_Logout(session);
dbg('C_Logout ✅');
pkcs11.C_CloseSession(session);
dbg('C_CloseSession ✅');
}
pkcs11.C_Finalize();
dbg('C_Finalize ✅');
}