PeculiarVentures / pkcs11js

A Node.js implementation of the PKCS#11 2.40 interface
MIT License
111 stars 33 forks source link

Help wanted, I am trying to sign a CSR using pkcs11 with private key stored in HSM, but the CSR signature is not valid. #80

Closed mohiniyadav closed 3 years ago

mohiniyadav commented 3 years ago

Providing code to review:

let keySize = 2048; var publicKeyTemplate = [ { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PUBLIC_KEY }, { type: pkcs11js.CKA_TOKEN, value: true }, { type: pkcs11js.CKA_LABEL, value: "My RSA Public Key" }, { type: pkcs11js.CKA_DERIVE, value: true }, { type: pkcs11js.CKA_PUBLIC_EXPONENT, value: new Buffer([1, 0, 1]) }, { type: pkcs11js.CKA_MODULUS_BITS, value: keySize }, { type: pkcs11js.CKA_VERIFY, value: true } ]; var privateKeyTemplate = [ { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_PRIVATE_KEY }, { type: pkcs11js.CKA_TOKEN, value: true }, { type: pkcs11js.CKA_LABEL, value: "My RSA Private Key" }, { type: pkcs11js.CKA_SIGN, value: true }, { type: pkcs11js.CKA_DERIVE, value: true }, { type: pkcs11js.CKA_EXTRACTABLE, value: true }, { type: pkcs11js.CKA_SENSITIVE, value: true } ];

console.log('Start key pair generation: %s', (new Date()).getTime());
var keys = pkcs11.C_GenerateKeyPair(session, { mechanism: pkcs11js.CKM_RSA_PKCS_KEY_PAIR_GEN }, publicKeyTemplate, privateKeyTemplate);
console.log('End key pair generation:   %s', (new Date()).getTime());
console.log('Key pair generated');

var attributeValueTemplate = [
    { type: pkcs11js.CKA_MODULUS, value: Buffer.from(new ArrayBuffer(keySize)) },
    { type: pkcs11js.CKA_PUBLIC_EXPONENT, value: Buffer.from(new ArrayBuffer(3)) }
];
var attributes = pkcs11.C_GetAttributeValue(session, keys.publicKey, attributeValueTemplate);
var pubKey = Buffer.from(attributes[0].value.buffer);
console.log('Modulus: %s', pubKey.toString('HEX'));
var pubExp = Buffer.from(attributes[1].value.buffer);
console.log('Exponent: %s', pubExp.toString('HEX'));

var rsaPubKey = new NodeRSA();
rsaPubKey.importKey({
    n: pubKey,
    e: pubExp
});
var pemPublicKey = rsaPubKey.exportKey('pkcs8-public-pem');
console.log('Public key \n %s', pemPublicKey);

// create a certification request (CSR)
var csr = forge.pki.createCertificationRequest();

csr.publicKey = forge.pki.publicKeyFromPem(pemPublicKey);
console.log('Modulus: %s', csr.publicKey.toString('HEX'));

csr.setSubject([{
    name: 'commonName',
    value: 'example.org'
}, {
    name: 'countryName',
    value: 'US'
}, {
    shortName: 'ST',
    value: 'Virginia'
}, {
    name: 'localityName',
    value: 'Blacksburg'
}, {
    name: 'organizationName',
    value: 'Test'
}, {
    shortName: 'OU',
    value: 'Test'
}]);

// set (optional) attributes
csr.setAttributes([
    {
    name: 'challengePassword',
    value: '1234'
    },
    {
    name: 'unstructuredName',
    value: 'My Company, Inc.'
    },
    {
    name: 'extensionRequest',
    extensions: [{
        name: 'subjectAltName',
        altNames: [{
            // 2 is DNS type
            type: 2,
            value: 'test.domain.com'
        }, {
            type: 2,
            value: 'other.domain.com'
        }, {
            type: 2,
            value: 'www.domain.net'
        }]
    }]
}]);

csr.signatureOid = csr.siginfo.algorithmOid = '1.2.840.113549.1.1.5';
csr.certificationRequestInfo = forge.pki.getCertificationRequestInfo(csr);
var toBeEncrypted = forge.asn1.toDer(csr.certificationRequestInfo);

pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_SHA1_RSA_PKCS }, data);
pkcs11.C_SignUpdate(session, Buffer.from(toBeEncrypted.getBytes()));
var signature = pkcs11.C_SignFinal(session, Buffer.from(new ArrayBuffer(256)));
var signatureBuffer = Buffer.from(signature);
console.log('Signature created %s', signatureBuffer.toString("HEX"));

var binSignature = signature.toString('binary');
console.log('Signature: %s', signature.toString('base64'));

csr.signature = signature.toString('binary');

var csrVerified = csr.verify();
console.log('CSR verified with result: %s', csrVerified);

var pemCsr = forge.pki.certificationRequestToPem(csr);
console.log('CSR created: \n %s', pemCsr);
microshine commented 3 years ago

Try node-webcrypto-p11. It uses pkcs11js and creates correct signatures.

There is one more our module which helps to generate CSR in 10 lines of code. See @peculiar/x509

mohiniyadav commented 3 years ago

I already have public and private key stored in hsm, I want to use those keys to sign certificate, I can see in @peculiar/x509, a new set of keypair is generated. const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);

How can I sign a CSR using pre-existing keys in HSM? @microshine

Thanks for replying.

microshine commented 3 years ago

I'll review your code and share my example in 2 hours

microshine commented 3 years ago

CSR generation using node-webcrypto-p11 and @peculiar/x509

import { Crypto } from "node-webcrypto-p11";
import * as x509 from "@peculiar/x509";

async function main() {
  // Init Crypto provider
  const crypto = new Crypto({
    library: "/usr/local/lib/softhsm/libsofthsm2.so",
    slot: 0,
    pin: "12345",
    readWrite: true,
  });

  // Generate RSA keys
  const alg = {
    name: "RSASSA-PKCS1-v1_5",
    hash: "SHA-256",
    publicExponent: new Uint8Array([1, 0, 1]),
    modulusLength: 2048,
  }
  const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);

  // Add keys to the token
  await crypto.keyStorage.setItem(keys.privateKey);
  await crypto.keyStorage.setItem(keys.publicKey);

  // Generate CSR
  const csr = await x509.Pkcs10CertificateRequestGenerator.create({
    name: "CN=example.org, C=US, ST=Virginia, L=Blacksburg, O=Test, OU=Test",
    keys,
    signingAlgorithm: alg,
    attributes: [
      new x509.ChallengePasswordAttribute("12345"),
    ],
    extensions: [
      new x509.SubjectAlternativeNameExtension({
        dns: [
          "test.domain.com",
          "other.domain.com",
          "www.domain.net",
        ],
      })
    ]
  }, crypto);
  console.log(csr.toString("pem"));
}

main().catch(e => console.error(e));

Output

-----BEGIN CERTIFICATE REQUEST-----
MIIDEzCCAfsCAQAwaTEUMBIGA1UEAxMLZXhhbXBsZS5vcmcxCzAJBgNVBAYTAlVT
MREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzENMAsGA1UE
ChMEVGVzdDENMAsGA1UECxMEVGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAMuubNLj6Zmekos3oOhfyxrq8i1wrKs1/pNT0oC7BFwb9RkBRrNeSO8Y
Hw5nlIkX7NJ3ChquctKfLbjvC5BYtGsjltR9uyLy8LRVsuPnSsx6Ggrs7U8SxpGw
5TStTm4vBKcFn5/ttYQdKlx6pJyqrTN7/0IRnuCp729jgeoj7CZ6D2Zdaqn8MEKU
Jx/RJGsN7vcffJcjWcxQ/vu7HUk4bouEbNA+H4aX/XmNmcs1fF6Gl77Kp4uhX889
MEhEeQTDjWf3K5dPlX1pQnGyG8uDbpQxMiFoigGYpbf0xVTxlOlAto5brsQa5Ipk
Gq9RHv5HLAPwJFqDkJustpbbiNhpppkCAwEAAaBlMBQGCSqGSIb3DQEJBzEHEwUx
MjM0NTBNBgkqhkiG9w0BCQ4xQDA+MDwGA1UdEQQ1MDOCD3Rlc3QuZG9tYWluLmNv
bYIQb3RoZXIuZG9tYWluLmNvbYIOd3d3LmRvbWFpbi5uZXQwDQYJKoZIhvcNAQEL
BQADggEBAKmndZsbFFKZIyTInVoNxNdFPAqDqLdVMRATfF9x4dkVqkYdQEwqa9Ru
xHHlMCD9Z08Aszpw/q1LjRoCDvxM8GJcCtoI8NhnF75EKfaHF561TTuebVoxPnR1
6qk2R/NAAHJ8jZi/5upLaqswEGEmJ8Zbam/vFNf1fbMozNLs9JX2oc8XRkbttS+6
ARcG7lanp5SzyabCuTNB93kkhjG4Oy7eSuwUmFxDpoOeOskvp3SuMI9u73I7zQr5
+7K1DLqmdKqEHKrAUbADB94sooIAcaFBow79DYc6MLR9y0nmwQSOBOoBYdg4r06G
cxZa5+FG8blvovjT8QML+kTwjL4mdyA=
-----END CERTIFICATE REQUEST-----

