summaryrefslogtreecommitdiffstats
path: root/third_party/js/PKI.js/src/common.ts
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/js/PKI.js/src/common.ts')
-rw-r--r--third_party/js/PKI.js/src/common.ts411
1 files changed, 411 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/common.ts b/third_party/js/PKI.js/src/common.ts
new file mode 100644
index 0000000000..e95772f4e7
--- /dev/null
+++ b/third_party/js/PKI.js/src/common.ts
@@ -0,0 +1,411 @@
+import * as asn1js from "asn1js";
+import * as pvutils from "pvutils";
+import { AlgorithmIdentifier } from "./AlgorithmIdentifier";
+import { EMPTY_BUFFER } from "./constants";
+import type { CryptoEngineAlgorithmOperation, CryptoEngineAlgorithmParams, ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
+import { ArgumentError } from "./errors";
+
+//#region Crypto engine related function
+export { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
+
+export interface GlobalCryptoEngine {
+ name: string;
+ crypto: ICryptoEngine | null;
+}
+export let engine: GlobalCryptoEngine = {
+ name: "none",
+ crypto: null,
+};
+
+function isCryptoEngine(engine: unknown): engine is ICryptoEngine {
+ return engine
+ && typeof engine === "object"
+ && "crypto" in engine
+ ? true
+ : false;
+}
+
+/**
+ * Sets global crypto engine
+ * @param name Name of the crypto engine
+ * @param crypto
+ * @param subtle
+ * @deprecated Since version 3.0.0
+ */
+export function setEngine(name: string, crypto: ICryptoEngine | Crypto, subtle: ICryptoEngine | SubtleCrypto): void;
+/**
+ * Sets global crypto engine
+ * @param name Name of the crypto engine
+ * @param crypto Crypto engine. If the parameter is omitted, `CryptoEngine` with `self.crypto` are used
+ * @since 3.0.0
+ */
+export function setEngine(name: string, crypto?: ICryptoEngine): void;
+export function setEngine(name: string, ...args: any[]): void {
+ let crypto: ICryptoEngine | null = null;
+ if (args.length < 2) {
+ // v3.0.0 implementation
+ if (args.length) {
+ crypto = args[0];
+ } else {
+ // crypto param is omitted, use CryptoEngine(self.crypto)
+ crypto = typeof self !== "undefined" && self.crypto ? new CryptoEngine({ name: "browser", crypto: self.crypto }) : null;
+ }
+ } else {
+ // prev implementation
+ const cryptoArg = args[0];
+ const subtleArg = args[1];
+ if (isCryptoEngine(subtleArg)) {
+ crypto = subtleArg;
+ } else if (isCryptoEngine(cryptoArg)) {
+ crypto = cryptoArg;
+ } else if ("subtle" in cryptoArg && "getRandomValues" in cryptoArg) {
+ crypto = new CryptoEngine({
+ crypto: cryptoArg,
+ });
+ }
+ }
+
+ if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
+ // We are in Node
+ if (typeof (global as any)[process.pid] === "undefined") {
+ (global as any)[process.pid] = {};
+ }
+ else {
+ if (typeof (global as any)[process.pid] !== "object") {
+ throw new Error(`Name global.${process.pid} already exists and it is not an object`);
+ }
+ }
+
+ if (typeof (global as any)[process.pid].pkijs === "undefined") {
+ (global as any)[process.pid].pkijs = {};
+ }
+ else {
+ if (typeof (global as any)[process.pid].pkijs !== "object") {
+ throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`);
+ }
+ }
+
+ (global as any)[process.pid].pkijs.engine = {
+ name: name,
+ crypto,
+ };
+ } else {
+ // We are in browser
+ engine = {
+ name: name,
+ crypto,
+ };
+ }
+}
+
+export function getEngine(): GlobalCryptoEngine {
+ //#region We are in Node
+ if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
+ let _engine;
+
+ try {
+ _engine = (global as any)[process.pid].pkijs.engine;
+ }
+ catch (ex) {
+ throw new Error("Please call 'setEngine' before call to 'getEngine'");
+ }
+
+ return _engine;
+ }
+ //#endregion
+
+ return engine;
+}
+//#endregion
+
+//#region Declaration of common functions
+
+/**
+ * Gets crypto subtle from the current "crypto engine"
+ * @param safety
+ * @returns Reruns {@link ICryptoEngine} or `null`
+ */
+export function getCrypto(safety?: boolean): ICryptoEngine | null;
+/**
+ * Gets crypto subtle from the current "crypto engine"
+ * @param safety
+ * @returns Reruns {@link ICryptoEngine} or throws en exception
+ * @throws Throws {@link Error} if `subtle` is empty
+ */
+export function getCrypto(safety: true): ICryptoEngine;
+export function getCrypto(safety = false): ICryptoEngine | null {
+ const _engine = getEngine();
+
+ if (!_engine.crypto && safety) {
+ throw new Error("Unable to create WebCrypto object");
+ }
+
+ return _engine.crypto;
+}
+
+/**
+ * Initialize input Uint8Array by random values (with help from current "crypto engine")
+ * @param view
+ */
+export function getRandomValues(view: Uint8Array) {
+ return getCrypto(true).getRandomValues(view);
+}
+
+/**
+ * Get OID for each specific algorithm
+ * @param algorithm WebCrypto Algorithm
+ * @param safety if `true` throws exception on unknown algorithm
+ * @param target name of the target
+ * @throws Throws {@link Error} exception if unknown WebCrypto algorithm
+ */
+export function getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string) {
+ return getCrypto(true).getOIDByAlgorithm(algorithm, safety, target);
+}
+
+/**
+ * Get default algorithm parameters for each kind of operation
+ * @param algorithmName Algorithm name to get common parameters for
+ * @param operation Kind of operation: "sign", "encrypt", "generateKey", "importKey", "exportKey", "verify"
+ */
+// TODO Add safety
+export function getAlgorithmParameters(algorithmName: string, operation: CryptoEngineAlgorithmOperation): CryptoEngineAlgorithmParams {
+ return getCrypto(true).getAlgorithmParameters(algorithmName, operation);
+}
+
+/**
+ * Create CMS ECDSA signature from WebCrypto ECDSA signature
+ * @param signatureBuffer WebCrypto result of "sign" function
+ */
+export function createCMSECDSASignature(signatureBuffer: ArrayBuffer): ArrayBuffer {
+ //#region Initial check for correct length
+ if ((signatureBuffer.byteLength % 2) !== 0)
+ return EMPTY_BUFFER;
+ //#endregion
+
+ //#region Initial variables
+ const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer
+
+ const rBuffer = new ArrayBuffer(length);
+ const rView = new Uint8Array(rBuffer);
+ rView.set(new Uint8Array(signatureBuffer, 0, length));
+
+ const rInteger = new asn1js.Integer({ valueHex: rBuffer });
+
+ const sBuffer = new ArrayBuffer(length);
+ const sView = new Uint8Array(sBuffer);
+ sView.set(new Uint8Array(signatureBuffer, length, length));
+
+ const sInteger = new asn1js.Integer({ valueHex: sBuffer });
+ //#endregion
+
+ return (new asn1js.Sequence({
+ value: [
+ rInteger.convertToDER(),
+ sInteger.convertToDER()
+ ]
+ })).toBER(false);
+}
+
+/**
+ * Create a single ArrayBuffer from CMS ECDSA signature
+ * @param cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature
+ * @param pointSize Size of EC point. Use {@link ECNamedCurves.find} to get correct point size
+ * @returns WebCrypto signature
+ */
+export function createECDSASignatureFromCMS(cmsSignature: asn1js.AsnType, pointSize: number): ArrayBuffer {
+ // Check input variables
+ if (!(cmsSignature instanceof asn1js.Sequence
+ && cmsSignature.valueBlock.value.length === 2
+ && cmsSignature.valueBlock.value[0] instanceof asn1js.Integer
+ && cmsSignature.valueBlock.value[1] instanceof asn1js.Integer))
+ return EMPTY_BUFFER;
+
+ const rValueView = cmsSignature.valueBlock.value[0].convertFromDER().valueBlock.valueHexView;
+ const sValueView = cmsSignature.valueBlock.value[1].convertFromDER().valueBlock.valueHexView;
+
+ const res = new Uint8Array(pointSize * 2);
+
+ res.set(rValueView, pointSize - rValueView.byteLength);
+ res.set(sValueView, (2 * pointSize) - sValueView.byteLength);
+
+ return res.buffer;
+}
+
+/**
+ * Gets WebCrypto algorithm by well-known OID
+ * @param oid algorithm identifier
+ * @param safety if true throws exception on unknown algorithm identifier
+ * @param target name of the target
+ * @returns WebCrypto algorithm or an empty object
+ */
+export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object;
+export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T;
+export function getAlgorithmByOID(oid: string, safety = false, target?: string): any {
+ return getCrypto(true).getAlgorithmByOID(oid, safety, target);
+}
+
+/**
+ * Getting hash algorithm by signature algorithm
+ * @param signatureAlgorithm Signature algorithm
+ */
+export function getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string {
+ return getCrypto(true).getHashAlgorithm(signatureAlgorithm);
+}
+
+/**
+ * ANS X9.63 Key Derivation Function having a "Counter" as a parameter
+ * @param hashFunction Used hash function
+ * @param zBuffer ArrayBuffer containing ECDH shared secret to derive from
+ * @param Counter
+ * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
+ * @param crypto Crypto engine
+ */
+async function kdfWithCounter(hashFunction: string, zBuffer: ArrayBuffer, Counter: number, SharedInfo: ArrayBuffer, crypto: ICryptoEngine): Promise<{ counter: number; result: ArrayBuffer; }> {
+ //#region Check of input parameters
+ switch (hashFunction.toUpperCase()) {
+ case "SHA-1":
+ case "SHA-256":
+ case "SHA-384":
+ case "SHA-512":
+ break;
+ default:
+ throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
+ }
+
+ ArgumentError.assert(zBuffer, "zBuffer", "ArrayBuffer");
+ if (zBuffer.byteLength === 0)
+ throw new ArgumentError("'zBuffer' has zero length, error");
+
+ ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
+ if (Counter > 255)
+ throw new ArgumentError("Please set 'Counter' argument to value less or equal to 255");
+ //#endregion
+
+ //#region Initial variables
+ const counterBuffer = new ArrayBuffer(4);
+ const counterView = new Uint8Array(counterBuffer);
+ counterView[0] = 0x00;
+ counterView[1] = 0x00;
+ counterView[2] = 0x00;
+ counterView[3] = Counter;
+
+ let combinedBuffer = EMPTY_BUFFER;
+ //#endregion
+
+ //#region Create a combined ArrayBuffer for digesting
+ combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, zBuffer);
+ combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, counterBuffer);
+ combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, SharedInfo);
+ //#endregion
+
+ //#region Return digest of combined ArrayBuffer and information about current counter
+ const result = await crypto.digest(
+ { name: hashFunction },
+ combinedBuffer);
+
+ return {
+ counter: Counter,
+ result
+ };
+ //#endregion
+}
+
+/**
+ * ANS X9.63 Key Derivation Function
+ * @param hashFunction Used hash function
+ * @param Zbuffer ArrayBuffer containing ECDH shared secret to derive from
+ * @param keydatalen Length (!!! in BITS !!!) of used kew derivation function
+ * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
+ * @param crypto Crypto engine
+ */
+export async function kdf(hashFunction: string, Zbuffer: ArrayBuffer, keydatalen: number, SharedInfo: ArrayBuffer, crypto = getCrypto(true)) {
+ //#region Initial variables
+ let hashLength = 0;
+ let maxCounter = 1;
+ //#endregion
+
+ //#region Check of input parameters
+ switch (hashFunction.toUpperCase()) {
+ case "SHA-1":
+ hashLength = 160; // In bits
+ break;
+ case "SHA-256":
+ hashLength = 256; // In bits
+ break;
+ case "SHA-384":
+ hashLength = 384; // In bits
+ break;
+ case "SHA-512":
+ hashLength = 512; // In bits
+ break;
+ default:
+ throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
+ }
+
+ ArgumentError.assert(Zbuffer, "Zbuffer", "ArrayBuffer");
+ if (Zbuffer.byteLength === 0)
+ throw new ArgumentError("'Zbuffer' has zero length, error");
+ ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
+ //#endregion
+
+ //#region Calculated maximum value of "Counter" variable
+ const quotient = keydatalen / hashLength;
+
+ if (Math.floor(quotient) > 0) {
+ maxCounter = Math.floor(quotient);
+
+ if ((quotient - maxCounter) > 0)
+ maxCounter++;
+ }
+ //#endregion
+
+ //#region Create an array of "kdfWithCounter"
+ const incomingResult = [];
+ for (let i = 1; i <= maxCounter; i++)
+ incomingResult.push(await kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo, crypto));
+ //#endregion
+
+ //#region Return combined digest with specified length
+ //#region Initial variables
+ let combinedBuffer = EMPTY_BUFFER;
+ let currentCounter = 1;
+ let found = true;
+ //#endregion
+
+ //#region Combine all buffer together
+ while (found) {
+ found = false;
+
+ for (const result of incomingResult) {
+ if (result.counter === currentCounter) {
+ combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, result.result);
+ found = true;
+ break;
+ }
+ }
+
+ currentCounter++;
+ }
+ //#endregion
+
+ //#region Create output buffer with specified length
+ keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits
+
+ if (combinedBuffer.byteLength > keydatalen) {
+ const newBuffer = new ArrayBuffer(keydatalen);
+ const newView = new Uint8Array(newBuffer);
+ const combinedView = new Uint8Array(combinedBuffer);
+
+ for (let i = 0; i < keydatalen; i++)
+ newView[i] = combinedView[i];
+
+ return newBuffer;
+ }
+
+ return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen"
+ //#endregion
+ //#endregion
+}
+//#endregion
+
+import { CryptoEngine } from "./CryptoEngine/CryptoEngine"; \ No newline at end of file