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.
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.
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"],
),
};