PeculiarVentures / graphene

A simple layer for interacting with PKCS #11 / PKCS11 / CryptoKI for Node in TypeScript. (Keywords: Javascript, PKCS#11, Crypto, Smart Card, HSM)
MIT License
168 stars 34 forks source link

Unable to fetch certificate #88

Closed AiNoKame closed 6 years ago

AiNoKame commented 6 years ago

I just started using graphene, and I'm trying to read a certificate in plain text that I stored using SoftHSMv2. Here's all I've done:

0) Convert regular PEM formatted private certificate to PKCS#8 format

> openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
-in /Users/AiNoKame/old-key.pem \
-out /Users/AiNoKame/Documents/GitHub/SoftHSMv2/new-key.pem

1) Initialize token

> softhsm2-util --init-token --slot 0 --label "test"
=== SO PIN (4-255 characters) ===
Please enter SO PIN: **** (1234)
Please reenter SO PIN: **** (1234)
=== User PIN (4-255 characters) ===
Please enter user PIN: **** (9876)
Please reenter user PIN: **** (9876)
The token has been initialized and is reassigned to slot 475181359

2) Import private certificate into token

> softhsm2-util \
--import /Users/AiNoKame/Documents/GitHub/SoftHSMv2/new-key.pem \
--no-public-key \
--slot 475181359 --label "new-key" --id abcd1234 --pin 9876

3) Run a simple nodejs app using graphene to fetch certificate

const graphene = require('graphene-pk11')
const {parseCertificate} = require('./parse_certificate')
const mod = graphene.Module.load('/usr/local/lib/softhsm/libsofthsm2.so', 'SoftHSM')

mod.initialize()

const session = mod.getSlots(0).open()

session.login('9876')

const fetchedCertificates = session.find({label: 'new-key'})
const certificate = fetchedCertificates.items(0).toType()

console.log(`======= certificate: `, certificate)
console.log(`======= Object.getOwnPropertyNames(certificate): `, Object.getOwnPropertyNames(certificate))
console.log(`======= certificate.lib: `, certificate.lib)
console.log(`======= certificate.handle: `, certificate.handle)
console.log(`======= certificate.handle.toString('hex'): `, certificate.handle.toString('hex'))
console.log(`======= certificate.id.toString('hex'): `, certificate.id.toString('hex'))
console.log(`======= certificate.label: `, certificate.label)
console.log(`======= certificate.subject: `, certificate.subject)
console.log(`======= certificate.value: `, certificate.value)

mod.finalize()

Output:

======= certificate:  PrivateKey {
  handle: <Buffer 02 00 00 00 00 00 00 00>,
  session:
   Session {
     handle: <Buffer 01 00 00 00 00 00 00 00>,
     slot:
      Slot {
        handle: <Buffer 2f b1 52 1c 00 00 00 00>,
        module: [Object],
        slotDescription: 'SoftHSM slot ID 0x1c52b12f',
        manufacturerID: 'SoftHSM project',
        flags: 1,
        hardwareVersion: [Object],
        firmwareVersion: [Object] },
     state: 0,
     flags: 4,
     deviceError: 0 } }
======= Object.getOwnPropertyNames(certificate):  [ 'lib', 'handle', 'session' ]
======= certificate.lib:  PKCS11 {}
======= certificate.handle:  <Buffer 02 00 00 00 00 00 00 00>
======= certificate.handle.toString('hex'):  0200000000000000
======= certificate.id.toString('hex'):  abcd1234
======= certificate.label:  new-key
======= certificate.subject:  <Buffer >
======= certificate.value:  undefined

I'm not seeing the certificate contents in my certificate object at all!

Any help would be appreciated!

microshine commented 6 years ago

Hello!

As I can see from your steps you don't import certificate to SoftHSM. You use PKCS#8 which is private key and import it to SoftHSM

Eech SessionObject has class property which you can use to understand what object class do you have https://github.com/PeculiarVentures/graphene/blob/master/index.d.ts#L343

