PeculiarVentures/webcrypto

Incompatibility with Node's Webcrypto when using Ed25519 keys

CMCDragonkai opened this issue · 6 comments

I've been using the https://github.com/PeculiarVentures/x509 library.

At first since webcrypto already available in nodejs, I thought it was sufficient to do this:

x509.cryptoProvider.set(webcrypto as Crypto);

However subsequently I found that that there's an API difference between the way x509 expects Ed25519 algorithm objects that are based on this library, compared to the ones that in NodeJS (https://nodejs.org/api/webcrypto.html#ed25519ed448x25519x448-key-pairs).

For example in NodeJS's webcrypto, the alg parameter can just be: { name: 'Ed25519' }.

However for peculiar's webcrypto, the alg parameter has to be { name: 'EdDSA', namedCurve: 'Ed25519' }.

This caused some incompatibilities in webcrypto.subtle.importKey and in x509.X509CertificateGenerator.createSelfSigned

In the case of the importKey, I have to use a webcrypto's expected alg parameter:

  const privateCryptoKey = await webcrypto.subtle.importKey(
    'jwk',
    privateKeyJWK,
    // { name: 'Ed25519' }, // This is nodejs webcrypto
    { name: 'EdDSA', namedCurve: 'Ed25519' }, // This is peculiar's webcrypto
    true,
    ['sign']
  );

During the generation of the certificate, if I used node's webcrypto, the certificate generation works without exceptions, however I notice that the signatureAlgorithm field is empty, and stays as:

signatureAlgorithm: { name: '', parameters: undefined },

The resulting certificate when output to PEM is not readable by openssl x509 -in ./tmp/cert -text.

unable to load certificate
140330083047232:error:0D0C40D8:asn1 encoding routines:c2i_ASN1_OBJECT:invalid object encoding:crypto/asn1/a_object.c:254:
140330083047232:error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:646:Field=algorithm, Type=X509_ALGOR
140330083047232:error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:646:Field=signature, Type=X509_CINF
140330083047232:error:0D08303A:asn1 encoding routines:asn1_template_noexp_                                                             d2i:nested asn1 error:crypto/asn1/tasn_dec.c:646:Field=cert_info, Type=X509                     AES-GCM', length: 256 },
140330083047232:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:crypto/pem/pem_oth.c:33:
CUSTOM CALLED

So it seems that if I want to use ed25519 with https://github.com/PeculiarVentures/x509, I have to use peculiar's webcrypto and not node's webcrypto.

Ed25519 is not specified in WebCrypto API this is why algorithm names are different for native webcrypto and @peculiar/webcrypto. For @peculiar/x509 it's possible to solve by extending EdAlgorithm. I'll prepare some examples and share them

I switched to using @peculiar/webcrypto, but I'm wondering if there's ways of "constructing" or customising the webcrypto object in case I need to plug it with different underlying implementations like for mobile platforms or otherwise.

panva commented

The Node.js implementation has since been updated to reflect the CFRG curves WICG draft.

I would suggest this polyfill to follow suit.

The draft is also partially supported in Deno. Chromium's implementation is ongoing and so is WebKit's.

@panva It sounds good. I've created the new ticket #60

Chromium's implementation is ongoing and so is WebKit's.

Chromium now supports Ed25519 algorithm, see https://blogs.igalia.com/jfernandez/2023/06/20/secure-curves-in-the-web-cryptography-api/.

Node.js crypto implementation is indeed incompatible with Web Cryptography API implemenation, see paulmillr/noble-ed25519#98.

FWIW this is what I use to generate ED23319 keys using node, deno, and bun https://github.com/guest271314/webbundle/blob/main/generateWebCryptoKeys.js:

import { writeFileSync } from "node:fs";
import { webcrypto } from "node:crypto";
const algorithm = { name: "Ed25519" };
const encoder = new TextEncoder();
const cryptoKey = await webcrypto.subtle.generateKey(
  algorithm,
  true, /* extractable */
  ["sign", "verify"],
);
const privateKey = JSON.stringify(
  await webcrypto.subtle.exportKey("jwk", cryptoKey.privateKey),
);
writeFileSync("./privateKey.json", encoder.encode(privateKey));
const publicKey = JSON.stringify(
  await webcrypto.subtle.exportKey("jwk", cryptoKey.publicKey),
);
writeFileSync("./publicKey.json", encoder.encode(publicKey));

Import private and public keys: https://github.com/guest271314/webbundle/blob/main/index.js#L11-L29

const privateKey = fs.readFileSync("./privateKey.json");
const publicKey = fs.readFileSync("./publicKey.json");
// https://github.com/tQsW/webcrypto-curve25519/blob/master/explainer.md
const cryptoKey = {
  privateKey: await webcrypto.subtle.importKey(
    "jwk",
    JSON.parse(decoder.decode(privateKey)),
    algorithm.name,
    true,
    ["sign"],
  ),
  publicKey: await webcrypto.subtle.importKey(
    "jwk",
    JSON.parse(decoder.decode(publicKey)),
    algorithm.name,
    true,
    ["verify"],
  ),
};