PeculiarVentures / node-webcrypto-p11

A WebCrypto Polyfill for Node in typescript built on PKCS#11.
MIT License
44 stars 15 forks source link

Signature not verifiable by anything else than graphene packages #54

Closed symbiont-aurelien-bonnel closed 4 years ago

symbiont-aurelien-bonnel commented 4 years ago

I am producing a signature using this library interacting with a Yubikey 5. That signature is verifiable using this package's crypto.subtle.verify function properly.

I am trying to get the built-in nodejs crypto verifier to also verify the signature - to no avail. As I understand it, the signature is not in openssl (asn1) format and therefore cannot be verified correctly. What transforms can be used to make the signature produced verifiable by the built-in crypto library/openssl?

Code below:

node-webcrypto-p11 signing & verifying - PASS

const { publicKey, privateKey } = await getECDSAKeySet(crypto)
const signingPayload = Buffer.from('rnadomdatahere')
const algorithm = { name: 'ECDSA', hash: 'SHA-384' }
const signature = await crypto.subtle.sign(algorithm, privateKey, signingPayload)

const verificationResult = await crypto.subtle.verify(algorithm, publicKey, signature, signingPayload)
expect(verificationResult).toBe(true)

node-webcrypt-p11 signing & nodejs crypto verifying - FAIL

const { publicKey, privateKey } = await getECDSAKeySet(crypto)
const signingPayload = Buffer.from('rnadomdatahere')
const algorithm = { name: 'ECDSA', hash: 'SHA-384' }
const signature = Buffer.from(await crypto.subtle.sign(algorithm, privateKey, signingPayload)).toString('hex')

// certificate is valid buffer representing matching certificate for private key
const verificationResult =  nodecrypto
    .createVerify('sha384')
    .update(payload)
    .verify(certificate, signature, 'hex')
expect(verificationResult).toBe(true)

Thanks

microshine commented 4 years ago

You are right. The difference is in the encoding format. OpenSSL uses ASN1 format for ECC signature.

@peculiar/webcrypto module is based on nodejs crypto and implements this transform

https://github.com/PeculiarVentures/webcrypto/blob/master/src/mechs/ec/crypto.ts#L53-L74

rmhrisk commented 4 years ago

@microshine to be clear the format returned by this package is consistent with webcrypto correct?

microshine commented 4 years ago

@peculiar/webcrypto returns ECC signature in WebCrypto format

One more convertation example of DER to WebCrypto format https://github.com/PeculiarVentures/node-webcrypto-ossl/blob/master/src/ec/ec_dsa.cpp#L88

rmhrisk commented 4 years ago

Thanks. So by design.

Maybe we can incorporate a converter for people in the p11 package to make it easy like we have in other libraries.

symbiont-aurelien-bonnel commented 4 years ago

@rmhrisk That would be nice and I think would greatly increase the usability of this library.

@microshine thanks for sharing the conversion example.

microshine commented 4 years ago

I added and published webcrypto-core@next module which includes ASN1 schemas for EC and RSA objects.

Install webcrypto-core

npm install webcrypto-core@next

Example app

const { AsnSerializer } = require("@peculiar/asn1-schema");
const core = require("webcrypto-core");
const { Crypto } = require("@peculiar/webcrypto");
const nodeCrypto = require("crypto");

async function main() {
  const crypto = new Crypto();

  const alg = {
    name: "ECDSA",
    namedCurve: "P-521",
    hash: "SHA-256"
  }
  const data = Buffer.from("Hello");

  // Generate ECDSA key and sign data
  const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
  const signature = await crypto.subtle.sign(alg, keys.privateKey, data);
  console.log("WebCrypto Signature:", Buffer.from(signature).toString("hex"));

  // Convert WebCrypto signature to DER
  const derSignature = core.asn1.EcDsaSignature.fromWebCryptoSignature(signature);
  const der = AsnSerializer.serialize(derSignature);
  console.log("DER Signature:", Buffer.from(der).toString("hex"));

  // nodejs
  // Convert WebCrypto Key to PEM
  const pkcs8 = await crypto.subtle.exportKey("pkcs8", keys.publicKey);
  const publicKey = core.PemConverter.fromBufferSource(pkcs8, "public key");

  // Verify signature using NodeJS crypto
  const cryptoAlg = alg.hash.replace("-", "");
  const signer = nodeCrypto.createVerify(cryptoAlg);
  signer.update(data);

  const options = {
    key: publicKey,
  };
  const ok = signer.verify(options, Buffer.from(der));
  console.log("Valid:", ok);
}

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

Output

WebCrypto Signature: 012cec66df15709a95f2cd878af36951762727d3ebe5f9cae96dd45931f66205cad49945cc96a7ae13be0615c8eb2447023161a3de574d2b59847440e8bed1c6124400911e3907efe07bc57b37c4df41b36390e263485610921d75b50f76db01c6bcd7c1d4b0e93f1fee7886bfd7eb9963ad0f4a0db927b5ac06b8da9073771dfbd4a856
DER Signature: 3081880242012cec66df15709a95f2cd878af36951762727d3ebe5f9cae96dd45931f66205cad49945cc96a7ae13be0615c8eb2447023161a3de574d2b59847440e8bed1c61244024200911e3907efe07bc57b37c4df41b36390e263485610921d75b50f76db01c6bcd7c1d4b0e93f1fee7886bfd7eb9963ad0f4a0db927b5ac06b8da9073771dfbd4a856
Valid: true

@symbiont-aurelien-bonnel Please test this code. If it works then I'll publish a new version of webcrypto-core

P.S. It must work for all WebCrypto implementations

symbiont-aurelien-bonnel commented 4 years ago

Thanks, I went the long way and used directly the asn1-schema functions to handle padding and serialize to asn1, works well. I can confirm this works too.

Thank you for the quick updates and the great work!

microshine commented 4 years ago

I published webcrpyto-core@1.0.19