From 5acb3e25ab40f7f37c1eeb1d3356952b1f364f14 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 26 Oct 2021 12:39:51 +0300 Subject: [PATCH] feat: Add EC Brainpool curves --- src/mechs/ec/crypto.ts | 53 ++++------------- src/mechs/ec/ec_dh.ts | 4 +- src/mechs/ec/ec_dsa.ts | 4 +- src/mechs/ec/helper.ts | 30 ++++++++++ src/mechs/ed/crypto.ts | 4 +- test/crypto.ts | 125 ++++++++++++++++++++++++++++++++--------- 6 files changed, 148 insertions(+), 72 deletions(-) diff --git a/src/mechs/ec/crypto.ts b/src/mechs/ec/crypto.ts index 9ec09f4..0183720 100644 --- a/src/mechs/ec/crypto.ts +++ b/src/mechs/ec/crypto.ts @@ -1,6 +1,7 @@ import crypto from "crypto"; import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema"; import { JsonParser, JsonSerializer } from "@peculiar/json-schema"; +import {BufferSourceConverter} from "pvtsutils"; import * as core from "webcrypto-core"; import { CryptoKey } from "../../keys"; import { getOidByNamedCurve } from "./helper"; @@ -12,7 +13,7 @@ export class EcCrypto { public static publicKeyUsages = ["verify"]; public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; - public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { const privateKey = new EcPrivateKey(); privateKey.algorithm = algorithm; privateKey.extractable = extractable; @@ -38,7 +39,7 @@ export class EcCrypto { privateKey.data = keys.privateKey; publicKey.data = keys.publicKey; - const res: CryptoKeyPair = { + const res = { privateKey, publicKey, }; @@ -61,12 +62,9 @@ export class EcCrypto { const signature = signer.sign(options); const ecSignature = AsnParser.parse(signature, core.asn1.EcDsaSignature); - const pointSize = this.getPointSize(key.algorithm.namedCurve); - const r = this.addPadding(pointSize, Buffer.from(ecSignature.r)); - const s = this.addPadding(pointSize, Buffer.from(ecSignature.s)); - - const signatureRaw = new Uint8Array(Buffer.concat([r, s])).buffer; - return signatureRaw; + const signatureRaw = core.EcUtils.encodeSignature(ecSignature, core.EcCurves.get(key.algorithm.namedCurve).size); + + return signatureRaw.buffer; } public static async verify(algorithm: EcdsaParams, key: EcPublicKey, signature: Uint8Array, data: Uint8Array): Promise { @@ -82,9 +80,10 @@ export class EcCrypto { }; const ecSignature = new core.asn1.EcDsaSignature(); - const pointSize = this.getPointSize(key.algorithm.namedCurve); - ecSignature.r = this.removePadding(signature.slice(0, pointSize)); - ecSignature.s = this.removePadding(signature.slice(pointSize, pointSize + pointSize)); + const namedCurve = core.EcCurves.get(key.algorithm.namedCurve); + const signaturePoint = core.EcUtils.decodeSignature(signature, namedCurve.size); + ecSignature.r = BufferSourceConverter.toArrayBuffer(signaturePoint.r); + ecSignature.s = BufferSourceConverter.toArrayBuffer(signaturePoint.s); const ecSignatureRaw = Buffer.from(AsnSerializer.serialize(ecSignature)); const ok = signer.verify(options, ecSignatureRaw); @@ -215,38 +214,8 @@ export class EcCrypto { case "P-521": return "secp521r1"; default: - throw new core.OperationError(`Cannot convert WebCrypto named curve to NodeJs. Unknown name '${curve}'`); + return curve; } } - private static getPointSize(namedCurve: string) { - switch (namedCurve) { - case "P-256": - case "K-256": - return 32; - case "P-384": - return 48; - case "P-521": - return 66; - default: - throw new Error(`Cannot get size for the named curve '${namedCurve}'`); - } - } - - private static addPadding(pointSize: number, bytes: Buffer) { - const res = Buffer.alloc(pointSize); - res.set(Buffer.from(bytes), pointSize - bytes.length); - return res; - } - - private static removePadding(bytes: Uint8Array) { - for (let i = 0; i < bytes.length; i++) { - if (!bytes[i]) { - continue; - } - return bytes.slice(i).buffer; - } - return new ArrayBuffer(0); - } - } diff --git a/src/mechs/ec/ec_dh.ts b/src/mechs/ec/ec_dh.ts index 496b121..77fd2cf 100644 --- a/src/mechs/ec/ec_dh.ts +++ b/src/mechs/ec/ec_dh.ts @@ -7,7 +7,9 @@ import { EcPublicKey } from "./public_key"; export class EcdhProvider extends core.EcdhProvider { - public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + public namedCurves = core.EcCurves.names; + + public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { const keys = await EcCrypto.generateKey( { ...algorithm, diff --git a/src/mechs/ec/ec_dsa.ts b/src/mechs/ec/ec_dsa.ts index 883ea91..24ba5d3 100644 --- a/src/mechs/ec/ec_dsa.ts +++ b/src/mechs/ec/ec_dsa.ts @@ -6,9 +6,9 @@ import { EcPublicKey } from "./public_key"; export class EcdsaProvider extends core.EcdsaProvider { - public namedCurves = ["P-256", "P-384", "P-521", "K-256"]; + public namedCurves = core.EcCurves.names; - public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { const keys = await EcCrypto.generateKey( { ...algorithm, diff --git a/src/mechs/ec/helper.ts b/src/mechs/ec/helper.ts index c6d88dc..88f68b1 100644 --- a/src/mechs/ec/helper.ts +++ b/src/mechs/ec/helper.ts @@ -13,6 +13,36 @@ const namedOIDs: { [key: string]: string } = { // K-256 "1.3.132.0.10": "K-256", "K-256": "1.3.132.0.10", + + // brainpool + "brainpoolP160r1": "1.3.36.3.3.2.8.1.1.1", + "1.3.36.3.3.2.8.1.1.1": "brainpoolP160r1", + "brainpoolP160t1": "1.3.36.3.3.2.8.1.1.2", + "1.3.36.3.3.2.8.1.1.2": "brainpoolP160t1", + "brainpoolP192r1": "1.3.36.3.3.2.8.1.1.3", + "1.3.36.3.3.2.8.1.1.3": "brainpoolP192r1", + "brainpoolP192t1": "1.3.36.3.3.2.8.1.1.4", + "1.3.36.3.3.2.8.1.1.4": "brainpoolP192t1", + "brainpoolP224r1": "1.3.36.3.3.2.8.1.1.5", + "1.3.36.3.3.2.8.1.1.5": "brainpoolP224r1", + "brainpoolP224t1": "1.3.36.3.3.2.8.1.1.6", + "1.3.36.3.3.2.8.1.1.6": "brainpoolP224t1", + "brainpoolP256r1": "1.3.36.3.3.2.8.1.1.7", + "1.3.36.3.3.2.8.1.1.7": "brainpoolP256r1", + "brainpoolP256t1": "1.3.36.3.3.2.8.1.1.8", + "1.3.36.3.3.2.8.1.1.8": "brainpoolP256t1", + "brainpoolP320r1": "1.3.36.3.3.2.8.1.1.9", + "1.3.36.3.3.2.8.1.1.9": "brainpoolP320r1", + "brainpoolP320t1": "1.3.36.3.3.2.8.1.1.10", + "1.3.36.3.3.2.8.1.1.10": "brainpoolP320t1", + "brainpoolP384r1": "1.3.36.3.3.2.8.1.1.11", + "1.3.36.3.3.2.8.1.1.11": "brainpoolP384r1", + "brainpoolP384t1": "1.3.36.3.3.2.8.1.1.12", + "1.3.36.3.3.2.8.1.1.12": "brainpoolP384t1", + "brainpoolP512r1": "1.3.36.3.3.2.8.1.1.13", + "1.3.36.3.3.2.8.1.1.13": "brainpoolP512r1", + "brainpoolP512t1": "1.3.36.3.3.2.8.1.1.14", + "1.3.36.3.3.2.8.1.1.14": "brainpoolP512t1", }; export function getNamedCurveByOid(oid: string) { diff --git a/src/mechs/ed/crypto.ts b/src/mechs/ed/crypto.ts index 6ffb206..08e17b1 100644 --- a/src/mechs/ed/crypto.ts +++ b/src/mechs/ed/crypto.ts @@ -12,7 +12,7 @@ export class EdCrypto { public static publicKeyUsages = ["verify"]; public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; - public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { const privateKey = new EdPrivateKey(); privateKey.algorithm = algorithm; privateKey.extractable = extractable; @@ -38,7 +38,7 @@ export class EdCrypto { privateKey.data = keys.privateKey; publicKey.data = keys.publicKey; - const res: CryptoKeyPair = { + const res = { privateKey, publicKey, }; diff --git a/test/crypto.ts b/test/crypto.ts index 6c5b2df..7c895b8 100644 --- a/test/crypto.ts +++ b/test/crypto.ts @@ -1,6 +1,7 @@ import assert from "assert"; import process from "process"; import { WebcryptoTest } from "@peculiar/webcrypto-test"; +import { Convert } from "pvtsutils"; import * as core from "webcrypto-core"; import { Crypto } from "../src"; @@ -12,7 +13,6 @@ const crypto = new Crypto(); WebcryptoTest.check(crypto as any, {}); context("Crypto", () => { - context("getRandomValues", () => { it("Uint8Array", () => { @@ -63,47 +63,48 @@ context("Crypto", () => { (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"]); + + 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"]); + 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); - }); + 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); }); }); - (nodeMajorVersion < 14 ? context.skip : context)("ECDH-ES", () => { + }); + + (nodeMajorVersion < 14 ? context.skip : 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; + const keys = await crypto.subtle.generateKey({ name: "ecdh-es", namedCurve: "x25519" } as globalThis.EcKeyGenParams, false, ["deriveBits", "deriveKey"]); 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; + const keys = await crypto.subtle.generateKey({ name: "ecdh-es", namedCurve: "x448" } as globalThis.EcKeyGenParams, true, ["deriveBits", "deriveKey"]); assert.strictEqual(keys.privateKey.algorithm.name, "ECDH-ES"); assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X448"); @@ -120,4 +121,78 @@ context("Crypto", () => { }); + context("Extra ECC named curves", () => { + const namedCurves = [ + "brainpoolP160r1", + "brainpoolP160t1", + "brainpoolP192r1", + "brainpoolP192t1", + "brainpoolP224r1", + "brainpoolP224t1", + "brainpoolP256r1", + "brainpoolP256t1", + "brainpoolP320r1", + "brainpoolP320t1", + "brainpoolP384r1", + "brainpoolP384t1", + "brainpoolP512r1", + "brainpoolP512t1", + ]; + + context("sign/verify + pkcs8/spki", () => { + const data = new Uint8Array(10); + + namedCurves.forEach((namedCurve) => { + it(namedCurve, async () => { + const alg: EcKeyGenParams = { name: "ECDSA", namedCurve }; + const signAlg = { ...alg, hash: "SHA-256" } as EcdsaParams; + + const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); + + const signature = await crypto.subtle.sign(signAlg, keys.privateKey, data); + + const ok = await crypto.subtle.verify(signAlg, keys.publicKey, signature, data); + assert.ok(ok); + + const pkcs8 = await crypto.subtle.exportKey("pkcs8", keys.privateKey); + const spki = await crypto.subtle.exportKey("spki", keys.publicKey); + + const privateKey = await crypto.subtle.importKey("pkcs8", pkcs8, alg, true, ["sign"]); + const publicKey = await crypto.subtle.importKey("spki", spki, alg, true, ["verify"]); + + const signature2 = await crypto.subtle.sign(signAlg, privateKey, data); + const ok2 = await crypto.subtle.verify(signAlg, keys.publicKey, signature2, data); + assert.ok(ok2); + + const ok3 = await crypto.subtle.verify(signAlg, publicKey, signature, data); + assert.ok(ok3); + }); + }); + }); + + context("deriveBits + jwk", () => { + namedCurves.forEach((namedCurve) => { + it(namedCurve, async () => { + const alg: EcKeyGenParams = { name: "ECDH", namedCurve }; + + const keys = await crypto.subtle.generateKey(alg, true, ["deriveBits", "deriveKey"]); + + const deriveAlg: EcdhKeyDeriveParams = { name: "ECDH", public: keys.publicKey }; + const derivedBits = await crypto.subtle.deriveBits(deriveAlg, keys.privateKey, 128); + + const privateJwk = await crypto.subtle.exportKey("jwk", keys.privateKey); + const publicJwk = await crypto.subtle.exportKey("jwk", keys.publicKey); + const privateKey = await crypto.subtle.importKey("jwk", privateJwk, alg, true, ["deriveBits"]); + const publicKey = await crypto.subtle.importKey("jwk", publicJwk, alg, true, []); + + const derivedBits2 = await crypto.subtle.deriveBits({ name: "ECDH", public: keys.publicKey } as EcdhKeyDeriveParams, privateKey, 128); + const derivedBits3 = await crypto.subtle.deriveBits({ name: "ECDH", public: publicKey } as EcdhKeyDeriveParams, keys.privateKey, 128); + + assert.strictEqual(Convert.ToHex(derivedBits2), Convert.ToHex(derivedBits)); + assert.strictEqual(Convert.ToHex(derivedBits3), Convert.ToHex(derivedBits)); + }); + }); + }); + }); + });