diff options
Diffstat (limited to 'third_party/js/PKI.js/src/common.ts')
-rw-r--r-- | third_party/js/PKI.js/src/common.ts | 411 |
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 |