CryptoEngine.ts (63909B)
1 import { sha1 } from "@noble/hashes/sha1"; 2 import { sha256, sha384, sha512 } from "@noble/hashes/sha2"; 3 import * as asn1js from "asn1js"; 4 import * as pvutils from "pvutils"; 5 import * as pvtsutils from "pvtsutils"; 6 import * as common from "../common"; 7 import { PublicKeyInfo } from "../PublicKeyInfo"; 8 import { PrivateKeyInfo } from "../PrivateKeyInfo"; 9 import { AlgorithmIdentifier } from "../AlgorithmIdentifier"; 10 import { EncryptedContentInfo } from "../EncryptedContentInfo"; 11 import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams"; 12 import { PBKDF2Params } from "../PBKDF2Params"; 13 import { PBES2Params } from "../PBES2Params"; 14 import { ArgumentError, AsnError, ParameterError } from "../errors"; 15 import * as type from "./CryptoEngineInterface"; 16 import { AbstractCryptoEngine } from "./AbstractCryptoEngine"; 17 import { EMPTY_STRING } from "../constants"; 18 import { ECNamedCurves } from "../ECNamedCurves"; 19 20 /** 21 * Making MAC key using algorithm described in B.2 of PKCS#12 standard. 22 */ 23 async function makePKCS12B2Key(hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) { 24 let u: number; // Output length of the hash function 25 let v: number; // Block size of the hash function 26 let md: (input: Uint8Array) => Uint8Array; // Hash function 27 28 // Determine the hash algorithm parameters 29 switch (hashAlgorithm.toUpperCase()) { 30 case "SHA-1": 31 u = 20; // 160 bits 32 v = 64; // 512 bits 33 md = sha1; 34 break; 35 case "SHA-256": 36 u = 32; // 256 bits 37 v = 64; // 512 bits 38 md = sha256; 39 break; 40 case "SHA-384": 41 u = 48; // 384 bits 42 v = 128; // 1024 bits 43 md = sha384; 44 break; 45 case "SHA-512": 46 u = 64; // 512 bits 47 v = 128; // 1024 bits 48 md = sha512; 49 break; 50 default: 51 throw new Error("Unsupported hashing algorithm"); 52 } 53 54 const originalPassword = new Uint8Array(password); 55 let decodedPassword = new TextDecoder().decode(password); 56 const encodedPassword = new TextEncoder().encode(decodedPassword); 57 if (encodedPassword.some((byte, i) => byte !== originalPassword[i])) { 58 decodedPassword = String.fromCharCode(...originalPassword); 59 } 60 61 // Transform the password into a byte array 62 const passwordTransformed = new Uint8Array(decodedPassword.length * 2 + 2); 63 const passwordView = new DataView(passwordTransformed.buffer); 64 for (let i = 0; i < decodedPassword.length; i++) { 65 passwordView.setUint16(i * 2, decodedPassword.charCodeAt(i), false); 66 } 67 // Add null-terminator 68 passwordView.setUint16(decodedPassword.length * 2, 0, false); 69 70 // Create a filled array D with the value 3 (ID for MACing) 71 const D = new Uint8Array(v).fill(3); 72 73 // Repeat the salt to fill the block size 74 const saltView = new Uint8Array(salt); 75 const S = new Uint8Array(v * Math.ceil(saltView.length / v)).map((_, i) => saltView[i % saltView.length]); 76 77 // Repeat the password to fill the block size 78 const P = new Uint8Array(v * Math.ceil(passwordTransformed.length / v)).map((_, i) => passwordTransformed[i % passwordTransformed.length]); 79 80 // Concatenate S and P to form I 81 let I = new Uint8Array(S.length + P.length); 82 I.set(S); 83 I.set(P, S.length); 84 85 // Calculate the number of hash iterations needed 86 const c = Math.ceil((keyLength >> 3) / u); 87 const result: number[] = []; 88 89 // Main loop to generate the key material 90 for (let i = 0; i < c; i++) { 91 // Concatenate D and I 92 let A: Uint8Array = new Uint8Array(D.length + I.length); 93 A.set(D); 94 A.set(I, D.length); 95 96 // Perform hash iterations 97 for (let j = 0; j < iterationCount; j++) { 98 A = md(A); 99 } 100 101 // Create a repeated block B from the hash output A 102 const B = new Uint8Array(v).map((_, i) => A[i % A.length]); 103 104 // Determine the number of blocks 105 const k = Math.ceil(saltView.length / v) + Math.ceil(passwordTransformed.length / v); 106 const iRound: number[] = []; 107 108 // Adjust I based on B 109 for (let j = 0; j < k; j++) { 110 const chunk = Array.from(I.slice(j * v, (j + 1) * v)); 111 let x = 0x1ff; 112 113 for (let l = B.length - 1; l >= 0; l--) { 114 x >>= 8; 115 x += B[l] + (chunk[l] || 0); 116 chunk[l] = x & 0xff; 117 } 118 119 iRound.push(...chunk); 120 } 121 122 // Update I for the next iteration 123 I = new Uint8Array(iRound); 124 125 // Collect the result 126 result.push(...A); 127 } 128 129 return new Uint8Array(result.slice(0, keyLength >> 3)).buffer; 130 } 131 132 function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } { 133 const res = typeof data === "string" 134 ? { name: data } 135 : data; 136 137 // TODO fix type casting `as EcdsaParams` 138 if ("hash" in (res as EcdsaParams)) { 139 return { 140 ...res, 141 hash: prepareAlgorithm((res as EcdsaParams).hash) 142 }; 143 } 144 145 return res; 146 } 147 148 /** 149 * Default cryptographic engine for Web Cryptography API 150 */ 151 export class CryptoEngine extends AbstractCryptoEngine { 152 153 public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> { 154 //#region Initial variables 155 let jwk: JsonWebKey = {}; 156 //#endregion 157 158 const alg = prepareAlgorithm(algorithm); 159 160 switch (format.toLowerCase()) { 161 case "raw": 162 return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages); 163 case "spki": 164 { 165 const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); 166 AsnError.assert(asn1, "keyData"); 167 168 const publicKeyInfo = new PublicKeyInfo(); 169 try { 170 publicKeyInfo.fromSchema(asn1.result); 171 } catch { 172 throw new ArgumentError("Incorrect keyData"); 173 } 174 175 switch (alg.name.toUpperCase()) { 176 case "RSA-PSS": 177 { 178 //#region Get information about used hash function 179 if (!alg.hash) { 180 throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); 181 } 182 switch (alg.hash.name.toUpperCase()) { 183 case "SHA-1": 184 jwk.alg = "PS1"; 185 break; 186 case "SHA-256": 187 jwk.alg = "PS256"; 188 break; 189 case "SHA-384": 190 jwk.alg = "PS384"; 191 break; 192 case "SHA-512": 193 jwk.alg = "PS512"; 194 break; 195 default: 196 throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); 197 } 198 //#endregion 199 } 200 // break omitted 201 // eslint-disable-next-line no-fallthrough 202 case "RSASSA-PKCS1-V1_5": 203 { 204 keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key 205 206 jwk.kty = "RSA"; 207 jwk.ext = extractable; 208 jwk.key_ops = keyUsages; 209 210 if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1") 211 throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); 212 213 //#region Get information about used hash function 214 if (!jwk.alg) { 215 if (!alg.hash) { 216 throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); 217 } 218 switch (alg.hash.name.toUpperCase()) { 219 case "SHA-1": 220 jwk.alg = "RS1"; 221 break; 222 case "SHA-256": 223 jwk.alg = "RS256"; 224 break; 225 case "SHA-384": 226 jwk.alg = "RS384"; 227 break; 228 case "SHA-512": 229 jwk.alg = "RS512"; 230 break; 231 default: 232 throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); 233 } 234 } 235 //#endregion 236 237 //#region Create RSA Public Key elements 238 const publicKeyJSON = publicKeyInfo.toJSON(); 239 Object.assign(jwk, publicKeyJSON); 240 //#endregion 241 } 242 break; 243 case "ECDSA": 244 keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key 245 // break omitted 246 // eslint-disable-next-line no-fallthrough 247 case "ECDH": 248 { 249 //#region Initial variables 250 jwk = { 251 kty: "EC", 252 ext: extractable, 253 key_ops: keyUsages 254 }; 255 //#endregion 256 257 //#region Get information about algorithm 258 if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") { 259 throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); 260 } 261 //#endregion 262 263 //#region Create ECDSA Public Key elements 264 const publicKeyJSON = publicKeyInfo.toJSON(); 265 Object.assign(jwk, publicKeyJSON); 266 //#endregion 267 } 268 break; 269 case "RSA-OAEP": 270 { 271 jwk.kty = "RSA"; 272 jwk.ext = extractable; 273 jwk.key_ops = keyUsages; 274 275 if (this.name.toLowerCase() === "safari") 276 jwk.alg = "RSA-OAEP"; 277 else { 278 if (!alg.hash) { 279 throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); 280 } 281 switch (alg.hash.name.toUpperCase()) { 282 case "SHA-1": 283 jwk.alg = "RSA-OAEP"; 284 break; 285 case "SHA-256": 286 jwk.alg = "RSA-OAEP-256"; 287 break; 288 case "SHA-384": 289 jwk.alg = "RSA-OAEP-384"; 290 break; 291 case "SHA-512": 292 jwk.alg = "RSA-OAEP-512"; 293 break; 294 default: 295 throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); 296 } 297 } 298 299 //#region Create ECDSA Public Key elements 300 const publicKeyJSON = publicKeyInfo.toJSON(); 301 Object.assign(jwk, publicKeyJSON); 302 //#endregion 303 } 304 break; 305 case "RSAES-PKCS1-V1_5": 306 { 307 jwk.kty = "RSA"; 308 jwk.ext = extractable; 309 jwk.key_ops = keyUsages; 310 jwk.alg = "PS1"; 311 312 const publicKeyJSON = publicKeyInfo.toJSON(); 313 Object.assign(jwk, publicKeyJSON); 314 } 315 break; 316 default: 317 throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); 318 } 319 } 320 break; 321 case "pkcs8": 322 { 323 const privateKeyInfo = new PrivateKeyInfo(); 324 325 //#region Parse "PrivateKeyInfo" object 326 const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); 327 AsnError.assert(asn1, "keyData"); 328 329 try { 330 privateKeyInfo.fromSchema(asn1.result); 331 } 332 catch { 333 throw new Error("Incorrect keyData"); 334 } 335 336 if (!privateKeyInfo.parsedKey) 337 throw new Error("Incorrect keyData"); 338 //#endregion 339 340 switch (alg.name.toUpperCase()) { 341 case "RSA-PSS": 342 { 343 //#region Get information about used hash function 344 switch (alg.hash?.name.toUpperCase()) { 345 case "SHA-1": 346 jwk.alg = "PS1"; 347 break; 348 case "SHA-256": 349 jwk.alg = "PS256"; 350 break; 351 case "SHA-384": 352 jwk.alg = "PS384"; 353 break; 354 case "SHA-512": 355 jwk.alg = "PS512"; 356 break; 357 default: 358 throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); 359 } 360 //#endregion 361 } 362 // break omitted 363 // eslint-disable-next-line no-fallthrough 364 case "RSASSA-PKCS1-V1_5": 365 { 366 keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key 367 368 jwk.kty = "RSA"; 369 jwk.ext = extractable; 370 jwk.key_ops = keyUsages; 371 372 //#region Get information about used hash function 373 if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1") 374 throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); 375 //#endregion 376 377 //#region Get information about used hash function 378 if (("alg" in jwk) === false) { 379 switch (alg.hash?.name.toUpperCase()) { 380 case "SHA-1": 381 jwk.alg = "RS1"; 382 break; 383 case "SHA-256": 384 jwk.alg = "RS256"; 385 break; 386 case "SHA-384": 387 jwk.alg = "RS384"; 388 break; 389 case "SHA-512": 390 jwk.alg = "RS512"; 391 break; 392 default: 393 throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); 394 } 395 } 396 //#endregion 397 398 //#region Create RSA Private Key elements 399 const privateKeyJSON = privateKeyInfo.toJSON(); 400 Object.assign(jwk, privateKeyJSON); 401 //#endregion 402 } 403 break; 404 case "ECDSA": 405 keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key 406 // break omitted 407 // eslint-disable-next-line no-fallthrough 408 case "ECDH": 409 { 410 //#region Initial variables 411 jwk = { 412 kty: "EC", 413 ext: extractable, 414 key_ops: keyUsages 415 }; 416 //#endregion 417 418 //#region Get information about used hash function 419 if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1") 420 throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); 421 //#endregion 422 423 //#region Create ECDSA Private Key elements 424 const privateKeyJSON = privateKeyInfo.toJSON(); 425 Object.assign(jwk, privateKeyJSON); 426 //#endregion 427 } 428 break; 429 case "RSA-OAEP": 430 { 431 jwk.kty = "RSA"; 432 jwk.ext = extractable; 433 jwk.key_ops = keyUsages; 434 435 //#region Get information about used hash function 436 if (this.name.toLowerCase() === "safari") 437 jwk.alg = "RSA-OAEP"; 438 else { 439 switch (alg.hash?.name.toUpperCase()) { 440 case "SHA-1": 441 jwk.alg = "RSA-OAEP"; 442 break; 443 case "SHA-256": 444 jwk.alg = "RSA-OAEP-256"; 445 break; 446 case "SHA-384": 447 jwk.alg = "RSA-OAEP-384"; 448 break; 449 case "SHA-512": 450 jwk.alg = "RSA-OAEP-512"; 451 break; 452 default: 453 throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); 454 } 455 } 456 //#endregion 457 458 //#region Create RSA Private Key elements 459 const privateKeyJSON = privateKeyInfo.toJSON(); 460 Object.assign(jwk, privateKeyJSON); 461 //#endregion 462 } 463 break; 464 case "RSAES-PKCS1-V1_5": 465 { 466 keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key 467 468 jwk.kty = "RSA"; 469 jwk.ext = extractable; 470 jwk.key_ops = keyUsages; 471 jwk.alg = "PS1"; 472 473 //#region Create RSA Private Key elements 474 const privateKeyJSON = privateKeyInfo.toJSON(); 475 Object.assign(jwk, privateKeyJSON); 476 //#endregion 477 } 478 break; 479 default: 480 throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); 481 } 482 } 483 break; 484 case "jwk": 485 jwk = keyData as JsonWebKey; 486 break; 487 default: 488 throw new Error(`Incorrect format: ${format}`); 489 } 490 491 //#region Special case for Safari browser (since its acting not as WebCrypto standard describes) 492 if (this.name.toLowerCase() === "safari") { 493 // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview) 494 try { 495 return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages); 496 } catch { 497 return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); 498 } 499 } 500 //#endregion 501 502 return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); 503 } 504 505 /** 506 * Export WebCrypto keys to different formats 507 * @param format 508 * @param key 509 */ 510 public override exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>; 511 public override exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>; 512 public override exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>; 513 public override async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> { 514 let jwk = await this.subtle.exportKey("jwk", key); 515 516 //#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation 517 if (this.name.toLowerCase() === "safari") { 518 // Some additional checks for Safari Technology Preview 519 if (jwk instanceof ArrayBuffer) { 520 jwk = JSON.parse(pvutils.arrayBufferToString(jwk)); 521 } 522 } 523 //#endregion 524 525 switch (format.toLowerCase()) { 526 case "raw": 527 return this.subtle.exportKey("raw", key); 528 case "spki": { 529 const publicKeyInfo = new PublicKeyInfo(); 530 531 try { 532 publicKeyInfo.fromJSON(jwk); 533 } 534 catch { 535 throw new Error("Incorrect key data"); 536 } 537 538 return publicKeyInfo.toSchema().toBER(false); 539 } 540 case "pkcs8": { 541 const privateKeyInfo = new PrivateKeyInfo(); 542 543 try { 544 privateKeyInfo.fromJSON(jwk); 545 } 546 catch { 547 throw new Error("Incorrect key data"); 548 } 549 550 return privateKeyInfo.toSchema().toBER(false); 551 } 552 case "jwk": 553 return jwk; 554 default: 555 throw new Error(`Incorrect format: ${format}`); 556 } 557 } 558 559 /** 560 * Convert WebCrypto keys between different export formats 561 * @param inputFormat 562 * @param outputFormat 563 * @param keyData 564 * @param algorithm 565 * @param extractable 566 * @param keyUsages 567 */ 568 public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) { 569 if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) { 570 return keyData; 571 } 572 573 const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages); 574 return this.exportKey(outputFormat, key); 575 } 576 577 /** 578 * Gets WebCrypto algorithm by wel-known OID 579 * @param oid algorithm identifier 580 * @param safety if `true` throws exception on unknown algorithm identifier 581 * @param target name of the target 582 * @returns Returns WebCrypto algorithm or an empty object 583 */ 584 public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object; 585 /** 586 * Gets WebCrypto algorithm by wel-known OID 587 * @param oid algorithm identifier 588 * @param safety if `true` throws exception on unknown algorithm identifier 589 * @param target name of the target 590 * @returns Returns WebCrypto algorithm 591 * @throws Throws {@link Error} exception if unknown algorithm identifier 592 */ 593 public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T; 594 public getAlgorithmByOID(oid: string, safety = false, target?: string): any { 595 switch (oid) { 596 case "1.2.840.113549.1.1.1": 597 return { 598 name: "RSAES-PKCS1-v1_5" 599 }; 600 case "1.2.840.113549.1.1.5": 601 return { 602 name: "RSASSA-PKCS1-v1_5", 603 hash: { 604 name: "SHA-1" 605 } 606 }; 607 case "1.2.840.113549.1.1.11": 608 return { 609 name: "RSASSA-PKCS1-v1_5", 610 hash: { 611 name: "SHA-256" 612 } 613 }; 614 case "1.2.840.113549.1.1.12": 615 return { 616 name: "RSASSA-PKCS1-v1_5", 617 hash: { 618 name: "SHA-384" 619 } 620 }; 621 case "1.2.840.113549.1.1.13": 622 return { 623 name: "RSASSA-PKCS1-v1_5", 624 hash: { 625 name: "SHA-512" 626 } 627 }; 628 case "1.2.840.113549.1.1.10": 629 return { 630 name: "RSA-PSS" 631 }; 632 case "1.2.840.113549.1.1.7": 633 return { 634 name: "RSA-OAEP" 635 }; 636 case "1.2.840.10045.2.1": 637 case "1.2.840.10045.4.1": 638 return { 639 name: "ECDSA", 640 hash: { 641 name: "SHA-1" 642 } 643 }; 644 case "1.2.840.10045.4.3.2": 645 return { 646 name: "ECDSA", 647 hash: { 648 name: "SHA-256" 649 } 650 }; 651 case "1.2.840.10045.4.3.3": 652 return { 653 name: "ECDSA", 654 hash: { 655 name: "SHA-384" 656 } 657 }; 658 case "1.2.840.10045.4.3.4": 659 return { 660 name: "ECDSA", 661 hash: { 662 name: "SHA-512" 663 } 664 }; 665 case "1.3.133.16.840.63.0.2": 666 return { 667 name: "ECDH", 668 kdf: "SHA-1" 669 }; 670 case "1.3.132.1.11.1": 671 return { 672 name: "ECDH", 673 kdf: "SHA-256" 674 }; 675 case "1.3.132.1.11.2": 676 return { 677 name: "ECDH", 678 kdf: "SHA-384" 679 }; 680 case "1.3.132.1.11.3": 681 return { 682 name: "ECDH", 683 kdf: "SHA-512" 684 }; 685 case "2.16.840.1.101.3.4.1.2": 686 return { 687 name: "AES-CBC", 688 length: 128 689 }; 690 case "2.16.840.1.101.3.4.1.22": 691 return { 692 name: "AES-CBC", 693 length: 192 694 }; 695 case "2.16.840.1.101.3.4.1.42": 696 return { 697 name: "AES-CBC", 698 length: 256 699 }; 700 case "2.16.840.1.101.3.4.1.6": 701 return { 702 name: "AES-GCM", 703 length: 128 704 }; 705 case "2.16.840.1.101.3.4.1.26": 706 return { 707 name: "AES-GCM", 708 length: 192 709 }; 710 case "2.16.840.1.101.3.4.1.46": 711 return { 712 name: "AES-GCM", 713 length: 256 714 }; 715 case "2.16.840.1.101.3.4.1.4": 716 return { 717 name: "AES-CFB", 718 length: 128 719 }; 720 case "2.16.840.1.101.3.4.1.24": 721 return { 722 name: "AES-CFB", 723 length: 192 724 }; 725 case "2.16.840.1.101.3.4.1.44": 726 return { 727 name: "AES-CFB", 728 length: 256 729 }; 730 case "2.16.840.1.101.3.4.1.5": 731 return { 732 name: "AES-KW", 733 length: 128 734 }; 735 case "2.16.840.1.101.3.4.1.25": 736 return { 737 name: "AES-KW", 738 length: 192 739 }; 740 case "2.16.840.1.101.3.4.1.45": 741 return { 742 name: "AES-KW", 743 length: 256 744 }; 745 case "1.2.840.113549.2.7": 746 return { 747 name: "HMAC", 748 hash: { 749 name: "SHA-1" 750 } 751 }; 752 case "1.2.840.113549.2.9": 753 return { 754 name: "HMAC", 755 hash: { 756 name: "SHA-256" 757 } 758 }; 759 case "1.2.840.113549.2.10": 760 return { 761 name: "HMAC", 762 hash: { 763 name: "SHA-384" 764 } 765 }; 766 case "1.2.840.113549.2.11": 767 return { 768 name: "HMAC", 769 hash: { 770 name: "SHA-512" 771 } 772 }; 773 case "1.2.840.113549.1.9.16.3.5": 774 return { 775 name: "DH" 776 }; 777 case "1.3.14.3.2.26": 778 return { 779 name: "SHA-1" 780 }; 781 case "2.16.840.1.101.3.4.2.1": 782 return { 783 name: "SHA-256" 784 }; 785 case "2.16.840.1.101.3.4.2.2": 786 return { 787 name: "SHA-384" 788 }; 789 case "2.16.840.1.101.3.4.2.3": 790 return { 791 name: "SHA-512" 792 }; 793 case "1.2.840.113549.1.5.12": 794 return { 795 name: "PBKDF2" 796 }; 797 //#region Special case - OIDs for ECC curves 798 case "1.2.840.10045.3.1.7": 799 return { 800 name: "P-256" 801 }; 802 case "1.3.132.0.34": 803 return { 804 name: "P-384" 805 }; 806 case "1.3.132.0.35": 807 return { 808 name: "P-521" 809 }; 810 //#endregion 811 default: 812 } 813 814 if (safety) { 815 throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`); 816 } 817 818 return {}; 819 } 820 821 public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string { 822 let result = EMPTY_STRING; 823 824 switch (algorithm.name.toUpperCase()) { 825 case "RSAES-PKCS1-V1_5": 826 result = "1.2.840.113549.1.1.1"; 827 break; 828 case "RSASSA-PKCS1-V1_5": 829 switch ((algorithm as any).hash.name.toUpperCase()) { 830 case "SHA-1": 831 result = "1.2.840.113549.1.1.5"; 832 break; 833 case "SHA-256": 834 result = "1.2.840.113549.1.1.11"; 835 break; 836 case "SHA-384": 837 result = "1.2.840.113549.1.1.12"; 838 break; 839 case "SHA-512": 840 result = "1.2.840.113549.1.1.13"; 841 break; 842 default: 843 } 844 break; 845 case "RSA-PSS": 846 result = "1.2.840.113549.1.1.10"; 847 break; 848 case "RSA-OAEP": 849 result = "1.2.840.113549.1.1.7"; 850 break; 851 case "ECDSA": 852 switch ((algorithm as any).hash.name.toUpperCase()) { 853 case "SHA-1": 854 result = "1.2.840.10045.4.1"; 855 break; 856 case "SHA-256": 857 result = "1.2.840.10045.4.3.2"; 858 break; 859 case "SHA-384": 860 result = "1.2.840.10045.4.3.3"; 861 break; 862 case "SHA-512": 863 result = "1.2.840.10045.4.3.4"; 864 break; 865 default: 866 } 867 break; 868 case "ECDH": 869 switch ((algorithm as any).kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function 870 { 871 case "SHA-1": 872 result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme 873 break; 874 case "SHA-256": 875 result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme 876 break; 877 case "SHA-384": 878 result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme 879 break; 880 case "SHA-512": 881 result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme 882 break; 883 default: 884 } 885 break; 886 case "AES-CTR": 887 break; 888 case "AES-CBC": 889 switch ((algorithm as any).length) { 890 case 128: 891 result = "2.16.840.1.101.3.4.1.2"; 892 break; 893 case 192: 894 result = "2.16.840.1.101.3.4.1.22"; 895 break; 896 case 256: 897 result = "2.16.840.1.101.3.4.1.42"; 898 break; 899 default: 900 } 901 break; 902 case "AES-CMAC": 903 break; 904 case "AES-GCM": 905 switch ((algorithm as any).length) { 906 case 128: 907 result = "2.16.840.1.101.3.4.1.6"; 908 break; 909 case 192: 910 result = "2.16.840.1.101.3.4.1.26"; 911 break; 912 case 256: 913 result = "2.16.840.1.101.3.4.1.46"; 914 break; 915 default: 916 } 917 break; 918 case "AES-CFB": 919 switch ((algorithm as any).length) { 920 case 128: 921 result = "2.16.840.1.101.3.4.1.4"; 922 break; 923 case 192: 924 result = "2.16.840.1.101.3.4.1.24"; 925 break; 926 case 256: 927 result = "2.16.840.1.101.3.4.1.44"; 928 break; 929 default: 930 } 931 break; 932 case "AES-KW": 933 switch ((algorithm as any).length) { 934 case 128: 935 result = "2.16.840.1.101.3.4.1.5"; 936 break; 937 case 192: 938 result = "2.16.840.1.101.3.4.1.25"; 939 break; 940 case 256: 941 result = "2.16.840.1.101.3.4.1.45"; 942 break; 943 default: 944 } 945 break; 946 case "HMAC": 947 switch ((algorithm as any).hash.name.toUpperCase()) { 948 case "SHA-1": 949 result = "1.2.840.113549.2.7"; 950 break; 951 case "SHA-256": 952 result = "1.2.840.113549.2.9"; 953 break; 954 case "SHA-384": 955 result = "1.2.840.113549.2.10"; 956 break; 957 case "SHA-512": 958 result = "1.2.840.113549.2.11"; 959 break; 960 default: 961 } 962 break; 963 case "DH": 964 result = "1.2.840.113549.1.9.16.3.5"; 965 break; 966 case "SHA-1": 967 result = "1.3.14.3.2.26"; 968 break; 969 case "SHA-256": 970 result = "2.16.840.1.101.3.4.2.1"; 971 break; 972 case "SHA-384": 973 result = "2.16.840.1.101.3.4.2.2"; 974 break; 975 case "SHA-512": 976 result = "2.16.840.1.101.3.4.2.3"; 977 break; 978 case "CONCAT": 979 break; 980 case "HKDF": 981 break; 982 case "PBKDF2": 983 result = "1.2.840.113549.1.5.12"; 984 break; 985 //#region Special case - OIDs for ECC curves 986 case "P-256": 987 result = "1.2.840.10045.3.1.7"; 988 break; 989 case "P-384": 990 result = "1.3.132.0.34"; 991 break; 992 case "P-521": 993 result = "1.3.132.0.35"; 994 break; 995 //#endregion 996 default: 997 } 998 999 if (!result && safety) { 1000 throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`); 1001 } 1002 1003 return result; 1004 } 1005 1006 public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams { 1007 let result: type.CryptoEngineAlgorithmParams = { 1008 algorithm: {}, 1009 usages: [] 1010 }; 1011 1012 switch (algorithmName.toUpperCase()) { 1013 case "RSAES-PKCS1-V1_5": 1014 case "RSASSA-PKCS1-V1_5": 1015 switch (operation.toLowerCase()) { 1016 case "generatekey": 1017 result = { 1018 algorithm: { 1019 name: "RSASSA-PKCS1-v1_5", 1020 modulusLength: 2048, 1021 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 1022 hash: { 1023 name: "SHA-256" 1024 } 1025 }, 1026 usages: ["sign", "verify"] 1027 }; 1028 break; 1029 case "verify": 1030 case "sign": 1031 case "importkey": 1032 result = { 1033 algorithm: { 1034 name: "RSASSA-PKCS1-v1_5", 1035 hash: { 1036 name: "SHA-256" 1037 } 1038 }, 1039 usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only 1040 }; 1041 break; 1042 case "exportkey": 1043 default: 1044 return { 1045 algorithm: { 1046 name: "RSASSA-PKCS1-v1_5" 1047 }, 1048 usages: [] 1049 }; 1050 } 1051 break; 1052 case "RSA-PSS": 1053 switch (operation.toLowerCase()) { 1054 case "sign": 1055 case "verify": 1056 result = { 1057 algorithm: { 1058 name: "RSA-PSS", 1059 hash: { 1060 name: "SHA-1" 1061 }, 1062 saltLength: 20 1063 }, 1064 usages: ["sign", "verify"] 1065 }; 1066 break; 1067 case "generatekey": 1068 result = { 1069 algorithm: { 1070 name: "RSA-PSS", 1071 modulusLength: 2048, 1072 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 1073 hash: { 1074 name: "SHA-1" 1075 } 1076 }, 1077 usages: ["sign", "verify"] 1078 }; 1079 break; 1080 case "importkey": 1081 result = { 1082 algorithm: { 1083 name: "RSA-PSS", 1084 hash: { 1085 name: "SHA-1" 1086 } 1087 }, 1088 usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only 1089 }; 1090 break; 1091 case "exportkey": 1092 default: 1093 return { 1094 algorithm: { 1095 name: "RSA-PSS" 1096 }, 1097 usages: [] 1098 }; 1099 } 1100 break; 1101 case "RSA-OAEP": 1102 switch (operation.toLowerCase()) { 1103 case "encrypt": 1104 case "decrypt": 1105 result = { 1106 algorithm: { 1107 name: "RSA-OAEP" 1108 }, 1109 usages: ["encrypt", "decrypt"] 1110 }; 1111 break; 1112 case "generatekey": 1113 result = { 1114 algorithm: { 1115 name: "RSA-OAEP", 1116 modulusLength: 2048, 1117 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 1118 hash: { 1119 name: "SHA-256" 1120 } 1121 }, 1122 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1123 }; 1124 break; 1125 case "importkey": 1126 result = { 1127 algorithm: { 1128 name: "RSA-OAEP", 1129 hash: { 1130 name: "SHA-256" 1131 } 1132 }, 1133 usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8" 1134 }; 1135 break; 1136 case "exportkey": 1137 default: 1138 return { 1139 algorithm: { 1140 name: "RSA-OAEP" 1141 }, 1142 usages: [] 1143 }; 1144 } 1145 break; 1146 case "ECDSA": 1147 switch (operation.toLowerCase()) { 1148 case "generatekey": 1149 result = { 1150 algorithm: { 1151 name: "ECDSA", 1152 namedCurve: "P-256" 1153 }, 1154 usages: ["sign", "verify"] 1155 }; 1156 break; 1157 case "importkey": 1158 result = { 1159 algorithm: { 1160 name: "ECDSA", 1161 namedCurve: "P-256" 1162 }, 1163 usages: ["verify"] // "sign" for "pkcs8" 1164 }; 1165 break; 1166 case "verify": 1167 case "sign": 1168 result = { 1169 algorithm: { 1170 name: "ECDSA", 1171 hash: { 1172 name: "SHA-256" 1173 } 1174 }, 1175 usages: ["sign"] 1176 }; 1177 break; 1178 default: 1179 return { 1180 algorithm: { 1181 name: "ECDSA" 1182 }, 1183 usages: [] 1184 }; 1185 } 1186 break; 1187 case "ECDH": 1188 switch (operation.toLowerCase()) { 1189 case "exportkey": 1190 case "importkey": 1191 case "generatekey": 1192 result = { 1193 algorithm: { 1194 name: "ECDH", 1195 namedCurve: "P-256" 1196 }, 1197 usages: ["deriveKey", "deriveBits"] 1198 }; 1199 break; 1200 case "derivekey": 1201 case "derivebits": 1202 result = { 1203 algorithm: { 1204 name: "ECDH", 1205 namedCurve: "P-256", 1206 public: [] // Must be a "publicKey" 1207 }, 1208 usages: ["encrypt", "decrypt"] 1209 }; 1210 break; 1211 default: 1212 return { 1213 algorithm: { 1214 name: "ECDH" 1215 }, 1216 usages: [] 1217 }; 1218 } 1219 break; 1220 case "AES-CTR": 1221 switch (operation.toLowerCase()) { 1222 case "importkey": 1223 case "exportkey": 1224 case "generatekey": 1225 result = { 1226 algorithm: { 1227 name: "AES-CTR", 1228 length: 256 1229 }, 1230 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1231 }; 1232 break; 1233 case "decrypt": 1234 case "encrypt": 1235 result = { 1236 algorithm: { 1237 name: "AES-CTR", 1238 counter: new Uint8Array(16), 1239 length: 10 1240 }, 1241 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1242 }; 1243 break; 1244 default: 1245 return { 1246 algorithm: { 1247 name: "AES-CTR" 1248 }, 1249 usages: [] 1250 }; 1251 } 1252 break; 1253 case "AES-CBC": 1254 switch (operation.toLowerCase()) { 1255 case "importkey": 1256 case "exportkey": 1257 case "generatekey": 1258 result = { 1259 algorithm: { 1260 name: "AES-CBC", 1261 length: 256 1262 }, 1263 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1264 }; 1265 break; 1266 case "decrypt": 1267 case "encrypt": 1268 result = { 1269 algorithm: { 1270 name: "AES-CBC", 1271 iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step 1272 }, 1273 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1274 }; 1275 break; 1276 default: 1277 return { 1278 algorithm: { 1279 name: "AES-CBC" 1280 }, 1281 usages: [] 1282 }; 1283 } 1284 break; 1285 case "AES-GCM": 1286 switch (operation.toLowerCase()) { 1287 case "importkey": 1288 case "exportkey": 1289 case "generatekey": 1290 result = { 1291 algorithm: { 1292 name: "AES-GCM", 1293 length: 256 1294 }, 1295 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1296 }; 1297 break; 1298 case "decrypt": 1299 case "encrypt": 1300 result = { 1301 algorithm: { 1302 name: "AES-GCM", 1303 iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step 1304 }, 1305 usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] 1306 }; 1307 break; 1308 default: 1309 return { 1310 algorithm: { 1311 name: "AES-GCM" 1312 }, 1313 usages: [] 1314 }; 1315 } 1316 break; 1317 case "AES-KW": 1318 switch (operation.toLowerCase()) { 1319 case "importkey": 1320 case "exportkey": 1321 case "generatekey": 1322 case "wrapkey": 1323 case "unwrapkey": 1324 result = { 1325 algorithm: { 1326 name: "AES-KW", 1327 length: 256 1328 }, 1329 usages: ["wrapKey", "unwrapKey"] 1330 }; 1331 break; 1332 default: 1333 return { 1334 algorithm: { 1335 name: "AES-KW" 1336 }, 1337 usages: [] 1338 }; 1339 } 1340 break; 1341 case "HMAC": 1342 switch (operation.toLowerCase()) { 1343 case "sign": 1344 case "verify": 1345 result = { 1346 algorithm: { 1347 name: "HMAC" 1348 }, 1349 usages: ["sign", "verify"] 1350 }; 1351 break; 1352 case "importkey": 1353 case "exportkey": 1354 case "generatekey": 1355 result = { 1356 algorithm: { 1357 name: "HMAC", 1358 length: 32, 1359 hash: { 1360 name: "SHA-256" 1361 } 1362 }, 1363 usages: ["sign", "verify"] 1364 }; 1365 break; 1366 default: 1367 return { 1368 algorithm: { 1369 name: "HMAC" 1370 }, 1371 usages: [] 1372 }; 1373 } 1374 break; 1375 case "HKDF": 1376 switch (operation.toLowerCase()) { 1377 case "derivekey": 1378 result = { 1379 algorithm: { 1380 name: "HKDF", 1381 hash: "SHA-256", 1382 salt: new Uint8Array([]), 1383 info: new Uint8Array([]) 1384 }, 1385 usages: ["encrypt", "decrypt"] 1386 }; 1387 break; 1388 default: 1389 return { 1390 algorithm: { 1391 name: "HKDF" 1392 }, 1393 usages: [] 1394 }; 1395 } 1396 break; 1397 case "PBKDF2": 1398 switch (operation.toLowerCase()) { 1399 case "derivekey": 1400 result = { 1401 algorithm: { 1402 name: "PBKDF2", 1403 hash: { name: "SHA-256" }, 1404 salt: new Uint8Array([]), 1405 iterations: 10000 1406 }, 1407 usages: ["encrypt", "decrypt"] 1408 }; 1409 break; 1410 default: 1411 return { 1412 algorithm: { 1413 name: "PBKDF2" 1414 }, 1415 usages: [] 1416 }; 1417 } 1418 break; 1419 default: 1420 } 1421 1422 return result; 1423 } 1424 1425 /** 1426 * Getting hash algorithm by signature algorithm 1427 * @param signatureAlgorithm Signature algorithm 1428 */ 1429 // TODO use safety 1430 getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string { 1431 let result = EMPTY_STRING; 1432 1433 switch (signatureAlgorithm.algorithmId) { 1434 case "1.2.840.10045.4.1": // ecdsa-with-SHA1 1435 case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1 1436 result = "SHA-1"; 1437 break; 1438 case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256 1439 case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256 1440 result = "SHA-256"; 1441 break; 1442 case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384 1443 case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384 1444 result = "SHA-384"; 1445 break; 1446 case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512 1447 case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512 1448 result = "SHA-512"; 1449 break; 1450 case "1.2.840.113549.1.1.10": // RSA-PSS 1451 { 1452 try { 1453 const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); 1454 if (params.hashAlgorithm) { 1455 const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId); 1456 if ("name" in algorithm) { 1457 result = algorithm.name; 1458 } 1459 else { 1460 return EMPTY_STRING; 1461 } 1462 } 1463 else 1464 result = "SHA-1"; 1465 } 1466 catch { 1467 // nothing 1468 } 1469 } 1470 break; 1471 default: 1472 } 1473 1474 return result; 1475 } 1476 1477 public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo> { 1478 //#region Check for input parameters 1479 ParameterError.assert(parameters, 1480 "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm", 1481 "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType"); 1482 1483 const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm"); 1484 1485 const pbkdf2OID = this.getOIDByAlgorithm({ 1486 name: "PBKDF2" 1487 }, true, "PBKDF2"); 1488 const hmacOID = this.getOIDByAlgorithm({ 1489 name: "HMAC", 1490 hash: { 1491 name: parameters.hmacHashAlgorithm 1492 } 1493 } as Algorithm, true, "hmacHashAlgorithm"); 1494 //#endregion 1495 1496 //#region Initial variables 1497 1498 // TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer? 1499 const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long 1500 const ivView = new Uint8Array(ivBuffer); 1501 this.getRandomValues(ivView); 1502 1503 const saltBuffer = new ArrayBuffer(64); 1504 const saltView = new Uint8Array(saltBuffer); 1505 this.getRandomValues(saltView); 1506 1507 const contentView = new Uint8Array(parameters.contentToEncrypt); 1508 1509 const pbkdf2Params = new PBKDF2Params({ 1510 salt: new asn1js.OctetString({ valueHex: saltBuffer }), 1511 iterationCount: parameters.iterationCount, 1512 prf: new AlgorithmIdentifier({ 1513 algorithmId: hmacOID, 1514 algorithmParams: new asn1js.Null() 1515 }) 1516 }); 1517 //#endregion 1518 1519 //#region Derive PBKDF2 key from "password" buffer 1520 const passwordView = new Uint8Array(parameters.password); 1521 1522 const pbkdfKey = await this.importKey("raw", 1523 passwordView, 1524 "PBKDF2", 1525 false, 1526 ["deriveKey"]); 1527 1528 //#endregion 1529 1530 //#region Derive key for "contentEncryptionAlgorithm" 1531 const derivedKey = await this.deriveKey({ 1532 name: "PBKDF2", 1533 hash: { 1534 name: parameters.hmacHashAlgorithm 1535 }, 1536 salt: saltView, 1537 iterations: parameters.iterationCount 1538 }, 1539 pbkdfKey, 1540 parameters.contentEncryptionAlgorithm, 1541 false, 1542 ["encrypt"]); 1543 //#endregion 1544 1545 //#region Encrypt content 1546 // TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM) 1547 const encryptedData = await this.encrypt( 1548 { 1549 name: parameters.contentEncryptionAlgorithm.name, 1550 iv: ivView 1551 }, 1552 derivedKey, 1553 contentView); 1554 //#endregion 1555 1556 //#region Store all parameters in EncryptedData object 1557 const pbes2Parameters = new PBES2Params({ 1558 keyDerivationFunc: new AlgorithmIdentifier({ 1559 algorithmId: pbkdf2OID, 1560 algorithmParams: pbkdf2Params.toSchema() 1561 }), 1562 encryptionScheme: new AlgorithmIdentifier({ 1563 algorithmId: contentEncryptionOID, 1564 algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer }) 1565 }) 1566 }); 1567 1568 return new EncryptedContentInfo({ 1569 contentType: parameters.contentType, 1570 contentEncryptionAlgorithm: new AlgorithmIdentifier({ 1571 algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2 1572 algorithmParams: pbes2Parameters.toSchema() 1573 }), 1574 encryptedContent: new asn1js.OctetString({ valueHex: encryptedData }) 1575 }); 1576 //#endregion 1577 } 1578 1579 /** 1580 * Decrypt data stored in "EncryptedContentInfo" object using parameters 1581 * @param parameters 1582 */ 1583 public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer> { 1584 //#region Check for input parameters 1585 ParameterError.assert(parameters, "password", "encryptedContentInfo"); 1586 1587 if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2 1588 throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`); 1589 //#endregion 1590 1591 //#region Initial variables 1592 let pbes2Parameters: PBES2Params; 1593 1594 try { 1595 pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams }); 1596 } 1597 catch { 1598 throw new Error("Incorrectly encoded \"pbes2Parameters\""); 1599 } 1600 1601 let pbkdf2Params; 1602 1603 try { 1604 pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams }); 1605 } 1606 catch { 1607 throw new Error("Incorrectly encoded \"pbkdf2Params\""); 1608 } 1609 1610 const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true); 1611 1612 const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex; 1613 const ivView = new Uint8Array(ivBuffer); 1614 1615 const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex; 1616 const saltView = new Uint8Array(saltBuffer); 1617 1618 const iterationCount = pbkdf2Params.iterationCount; 1619 1620 let hmacHashAlgorithm = "SHA-1"; 1621 1622 if (pbkdf2Params.prf) { 1623 const algorithm = this.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true); 1624 hmacHashAlgorithm = algorithm.hash.name; 1625 } 1626 //#endregion 1627 1628 //#region Derive PBKDF2 key from "password" buffer 1629 const pbkdfKey = await this.importKey("raw", 1630 parameters.password, 1631 "PBKDF2", 1632 false, 1633 ["deriveKey"]); 1634 //#endregion 1635 1636 //#region Derive key for "contentEncryptionAlgorithm" 1637 const result = await this.deriveKey( 1638 { 1639 name: "PBKDF2", 1640 hash: { 1641 name: hmacHashAlgorithm 1642 }, 1643 salt: saltView, 1644 iterations: iterationCount 1645 }, 1646 pbkdfKey, 1647 contentEncryptionAlgorithm as any, 1648 false, 1649 ["decrypt"]); 1650 //#endregion 1651 1652 //#region Decrypt internal content using derived key 1653 //#region Create correct data block for decryption 1654 const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent(); 1655 //#endregion 1656 1657 return this.decrypt({ 1658 name: contentEncryptionAlgorithm.name, 1659 iv: ivView 1660 }, 1661 result, 1662 dataBuffer); 1663 //#endregion 1664 } 1665 1666 public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> { 1667 //#region Check for input parameters 1668 if ((parameters instanceof Object) === false) 1669 throw new Error("Parameters must have type \"Object\""); 1670 1671 ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp"); 1672 //#endregion 1673 1674 //#region Choose correct length for HMAC key 1675 let length: number; 1676 1677 switch (parameters.hashAlgorithm.toLowerCase()) { 1678 case "sha-1": 1679 length = 160; 1680 break; 1681 case "sha-256": 1682 length = 256; 1683 break; 1684 case "sha-384": 1685 length = 384; 1686 break; 1687 case "sha-512": 1688 length = 512; 1689 break; 1690 default: 1691 throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); 1692 } 1693 //#endregion 1694 1695 //#region Initial variables 1696 const hmacAlgorithm = { 1697 name: "HMAC", 1698 length, 1699 hash: { 1700 name: parameters.hashAlgorithm 1701 } 1702 }; 1703 //#endregion 1704 1705 //#region Create PKCS#12 key for integrity checking 1706 const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); 1707 //#endregion 1708 1709 //#region Import HMAC key 1710 1711 const hmacKey = await this.importKey("raw", 1712 new Uint8Array(pkcsKey), 1713 hmacAlgorithm, 1714 false, 1715 ["sign"]); 1716 //#endregion 1717 1718 //#region Make signed HMAC value 1719 return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp)); 1720 //#endregion 1721 } 1722 1723 public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean> { 1724 //#region Check for input parameters 1725 ParameterError.assert(parameters, 1726 "password", "hashAlgorithm", "salt", 1727 "iterationCount", "contentToVerify", "signatureToVerify"); 1728 //#endregion 1729 1730 //#region Choose correct length for HMAC key 1731 let length = 0; 1732 1733 switch (parameters.hashAlgorithm.toLowerCase()) { 1734 case "sha-1": 1735 length = 160; 1736 break; 1737 case "sha-256": 1738 length = 256; 1739 break; 1740 case "sha-384": 1741 length = 384; 1742 break; 1743 case "sha-512": 1744 length = 512; 1745 break; 1746 default: 1747 throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); 1748 } 1749 //#endregion 1750 1751 //#region Initial variables 1752 const hmacAlgorithm = { 1753 name: "HMAC", 1754 length, 1755 hash: { 1756 name: parameters.hashAlgorithm 1757 } 1758 }; 1759 //#endregion 1760 1761 //#region Create PKCS#12 key for integrity checking 1762 const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); 1763 //#endregion 1764 1765 //#region Import HMAC key 1766 const hmacKey = await this.importKey("raw", 1767 new Uint8Array(pkcsKey), 1768 hmacAlgorithm, 1769 false, 1770 ["verify"]); 1771 //#endregion 1772 1773 //#region Make signed HMAC value 1774 return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)); 1775 //#endregion 1776 } 1777 1778 public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise<type.CryptoEngineSignatureParams> { 1779 // Check hashing algorithm 1780 this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); 1781 1782 // Initial variables 1783 const signatureAlgorithm = new AlgorithmIdentifier(); 1784 1785 //#region Get "default parameters" for the current algorithm 1786 const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign"); 1787 if (!Object.keys(parameters.algorithm).length) { 1788 throw new Error("Parameter 'algorithm' is empty"); 1789 } 1790 // Use the hash from the privateKey.algorithm.hash.name for keys with hash algorithms (like RSA) 1791 const algorithm = parameters.algorithm as any; // TODO remove `as any` 1792 if ("hash" in privateKey.algorithm && privateKey.algorithm.hash && (privateKey.algorithm.hash as Algorithm).name) { 1793 algorithm.hash.name = (privateKey.algorithm.hash as Algorithm).name; 1794 } else { 1795 algorithm.hash.name = hashAlgorithm; 1796 } 1797 //#endregion 1798 1799 //#region Fill internal structures based on "privateKey" and "hashAlgorithm" 1800 switch (privateKey.algorithm.name.toUpperCase()) { 1801 case "RSASSA-PKCS1-V1_5": 1802 case "ECDSA": 1803 signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true); 1804 break; 1805 case "RSA-PSS": 1806 { 1807 //#region Set "saltLength" as the length (in octets) of the hash function result 1808 switch (algorithm.hash.name.toUpperCase()) { 1809 case "SHA-256": 1810 algorithm.saltLength = 32; 1811 break; 1812 case "SHA-384": 1813 algorithm.saltLength = 48; 1814 break; 1815 case "SHA-512": 1816 algorithm.saltLength = 64; 1817 break; 1818 default: 1819 } 1820 //#endregion 1821 1822 //#region Fill "RSASSA_PSS_params" object 1823 const paramsObject: Partial<IRSASSAPSSParams> = {}; 1824 1825 if (algorithm.hash.name.toUpperCase() !== "SHA-1") { 1826 const hashAlgorithmOID = this.getOIDByAlgorithm({ name: algorithm.hash.name }, true, "hashAlgorithm"); 1827 1828 paramsObject.hashAlgorithm = new AlgorithmIdentifier({ 1829 algorithmId: hashAlgorithmOID, 1830 algorithmParams: new asn1js.Null() 1831 }); 1832 1833 paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({ 1834 algorithmId: "1.2.840.113549.1.1.8", // MGF1 1835 algorithmParams: paramsObject.hashAlgorithm.toSchema() 1836 }); 1837 } 1838 1839 if (algorithm.saltLength !== 20) 1840 paramsObject.saltLength = algorithm.saltLength; 1841 1842 const pssParameters = new RSASSAPSSParams(paramsObject); 1843 //#endregion 1844 1845 //#region Automatically set signature algorithm 1846 signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10"; 1847 signatureAlgorithm.algorithmParams = pssParameters.toSchema(); 1848 //#endregion 1849 } 1850 break; 1851 default: 1852 throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`); 1853 } 1854 //#endregion 1855 1856 return { 1857 signatureAlgorithm, 1858 parameters 1859 }; 1860 } 1861 1862 public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer> { 1863 const signature = await this.sign(parameters.algorithm, 1864 privateKey, 1865 data); 1866 1867 //#region Special case for ECDSA algorithm 1868 if (parameters.algorithm.name === "ECDSA") { 1869 return common.createCMSECDSASignature(signature); 1870 } 1871 //#endregion 1872 1873 return signature; 1874 } 1875 1876 public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams { 1877 const parameters = {} as any; 1878 1879 //#region Find signer's hashing algorithm 1880 const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); 1881 if (shaAlgorithm === EMPTY_STRING) 1882 throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); 1883 //#endregion 1884 1885 //#region Get information about public key algorithm and default parameters for import 1886 let algorithmId: string; 1887 if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") 1888 algorithmId = signatureAlgorithm.algorithmId; 1889 else 1890 algorithmId = publicKeyInfo.algorithm.algorithmId; 1891 1892 const algorithmObject = this.getAlgorithmByOID(algorithmId, true); 1893 1894 parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); 1895 if ("hash" in parameters.algorithm.algorithm) 1896 parameters.algorithm.algorithm.hash.name = shaAlgorithm; 1897 1898 //#region Special case for ECDSA 1899 if (algorithmObject.name === "ECDSA") { 1900 //#region Get information about named curve 1901 const publicKeyAlgorithm = publicKeyInfo.algorithm; 1902 if (!publicKeyAlgorithm.algorithmParams) { 1903 throw new Error("Algorithm parameters for ECDSA public key are missed"); 1904 } 1905 const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams; 1906 if ("idBlock" in publicKeyAlgorithm.algorithmParams) { 1907 if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) { 1908 throw new Error("Incorrect type for ECDSA public key parameters"); 1909 } 1910 } 1911 1912 const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true); 1913 //#endregion 1914 1915 parameters.algorithm.algorithm.namedCurve = curveObject.name; 1916 } 1917 //#endregion 1918 //#endregion 1919 1920 return parameters; 1921 } 1922 1923 public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey> { 1924 if (!parameters) { 1925 parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm); 1926 } 1927 1928 const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false); 1929 1930 return this.importKey("spki", 1931 publicKeyInfoBuffer, 1932 parameters.algorithm.algorithm as Algorithm, 1933 true, 1934 parameters.algorithm.usages 1935 ); 1936 } 1937 1938 public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean> { 1939 //#region Find signer's hashing algorithm 1940 let publicKey: CryptoKey; 1941 if (!shaAlgorithm) { 1942 shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); 1943 if (!shaAlgorithm) 1944 throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); 1945 1946 //#region Import public key 1947 publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm); 1948 //#endregion 1949 } else { 1950 const parameters = {} as type.CryptoEnginePublicKeyParams; 1951 1952 //#region Get information about public key algorithm and default parameters for import 1953 let algorithmId; 1954 if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") 1955 algorithmId = signatureAlgorithm.algorithmId; 1956 else 1957 algorithmId = publicKeyInfo.algorithm.algorithmId; 1958 1959 const algorithmObject = this.getAlgorithmByOID(algorithmId, true); 1960 1961 parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); 1962 if ("hash" in parameters.algorithm.algorithm) 1963 (parameters.algorithm.algorithm as any).hash.name = shaAlgorithm; 1964 1965 //#region Special case for ECDSA 1966 if (algorithmObject.name === "ECDSA") { 1967 //#region Get information about named curve 1968 let algorithmParamsChecked = false; 1969 1970 if (("algorithmParams" in publicKeyInfo.algorithm) === true) { 1971 if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) { 1972 if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6)) 1973 algorithmParamsChecked = true; 1974 } 1975 } 1976 1977 if (algorithmParamsChecked === false) { 1978 throw new Error("Incorrect type for ECDSA public key parameters"); 1979 } 1980 1981 const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true); 1982 //#endregion 1983 1984 (parameters.algorithm.algorithm as any).namedCurve = curveObject.name; 1985 } 1986 //#endregion 1987 //#endregion 1988 1989 //#region Import public key 1990 1991 publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!! 1992 //#endregion 1993 } 1994 //#endregion 1995 1996 //#region Verify signature 1997 //#region Get default algorithm parameters for verification 1998 const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify"); 1999 if ("hash" in algorithm.algorithm) 2000 (algorithm.algorithm as any).hash.name = shaAlgorithm; 2001 //#endregion 2002 2003 //#region Special case for ECDSA signatures 2004 let signatureValue: Uint8Array | ArrayBuffer = signature.valueBlock.valueHexView; 2005 2006 if (publicKey.algorithm.name === "ECDSA") { 2007 const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve); 2008 if (!namedCurve) { 2009 throw new Error("Unsupported named curve in use"); 2010 } 2011 const asn1 = asn1js.fromBER(signatureValue); 2012 AsnError.assert(asn1, "Signature value"); 2013 signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size); 2014 } 2015 //#endregion 2016 2017 //#region Special case for RSA-PSS 2018 if (publicKey.algorithm.name === "RSA-PSS") { 2019 const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); 2020 2021 if ("saltLength" in pssParameters) 2022 (algorithm.algorithm as any).saltLength = pssParameters.saltLength; 2023 else 2024 (algorithm.algorithm as any).saltLength = 20; 2025 2026 let hashAlgo = "SHA-1"; 2027 2028 if ("hashAlgorithm" in pssParameters) { 2029 const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true); 2030 2031 hashAlgo = hashAlgorithm.name; 2032 } 2033 2034 (algorithm.algorithm as any).hash.name = hashAlgo; 2035 } 2036 //#endregion 2037 2038 return this.verify((algorithm.algorithm as any), 2039 publicKey, 2040 signatureValue as BufferSource, 2041 data, 2042 ); 2043 //#endregion 2044 } 2045 2046 }