There is another npm module which allows to use PKCS#11 tokens easier node-webcrypto-p11

you can get list of allowed keys or certificates by using CryptoStorage https://github.com/PeculiarVentures/node-webcrypto-p11#key-storage https://github.com/PeculiarVentures/node-webcrypto-p11#certificate-storage

AiNoKame commented 6 years ago

@microshine Thanks for the quick response! 😄 Regarding node-webcrypto-p11, I was a bit worried when I saw this https://www.npmjs.com/package/node-webcrypto-p11#warning

Question 1: Is there a way to get the certificate contents via only graphene?

Also regarding your comment:

As I can see from your steps you don't import certificate to SoftHSM. You use PKCS#8 which is private key and import it to SoftHSM

Question 2: Do you mean according to my steps, I forgot to import the certificate to SoftHSM (I thought I did in step 2 😉 )? Or do you mean, I shouldn't use SoftHSM to import the certificate?

Follow-up question (that's more about the use of SoftHSM 😅 ):

Question 3: Is there a way to import a certificate using softhsm2-util --import so that graphene recognizes the certificate as an x509 certificate instead of a public/private key?

microshine commented 6 years ago

1

You can use this example. I use filter for find function. objects contains all certificates from token

var graphene = require("graphene-pk11");

const lib = "/usr/local/lib/softhsm/libsofthsm2.so";

const mod = graphene.Module.load(lib, "LIB");
mod.initialize();

try {
    const slot = mod.getSlots(0);
    const session = slot.open(4);

    const objects = session.find({class: graphene.ObjectClass.CERTIFICATE});
    for (let i=0; i<objects.length; i++) {
        const cert = objects.items(i).toType();

        console.log(cert.value.toString("hex"));
    }

} catch (e) {
    console.error(e);
}

mod.finalize();

2

You use

--import /Users/AiNoKame/Documents/GitHub/SoftHSMv2/new-key.pem

As I can see it's private key. I don't see openssl certificate creation and cert.pem file importing

3

It looks you cannot use softhsm2-util to import certificate

--import <path>   Import a key pair from the given path.
                  The file must be in PKCS#8-format.
                  Use with --slot or --token or --serial, --file-pin,
                  --label, --id, --no-public-key, and --pin.

You can use graphene to do it https://github.com/PeculiarVentures/node-webcrypto-p11/blob/master/lib/cert.ts#L181

AiNoKame commented 6 years ago

@microshine 😄 Thanks again for the quick response! I've tried several things since yesterday, but I still can't find a way to see my private certificate contents in plain text 😞

1) First, re-initialized my token and used pkcs11-tool to generate a fresh key pair in case the issue was my imported private key:

pkcs11-tool --module=/usr/local/lib/softhsm/libsofthsm2.so -l -k \
-d abcd1234 -a new-key --key-type rsa:2048

2) I copied your code completely from Answer 1 above, and nothing was logged

3) I tried using your suggested node-webcryto-p11 module and wrote another small node app:

const p11 = require('node-webcrypto-p11')

const crypto = new p11.WebCrypto({
  library: '/usr/local/lib/softhsm/libsofthsm2.so',
  slot: 0,
  readWrite: true
})

Promise.resolve()
.then(() => crypto.keyStorage.keys())
.then(indexes => {
  console.log('\n\n========== indexes', indexes)
  return crypto.keyStorage.getItem(indexes[0])
})
.then(cert => {
  console.log('\n\n========== cert', cert)
  console.log('\n\n========== cert.toJSON()', cert.toJSON())
})
.catch(error => {
  console.error('ERROR', error)
})

Output:

========== indexes [ 'public-0200000000000000-abcd1234' ]

========== cert RsaCryptoKey {
  p11Object:
   PublicKey {
     handle: <Buffer 02 00 00 00 00 00 00 00>,
     session:
      Session {
        handle: <Buffer 01 00 00 00 00 00 00 00>,
        slot: [Object],
        state: 2,
        flags: 6,
        deviceError: 0 } },
  usages: [ 'encrypt', 'verify', 'wrapKey' ],
  type: 'public',
  extractable: true,
  algorithm:
   { name: 'RSASSA-PKCS1-v1_5',
     hash: { name: 'SHA-256' },
     modulusLength: 2048,
     publicExponent: Uint8Array [ 1, 0, 1 ] },
  id: 'public-0200000000000000-abcd1234' }

