common.ts (13278B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { AlgorithmIdentifier } from "./AlgorithmIdentifier"; 4 import { EMPTY_BUFFER } from "./constants"; 5 import type { CryptoEngineAlgorithmOperation, CryptoEngineAlgorithmParams, ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; 6 import { ArgumentError } from "./errors"; 7 8 //#region Crypto engine related function 9 export { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; 10 11 export interface GlobalCryptoEngine { 12 name: string; 13 crypto: ICryptoEngine | null; 14 } 15 export let engine: GlobalCryptoEngine = { 16 name: "none", 17 crypto: null, 18 }; 19 20 function isCryptoEngine(engine: unknown): engine is ICryptoEngine { 21 return engine 22 && typeof engine === "object" 23 && "crypto" in engine 24 ? true 25 : false; 26 } 27 28 /** 29 * Sets global crypto engine 30 * @param name Name of the crypto engine 31 * @param crypto 32 * @param subtle 33 * @deprecated Since version 3.0.0 34 */ 35 export function setEngine(name: string, crypto: ICryptoEngine | Crypto, subtle: ICryptoEngine | SubtleCrypto): void; 36 /** 37 * Sets global crypto engine 38 * @param name Name of the crypto engine 39 * @param crypto Crypto engine. If the parameter is omitted, `CryptoEngine` with `self.crypto` are used 40 * @since 3.0.0 41 */ 42 export function setEngine(name: string, crypto?: ICryptoEngine): void; 43 export function setEngine(name: string, ...args: any[]): void { 44 let crypto: ICryptoEngine | null = null; 45 if (args.length < 2) { 46 // v3.0.0 implementation 47 if (args.length) { 48 crypto = args[0]; 49 } else { 50 // crypto param is omitted, use CryptoEngine(self.crypto) 51 crypto = typeof self !== "undefined" && self.crypto ? new CryptoEngine({ name: "browser", crypto: self.crypto }) : null; 52 } 53 } else { 54 // prev implementation 55 const cryptoArg = args[0]; 56 const subtleArg = args[1]; 57 if (isCryptoEngine(subtleArg)) { 58 crypto = subtleArg; 59 } else if (isCryptoEngine(cryptoArg)) { 60 crypto = cryptoArg; 61 } else if ("subtle" in cryptoArg && "getRandomValues" in cryptoArg) { 62 crypto = new CryptoEngine({ 63 crypto: cryptoArg, 64 }); 65 } 66 } 67 68 if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) { 69 // We are in Node 70 if (typeof (global as any)[process.pid] === "undefined") { 71 (global as any)[process.pid] = {}; 72 } 73 else { 74 if (typeof (global as any)[process.pid] !== "object") { 75 throw new Error(`Name global.${process.pid} already exists and it is not an object`); 76 } 77 } 78 79 if (typeof (global as any)[process.pid].pkijs === "undefined") { 80 (global as any)[process.pid].pkijs = {}; 81 } 82 else { 83 if (typeof (global as any)[process.pid].pkijs !== "object") { 84 throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`); 85 } 86 } 87 88 (global as any)[process.pid].pkijs.engine = { 89 name: name, 90 crypto, 91 }; 92 } else { 93 // We are in browser 94 engine = { 95 name: name, 96 crypto, 97 }; 98 } 99 } 100 101 export function getEngine(): GlobalCryptoEngine { 102 //#region We are in Node 103 if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) { 104 let _engine; 105 106 try { 107 _engine = (global as any)[process.pid].pkijs.engine; 108 } 109 catch { 110 throw new Error("Please call 'setEngine' before call to 'getEngine'"); 111 } 112 113 return _engine; 114 } 115 //#endregion 116 117 return engine; 118 } 119 //#endregion 120 121 //#region Declaration of common functions 122 123 /** 124 * Gets crypto subtle from the current "crypto engine" 125 * @param safety 126 * @returns Reruns {@link ICryptoEngine} or `null` 127 */ 128 export function getCrypto(safety?: boolean): ICryptoEngine | null; 129 /** 130 * Gets crypto subtle from the current "crypto engine" 131 * @param safety 132 * @returns Reruns {@link ICryptoEngine} or throws en exception 133 * @throws Throws {@link Error} if `subtle` is empty 134 */ 135 export function getCrypto(safety: true): ICryptoEngine; 136 export function getCrypto(safety = false): ICryptoEngine | null { 137 const _engine = getEngine(); 138 139 if (!_engine.crypto && safety) { 140 throw new Error("Unable to create WebCrypto object"); 141 } 142 143 return _engine.crypto; 144 } 145 146 /** 147 * Initialize input Uint8Array by random values (with help from current "crypto engine") 148 * @param view 149 */ 150 export function getRandomValues(view: Uint8Array) { 151 return getCrypto(true).getRandomValues(view); 152 } 153 154 /** 155 * Get OID for each specific algorithm 156 * @param algorithm WebCrypto Algorithm 157 * @param safety if `true` throws exception on unknown algorithm 158 * @param target name of the target 159 * @throws Throws {@link Error} exception if unknown WebCrypto algorithm 160 */ 161 export function getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string) { 162 return getCrypto(true).getOIDByAlgorithm(algorithm, safety, target); 163 } 164 165 /** 166 * Get default algorithm parameters for each kind of operation 167 * @param algorithmName Algorithm name to get common parameters for 168 * @param operation Kind of operation: "sign", "encrypt", "generateKey", "importKey", "exportKey", "verify" 169 */ 170 // TODO Add safety 171 export function getAlgorithmParameters(algorithmName: string, operation: CryptoEngineAlgorithmOperation): CryptoEngineAlgorithmParams { 172 return getCrypto(true).getAlgorithmParameters(algorithmName, operation); 173 } 174 175 /** 176 * Create CMS ECDSA signature from WebCrypto ECDSA signature 177 * @param signatureBuffer WebCrypto result of "sign" function 178 */ 179 export function createCMSECDSASignature(signatureBuffer: ArrayBuffer): ArrayBuffer { 180 //#region Initial check for correct length 181 if ((signatureBuffer.byteLength % 2) !== 0) 182 return EMPTY_BUFFER; 183 //#endregion 184 185 //#region Initial variables 186 const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer 187 188 const rBuffer = new ArrayBuffer(length); 189 const rView = new Uint8Array(rBuffer); 190 rView.set(new Uint8Array(signatureBuffer, 0, length)); 191 192 const rInteger = new asn1js.Integer({ valueHex: rBuffer }); 193 194 const sBuffer = new ArrayBuffer(length); 195 const sView = new Uint8Array(sBuffer); 196 sView.set(new Uint8Array(signatureBuffer, length, length)); 197 198 const sInteger = new asn1js.Integer({ valueHex: sBuffer }); 199 //#endregion 200 201 return (new asn1js.Sequence({ 202 value: [ 203 rInteger.convertToDER(), 204 sInteger.convertToDER() 205 ] 206 })).toBER(false); 207 } 208 209 /** 210 * Create a single ArrayBuffer from CMS ECDSA signature 211 * @param cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature 212 * @param pointSize Size of EC point. Use {@link ECNamedCurves.find} to get correct point size 213 * @returns WebCrypto signature 214 */ 215 export function createECDSASignatureFromCMS(cmsSignature: asn1js.AsnType, pointSize: number): ArrayBuffer { 216 // Check input variables 217 if (!(cmsSignature instanceof asn1js.Sequence 218 && cmsSignature.valueBlock.value.length === 2 219 && cmsSignature.valueBlock.value[0] instanceof asn1js.Integer 220 && cmsSignature.valueBlock.value[1] instanceof asn1js.Integer)) 221 return EMPTY_BUFFER; 222 223 const rValueView = cmsSignature.valueBlock.value[0].convertFromDER().valueBlock.valueHexView; 224 const sValueView = cmsSignature.valueBlock.value[1].convertFromDER().valueBlock.valueHexView; 225 226 const res = new Uint8Array(pointSize * 2); 227 228 res.set(rValueView, pointSize - rValueView.byteLength); 229 res.set(sValueView, (2 * pointSize) - sValueView.byteLength); 230 231 return res.buffer; 232 } 233 234 /** 235 * Gets WebCrypto algorithm by well-known OID 236 * @param oid algorithm identifier 237 * @param safety if true throws exception on unknown algorithm identifier 238 * @param target name of the target 239 * @returns WebCrypto algorithm or an empty object 240 */ 241 export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object; 242 export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; 243 export function getAlgorithmByOID(oid: string, safety = false, target?: string): any { 244 return getCrypto(true).getAlgorithmByOID(oid, safety, target); 245 } 246 247 /** 248 * Getting hash algorithm by signature algorithm 249 * @param signatureAlgorithm Signature algorithm 250 */ 251 export function getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string { 252 return getCrypto(true).getHashAlgorithm(signatureAlgorithm); 253 } 254 255 /** 256 * ANS X9.63 Key Derivation Function having a "Counter" as a parameter 257 * @param hashFunction Used hash function 258 * @param zBuffer ArrayBuffer containing ECDH shared secret to derive from 259 * @param Counter 260 * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure 261 * @param crypto Crypto engine 262 */ 263 async function kdfWithCounter(hashFunction: string, zBuffer: ArrayBuffer, Counter: number, SharedInfo: ArrayBuffer, crypto: ICryptoEngine): Promise<{ counter: number; result: ArrayBuffer; }> { 264 //#region Check of input parameters 265 switch (hashFunction.toUpperCase()) { 266 case "SHA-1": 267 case "SHA-256": 268 case "SHA-384": 269 case "SHA-512": 270 break; 271 default: 272 throw new ArgumentError(`Unknown hash function: ${hashFunction}`); 273 } 274 275 ArgumentError.assert(zBuffer, "zBuffer", "ArrayBuffer"); 276 if (zBuffer.byteLength === 0) 277 throw new ArgumentError("'zBuffer' has zero length, error"); 278 279 ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer"); 280 if (Counter > 255) 281 throw new ArgumentError("Please set 'Counter' argument to value less or equal to 255"); 282 //#endregion 283 284 //#region Initial variables 285 const counterBuffer = new ArrayBuffer(4); 286 const counterView = new Uint8Array(counterBuffer); 287 counterView[0] = 0x00; 288 counterView[1] = 0x00; 289 counterView[2] = 0x00; 290 counterView[3] = Counter; 291 292 let combinedBuffer = EMPTY_BUFFER; 293 //#endregion 294 295 //#region Create a combined ArrayBuffer for digesting 296 combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, zBuffer); 297 combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, counterBuffer); 298 combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, SharedInfo); 299 //#endregion 300 301 //#region Return digest of combined ArrayBuffer and information about current counter 302 const result = await crypto.digest( 303 { name: hashFunction }, 304 combinedBuffer); 305 306 return { 307 counter: Counter, 308 result 309 }; 310 //#endregion 311 } 312 313 /** 314 * ANS X9.63 Key Derivation Function 315 * @param hashFunction Used hash function 316 * @param Zbuffer ArrayBuffer containing ECDH shared secret to derive from 317 * @param keydatalen Length (!!! in BITS !!!) of used kew derivation function 318 * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure 319 * @param crypto Crypto engine 320 */ 321 export async function kdf(hashFunction: string, Zbuffer: ArrayBuffer, keydatalen: number, SharedInfo: ArrayBuffer, crypto = getCrypto(true)) { 322 //#region Initial variables 323 let hashLength = 0; 324 let maxCounter = 1; 325 //#endregion 326 327 //#region Check of input parameters 328 switch (hashFunction.toUpperCase()) { 329 case "SHA-1": 330 hashLength = 160; // In bits 331 break; 332 case "SHA-256": 333 hashLength = 256; // In bits 334 break; 335 case "SHA-384": 336 hashLength = 384; // In bits 337 break; 338 case "SHA-512": 339 hashLength = 512; // In bits 340 break; 341 default: 342 throw new ArgumentError(`Unknown hash function: ${hashFunction}`); 343 } 344 345 ArgumentError.assert(Zbuffer, "Zbuffer", "ArrayBuffer"); 346 if (Zbuffer.byteLength === 0) 347 throw new ArgumentError("'Zbuffer' has zero length, error"); 348 ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer"); 349 //#endregion 350 351 //#region Calculated maximum value of "Counter" variable 352 const quotient = keydatalen / hashLength; 353 354 if (Math.floor(quotient) > 0) { 355 maxCounter = Math.floor(quotient); 356 357 if ((quotient - maxCounter) > 0) 358 maxCounter++; 359 } 360 //#endregion 361 362 //#region Create an array of "kdfWithCounter" 363 const incomingResult = []; 364 for (let i = 1; i <= maxCounter; i++) 365 incomingResult.push(await kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo, crypto)); 366 //#endregion 367 368 //#region Return combined digest with specified length 369 //#region Initial variables 370 let combinedBuffer = EMPTY_BUFFER; 371 let currentCounter = 1; 372 let found = true; 373 //#endregion 374 375 //#region Combine all buffer together 376 while (found) { 377 found = false; 378 379 for (const result of incomingResult) { 380 if (result.counter === currentCounter) { 381 combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, result.result); 382 found = true; 383 break; 384 } 385 } 386 387 currentCounter++; 388 } 389 //#endregion 390 391 //#region Create output buffer with specified length 392 keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits 393 394 if (combinedBuffer.byteLength > keydatalen) { 395 const newBuffer = new ArrayBuffer(keydatalen); 396 const newView = new Uint8Array(newBuffer); 397 const combinedView = new Uint8Array(combinedBuffer); 398 399 for (let i = 0; i < keydatalen; i++) 400 newView[i] = combinedView[i]; 401 402 return newBuffer; 403 } 404 405 return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen" 406 //#endregion 407 //#endregion 408 } 409 //#endregion 410 411 import { CryptoEngine } from "./CryptoEngine/CryptoEngine";