Skip to content

Commit

Permalink
feat: Support braintpool curves
Browse files Browse the repository at this point in the history
  • Loading branch information
microshine committed Oct 26, 2021
1 parent f517801 commit 2f1868e
Show file tree
Hide file tree
Showing 18 changed files with 178 additions and 265 deletions.
30 changes: 15 additions & 15 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,23 @@ export interface ProviderInfo {

export class SubtleCrypto implements core.NativeSubtleCrypto {
constructor(crypto: Crypto);
public decrypt(algorithm: string | RsaOaepParams | AesCtrParams | AesCbcParams | AesCmacParams | AesGcmParams | AesCfbParams, key: CryptoKey, data: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer): Promise<ArrayBuffer>;
public deriveBits(algorithm: string | EcdhKeyDeriveParams | DhKeyDeriveParams | ConcatParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number): Promise<ArrayBuffer>;
public deriveKey(algorithm: string | EcdhKeyDeriveParams | DhKeyDeriveParams | ConcatParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, derivedKeyType: string | ConcatParams | HkdfParams | Pbkdf2Params | AesDerivedKeyParams | HmacImportParams, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public digest(algorithm: string | Algorithm, data: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer): Promise<ArrayBuffer>;
public encrypt(algorithm: string | RsaOaepParams | AesCtrParams | AesCbcParams | AesCmacParams | AesGcmParams | AesCfbParams, key: CryptoKey, data: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer): Promise<ArrayBuffer>;
public decrypt(algorithm: string | RsaOaepParams | AesCtrParams | AesCbcParams | core.AesCmacParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>;
public deriveBits(algorithm: string | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number): Promise<ArrayBuffer>;
public deriveKey(algorithm: string | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, derivedKeyType: string | HkdfParams | Pbkdf2Params | AesDerivedKeyParams | HmacImportParams, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public digest(algorithm: string | Algorithm, data: BufferSource): Promise<ArrayBuffer>;
public encrypt(algorithm: string | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>;
public exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
public exportKey(format: "raw" | "pkcs8" | "spki", key: CryptoKey): Promise<ArrayBuffer>;
public exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;
public generateKey(algorithm: string, extractable: boolean, keyUsages: string[]): Promise<CryptoKey | CryptoKeyPair>;
public generateKey(algorithm: (RsaHashedKeyGenParams | EcKeyGenParams | DhKeyGenParams) & Pkcs11KeyGenParams, extractable: boolean, keyUsages: string[]): Promise<CryptoKeyPair>;
public generateKey(algorithm: (Pbkdf2Params | AesKeyGenParams | HmacKeyGenParams) & Pkcs11KeyGenParams, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public importKey(format: "jwk", keyData: JsonWebKey, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public importKey(format: "raw" | "pkcs8" | "spki", keyData: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public importKey(format: string, keyData: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer | JsonWebKey, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public sign(algorithm: string | AesCmacParams | RsaPssParams | EcdsaParams, key: CryptoKey, data: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer): Promise<ArrayBuffer>;
public unwrapKey(format: string, wrappedKey: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer, unwrappingKey: CryptoKey, unwrapAlgorithm: string | Algorithm, unwrappedKeyAlgorithm: string | Algorithm, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>;
public verify(algorithm: string | AesCmacParams | RsaPssParams | EcdsaParams, key: CryptoKey, signature: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer, data: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | ArrayBuffer): Promise<boolean>;
public generateKey(algorithm: string, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey | core.CryptoKeyPair>;
public generateKey(algorithm: (RsaHashedKeyGenParams | EcKeyGenParams) & Pkcs11KeyGenParams, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<core.CryptoKeyPair>;
public generateKey(algorithm: (Pbkdf2Params | AesKeyGenParams | HmacKeyGenParams) & Pkcs11KeyGenParams, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public importKey(format: "jwk", keyData: JsonWebKey, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public importKey(format: "raw" | "pkcs8" | "spki", keyData: BufferSource, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public importKey(format: string, keyData: BufferSource | JsonWebKey, algorithm: string | Pkcs11ImportAlgorithms, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public sign(algorithm: string | core.AesCmacParams | RsaPssParams | EcdsaParams, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>;
public unwrapKey(format: string, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: string | Algorithm, unwrappedKeyAlgorithm: string | Algorithm, extractable: boolean, keyUsages: Iterable<KeyUsage>): Promise<CryptoKey>;
public verify(algorithm: string | core.AesCmacParams | RsaPssParams | EcdsaParams, key: CryptoKey, signature: BufferSource, data: BufferSource): Promise<boolean>;
public wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | Algorithm): Promise<ArrayBuffer>;
}

Expand Down Expand Up @@ -200,7 +200,7 @@ export class Crypto implements core.NativeCrypto, core.CryptoStorages {
*/
constructor(props: CryptoParams);

public getRandomValues<T extends Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array | DataView | null>(array: T): T;
public getRandomValues<T extends ArrayBufferView | null>(array: T): T;

public open(rw?: boolean): void;
public reset(): void;
Expand Down
5 changes: 4 additions & 1 deletion src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ export class Crypto implements core.Crypto, core.CryptoStorages, types.ISessionC
* @param array Initialize array
*/
// Based on: https://github.com/KenanY/get-random-values
public getRandomValues<T extends ArrayBufferView>(array: T): T {
public getRandomValues<T extends ArrayBufferView | null>(array: T): T {
if (!ArrayBuffer.isView(array)) {
throw new TypeError("Failed to execute 'getRandomValues' on 'Crypto': parameter 1 is not of type 'ArrayBufferView'");
}
if (array.byteLength > 65536) {
throw new core.CryptoError(`Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (${array.byteLength}) exceeds the number of bytes of entropy available via this API (65536).`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/key_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ export class KeyStorage implements core.CryptoKeyStorage {
const p11Key = data as CryptoKey;

// don't copy object from token
if (!(this.hasItem(data) && p11Key.key.token)) {
const hasItem = await this.hasItem(data);
if (!(hasItem && p11Key.key.token)) {
const template = this.crypto.templateBuilder.build({
action: "copy",
type: p11Key.type,
Expand Down
134 changes: 38 additions & 96 deletions src/mechs/ec/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema";
import { AsnConvert, AsnParser, AsnSerializer, OctetString } from "@peculiar/asn1-schema";
import { JsonParser, JsonSerializer } from "@peculiar/json-schema";
import * as graphene from "graphene-pk11";
import { Convert } from "pvtsutils";
Expand All @@ -10,7 +10,9 @@ import * as types from "../../types";
import * as utils from "../../utils";

import { EcCryptoKey } from "./key";
import { EcUtils } from "./utils";

// tslint:disable-next-line: variable-name
const id_ecPublicKey = "1.2.840.10045.2.1";

export class EcCrypto implements types.IContainer {

Expand All @@ -20,8 +22,8 @@ export class EcCrypto implements types.IContainer {
public constructor(public container: types.ISessionContainer) {
}

public async generateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> {
return new Promise<CryptoKeyPair>((resolve, reject) => {
public async generateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
return new Promise<core.CryptoKeyPair>((resolve, reject) => {
// Create PKCS#11 templates
const attrs: types.Pkcs11Attributes = {
id: utils.GUID(),
Expand All @@ -43,7 +45,7 @@ export class EcCrypto implements types.IContainer {
});

// EC params
publicTemplate.paramsEC = this.getJsonNamedCurve(algorithm.namedCurve).value;
publicTemplate.paramsEC = Buffer.from(core.EcCurves.get(algorithm.namedCurve).raw);

// PKCS11 generation
this.container.session.generateKeyPair(graphene.KeyGenMechanism.EC, publicTemplate, privateTemplate, (err, keys) => {
Expand Down Expand Up @@ -118,8 +120,8 @@ export class EcCrypto implements types.IContainer {
return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages);
}
case "raw": {
const curve = this.getJsonNamedCurve(algorithm.namedCurve);
const ecPoint = EcUtils.decodePoint(Buffer.from(keyData as Uint8Array), curve, false);
const curve = core.EcCurves.get(algorithm.namedCurve);
const ecPoint = core.EcUtils.decodePoint(keyData as Uint8Array, curve.size);
const jwk: JsonWebKey = {
kty: "EC",
crv: algorithm.namedCurve,
Expand Down Expand Up @@ -155,40 +157,7 @@ export class EcCrypto implements types.IContainer {
return utils.digest(hashAlgorithm.replace("-", ""), data);
}

public getJsonNamedCurve(name: string): graphene.INamedCurve {
let namedCurve: string;
switch (name) {
case "P-192":
namedCurve = "secp192r1";
break;
case "K-256":
const p256 = graphene.NamedCurve.getByName("secp256r1");
return {
name: "secp256k1",
oid: "1.3.132.0.10",
value: Buffer.from("06052b8104000A", "hex"),
size: p256.size,
};
case "P-256":
namedCurve = "secp256r1";
break;
case "P-384":
namedCurve = "secp384r1";
break;
case "P-521":
namedCurve = "secp521r1";
break;
case "X25519":
namedCurve = "curve25519";
break;
default:
throw new Error(`Unsupported namedCurve in use ${name}`);
}
return graphene.NamedCurve.getByName(namedCurve);
}

protected importJwkPrivateKey(jwk: JsonWebKey, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) {
const namedCurve = this.getJsonNamedCurve(algorithm.namedCurve);
const template = this.createTemplate({
action: "import",
type: "private",
Expand All @@ -203,7 +172,7 @@ export class EcCrypto implements types.IContainer {
});

// Set EC private key attributes
template.paramsEC = namedCurve.value;
template.paramsEC = Buffer.from(core.EcCurves.get(algorithm.namedCurve).raw);
template.value = utils.b64UrlDecode(jwk.d!);

const p11key = this.container.session.create(template).toType<graphene.Key>();
Expand All @@ -212,7 +181,7 @@ export class EcCrypto implements types.IContainer {
}

protected importJwkPublicKey(jwk: JsonWebKey, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) {
const namedCurve = this.getJsonNamedCurve(algorithm.namedCurve);
const namedCurve = core.EcCurves.get(algorithm.namedCurve);
const template = this.createTemplate({
action: "import",
type: "public",
Expand All @@ -226,12 +195,14 @@ export class EcCrypto implements types.IContainer {
});

// Set EC public key attributes
template.paramsEC = namedCurve.value;
template.paramsEC = Buffer.from(namedCurve.raw);;
let pointEc: Buffer;
if (namedCurve.name === "curve25519") {
pointEc = utils.b64UrlDecode(jwk.x!);
} else {
pointEc = EcUtils.encodePoint({ x: utils.b64UrlDecode(jwk.x!), y: utils.b64UrlDecode(jwk.y!) }, namedCurve);
const point = core.EcUtils.encodePoint({ x: utils.b64UrlDecode(jwk.x!), y: utils.b64UrlDecode(jwk.y!) }, namedCurve.size)
const derPoint = AsnConvert.serialize(new OctetString(point))
pointEc = Buffer.from(derPoint);
}
template.pointEC = pointEc;

Expand All @@ -244,12 +215,17 @@ export class EcCrypto implements types.IContainer {
const pkey: graphene.ITemplate = key.key.getAttribute({
pointEC: null,
});
// TODO: lib.dom.d.ts has typedCurve
const curve = this.getJsonNamedCurve((key.algorithm as EcKeyGenParams).namedCurve);
const ecPoint = EcUtils.decodePoint(pkey.pointEC!, curve, true);
const curve = core.EcCurves.get(key.algorithm.namedCurve);
// Parse DER-encoded of ANSI X9.62 ECPoint value ''Q''
const p11PointEC = pkey.pointEC;
if (!p11PointEC) {
throw new Error("Cannot get required ECPoint attribute");
}
const derEcPoint = AsnConvert.parse(p11PointEC, OctetString);
const ecPoint = core.EcUtils.decodePoint(derEcPoint, curve.size);
const jwk: JsonWebKey = {
kty: "EC",
crv: (key.algorithm as EcKeyGenParams).namedCurve,
crv: key.algorithm.namedCurve,
ext: true,
key_ops: key.usages,
x: Convert.ToBase64Url(ecPoint.x),
Expand Down Expand Up @@ -295,58 +271,38 @@ export class EcCrypto implements types.IContainer {
protected spki2jwk(raw: ArrayBuffer): JsonWebKey {
const keyInfo = AsnParser.parse(raw, core.asn1.PublicKeyInfo);

if (keyInfo.publicKeyAlgorithm.algorithm !== "1.2.840.10045.2.1") {
if (keyInfo.publicKeyAlgorithm.algorithm !== id_ecPublicKey) {
throw new Error("SPKI is not EC public key");
}

const namedCurve = this.getNamedCurveByOid(AsnParser.parse(keyInfo.publicKeyAlgorithm.parameters!, core.asn1.ObjectIdentifier));
const namedCurveId = AsnParser.parse(keyInfo.publicKeyAlgorithm.parameters!, core.asn1.ObjectIdentifier);
const namedCurve = core.EcCurves.get(namedCurveId.value);

const ecPublicKey = new core.asn1.EcPublicKey(keyInfo.publicKey);
const json = JsonSerializer.toJSON(ecPublicKey);

return {
kty: "EC",
crv: namedCurve,
crv: namedCurve.name,
...json,
};
}

protected jwk2pkcs(jwk: JsonWebKey): ArrayBuffer {
Assert.requiredParameter(jwk.crv, "crv");
const namedCurveId = this.getNamedCurveId(jwk.crv);
const namedCurve = core.EcCurves.get(jwk.crv);

const ecPrivateKey = JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPrivateKey });

const keyInfo = new core.asn1.PrivateKeyInfo();
keyInfo.privateKeyAlgorithm = new core.asn1.AlgorithmIdentifier();
keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.10045.2.1";
keyInfo.privateKeyAlgorithm.parameters = AsnSerializer.serialize(namedCurveId);
keyInfo.privateKeyAlgorithm.algorithm = id_ecPublicKey;
keyInfo.privateKeyAlgorithm.parameters = namedCurve.raw;
keyInfo.privateKey = AsnSerializer.serialize(ecPrivateKey);

return AsnSerializer.serialize(keyInfo);
}

private getNamedCurveId(namedCurve: string) {
const namedCurveId = new core.asn1.ObjectIdentifier();
switch (namedCurve.toUpperCase()) {
case "K-256":
namedCurveId.value = "1.3.132.0.10";
break;
case "P-256":
namedCurveId.value = "1.2.840.10045.3.1.7";
break;
case "P-384":
namedCurveId.value = "1.3.132.0.34";
break;
case "P-521":
namedCurveId.value = "1.3.132.0.35";
break;
default:
throw new Error(`Unsupported EC named curve '${namedCurve}'`);
}
return namedCurveId;
}

protected getCoordinate(b64: string, coordinateLength: number) {
const buf = Convert.FromBase64Url(b64);
const offset = coordinateLength - buf.byteLength;
Expand All @@ -360,53 +316,39 @@ export class EcCrypto implements types.IContainer {
if (!jwk.crv) {
throw new Error("Absent mandatory parameter \"crv\"");
}
const namedCurveId = this.getNamedCurveId(jwk.crv);
const namedCurve = core.EcCurves.get(jwk.crv);

const ecPublicKey = JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPublicKey });

const keyInfo = new core.asn1.PublicKeyInfo();
keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.10045.2.1";
keyInfo.publicKeyAlgorithm.parameters = AsnSerializer.serialize(namedCurveId);
keyInfo.publicKeyAlgorithm.algorithm = id_ecPublicKey;
keyInfo.publicKeyAlgorithm.parameters = namedCurve.raw;
keyInfo.publicKey = ecPublicKey.value;
return AsnSerializer.serialize(keyInfo);
}

protected pkcs2jwk(raw: ArrayBuffer): JsonWebKey {
const keyInfo = AsnParser.parse(raw, core.asn1.PrivateKeyInfo);

if (keyInfo.privateKeyAlgorithm.algorithm !== "1.2.840.10045.2.1") {
if (keyInfo.privateKeyAlgorithm.algorithm !== id_ecPublicKey) {
throw new Error("PKCS8 is not EC private key");
}

if (!keyInfo.privateKeyAlgorithm.parameters) {
throw new Error("Cannot get required Named curve parameters from ASN.1 PrivateKeyInfo structure");
}

const namedCurve = this.getNamedCurveByOid(AsnParser.parse(keyInfo.privateKeyAlgorithm.parameters, core.asn1.ObjectIdentifier));
const namedCurveId = AsnParser.parse(keyInfo.privateKeyAlgorithm.parameters!, core.asn1.ObjectIdentifier);
const namedCurve = core.EcCurves.get(namedCurveId.value);

const ecPrivateKey = AsnParser.parse(keyInfo.privateKey, core.asn1.EcPrivateKey);
const json = JsonSerializer.toJSON(ecPrivateKey);

return {
kty: "EC",
crv: namedCurve,
crv: namedCurve.name,
...json,
};
}

private getNamedCurveByOid(id: core.asn1.ObjectIdentifier) {
switch (id.value) {
case "1.3.132.0.10": // K-256
return "K-256";
case "1.2.840.10045.3.1.7": // P-256
return "P-256";
case "1.3.132.0.34": // P-384
return "P-384";
case "1.3.132.0.35": // P-521
return "P-521";
default:
throw new Error(`Unsupported EC named curve '${id.value}'`);
}
}

}
4 changes: 3 additions & 1 deletion src/mechs/ec/ec_dh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { EcCryptoKey } from "./key";

export class EcdhProvider extends core.EcdhProvider implements types.IContainer {

public namedCurves = core.EcCurves.names;

public usages: core.ProviderKeyPairUsage = {
privateKey: ["sign", "deriveKey", "deriveBits"],
publicKey: ["verify"],
Expand All @@ -22,7 +24,7 @@ export class EcdhProvider extends core.EcdhProvider implements types.IContainer
this.crypto = new EcCrypto(container);
}

public async onGenerateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> {
public async onGenerateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<core.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
Expand Down
Loading

0 comments on commit 2f1868e

Please sign in to comment.