ASN.1

SEQUENCE (3 elem)
  SEQUENCE (4 elem)
    INTEGER 0
    SEQUENCE (6 elem)
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.3 commonName (X.520 DN component)
          PrintableString example.org
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.6 countryName (X.520 DN component)
          PrintableString US
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.8 stateOrProvinceName (X.520 DN component)
          PrintableString Virginia
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.7 localityName (X.520 DN component)
          PrintableString Blacksburg
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.10 organizationName (X.520 DN component)
          PrintableString Test
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.11 organizationalUnitName (X.520 DN component)
          PrintableString Test
    SEQUENCE (2 elem)
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption (PKCS #1)
        NULL
      BIT STRING (2160 bit) 001100001000001000000001000010100000001010000010000000010000000100000…
        SEQUENCE (2 elem)
          INTEGER (2048 bit) 257123881271531590243586916281357399888535460018445819024090528777625…
          INTEGER 65537
    [0] (2 elem)
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.9.7 challengePassword (PKCS #9)
        SET (1 elem)
          PrintableString 12345
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
        SET (1 elem)
          SEQUENCE (1 elem)
            SEQUENCE (2 elem)
              OBJECT IDENTIFIER 2.5.29.17 subjectAltName (X.509 extension)
              OCTET STRING (53 byte) 3033820F746573742E646F6D61696E2E636F6D82106F746865722E646F6D61696E2E63…
                SEQUENCE (3 elem)
                  [2] (15 byte) test.domain.com
                  [2] (16 byte) other.domain.com
                  [2] (14 byte) www.domain.net
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
    NULL
  BIT STRING (2048 bit) 101010011010011101110101100110110001101100010100010100101001100100100…
microshine commented 3 years ago

If you need to use keys from the token use KeyStorage API

If you need to add a request or certificate to the token use CertificateStorage API

MhmodTayel commented 2 years ago

I tried this example but i get this error. [Pkcs11Error: CKR_FUNCTION_NOT_SUPPORTED] { method: 'C_CopyObject', nativeStack: ' at Error (native) C_CopyObject:584', code: 84 } Screenshot from 2022-09-05 11-25-44

Could you please help me with it

microshine commented 2 years ago

Looks like your PKCS11 provider doesn't support C_CopyObject function. You should create your objects directly on the token. node-webcrypto-p11 allows doing it via the algorithm argument.

Try this

const alg = {
  name: "RSASSA-PKCS1-v1_5",
  hash: "SHA-256",
  publicExponent: new Uint8Array([1, 0, 1]),
  modulusLength: 2048,
  token: true,
  sensitive: true,
}
MhmodTayel commented 2 years ago

It works just fine. Thank you so much

MhmodTayel commented 2 years ago

When i remove the token and plug it in again I found that the key pairs were removed from the token @microshine

MhmodTayel commented 2 years ago

Do i have to use crypto.subtle.importKey to save key pairs in token ? @microshine

microshine commented 2 years ago

When i remove the token and plug it in again I found that the key pairs were removed from the token

If you pass the token: true property into the algorithm (generateKey or improtKey) it should save the key into the token

Do i have to use crypto.subtle.importKey to save key pairs in token ?

Are you importing or generating the key?

@MhmodTayel What PKCS#11 library do you use?

MhmodTayel commented 2 years ago

I'm using epass2003auto token with it's libcastle module also I'm using the example you provide above. but when I generate key pairs and list all objects in the token I saw them but when I unplug the token and plug it again I didn't see them and after few tries i got anther error CryptoError: Rsa: Can not generate new key CKR_DEVICE_MEMORY:49 in spite of there is no key pairs in the token Screenshot from 2022-09-08 10-41-02 Screenshot from 2022-09-08 10-31-33

microshine commented 2 years ago

Here are some examples

Remove all objects from the token

await crypto.keyStorage.clear();
await crypto.certStorage.clear();

List all keys and certs

const keys = await crypto.keyStorage.keys();
for (const index of keys) {
  console.log(index);
}

const certs = await crypto.certStorage.keys();
for (const index of certs) {
  console.log(index);
}

I'm confused that it throws the CKR_DEVICE_MEMORY error, but you don't see these objects on the token. Please try to generate the keys using the token: true property and list all keys by the script I've shared.

Could you try to create the self-signed certificate or request via https://tools.fortifyapp.com. It requires the Fortify app which you can download from https://fortifyapp.com. This app uses node-webcrypto-p11 and supports epass2003 token

microshine commented 2 years ago

I don't have ePass2003 token but this script works fine with SoftHSM

import { Crypto } from "node-webcrypto-p11";

const crypto = new Crypto({
  library: "/usr/local/lib/softhsm/libsofthsm2.so",
  slot: 0,
  pin: "12345",
  readWrite: true,
});

// await crypto.keyStorage.clear();

const alg = {
  name: "RSASSA-PKCS1-v1_5",
  hash: "SHA-256",
  publicExponent: new Uint8Array([1, 0, 1]),
  modulusLength: 2048,
  token: true,
  sensitive: true,
};
await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);

const keys = await crypto.keyStorage.keys();
for (const index of keys) {
  console.log(index);
}

crypto.keyStorage.keys receives keys with token: true only

MhmodTayel commented 2 years ago

I removed all objects from the token and I was able to generate key pairs but The issue of keeping key pairs in the token after unplug it and plug it again still exist

Screenshot from 2022-09-08 11-18-59

microshine commented 2 years ago

Does node-webcrypto-p11 show an empty list too after token replugin?

MhmodTayel commented 2 years ago

yes

MhmodTayel commented 2 years ago

Screenshot from 2022-09-08 11-27-47

microshine commented 2 years ago

it's really strange

Try to get access to PKCS#11 object. Each key keeps it in p11Object field.

console.log("Token:", rsaKeys.privateKey.p11Object.token);

If it returns true and disappears after token removing, I guess the problem is on PKCS#11 library

MhmodTayel commented 2 years ago

The issue of CKR_DEVICE_MEMORY still exist in spite of there is no object in the token Screenshot from 2022-09-08 11-39-17

microshine commented 2 years ago

Could we have a chat via Skype or Hangouts?

Skype: microshine82 Hangouts: microshine82@gmail.com

microshine commented 2 years ago

@MhmodTayel Please try one more script.

graphene-p11 is a wrapper over the PKCS#11 interface. This script allows printing all PKCS#11 objects from the token

import * as graphene from "graphene-p11";

async function main() {
  const softhsm = graphene.Module.load("/usr/local/lib/softhsm/libsofthsm2.so");
  softhsm.initialize();
  try {
    const slot = softhsm.getSlots(0);
    const session = slot.open(graphene.SessionFlag.SERIAL_SESSION);
    session.login("12345");
    const objects = session.find();
    for (const obj of objects) {
      console.log(graphene.ObjectClass[obj.class]);
    }
  } finally {
    softhsm.finalize();
  }
}

main().catch(e => console.error(e));

Log

PUBLIC_KEY
PRIVATE_KEY
PRIVATE_KEY
PRIVATE_KEY
PUBLIC_KEY
PUBLIC_KEY
MhmodTayel commented 2 years ago

Still the same Screenshot from 2022-09-08 12-13-56

MhmodTayel commented 2 years ago

It works fine with softhsm Screenshot from 2022-09-08 12-15-09

microshine commented 2 years ago

Do you use the latest version of libcastle?

MhmodTayel commented 2 years ago

yes

atasen19 commented 1 year ago

Hello @microshine, I hope you and your lovely ones feel great. Can you please tell me that what is the problem here?

image