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/ed/crypto.ts

253 lines
6.4 KiB
TypeScript

import { Buffer } from "buffer";
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 { EdPrivateKey } from "./private_key";
import { EdPublicKey } from "./public_key";
import { CryptoKey } from "../../keys";
export class EdCrypto {
public static publicKeyUsages = ["verify"];
public static privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
public static async generateKey(
algorithm: EcKeyGenParams,
extractable: boolean,
keyUsages: KeyUsage[]
): Promise<CryptoKeyPair> {
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 = {
privateKey,
publicKey,
};
return res;
}
public static async sign(
algorithm: Algorithm,
key: EdPrivateKey,
data: Uint8Array
): Promise<ArrayBuffer> {
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<boolean> {
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<ArrayBuffer> {
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<JsonWebKey | ArrayBuffer> {
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<CryptoKey> {
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;
}
}