feat: Add EC Brainpool curves

This commit is contained in:
microshine 2021-10-26 12:39:51 +03:00
parent 37dff397d5
commit 5acb3e25ab
6 changed files with 148 additions and 72 deletions

View File

@ -1,6 +1,7 @@
import crypto from "crypto"; import crypto from "crypto";
import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema"; import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema";
import { JsonParser, JsonSerializer } from "@peculiar/json-schema"; import { JsonParser, JsonSerializer } from "@peculiar/json-schema";
import {BufferSourceConverter} from "pvtsutils";
import * as core from "webcrypto-core"; import * as core from "webcrypto-core";
import { CryptoKey } from "../../keys"; import { CryptoKey } from "../../keys";
import { getOidByNamedCurve } from "./helper"; import { getOidByNamedCurve } from "./helper";
@ -12,7 +13,7 @@ export class EcCrypto {
public static publicKeyUsages = ["verify"]; public static publicKeyUsages = ["verify"];
public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
const privateKey = new EcPrivateKey(); const privateKey = new EcPrivateKey();
privateKey.algorithm = algorithm; privateKey.algorithm = algorithm;
privateKey.extractable = extractable; privateKey.extractable = extractable;
@ -38,7 +39,7 @@ export class EcCrypto {
privateKey.data = keys.privateKey; privateKey.data = keys.privateKey;
publicKey.data = keys.publicKey; publicKey.data = keys.publicKey;
const res: CryptoKeyPair = { const res = {
privateKey, privateKey,
publicKey, publicKey,
}; };
@ -61,12 +62,9 @@ export class EcCrypto {
const signature = signer.sign(options); const signature = signer.sign(options);
const ecSignature = AsnParser.parse(signature, core.asn1.EcDsaSignature); const ecSignature = AsnParser.parse(signature, core.asn1.EcDsaSignature);
const pointSize = this.getPointSize(key.algorithm.namedCurve); const signatureRaw = core.EcUtils.encodeSignature(ecSignature, core.EcCurves.get(key.algorithm.namedCurve).size);
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.buffer;
return signatureRaw;
} }
public static async verify(algorithm: EcdsaParams, key: EcPublicKey, signature: Uint8Array, data: Uint8Array): Promise<boolean> { public static async verify(algorithm: EcdsaParams, key: EcPublicKey, signature: Uint8Array, data: Uint8Array): Promise<boolean> {
@ -82,9 +80,10 @@ export class EcCrypto {
}; };
const ecSignature = new core.asn1.EcDsaSignature(); const ecSignature = new core.asn1.EcDsaSignature();
const pointSize = this.getPointSize(key.algorithm.namedCurve); const namedCurve = core.EcCurves.get(key.algorithm.namedCurve);
ecSignature.r = this.removePadding(signature.slice(0, pointSize)); const signaturePoint = core.EcUtils.decodeSignature(signature, namedCurve.size);
ecSignature.s = this.removePadding(signature.slice(pointSize, pointSize + pointSize)); ecSignature.r = BufferSourceConverter.toArrayBuffer(signaturePoint.r);
ecSignature.s = BufferSourceConverter.toArrayBuffer(signaturePoint.s);
const ecSignatureRaw = Buffer.from(AsnSerializer.serialize(ecSignature)); const ecSignatureRaw = Buffer.from(AsnSerializer.serialize(ecSignature));
const ok = signer.verify(options, ecSignatureRaw); const ok = signer.verify(options, ecSignatureRaw);
@ -215,38 +214,8 @@ export class EcCrypto {
case "P-521": case "P-521":
return "secp521r1"; return "secp521r1";
default: 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);
}
} }

View File

@ -7,7 +7,9 @@ import { EcPublicKey } from "./public_key";
export class EcdhProvider extends core.EcdhProvider { export class EcdhProvider extends core.EcdhProvider {
public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { public namedCurves = core.EcCurves.names;
public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
const keys = await EcCrypto.generateKey( const keys = await EcCrypto.generateKey(
{ {
...algorithm, ...algorithm,

View File

@ -6,9 +6,9 @@ import { EcPublicKey } from "./public_key";
export class EcdsaProvider extends core.EcdsaProvider { 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<CryptoKeyPair> { public async onGenerateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
const keys = await EcCrypto.generateKey( const keys = await EcCrypto.generateKey(
{ {
...algorithm, ...algorithm,

View File

@ -13,6 +13,36 @@ const namedOIDs: { [key: string]: string } = {
// K-256 // K-256
"1.3.132.0.10": "K-256", "1.3.132.0.10": "K-256",
"K-256": "1.3.132.0.10", "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) { export function getNamedCurveByOid(oid: string) {

View File

@ -12,7 +12,7 @@ export class EdCrypto {
public static publicKeyUsages = ["verify"]; public static publicKeyUsages = ["verify"];
public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> { public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
const privateKey = new EdPrivateKey(); const privateKey = new EdPrivateKey();
privateKey.algorithm = algorithm; privateKey.algorithm = algorithm;
privateKey.extractable = extractable; privateKey.extractable = extractable;
@ -38,7 +38,7 @@ export class EdCrypto {
privateKey.data = keys.privateKey; privateKey.data = keys.privateKey;
publicKey.data = keys.publicKey; publicKey.data = keys.publicKey;
const res: CryptoKeyPair = { const res = {
privateKey, privateKey,
publicKey, publicKey,
}; };

View File

@ -1,6 +1,7 @@
import assert from "assert"; import assert from "assert";
import process from "process"; import process from "process";
import { WebcryptoTest } from "@peculiar/webcrypto-test"; import { WebcryptoTest } from "@peculiar/webcrypto-test";
import { Convert } from "pvtsutils";
import * as core from "webcrypto-core"; import * as core from "webcrypto-core";
import { Crypto } from "../src"; import { Crypto } from "../src";
@ -12,7 +13,6 @@ const crypto = new Crypto();
WebcryptoTest.check(crypto as any, {}); WebcryptoTest.check(crypto as any, {});
context("Crypto", () => { context("Crypto", () => {
context("getRandomValues", () => { context("getRandomValues", () => {
it("Uint8Array", () => { it("Uint8Array", () => {
@ -66,13 +66,14 @@ context("Crypto", () => {
context("generateKey", () => { context("generateKey", () => {
it("Ed25519", async () => { it("Ed25519", async () => {
const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed25519" } as globalThis.EcKeyGenParams, false, ["sign", "verify"]) as CryptoKeyPair; 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.name, "EdDSA");
assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed25519"); assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed25519");
}); });
it("Ed448", async () => { it("Ed448", async () => {
const keys = await crypto.subtle.generateKey({ name: "eddsa", namedCurve: "ed448" } as globalThis.EcKeyGenParams, true, ["sign", "verify"]) as CryptoKeyPair; 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.name, "EdDSA");
assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed448"); assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "Ed448");
@ -97,13 +98,13 @@ context("Crypto", () => {
context("generateKey", () => { context("generateKey", () => {
it("X25519", async () => { 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.name, "ECDH-ES");
assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X25519"); assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X25519");
}); });
it("X448", async () => { 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.name, "ECDH-ES");
assert.strictEqual((keys.privateKey.algorithm as EcKeyAlgorithm).namedCurve, "X448"); 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));
});
});
});
});
}); });