@yoursunny/webcrypto-ed25519
Version:
Ed25519 Ponyfill & Polyfill for WebCrypto
181 lines • 6.83 kB
JavaScript
import * as ed from "@noble/ed25519";
import * as asn1 from "@yoursunny/asn1";
// @ts-expect-error no typing
import { toBase64Url as b64encode, toBuffer as b64decode } from "b64u-lite";
import { C, isEd25519Algorithm } from "./common.js";
export { Ed25519Algorithm } from "./common.js";
function asUint8Array(b) {
if (b instanceof Uint8Array) {
return b;
}
if (b instanceof ArrayBuffer) {
return new Uint8Array(b);
}
return new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
}
function asArrayBuffer(b) {
if (b.byteLength === b.buffer.byteLength) {
return b.buffer;
}
return b.buffer.slice(b.byteOffset, b.byteLength);
}
const slot = "8d9df0f7-1363-4d2c-8152-ce4ed78f27d8";
class Ponyfill {
super_;
constructor(super_) {
this.super_ = super_;
this.orig_ = {};
for (const method of ["generateKey", "exportKey", "importKey", "encrypt", "decrypt", "wrapKey", "unwrapKey", "deriveBits", "deriveKey", "sign", "verify", "digest"]) {
if (this[method]) {
this.orig_[method] = super_[method];
}
else {
this[method] = super_[method].bind(super_);
}
}
}
orig_;
async generateKey(algorithm, extractable, keyUsages) {
if (isEd25519Algorithm(algorithm)) {
const pvt = ed.utils.randomPrivateKey();
const pub = await ed.getPublicKeyAsync(pvt);
const usages = Array.from(keyUsages);
const privateKey = {
algorithm,
extractable,
type: "private",
usages,
[slot]: pvt,
};
const publicKey = {
algorithm,
extractable: true,
type: "public",
usages,
[slot]: pub,
};
return { privateKey, publicKey };
}
return this.orig_.generateKey.apply(this.super_, arguments);
}
async exportKey(format, key) {
if (isEd25519Algorithm(key.algorithm) && key.extractable) {
const raw = key[slot];
switch (format) {
case "jwk": {
const jwk = {
kty: C.kty,
crv: C.crv,
};
if (key.type === "public") {
jwk.x = b64encode(raw);
}
else {
jwk.d = b64encode(raw);
jwk.x = b64encode(await ed.getPublicKeyAsync(raw));
}
return jwk;
}
case "spki": {
return asArrayBuffer(asn1.pack([
"30",
[
["30", [["06", "2B6570"]]],
["03", raw],
],
]));
}
}
}
return this.orig_.exportKey.apply(this.super_, arguments);
}
async importKey(format, keyData, algorithm, extractable, keyUsages) {
if (isEd25519Algorithm(algorithm)) {
const usages = Array.from(keyUsages);
switch (format) {
case "jwk": {
const jwk = keyData;
if (jwk.kty !== C.kty || jwk.crv !== C.crv || !jwk.x) {
break;
}
const key = {
algorithm,
extractable,
type: jwk.d ? "private" : "public",
usages,
[slot]: b64decode(jwk.d ?? jwk.x),
};
return key;
}
case "spki": {
const der = asn1.parseVerbose(asUint8Array(keyData));
const algo = der.children?.[0]?.children?.[0]?.value;
const raw = der.children?.[1]?.value;
if (!(algo instanceof Uint8Array) || ed.etc.bytesToHex(algo) !== C.oid || !(raw instanceof Uint8Array)) {
break;
}
const key = {
algorithm,
extractable: true,
type: "public",
usages,
[slot]: raw,
};
return key;
}
}
}
return this.orig_.importKey.apply(this.super_, arguments);
}
async sign(algorithm, key, data) {
if (isEd25519Algorithm(algorithm) && isEd25519Algorithm(key.algorithm) && key.type === "private" && key.usages.includes("sign")) {
return asArrayBuffer(await ed.signAsync(asUint8Array(data), key[slot]));
}
return this.orig_.sign.apply(this.super_, arguments);
}
async verify(algorithm, key, signature, data) {
if (isEd25519Algorithm(algorithm) && isEd25519Algorithm(key.algorithm) && key.type === "public" && key.usages.includes("verify")) {
return ed.verifyAsync(asUint8Array(signature), asUint8Array(data), key[slot]);
}
return this.orig_.verify.apply(this.super_, arguments);
}
}
async function checkNativeSupport() {
try {
// https://datatracker.ietf.org/doc/html/rfc8037#appendix-A
const jwk = {
kty: "OKP",
crv: "Ed25519",
d: "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
x: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
};
const pvt = await crypto.subtle.importKey("jwk", jwk, "Ed25519", false, ["sign"]);
delete jwk.d;
const pub = await crypto.subtle.importKey("jwk", jwk, "Ed25519", true, ["verify"]);
const data = new TextEncoder().encode("eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc");
const sig = await crypto.subtle.sign("Ed25519", pvt, data);
const verified = await crypto.subtle.verify("Ed25519", pub, sig, data);
return verified && ed.etc.bytesToHex(new Uint8Array(sig)) ===
"860c98d2297f3060a33f42739672d61b53cf3adefed3d3c672f320dc021b411e9d59b8628dc351e248b88b29468e0e41855b0fb7d83bb15be902bfccb8cd0a02";
}
catch {
return false;
}
}
export const hasNativeSupport = await checkNativeSupport();
export function ponyfillEd25519() {
if (hasNativeSupport) {
return crypto.subtle;
}
return new Ponyfill(crypto.subtle);
}
export function polyfillEd25519() {
if (hasNativeSupport) {
return;
}
Object.defineProperty(globalThis.crypto, "subtle", {
value: ponyfillEd25519(),
configurable: true,
});
}
//# sourceMappingURL=browser.js.map