========== cert.toJSON() { algorithm:
   { name: 'RSASSA-PKCS1-v1_5',
     hash: { name: 'SHA-256' },
     modulusLength: 2048,
     publicExponent: Uint8Array [ 1, 0, 1 ] },
  type: 'public',
  usages: [ 'encrypt', 'verify', 'wrapKey' ],
  extractable: true }

...I'm not sure why indexes [ 'public-0200000000000000-abcd1234' ] only contained the public key (and not the private key, which is what I need), and even then, there was no plain text data that I could find in the public key

Am I completely on the wrong path here? I just want to be able to securely store a private key in my HSM and use a node module to fetch the private key in plain text for use in my node app

microshine commented 6 years ago

It's not secure to put private key to HSM and then export it as plain text.

May I ask you? What are you using for exported private key? Maybe I can suggest you another way

Otherwise I can write example how to export private key from KeyStorage

rmhrisk commented 6 years ago

Mainstream HSMs do not support clear text export of private keys. I think there must be some confusion here.

AiNoKame commented 6 years ago

I'd love to see an example on how to export private key from KeyStorage! 😃

I have a pretty simple use case - I just want to store my private keys on an HSM and access those private keys using a node module when creating my https node server (https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener)

microshine commented 6 years ago

I see. You need PKCS#8 private key

// @ts-check
const { WebCrypto } = require("node-webcrypto-p11");

const crypto = new WebCrypto({
    library: '/usr/local/lib/softhsm/libsofthsm2.so',
    slot: 0,
    readWrite: true,
    pin: "12345", // you need login to get private objects
});

async function main() {
    if (crypto.isLoggedIn) {
        crypto.login("12345"); // you need login to get private objects
    }

    const indexes = await crypto.keyStorage.keys();
    let privateKey;
    for(const index of indexes) {
        const parts = index.split("-");
        if (parts[0] === "private") {
            privateKey = await crypto.keyStorage.getItem(index);
            break;
        }
    }

    if (privateKey) {
        // NOTE: You can export only exportable private key
        const raw = await crypto.subtle.exportKey("pkcs8", privateKey);
        const bufRaw = new Buffer(raw);

        console.log(bufRaw.toString("hex"));
    } else {
        console.log("Private key is not found");
    }
}

main()
    .catch((err) => {
        console.error(err);
    })
microshine commented 6 years ago

bufRaw - PKCS#8 private key in DER format If you need PEM you need to do it manually

AiNoKame commented 6 years ago

Thanks for all the support so far, @microshine and @rmhrisk 😸 I think I'm asking for the impossible here 😆 I just opened up another ticket at nodejs (https://github.com/nodejs/help/issues/964) now that I'm a bit more knowledgeable about the actual functionality of an HSM, but I'm doubtful regarding any positive response there

rmhrisk commented 6 years ago

Re:

I have a pretty simple use case - I just want to store my private keys on an HSM and access those private keys using a node module when creating my https node server (https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener)

HSMs are used most commonly when keys live cradle to grave in the HSM.

rmhrisk commented 6 years ago

FYI: Node crypto is a just a thin layer ontop of OpenSSL. It is possible to have node honor your OpenSSL configuration file when doing crypto, specifically with TLS. It is possible to specify a "OpenSSL engine module" in your configuration. You can usually get your HSM to be loaded in this fashion.

AiNoKame commented 6 years ago

Thanks for the info, @rmhrisk ! I responded to your comment on the nodejs issue (https://github.com/nodejs/help/issues/964) 😄 I'm going to close this issue on the graphene repo since I think it's evolved into a generic nodejs question, so if you happen to have any further comments, please add them to the nodejs thread!