This repository has been archived on 2023-04-04. You can view files and clone it, but cannot push or open issues or pull requests.
webcrypto/src/mechs/ec/crypto.ts

222 lines
8.6 KiB
TypeScript
Raw Normal View History

2020-03-13 11:06:53 +00:00
import crypto from "crypto";
2019-01-25 10:43:13 +00:00
import { AsnParser, AsnSerializer } from "@peculiar/asn1-schema";
import { JsonParser, JsonSerializer } from "@peculiar/json-schema";
2021-10-26 09:39:51 +00:00
import {BufferSourceConverter} from "pvtsutils";
2019-01-25 10:43:13 +00:00
import * as core from "webcrypto-core";
import { CryptoKey } from "../../keys";
import { getOidByNamedCurve } from "./helper";
import { EcPrivateKey } from "./private_key";
import { EcPublicKey } from "./public_key";
export class EcCrypto {
public static publicKeyUsages = ["verify"];
public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
2022-03-02 18:35:31 +00:00
public static async generateKey(algorithm: EcKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> {
2019-01-25 10:43:13 +00:00
const privateKey = new EcPrivateKey();
privateKey.algorithm = algorithm;
privateKey.extractable = extractable;
privateKey.usages = keyUsages.filter((usage) => this.privateKeyUsages.indexOf(usage) !== -1);
const publicKey = new EcPublicKey();
publicKey.algorithm = algorithm;
publicKey.extractable = true;
publicKey.usages = keyUsages.filter((usage) => this.publicKeyUsages.indexOf(usage) !== -1);
const keys = crypto.generateKeyPairSync("ec", {
namedCurve: this.getOpenSSLNamedCurve(algorithm.namedCurve),
publicKeyEncoding: {
format: "der",
type: "spki",
},
privateKeyEncoding: {
format: "der",
type: "pkcs8",
},
});
privateKey.data = keys.privateKey;
publicKey.data = keys.publicKey;
2021-10-26 09:39:51 +00:00
const res = {
2019-01-25 10:43:13 +00:00
privateKey,
publicKey,
};
return res;
}
public static async sign(algorithm: EcdsaParams, key: EcPrivateKey, data: Uint8Array): Promise<ArrayBuffer> {
const cryptoAlg = (algorithm.hash as Algorithm).name.replace("-", "");
const signer = crypto.createSign(cryptoAlg);
signer.update(Buffer.from(data));
2019-02-17 07:35:59 +00:00
if (!key.pem) {
key.pem = `-----BEGIN PRIVATE KEY-----\n${key.data.toString("base64")}\n-----END PRIVATE KEY-----`;
}
2019-01-25 10:43:13 +00:00
const options = {
2019-02-17 07:35:59 +00:00
key: key.pem,
2019-01-25 10:43:13 +00:00
};
const signature = signer.sign(options);
2020-04-06 12:21:38 +00:00
const ecSignature = AsnParser.parse(signature, core.asn1.EcDsaSignature);
2019-01-25 10:43:13 +00:00
2021-10-26 09:39:51 +00:00
const signatureRaw = core.EcUtils.encodeSignature(ecSignature, core.EcCurves.get(key.algorithm.namedCurve).size);
return signatureRaw.buffer;
2019-01-25 10:43:13 +00:00
}
public static async verify(algorithm: EcdsaParams, key: EcPublicKey, signature: Uint8Array, data: Uint8Array): Promise<boolean> {
const cryptoAlg = (algorithm.hash as Algorithm).name.replace("-", "");
const signer = crypto.createVerify(cryptoAlg);
signer.update(Buffer.from(data));
2019-02-17 07:35:59 +00:00
if (!key.pem) {
key.pem = `-----BEGIN PUBLIC KEY-----\n${key.data.toString("base64")}\n-----END PUBLIC KEY-----`;
}
2019-01-25 10:43:13 +00:00
const options = {
2019-02-17 07:35:59 +00:00
key: key.pem,
2019-01-25 10:43:13 +00:00
};
2020-04-06 12:21:38 +00:00
const ecSignature = new core.asn1.EcDsaSignature();
2021-10-26 09:39:51 +00:00
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);
2019-01-25 10:43:13 +00:00
2019-02-15 22:43:08 +00:00
const ecSignatureRaw = Buffer.from(AsnSerializer.serialize(ecSignature));
const ok = signer.verify(options, ecSignatureRaw);
2019-01-25 10:43:13 +00:00
return ok;
}
public static async deriveBits(algorithm: EcdhKeyDeriveParams, baseKey: CryptoKey, length: number): Promise<ArrayBuffer> {
const cryptoAlg = this.getOpenSSLNamedCurve((baseKey.algorithm as EcKeyAlgorithm).namedCurve);
const ecdh = crypto.createECDH(cryptoAlg);
2020-04-06 12:21:38 +00:00
const asnPrivateKey = AsnParser.parse(baseKey.data, core.asn1.PrivateKeyInfo);
const asnEcPrivateKey = AsnParser.parse(asnPrivateKey.privateKey, core.asn1.EcPrivateKey);
2019-01-25 10:43:13 +00:00
ecdh.setPrivateKey(Buffer.from(asnEcPrivateKey.privateKey));
2020-04-06 12:21:38 +00:00
const asnPublicKey = AsnParser.parse((algorithm.public as CryptoKey).data, core.asn1.PublicKeyInfo);
2019-01-25 10:43:13 +00:00
const bits = ecdh.computeSecret(Buffer.from(asnPublicKey.publicKey));
return new Uint8Array(bits).buffer.slice(0, length >> 3);
}
public static async exportKey(format: KeyFormat, key: CryptoKey): Promise<JsonWebKey | ArrayBuffer> {
switch (format.toLowerCase()) {
case "jwk":
return JsonSerializer.toJSON(key);
case "pkcs8":
case "spki":
return new Uint8Array(key.data).buffer;
2020-03-13 11:06:53 +00:00
case "raw": {
2020-04-06 12:21:38 +00:00
const publicKeyInfo = AsnParser.parse(key.data, core.asn1.PublicKeyInfo);
2019-01-25 10:43:13 +00:00
return publicKeyInfo.publicKey;
2020-03-13 11:06:53 +00:00
}
2019-01-25 10:43:13 +00:00
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<CryptoKey> {
switch (format.toLowerCase()) {
2020-03-13 11:06:53 +00:00
case "jwk": {
2019-01-25 10:43:13 +00:00
const jwk = keyData as JsonWebKey;
if (jwk.d) {
2020-04-06 12:21:38 +00:00
const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.EcPrivateKey });
2019-01-25 10:43:13 +00:00
return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages);
} else {
2020-04-06 12:21:38 +00:00
const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.EcPublicKey });
2019-01-25 10:43:13 +00:00
return this.importPublicKey(asnKey, algorithm, extractable, keyUsages);
}
2020-03-13 11:06:53 +00:00
}
2019-01-25 10:43:13 +00:00
case "raw": {
2020-04-06 12:21:38 +00:00
const asnKey = new core.asn1.EcPublicKey(keyData as ArrayBuffer);
2019-01-25 10:43:13 +00:00
return this.importPublicKey(asnKey, algorithm, extractable, keyUsages);
}
case "spki": {
2020-04-06 12:21:38 +00:00
const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PublicKeyInfo);
const asnKey = new core.asn1.EcPublicKey(keyInfo.publicKey);
this.assertKeyParameters(keyInfo.publicKeyAlgorithm.parameters, algorithm.namedCurve);
2019-01-25 10:43:13 +00:00
return this.importPublicKey(asnKey, algorithm, extractable, keyUsages);
}
case "pkcs8": {
2020-04-06 12:21:38 +00:00
const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PrivateKeyInfo);
const asnKey = AsnParser.parse(keyInfo.privateKey, core.asn1.EcPrivateKey);
this.assertKeyParameters(keyInfo.privateKeyAlgorithm.parameters, algorithm.namedCurve);
2019-01-25 10:43:13 +00:00
return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages);
}
default:
throw new core.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'");
}
}
protected static assertKeyParameters(parameters: ArrayBuffer | null | undefined, namedCurve: string) {
if (!parameters) {
throw new core.CryptoError("Key info doesn't have required parameters");
}
let namedCurveIdentifier = "";
try {
namedCurveIdentifier = AsnParser.parse(parameters, core.asn1.ObjectIdentifier).value;
} catch (e) {
throw new core.CryptoError("Cannot read key info parameters");
}
if (getOidByNamedCurve(namedCurve) !== namedCurveIdentifier) {
throw new core.CryptoError("Key info parameter doesn't match to named curve");
}
}
2020-04-06 12:21:38 +00:00
protected static async importPrivateKey(asnKey: core.asn1.EcPrivateKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) {
const keyInfo = new core.asn1.PrivateKeyInfo();
2019-01-25 10:43:13 +00:00
keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.10045.2.1";
2020-04-06 12:21:38 +00:00
keyInfo.privateKeyAlgorithm.parameters = AsnSerializer.serialize(new core.asn1.ObjectIdentifier(getOidByNamedCurve(algorithm.namedCurve)));
2019-01-25 10:43:13 +00:00
keyInfo.privateKey = AsnSerializer.serialize(asnKey);
const key = new EcPrivateKey();
key.data = Buffer.from(AsnSerializer.serialize(keyInfo));
key.algorithm = Object.assign({}, algorithm) as EcKeyAlgorithm;
key.extractable = extractable;
key.usages = keyUsages;
return key;
}
2020-04-06 12:21:38 +00:00
protected static async importPublicKey(asnKey: core.asn1.EcPublicKey, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]) {
const keyInfo = new core.asn1.PublicKeyInfo();
2019-01-25 10:43:13 +00:00
keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.10045.2.1";
const namedCurve = getOidByNamedCurve(algorithm.namedCurve);
keyInfo.publicKeyAlgorithm.parameters = AsnSerializer.serialize(new core.asn1.ObjectIdentifier(namedCurve));
2019-01-25 10:43:13 +00:00
keyInfo.publicKey = asnKey.value;
const key = new EcPublicKey();
key.data = Buffer.from(AsnSerializer.serialize(keyInfo));
key.algorithm = Object.assign({}, algorithm) as EcKeyAlgorithm;
key.extractable = extractable;
key.usages = keyUsages;
return key;
}
private static getOpenSSLNamedCurve(curve: string) {
switch (curve.toUpperCase()) {
case "P-256":
return "prime256v1";
case "K-256":
return "secp256k1";
case "P-384":
return "secp384r1";
case "P-521":
return "secp521r1";
default:
2021-10-26 09:39:51 +00:00
return curve;
2019-02-15 22:43:08 +00:00
}
}
2019-01-25 10:43:13 +00:00
}