const ALGORITHMS = { "P-256": { name: "ECDSA", namedCurve: "P-256" }, Ed25519: { name: "Ed25519" },}; // PEM -> JWK: import the SPKI bytes as an extractable key, export as "jwk"async function pemToJwk(pem, curve) { const spki = Uint8Array.from(atob(pem.replace(/-----[^-]+-----|\s+/g, "")), (c) => c.charCodeAt(0)); const key = await crypto.subtle.importKey("spki", spki, ALGORITHMS[curve], true, ["verify"]); const { key_ops, ext, ...jwk } = await crypto.subtle.exportKey("jwk", key); // drop WebCrypto-only members return jwk;} // JWK -> PEM: import the JWK, export SPKI, base64 with 64-character linesasync function jwkToPem(jwk) { const key = await crypto.subtle.importKey("jwk", jwk, ALGORITHMS[jwk.crv], true, ["verify"]); const spki = new Uint8Array(await crypto.subtle.exportKey("spki", key)); const b64 = btoa(String.fromCharCode(...spki)).match(/.{1,64}/g).join("\n"); return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`;}How it works
- The PEM body is base64 of the SPKI DER;
importKey("spki", …, true, ["verify"])reads it as an extractable key. exportKey("jwk", key)returns the JWK; the WebCrypto-specifickey_opsandextmembers are removed before it is used elsewhere.importKey("jwk", jwk, …)validates the JWK, andexportKey("spki", key)returns the DER that becomes the PEM body.match(/.{1,64}/g)wraps the base64 at 64 characters, the line length OpenSSL and every other library write.
Gotchas
- Ed25519 keys export with
"alg":"Ed25519"(the fully-specified algorithm name of RFC 9864), not the older"EdDSA". Libraries that only knowEdDSAmay reject such a JWK; drop or rewritealgif the consumer is older. - The algorithm passed to
importKeymust match the key: a P-256 SPKI imported asECDHgives a key that cannot verify signatures, and one imported with the wrong curve fails withDataError. - Only extractable keys can be exported:
importKey(…, false, …)followed byexportKeyfails withInvalidAccessError. - The exported JWK carries
key_ops(["verify"]) andext: true. Leaving them in a published JWKS restricts how other libraries may use the key.