From fa0f5ebaca879ac74b32889b221729b8bfc9669d Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Feb 2021 22:51:19 +0300 Subject: [PATCH 1/5] feat: Implement EdDSA and ECDH-ES mechanisms --- src/mechs/ed/crypto.ts | 166 ++++++++++++++++++++++++++++++++++++ src/mechs/ed/ecdh_es.ts | 37 ++++++++ src/mechs/ed/eddsa.ts | 42 +++++++++ src/mechs/ed/helper.ts | 32 +++++++ src/mechs/ed/index.ts | 4 + src/mechs/ed/private_key.ts | 44 ++++++++++ src/mechs/ed/public_key.ts | 49 +++++++++++ src/mechs/index.ts | 1 + src/subtle.ts | 14 +++ test/crypto.ts | 73 ++++++++++++++-- 10 files changed, 455 insertions(+), 7 deletions(-) create mode 100644 src/mechs/ed/crypto.ts create mode 100644 src/mechs/ed/ecdh_es.ts create mode 100644 src/mechs/ed/eddsa.ts create mode 100644 src/mechs/ed/helper.ts create mode 100644 src/mechs/ed/index.ts create mode 100644 src/mechs/ed/private_key.ts create mode 100644 src/mechs/ed/public_key.ts diff --git a/src/mechs/ed/crypto.ts b/src/mechs/ed/crypto.ts new file mode 100644 index 0000000..6ffb206 --- /dev/null +++ b/src/mechs/ed/crypto.ts @@ -0,0 +1,166 @@ +import crypto from "crypto"; +import { AsnParser } from "@peculiar/asn1-schema"; +import { JsonParser, JsonSerializer } from "@peculiar/json-schema"; +import { Convert } from "pvtsutils"; +import * as core from "webcrypto-core"; +import { CryptoKey } from "../../keys"; +import { EdPrivateKey } from "./private_key"; +import { EdPublicKey } from "./public_key"; + +export class EdCrypto { + + public static publicKeyUsages = ["verify"]; + public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; + + public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const privateKey = new EdPrivateKey(); + privateKey.algorithm = algorithm; + privateKey.extractable = extractable; + privateKey.usages = keyUsages.filter((usage) => this.privateKeyUsages.indexOf(usage) !== -1); + + const publicKey = new EdPublicKey(); + publicKey.algorithm = algorithm; + publicKey.extractable = true; + publicKey.usages = keyUsages.filter((usage) => this.publicKeyUsages.indexOf(usage) !== -1); + + const type = algorithm.namedCurve.toLowerCase() as "x448"; // "x448" | "ed448" | "x25519" | "ed25519" + const keys = crypto.generateKeyPairSync(type, { + publicKeyEncoding: { + format: "der", + type: "spki", + }, + privateKeyEncoding: { + format: "der", + type: "pkcs8", + }, + }); + + privateKey.data = keys.privateKey; + publicKey.data = keys.publicKey; + + const res: CryptoKeyPair = { + privateKey, + publicKey, + }; + + return res; + } + + public static async sign(algorithm: Algorithm, key: EdPrivateKey, data: Uint8Array): Promise { + if (!key.pem) { + key.pem = `-----BEGIN PRIVATE KEY-----\n${key.data.toString("base64")}\n-----END PRIVATE KEY-----`; + } + const options = { + key: key.pem, + }; + const signature = crypto.sign(null, Buffer.from(data), options); + + return core.BufferSourceConverter.toArrayBuffer(signature); + } + + public static async verify(algorithm: EcdsaParams, key: EdPublicKey, signature: Uint8Array, data: Uint8Array): Promise { + if (!key.pem) { + key.pem = `-----BEGIN PUBLIC KEY-----\n${key.data.toString("base64")}\n-----END PUBLIC KEY-----`; + } + const options = { + key: key.pem, + }; + const ok = crypto.verify(null, Buffer.from(data), options, Buffer.from(signature)); + return ok; + } + + public static async deriveBits(algorithm: EcdhKeyDeriveParams, baseKey: CryptoKey, length: number): Promise { + const publicKey = crypto.createPublicKey({ + key: (algorithm.public as CryptoKey).data, + format: "der", + type: "spki", + }); + const privateKey = crypto.createPrivateKey({ + key: baseKey.data, + format: "der", + type: "pkcs8", + }); + const bits = crypto.diffieHellman({ + publicKey, + privateKey, + }); + + return new Uint8Array(bits).buffer.slice(0, length >> 3); + } + + public static async exportKey(format: KeyFormat, key: CryptoKey): Promise { + switch (format.toLowerCase()) { + case "jwk": + return JsonSerializer.toJSON(key); + case "pkcs8": + case "spki": + return new Uint8Array(key.data).buffer; + case "raw": { + const publicKeyInfo = AsnParser.parse(key.data, core.asn1.PublicKeyInfo); + return publicKeyInfo.publicKey; + } + default: + throw new core.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'"); + } + } + + public static async importKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + switch (format.toLowerCase()) { + case "jwk": { + const jwk = keyData as JsonWebKey; + if (jwk.d) { + const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.CurvePrivateKey }); + return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); + } else { + if (!jwk.x) { + throw new TypeError("keyData: Cannot get required 'x' filed"); + } + return this.importPublicKey(Convert.FromBase64Url(jwk.x), algorithm, extractable, keyUsages); + } + } + case "raw": { + return this.importPublicKey(keyData as ArrayBuffer, algorithm, extractable, keyUsages); + } + case "spki": { + const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PublicKeyInfo); + return this.importPublicKey(keyInfo.publicKey, algorithm, extractable, keyUsages); + } + case "pkcs8": { + const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PrivateKeyInfo); + const asnKey = AsnParser.parse(keyInfo.privateKey, core.asn1.CurvePrivateKey); + return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); + } + default: + throw new core.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'"); + } + } + + protected static importPrivateKey(asnKey: core.asn1.CurvePrivateKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) { + const key = new EdPrivateKey(); + key.fromJSON({ + crv: algorithm.namedCurve, + d: Convert.ToBase64Url(asnKey.d), + }); + + key.algorithm = Object.assign({}, algorithm) as EcKeyAlgorithm; + key.extractable = extractable; + key.usages = keyUsages; + + return key; + } + + protected static async importPublicKey(asnKey: ArrayBuffer, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) { + const key = new EdPublicKey(); + key.fromJSON({ + crv: algorithm.namedCurve, + x: Convert.ToBase64Url(asnKey), + }); + + key.algorithm = Object.assign({}, algorithm) as EcKeyAlgorithm; + key.extractable = extractable; + key.usages = keyUsages; + + return key; + } + +} diff --git a/src/mechs/ed/ecdh_es.ts b/src/mechs/ed/ecdh_es.ts new file mode 100644 index 0000000..7dcc2f0 --- /dev/null +++ b/src/mechs/ed/ecdh_es.ts @@ -0,0 +1,37 @@ +import * as core from "webcrypto-core"; +import { CryptoKey } from "../../keys"; +import { getCryptoKey, setCryptoKey } from "../storage"; +import { EdCrypto } from "./crypto"; + +export class EcdhEsProvider extends core.EcdhEsProvider { + + public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const keys = await EdCrypto.generateKey( + { + name: this.name, + namedCurve: algorithm.namedCurve.toUpperCase(), + }, + extractable, + keyUsages); + + return { + privateKey: setCryptoKey(keys.privateKey as CryptoKey), + publicKey: setCryptoKey(keys.publicKey as CryptoKey), + }; + } + + public async onDeriveBits(algorithm: EcdhKeyDeriveParams, baseKey: core.CryptoKey, length: number): Promise { + const bits = await EdCrypto.deriveBits({...algorithm, public: getCryptoKey(algorithm.public)}, getCryptoKey(baseKey), length); + return bits; + } + + public async onExportKey(format: KeyFormat, key: CryptoKey): Promise { + return EdCrypto.exportKey(format, getCryptoKey(key)); + } + + public async onImportKey(format: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const key = await EdCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + return setCryptoKey(key); + } + +} \ No newline at end of file diff --git a/src/mechs/ed/eddsa.ts b/src/mechs/ed/eddsa.ts new file mode 100644 index 0000000..037bee4 --- /dev/null +++ b/src/mechs/ed/eddsa.ts @@ -0,0 +1,42 @@ +import * as core from "webcrypto-core"; +import { CryptoKey } from "../../keys"; +import { getCryptoKey, setCryptoKey } from "../storage"; +import { EdCrypto } from "./crypto"; +import { EdPrivateKey } from "./private_key"; +import { EdPublicKey } from "./public_key"; + +export class EdDsaProvider extends core.EdDsaProvider { + + public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const keys = await EdCrypto.generateKey( + { + name: this.name, + namedCurve: algorithm.namedCurve.replace(/^ed/i, "Ed"), + }, + extractable, + keyUsages); + + return { + privateKey: setCryptoKey(keys.privateKey as CryptoKey), + publicKey: setCryptoKey(keys.publicKey as CryptoKey), + }; + } + + public async onSign(algorithm: EcdsaParams, key: CryptoKey, data: ArrayBuffer): Promise { + return EdCrypto.sign(algorithm, getCryptoKey(key) as EdPrivateKey, new Uint8Array(data)); + } + + public async onVerify(algorithm: EcdsaParams, key: CryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + return EdCrypto.verify(algorithm, getCryptoKey(key) as EdPublicKey, new Uint8Array(signature), new Uint8Array(data)); + } + + public async onExportKey(format: KeyFormat, key: CryptoKey): Promise { + return EdCrypto.exportKey(format, getCryptoKey(key)); + } + + public async onImportKey(format: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const key = await EdCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + return setCryptoKey(key); + } + +} \ No newline at end of file diff --git a/src/mechs/ed/helper.ts b/src/mechs/ed/helper.ts new file mode 100644 index 0000000..e9482ab --- /dev/null +++ b/src/mechs/ed/helper.ts @@ -0,0 +1,32 @@ +import * as core from "webcrypto-core"; + +const edOIDs: { [key: string]: string } = { + // Ed448 + [core.asn1.idEd448]: "Ed448", + "ed448": core.asn1.idEd448, + // X448 + [core.asn1.idX448]: "X448", + "x448": core.asn1.idX448, + // Ed25519 + [core.asn1.idEd25519]: "Ed25519", + "ed25519": core.asn1.idEd25519, + // X25519 + [core.asn1.idX25519]: "X25519", + "x25519": core.asn1.idX25519, +}; + +export function getNamedCurveByOid(oid: string) { + const namedCurve = edOIDs[oid]; + if (!namedCurve) { + throw new core.OperationError(`Cannot convert OID(${oid}) to WebCrypto named curve`); + } + return namedCurve; +} + +export function getOidByNamedCurve(namedCurve: string) { + const oid = edOIDs[namedCurve.toLowerCase()]; + if (!oid) { + throw new core.OperationError(`Cannot convert WebCrypto named curve '${namedCurve}' to OID`); + } + return oid; +} diff --git a/src/mechs/ed/index.ts b/src/mechs/ed/index.ts new file mode 100644 index 0000000..efa765c --- /dev/null +++ b/src/mechs/ed/index.ts @@ -0,0 +1,4 @@ +export * from "./eddsa"; +export * from "./ecdh_es"; +export * from "./private_key"; +export * from "./public_key"; diff --git a/src/mechs/ed/private_key.ts b/src/mechs/ed/private_key.ts new file mode 100644 index 0000000..8a3a105 --- /dev/null +++ b/src/mechs/ed/private_key.ts @@ -0,0 +1,44 @@ +import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema"; +import { IJsonConvertible, JsonParser, JsonSerializer } from "@peculiar/json-schema"; +import * as core from "webcrypto-core"; +import { AsymmetricKey } from "../../keys"; +import { getOidByNamedCurve } from "./helper"; + +export class EdPrivateKey extends AsymmetricKey implements IJsonConvertible { + public readonly type: "private" = "private"; + public algorithm!: EcKeyAlgorithm; + + public getKey() { + const keyInfo = AsnParser.parse(this.data, core.asn1.PrivateKeyInfo); + return AsnParser.parse(keyInfo.privateKey, core.asn1.CurvePrivateKey); + } + + public toJSON() { + const key = this.getKey(); + + const json: JsonWebKey = { + kty: "OKP", + crv: this.algorithm.namedCurve, + key_ops: this.usages, + ext: this.extractable, + }; + + return Object.assign(json, JsonSerializer.toJSON(key)); + } + + public fromJSON(json: JsonWebKey) { + if (!json.crv) { + throw new core.OperationError(`Cannot get named curve from JWK. Property 'crv' is required`); + } + + const keyInfo = new core.asn1.PrivateKeyInfo(); + keyInfo.privateKeyAlgorithm.algorithm = getOidByNamedCurve(json.crv); + const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.CurvePrivateKey }); + keyInfo.privateKey = AsnSerializer.serialize(key); + + this.data = Buffer.from(AsnSerializer.serialize(keyInfo)); + + return this; + } + +} diff --git a/src/mechs/ed/public_key.ts b/src/mechs/ed/public_key.ts new file mode 100644 index 0000000..d475be8 --- /dev/null +++ b/src/mechs/ed/public_key.ts @@ -0,0 +1,49 @@ +import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema"; +import { IJsonConvertible } from "@peculiar/json-schema"; +import { Convert } from "pvtsutils"; +import * as core from "webcrypto-core"; +import { AsymmetricKey } from "../../keys/asymmetric"; +import { getOidByNamedCurve } from "./helper"; + +export class EdPublicKey extends AsymmetricKey implements IJsonConvertible { + + public readonly type: "public" = "public"; + public algorithm!: EcKeyAlgorithm; + + public getKey() { + const keyInfo = AsnParser.parse(this.data, core.asn1.PublicKeyInfo); + return keyInfo.publicKey; + } + + public toJSON() { + const key = this.getKey(); + + const json: JsonWebKey = { + kty: "OKP", + crv: this.algorithm.namedCurve, + key_ops: this.usages, + ext: this.extractable, + }; + + return Object.assign(json, { + x: Convert.ToBase64Url(key) + }); + } + + public fromJSON(json: JsonWebKey) { + if (!json.crv) { + throw new core.OperationError(`Cannot get named curve from JWK. Property 'crv' is required`); + } + if (!json.x) { + throw new core.OperationError(`Cannot get property from JWK. Property 'x' is required`); + } + + const keyInfo = new core.asn1.PublicKeyInfo(); + keyInfo.publicKeyAlgorithm.algorithm = getOidByNamedCurve(json.crv); + keyInfo.publicKey = Convert.FromBase64Url(json.x); + + this.data = Buffer.from(AsnSerializer.serialize(keyInfo)); + + return this; + } +} diff --git a/src/mechs/index.ts b/src/mechs/index.ts index c0257d0..dfbd8bf 100644 --- a/src/mechs/index.ts +++ b/src/mechs/index.ts @@ -2,6 +2,7 @@ export * from "./aes"; export * from "./des"; export * from "./rsa"; export * from "./ec"; +export * from "./ed"; export * from "./sha"; export * from "./pbkdf"; export * from "./hmac"; diff --git a/src/subtle.ts b/src/subtle.ts index 6e577a4..f9f32b3 100644 --- a/src/subtle.ts +++ b/src/subtle.ts @@ -1,9 +1,12 @@ +import * as process from "process"; import * as core from "webcrypto-core"; import { AesCbcProvider, AesCmacProvider, AesCtrProvider, AesEcbProvider, AesGcmProvider, AesKwProvider, DesCbcProvider, DesEde3CbcProvider, EcdhProvider, EcdsaProvider, HkdfProvider, + EdDsaProvider, + EcdhEsProvider, HmacProvider, Pbkdf2Provider, RsaEsProvider, RsaOaepProvider, RsaPssProvider, RsaSsaProvider, @@ -58,5 +61,16 @@ export class SubtleCrypto extends core.SubtleCrypto { //#region HKDF this.providers.set(new HkdfProvider()); //#endregion + + const nodeMajorVersion = /^v(\d+)/.exec(process.version)?.[1]; + if (nodeMajorVersion && parseInt(nodeMajorVersion, 10) >= 14) { + //#region EdDSA + this.providers.set(new EdDsaProvider()); + //#endregion + + //#region ECDH-ES + this.providers.set(new EcdhEsProvider()); + //#endregion + } } } diff --git a/test/crypto.ts b/test/crypto.ts index 246b496..05f13d6 100644 --- a/test/crypto.ts +++ b/test/crypto.ts @@ -47,14 +47,73 @@ context("Crypto", () => { info: new Uint8Array([1, 2, 3, 4, 5]), salt: new Uint8Array([1, 2, 3, 4, 5]), } as globalThis.HkdfParams, - hkdf, - { - name: "HMAC", - hash: "SHA-1", - } as globalThis.HmacImportParams, - false, - ["sign"]); + hkdf, + { + name: "HMAC", + hash: "SHA-1", + } as globalThis.HmacImportParams, + false, + ["sign"]); assert.strictEqual((hmac.algorithm as globalThis.HmacKeyAlgorithm).length, 512); }); + context("EdDSA", () => { + + context("generateKey", () => { + + it("Ed25519", async () => { + const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed25519" } as globalThis.EcKeyGenParams, false, ["sign", "verify"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed25519"); + }); + + it("Ed448", async () => { + const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed448" } as globalThis.EcKeyGenParams, true, ["sign", "verify"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed448"); + + const data = await crypto.subtle.exportKey("jwk", keys.privateKey); + assert.strictEqual(data.kty, "OKP"); + assert.strictEqual(data.crv, "Ed448"); + assert.strictEqual(!!data.d, true); + const privateKey = await crypto.subtle.importKey("jwk", data, { name: "eddsa", namedCurve: "ed448" } as EcKeyImportParams, false, ["sign"]); + + const message = Buffer.from("message"); + const signature = await crypto.subtle.sign({ name: "EdDSA" }, privateKey, message); + const ok = await crypto.subtle.verify({ name: "EdDSA" }, keys.publicKey, signature, message); + assert.strictEqual(ok, true); + }); + + }); + + }); + + context("ECDH-ES", () => { + + context("generateKey", () => { + + it("X25519", async () => { + const keys = await crypto.subtle.generateKey({ name: "ecdh-es", namedCurve: "x25519" } as globalThis.EcKeyGenParams, false, ["deriveBits", "deriveKey"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "ECDH-ES"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X25519"); + }); + + it("X448", async () => { + const keys = await crypto.subtle.generateKey({ name: "ecdh-es", namedCurve: "x448" } as globalThis.EcKeyGenParams, true, ["deriveBits", "deriveKey"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "ECDH-ES"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X448"); + + const bits = await crypto.subtle.deriveBits({ name: "ECDH-ES", public: keys.publicKey } as globalThis.EcdhKeyDeriveParams, keys.privateKey, 256); + assert.strictEqual(bits.byteLength, 32); + + const data = await crypto.subtle.exportKey("jwk", keys.publicKey); + assert.strictEqual(data.kty, "OKP"); + assert.strictEqual(data.crv, "X448"); + assert.strictEqual(!!data.x, true); + }); + + }); + + }); + }); From 2e3f97c35470fc1d4510684f3b615108cfa5e43b Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Feb 2021 22:51:30 +0300 Subject: [PATCH 2/5] chore: Update dependencies --- package-lock.json | 170 +++++++++++++++++++++++----------------------- package.json | 14 ++-- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index a30b7eb..68ea53e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -254,9 +254,9 @@ } }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -266,7 +266,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -433,9 +433,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/json5": { @@ -451,76 +451,77 @@ "dev": true }, "@types/node": { - "version": "12.19.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.12.tgz", - "integrity": "sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==", + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz", - "integrity": "sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.2.tgz", + "integrity": "sha512-uMGfG7GFYK/nYutK/iqYJv6K/Xuog/vrRRZX9aEP4Zv1jsYXuvFUMDFLhUnc8WFv3D2R5QhNQL3VYKmvLS5zsQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.12.0", - "@typescript-eslint/scope-manager": "4.12.0", + "@typescript-eslint/experimental-utils": "4.14.2", + "@typescript-eslint/scope-manager": "4.14.2", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" } }, "@typescript-eslint/experimental-utils": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz", - "integrity": "sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.2.tgz", + "integrity": "sha512-mV9pmET4C2y2WlyHmD+Iun8SAEqkLahHGBkGqDVslHkmoj3VnxnGP4ANlwuxxfq1BsKdl/MPieDbohCEQgKrwA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", + "@typescript-eslint/scope-manager": "4.14.2", + "@typescript-eslint/types": "4.14.2", + "@typescript-eslint/typescript-estree": "4.14.2", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.12.0.tgz", - "integrity": "sha512-9XxVADAo9vlfjfoxnjboBTxYOiNY93/QuvcPgsiKvHxW6tOZx1W4TvkIQ2jB3k5M0pbFP5FlXihLK49TjZXhuQ==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.2.tgz", + "integrity": "sha512-ipqSP6EuUsMu3E10EZIApOJgWSpcNXeKZaFeNKQyzqxnQl8eQCbV+TSNsl+s2GViX2d18m1rq3CWgnpOxDPgHg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", + "@typescript-eslint/scope-manager": "4.14.2", + "@typescript-eslint/types": "4.14.2", + "@typescript-eslint/typescript-estree": "4.14.2", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz", - "integrity": "sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.2.tgz", + "integrity": "sha512-cuV9wMrzKm6yIuV48aTPfIeqErt5xceTheAgk70N1V4/2Ecj+fhl34iro/vIssJlb7XtzcaD07hWk7Jk0nKghg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0" + "@typescript-eslint/types": "4.14.2", + "@typescript-eslint/visitor-keys": "4.14.2" } }, "@typescript-eslint/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.12.0.tgz", - "integrity": "sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.2.tgz", + "integrity": "sha512-LltxawRW6wXy4Gck6ZKlBD05tCHQUj4KLn4iR69IyRiDHX3d3NCAhO+ix5OR2Q+q9bjCrHE/HKt+riZkd1At8Q==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz", - "integrity": "sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.2.tgz", + "integrity": "sha512-ESiFl8afXxt1dNj8ENEZT12p+jl9PqRur+Y19m0Z/SPikGL6rqq4e7Me60SU9a2M28uz48/8yct97VQYaGl0Vg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0", + "@typescript-eslint/types": "4.14.2", + "@typescript-eslint/visitor-keys": "4.14.2", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -530,12 +531,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz", - "integrity": "sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.2.tgz", + "integrity": "sha512-KBB+xLBxnBdTENs/rUgeUKO0UkPBRs2vD09oMRRIkj5BEN8PX1ToXV532desXfpQnZsYTyLLviS7JrPhdL154w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/types": "4.14.2", "eslint-visitor-keys": "^2.0.0" } }, @@ -1169,13 +1170,13 @@ "dev": true }, "eslint": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.17.0.tgz", - "integrity": "sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", + "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -1199,7 +1200,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -1452,9 +1453,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -1478,9 +1479,9 @@ "dev": true }, "fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -1601,9 +1602,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "foreground-child": { @@ -2949,11 +2950,11 @@ "dev": true }, "pvtsutils": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.1.tgz", - "integrity": "sha512-Evbhe6L4Sxwu4SPLQ4LQZhgfWDQO3qa1lju9jM5cxsQp8vE10VipcSmo7hiJW48TmiHgVLgDtC2TL6/+ND+IVg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.2.tgz", + "integrity": "sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==", "requires": { - "tslib": "^2.0.3" + "tslib": "^2.1.0" } }, "pvutils": { @@ -3110,12 +3111,21 @@ } }, "rollup": { - "version": "2.36.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.36.1.tgz", - "integrity": "sha512-eAfqho8dyzuVvrGqpR0ITgEdq0zG2QJeWYh+HeuTbpcaXk8vNFc48B7bJa1xYosTCKx0CuW+447oQOW8HgBIZQ==", + "version": "2.38.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.4.tgz", + "integrity": "sha512-B0LcJhjiwKkTl79aGVF/u5KdzsH8IylVfV56Ut6c9ouWLJcUK17T83aZBetNYSnZtXf2OHD4+2PbmRW+Fp5ulg==", "dev": true, "requires": { - "fsevents": "~2.1.2" + "fsevents": "~2.3.1" + }, + "dependencies": { + "fsevents": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "dev": true, + "optional": true + } } }, "rollup-plugin-typescript2": { @@ -3444,9 +3454,9 @@ }, "dependencies": { "ajv": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", - "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.4.tgz", + "integrity": "sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -3560,9 +3570,9 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tsutils": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz", - "integrity": "sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -3670,25 +3680,15 @@ } }, "webcrypto-core": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.10.tgz", - "integrity": "sha512-9q47cYIi1NFGBg0C4FTJGy9zWwFvrowJ28V3Faxs9Hu7TAEFbrfxOIy7fsniSLsfjZqK/VMeiVUHoWWuxtiA8A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.0.tgz", + "integrity": "sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==", "requires": { "@peculiar/asn1-schema": "^2.0.27", "@peculiar/json-schema": "^1.1.12", "asn1js": "^2.0.26", "pvtsutils": "^1.1.2", "tslib": "^2.1.0" - }, - "dependencies": { - "pvtsutils": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.2.tgz", - "integrity": "sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==", - "requires": { - "tslib": "^2.1.0" - } - } } }, "which": { diff --git a/package.json b/package.json index e1514f9..ed35b00 100644 --- a/package.json +++ b/package.json @@ -57,16 +57,16 @@ "devDependencies": { "@peculiar/webcrypto-test": "^1.0.7", "@types/mocha": "^8.2.0", - "@types/node": "^12.19.12", - "@typescript-eslint/eslint-plugin": "^4.12.0", - "@typescript-eslint/parser": "^4.12.0", + "@types/node": "^14.14.22", + "@typescript-eslint/eslint-plugin": "^4.14.2", + "@typescript-eslint/parser": "^4.14.2", "coveralls": "^3.1.0", - "eslint": "^7.17.0", + "eslint": "^7.19.0", "eslint-plugin-import": "^2.22.1", "mocha": "^8.2.1", "nyc": "^15.1.0", "rimraf": "^3.0.2", - "rollup": "^2.36.1", + "rollup": "^2.38.4", "rollup-plugin-typescript2": "^0.29.0", "ts-node": "^9.1.1", "typescript": "^4.1.3" @@ -74,9 +74,9 @@ "dependencies": { "@peculiar/asn1-schema": "^2.0.27", "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.1.1", + "pvtsutils": "^1.1.2", "tslib": "^2.1.0", - "webcrypto-core": "^1.1.10" + "webcrypto-core": "^1.2.0" }, "nyc": { "extension": [ From 2473bf2493260df0cf65515bebd0179ed721f9a2 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Feb 2021 22:55:47 +0300 Subject: [PATCH 3/5] chore: Add mechanism description --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 501a359..166e523 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ npm install @peculiar/webcrypto | AES-KW | X | | X | | | X | | | ECDSA1 | X | | X | X | | | | | ECDH1 | X | | X | | | | X | +| EdDSA2,3 | X | | X | X | | | | +| ECDH-ES2,4 | X | | X | | | | X | | HKDF | | | X | | | | X | | PBKDF2 | | | X | | | | X | | DES-CBC2| X | | X | | X | X | | @@ -58,6 +60,10 @@ npm install @peculiar/webcrypto 2 Mechanism is not defined by the WebCrypto specifications. Use of mechanism in a safe way is hard, it was added for the purpose of enabling interoperability with an existing system. We recommend against its use unless needed for interoperability. +3 Mechanism supports extended list of named curves `Ed25519`, and `Ed448` + +4 Mechanism supports extended list of named curves `X25519`, and `X448` + ## Using ```javascript From a402f4ac95d3d63be3b51c088ef3dee6530ec3c9 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Feb 2021 22:56:25 +0300 Subject: [PATCH 4/5] chore: Add keywords for new mechanisms --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ed35b00..31c414f 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,12 @@ "aes", "des", "hmac", - "pbkdf2" + "pbkdf2", + "eddsa", + "x25519", + "ed25519", + "x448", + "ed448" ], "author": "PeculiarVentures", "contributors": [ From 8713a49497b8fd19e0d1e793af71073a2e1fe693 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 4 Feb 2021 23:21:16 +0300 Subject: [PATCH 5/5] test: use process.version for old NodeJS version --- test/crypto.ts | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/test/crypto.ts b/test/crypto.ts index 05f13d6..c126a5d 100644 --- a/test/crypto.ts +++ b/test/crypto.ts @@ -1,8 +1,11 @@ import assert from "assert"; +import process from "process"; import { WebcryptoTest } from "@peculiar/webcrypto-test"; import * as core from "webcrypto-core"; import { Crypto } from "../src"; +const nodeMajorVersion = parseInt(/^v(\d+)/.exec(process.version)![1], 10); + const crypto = new Crypto(); WebcryptoTest.check(crypto as any, {}); @@ -57,38 +60,38 @@ context("Crypto", () => { assert.strictEqual((hmac.algorithm as globalThis.HmacKeyAlgorithm).length, 512); }); - context("EdDSA", () => { + (nodeMajorVersion < 14 ? context.skip : context)("EdDSA", () => { - context("generateKey", () => { + context("generateKey", () => { - it("Ed25519", async () => { - const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed25519" } as globalThis.EcKeyGenParams, false, ["sign", "verify"]) as CryptoKeyPair; - assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); - assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed25519"); - }); + it("Ed25519", async () => { + const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed25519" } as globalThis.EcKeyGenParams, false, ["sign", "verify"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed25519"); + }); - it("Ed448", async () => { - const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed448" } as globalThis.EcKeyGenParams, true, ["sign", "verify"]) as CryptoKeyPair; - assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); - assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed448"); + it("Ed448", async () => { + const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed448" } as globalThis.EcKeyGenParams, true, ["sign", "verify"]) as CryptoKeyPair; + assert.strictEqual(keys.privateKey.algorithm.name, "EdDSA"); + assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed448"); - const data = await crypto.subtle.exportKey("jwk", keys.privateKey); - assert.strictEqual(data.kty, "OKP"); - assert.strictEqual(data.crv, "Ed448"); - assert.strictEqual(!!data.d, true); - const privateKey = await crypto.subtle.importKey("jwk", data, { name: "eddsa", namedCurve: "ed448" } as EcKeyImportParams, false, ["sign"]); + const data = await crypto.subtle.exportKey("jwk", keys.privateKey); + assert.strictEqual(data.kty, "OKP"); + assert.strictEqual(data.crv, "Ed448"); + assert.strictEqual(!!data.d, true); + const privateKey = await crypto.subtle.importKey("jwk", data, { name: "eddsa", namedCurve: "ed448" } as EcKeyImportParams, false, ["sign"]); + + const message = Buffer.from("message"); + const signature = await crypto.subtle.sign({ name: "EdDSA" }, privateKey, message); + const ok = await crypto.subtle.verify({ name: "EdDSA" }, keys.publicKey, signature, message); + assert.strictEqual(ok, true); + }); - const message = Buffer.from("message"); - const signature = await crypto.subtle.sign({ name: "EdDSA" }, privateKey, message); - const ok = await crypto.subtle.verify({ name: "EdDSA" }, keys.publicKey, signature, message); - assert.strictEqual(ok, true); }); }); - }); - - context("ECDH-ES", () => { + (nodeMajorVersion < 14 ? context.skip : context)("ECDH-ES", () => { context("generateKey", () => {