import { sha1 } from "@noble/hashes/sha1"; import { sha256, sha384, sha512 } from "@noble/hashes/sha2"; import * as asn1js from "asn1js"; import * as pvutils from "pvutils"; import * as pvtsutils from "pvtsutils"; import * as common from "../common"; import { PublicKeyInfo } from "../PublicKeyInfo"; import { PrivateKeyInfo } from "../PrivateKeyInfo"; import { AlgorithmIdentifier } from "../AlgorithmIdentifier"; import { EncryptedContentInfo } from "../EncryptedContentInfo"; import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams"; import { PBKDF2Params } from "../PBKDF2Params"; import { PBES2Params } from "../PBES2Params"; import { ArgumentError, AsnError, ParameterError } from "../errors"; import * as type from "./CryptoEngineInterface"; import { AbstractCryptoEngine } from "./AbstractCryptoEngine"; import { EMPTY_STRING } from "../constants"; import { ECNamedCurves } from "../ECNamedCurves"; /** * Making MAC key using algorithm described in B.2 of PKCS#12 standard. */ async function makePKCS12B2Key(hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) { let u: number; // Output length of the hash function let v: number; // Block size of the hash function let md: (input: Uint8Array) => Uint8Array; // Hash function // Determine the hash algorithm parameters switch (hashAlgorithm.toUpperCase()) { case "SHA-1": u = 20; // 160 bits v = 64; // 512 bits md = sha1; break; case "SHA-256": u = 32; // 256 bits v = 64; // 512 bits md = sha256; break; case "SHA-384": u = 48; // 384 bits v = 128; // 1024 bits md = sha384; break; case "SHA-512": u = 64; // 512 bits v = 128; // 1024 bits md = sha512; break; default: throw new Error("Unsupported hashing algorithm"); } const originalPassword = new Uint8Array(password); let decodedPassword = new TextDecoder().decode(password); const encodedPassword = new TextEncoder().encode(decodedPassword); if (encodedPassword.some((byte, i) => byte !== originalPassword[i])) { decodedPassword = String.fromCharCode(...originalPassword); } // Transform the password into a byte array const passwordTransformed = new Uint8Array(decodedPassword.length * 2 + 2); const passwordView = new DataView(passwordTransformed.buffer); for (let i = 0; i < decodedPassword.length; i++) { passwordView.setUint16(i * 2, decodedPassword.charCodeAt(i), false); } // Add null-terminator passwordView.setUint16(decodedPassword.length * 2, 0, false); // Create a filled array D with the value 3 (ID for MACing) const D = new Uint8Array(v).fill(3); // Repeat the salt to fill the block size const saltView = new Uint8Array(salt); const S = new Uint8Array(v * Math.ceil(saltView.length / v)).map((_, i) => saltView[i % saltView.length]); // Repeat the password to fill the block size const P = new Uint8Array(v * Math.ceil(passwordTransformed.length / v)).map((_, i) => passwordTransformed[i % passwordTransformed.length]); // Concatenate S and P to form I let I = new Uint8Array(S.length + P.length); I.set(S); I.set(P, S.length); // Calculate the number of hash iterations needed const c = Math.ceil((keyLength >> 3) / u); const result: number[] = []; // Main loop to generate the key material for (let i = 0; i < c; i++) { // Concatenate D and I let A: Uint8Array = new Uint8Array(D.length + I.length); A.set(D); A.set(I, D.length); // Perform hash iterations for (let j = 0; j < iterationCount; j++) { A = md(A); } // Create a repeated block B from the hash output A const B = new Uint8Array(v).map((_, i) => A[i % A.length]); // Determine the number of blocks const k = Math.ceil(saltView.length / v) + Math.ceil(passwordTransformed.length / v); const iRound: number[] = []; // Adjust I based on B for (let j = 0; j < k; j++) { const chunk = Array.from(I.slice(j * v, (j + 1) * v)); let x = 0x1ff; for (let l = B.length - 1; l >= 0; l--) { x >>= 8; x += B[l] + (chunk[l] || 0); chunk[l] = x & 0xff; } iRound.push(...chunk); } // Update I for the next iteration I = new Uint8Array(iRound); // Collect the result result.push(...A); } return new Uint8Array(result.slice(0, keyLength >> 3)).buffer; } function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } { const res = typeof data === "string" ? { name: data } : data; // TODO fix type casting `as EcdsaParams` if ("hash" in (res as EcdsaParams)) { return { ...res, hash: prepareAlgorithm((res as EcdsaParams).hash) }; } return res; } /** * Default cryptographic engine for Web Cryptography API */ export class CryptoEngine extends AbstractCryptoEngine { public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise { //#region Initial variables let jwk: JsonWebKey = {}; //#endregion const alg = prepareAlgorithm(algorithm); switch (format.toLowerCase()) { case "raw": return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages); case "spki": { const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); AsnError.assert(asn1, "keyData"); const publicKeyInfo = new PublicKeyInfo(); try { publicKeyInfo.fromSchema(asn1.result); } catch { throw new ArgumentError("Incorrect keyData"); } switch (alg.name.toUpperCase()) { case "RSA-PSS": { //#region Get information about used hash function if (!alg.hash) { throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); } switch (alg.hash.name.toUpperCase()) { case "SHA-1": jwk.alg = "PS1"; break; case "SHA-256": jwk.alg = "PS256"; break; case "SHA-384": jwk.alg = "PS384"; break; case "SHA-512": jwk.alg = "PS512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); } //#endregion } // break omitted // eslint-disable-next-line no-fallthrough case "RSASSA-PKCS1-V1_5": { keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1") throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); //#region Get information about used hash function if (!jwk.alg) { if (!alg.hash) { throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); } switch (alg.hash.name.toUpperCase()) { case "SHA-1": jwk.alg = "RS1"; break; case "SHA-256": jwk.alg = "RS256"; break; case "SHA-384": jwk.alg = "RS384"; break; case "SHA-512": jwk.alg = "RS512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); } } //#endregion //#region Create RSA Public Key elements const publicKeyJSON = publicKeyInfo.toJSON(); Object.assign(jwk, publicKeyJSON); //#endregion } break; case "ECDSA": keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key // break omitted // eslint-disable-next-line no-fallthrough case "ECDH": { //#region Initial variables jwk = { kty: "EC", ext: extractable, key_ops: keyUsages }; //#endregion //#region Get information about algorithm if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") { throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); } //#endregion //#region Create ECDSA Public Key elements const publicKeyJSON = publicKeyInfo.toJSON(); Object.assign(jwk, publicKeyJSON); //#endregion } break; case "RSA-OAEP": { jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; if (this.name.toLowerCase() === "safari") jwk.alg = "RSA-OAEP"; else { if (!alg.hash) { throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed"); } switch (alg.hash.name.toUpperCase()) { case "SHA-1": jwk.alg = "RSA-OAEP"; break; case "SHA-256": jwk.alg = "RSA-OAEP-256"; break; case "SHA-384": jwk.alg = "RSA-OAEP-384"; break; case "SHA-512": jwk.alg = "RSA-OAEP-512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`); } } //#region Create ECDSA Public Key elements const publicKeyJSON = publicKeyInfo.toJSON(); Object.assign(jwk, publicKeyJSON); //#endregion } break; case "RSAES-PKCS1-V1_5": { jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; jwk.alg = "PS1"; const publicKeyJSON = publicKeyInfo.toJSON(); Object.assign(jwk, publicKeyJSON); } break; default: throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); } } break; case "pkcs8": { const privateKeyInfo = new PrivateKeyInfo(); //#region Parse "PrivateKeyInfo" object const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource)); AsnError.assert(asn1, "keyData"); try { privateKeyInfo.fromSchema(asn1.result); } catch { throw new Error("Incorrect keyData"); } if (!privateKeyInfo.parsedKey) throw new Error("Incorrect keyData"); //#endregion switch (alg.name.toUpperCase()) { case "RSA-PSS": { //#region Get information about used hash function switch (alg.hash?.name.toUpperCase()) { case "SHA-1": jwk.alg = "PS1"; break; case "SHA-256": jwk.alg = "PS256"; break; case "SHA-384": jwk.alg = "PS384"; break; case "SHA-512": jwk.alg = "PS512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); } //#endregion } // break omitted // eslint-disable-next-line no-fallthrough case "RSASSA-PKCS1-V1_5": { keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; //#region Get information about used hash function if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1") throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); //#endregion //#region Get information about used hash function if (("alg" in jwk) === false) { switch (alg.hash?.name.toUpperCase()) { case "SHA-1": jwk.alg = "RS1"; break; case "SHA-256": jwk.alg = "RS256"; break; case "SHA-384": jwk.alg = "RS384"; break; case "SHA-512": jwk.alg = "RS512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); } } //#endregion //#region Create RSA Private Key elements const privateKeyJSON = privateKeyInfo.toJSON(); Object.assign(jwk, privateKeyJSON); //#endregion } break; case "ECDSA": keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key // break omitted // eslint-disable-next-line no-fallthrough case "ECDH": { //#region Initial variables jwk = { kty: "EC", ext: extractable, key_ops: keyUsages }; //#endregion //#region Get information about used hash function if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1") throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); //#endregion //#region Create ECDSA Private Key elements const privateKeyJSON = privateKeyInfo.toJSON(); Object.assign(jwk, privateKeyJSON); //#endregion } break; case "RSA-OAEP": { jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; //#region Get information about used hash function if (this.name.toLowerCase() === "safari") jwk.alg = "RSA-OAEP"; else { switch (alg.hash?.name.toUpperCase()) { case "SHA-1": jwk.alg = "RSA-OAEP"; break; case "SHA-256": jwk.alg = "RSA-OAEP-256"; break; case "SHA-384": jwk.alg = "RSA-OAEP-384"; break; case "SHA-512": jwk.alg = "RSA-OAEP-512"; break; default: throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`); } } //#endregion //#region Create RSA Private Key elements const privateKeyJSON = privateKeyInfo.toJSON(); Object.assign(jwk, privateKeyJSON); //#endregion } break; case "RSAES-PKCS1-V1_5": { keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key jwk.kty = "RSA"; jwk.ext = extractable; jwk.key_ops = keyUsages; jwk.alg = "PS1"; //#region Create RSA Private Key elements const privateKeyJSON = privateKeyInfo.toJSON(); Object.assign(jwk, privateKeyJSON); //#endregion } break; default: throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`); } } break; case "jwk": jwk = keyData as JsonWebKey; break; default: throw new Error(`Incorrect format: ${format}`); } //#region Special case for Safari browser (since its acting not as WebCrypto standard describes) if (this.name.toLowerCase() === "safari") { // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview) try { return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages); } catch { return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); } } //#endregion return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages); } /** * Export WebCrypto keys to different formats * @param format * @param key */ public override exportKey(format: "jwk", key: CryptoKey): Promise; public override exportKey(format: Exclude, key: CryptoKey): Promise; public override exportKey(format: string, key: CryptoKey): Promise; public override async exportKey(format: KeyFormat, key: CryptoKey): Promise { let jwk = await this.subtle.exportKey("jwk", key); //#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation if (this.name.toLowerCase() === "safari") { // Some additional checks for Safari Technology Preview if (jwk instanceof ArrayBuffer) { jwk = JSON.parse(pvutils.arrayBufferToString(jwk)); } } //#endregion switch (format.toLowerCase()) { case "raw": return this.subtle.exportKey("raw", key); case "spki": { const publicKeyInfo = new PublicKeyInfo(); try { publicKeyInfo.fromJSON(jwk); } catch { throw new Error("Incorrect key data"); } return publicKeyInfo.toSchema().toBER(false); } case "pkcs8": { const privateKeyInfo = new PrivateKeyInfo(); try { privateKeyInfo.fromJSON(jwk); } catch { throw new Error("Incorrect key data"); } return privateKeyInfo.toSchema().toBER(false); } case "jwk": return jwk; default: throw new Error(`Incorrect format: ${format}`); } } /** * Convert WebCrypto keys between different export formats * @param inputFormat * @param outputFormat * @param keyData * @param algorithm * @param extractable * @param keyUsages */ public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) { if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) { return keyData; } const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages); return this.exportKey(outputFormat, key); } /** * Gets WebCrypto algorithm by wel-known OID * @param oid algorithm identifier * @param safety if `true` throws exception on unknown algorithm identifier * @param target name of the target * @returns Returns WebCrypto algorithm or an empty object */ public getAlgorithmByOID(oid: string, safety?: boolean, target?: string): T | object; /** * Gets WebCrypto algorithm by wel-known OID * @param oid algorithm identifier * @param safety if `true` throws exception on unknown algorithm identifier * @param target name of the target * @returns Returns WebCrypto algorithm * @throws Throws {@link Error} exception if unknown algorithm identifier */ public getAlgorithmByOID(oid: string, safety: true, target?: string): T; public getAlgorithmByOID(oid: string, safety = false, target?: string): any { switch (oid) { case "1.2.840.113549.1.1.1": return { name: "RSAES-PKCS1-v1_5" }; case "1.2.840.113549.1.1.5": return { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-1" } }; case "1.2.840.113549.1.1.11": return { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }; case "1.2.840.113549.1.1.12": return { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-384" } }; case "1.2.840.113549.1.1.13": return { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-512" } }; case "1.2.840.113549.1.1.10": return { name: "RSA-PSS" }; case "1.2.840.113549.1.1.7": return { name: "RSA-OAEP" }; case "1.2.840.10045.2.1": case "1.2.840.10045.4.1": return { name: "ECDSA", hash: { name: "SHA-1" } }; case "1.2.840.10045.4.3.2": return { name: "ECDSA", hash: { name: "SHA-256" } }; case "1.2.840.10045.4.3.3": return { name: "ECDSA", hash: { name: "SHA-384" } }; case "1.2.840.10045.4.3.4": return { name: "ECDSA", hash: { name: "SHA-512" } }; case "1.3.133.16.840.63.0.2": return { name: "ECDH", kdf: "SHA-1" }; case "1.3.132.1.11.1": return { name: "ECDH", kdf: "SHA-256" }; case "1.3.132.1.11.2": return { name: "ECDH", kdf: "SHA-384" }; case "1.3.132.1.11.3": return { name: "ECDH", kdf: "SHA-512" }; case "2.16.840.1.101.3.4.1.2": return { name: "AES-CBC", length: 128 }; case "2.16.840.1.101.3.4.1.22": return { name: "AES-CBC", length: 192 }; case "2.16.840.1.101.3.4.1.42": return { name: "AES-CBC", length: 256 }; case "2.16.840.1.101.3.4.1.6": return { name: "AES-GCM", length: 128 }; case "2.16.840.1.101.3.4.1.26": return { name: "AES-GCM", length: 192 }; case "2.16.840.1.101.3.4.1.46": return { name: "AES-GCM", length: 256 }; case "2.16.840.1.101.3.4.1.4": return { name: "AES-CFB", length: 128 }; case "2.16.840.1.101.3.4.1.24": return { name: "AES-CFB", length: 192 }; case "2.16.840.1.101.3.4.1.44": return { name: "AES-CFB", length: 256 }; case "2.16.840.1.101.3.4.1.5": return { name: "AES-KW", length: 128 }; case "2.16.840.1.101.3.4.1.25": return { name: "AES-KW", length: 192 }; case "2.16.840.1.101.3.4.1.45": return { name: "AES-KW", length: 256 }; case "1.2.840.113549.2.7": return { name: "HMAC", hash: { name: "SHA-1" } }; case "1.2.840.113549.2.9": return { name: "HMAC", hash: { name: "SHA-256" } }; case "1.2.840.113549.2.10": return { name: "HMAC", hash: { name: "SHA-384" } }; case "1.2.840.113549.2.11": return { name: "HMAC", hash: { name: "SHA-512" } }; case "1.2.840.113549.1.9.16.3.5": return { name: "DH" }; case "1.3.14.3.2.26": return { name: "SHA-1" }; case "2.16.840.1.101.3.4.2.1": return { name: "SHA-256" }; case "2.16.840.1.101.3.4.2.2": return { name: "SHA-384" }; case "2.16.840.1.101.3.4.2.3": return { name: "SHA-512" }; case "1.2.840.113549.1.5.12": return { name: "PBKDF2" }; //#region Special case - OIDs for ECC curves case "1.2.840.10045.3.1.7": return { name: "P-256" }; case "1.3.132.0.34": return { name: "P-384" }; case "1.3.132.0.35": return { name: "P-521" }; //#endregion default: } if (safety) { throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`); } return {}; } public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string { let result = EMPTY_STRING; switch (algorithm.name.toUpperCase()) { case "RSAES-PKCS1-V1_5": result = "1.2.840.113549.1.1.1"; break; case "RSASSA-PKCS1-V1_5": switch ((algorithm as any).hash.name.toUpperCase()) { case "SHA-1": result = "1.2.840.113549.1.1.5"; break; case "SHA-256": result = "1.2.840.113549.1.1.11"; break; case "SHA-384": result = "1.2.840.113549.1.1.12"; break; case "SHA-512": result = "1.2.840.113549.1.1.13"; break; default: } break; case "RSA-PSS": result = "1.2.840.113549.1.1.10"; break; case "RSA-OAEP": result = "1.2.840.113549.1.1.7"; break; case "ECDSA": switch ((algorithm as any).hash.name.toUpperCase()) { case "SHA-1": result = "1.2.840.10045.4.1"; break; case "SHA-256": result = "1.2.840.10045.4.3.2"; break; case "SHA-384": result = "1.2.840.10045.4.3.3"; break; case "SHA-512": result = "1.2.840.10045.4.3.4"; break; default: } break; case "ECDH": switch ((algorithm as any).kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function { case "SHA-1": result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme break; case "SHA-256": result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme break; case "SHA-384": result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme break; case "SHA-512": result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme break; default: } break; case "AES-CTR": break; case "AES-CBC": switch ((algorithm as any).length) { case 128: result = "2.16.840.1.101.3.4.1.2"; break; case 192: result = "2.16.840.1.101.3.4.1.22"; break; case 256: result = "2.16.840.1.101.3.4.1.42"; break; default: } break; case "AES-CMAC": break; case "AES-GCM": switch ((algorithm as any).length) { case 128: result = "2.16.840.1.101.3.4.1.6"; break; case 192: result = "2.16.840.1.101.3.4.1.26"; break; case 256: result = "2.16.840.1.101.3.4.1.46"; break; default: } break; case "AES-CFB": switch ((algorithm as any).length) { case 128: result = "2.16.840.1.101.3.4.1.4"; break; case 192: result = "2.16.840.1.101.3.4.1.24"; break; case 256: result = "2.16.840.1.101.3.4.1.44"; break; default: } break; case "AES-KW": switch ((algorithm as any).length) { case 128: result = "2.16.840.1.101.3.4.1.5"; break; case 192: result = "2.16.840.1.101.3.4.1.25"; break; case 256: result = "2.16.840.1.101.3.4.1.45"; break; default: } break; case "HMAC": switch ((algorithm as any).hash.name.toUpperCase()) { case "SHA-1": result = "1.2.840.113549.2.7"; break; case "SHA-256": result = "1.2.840.113549.2.9"; break; case "SHA-384": result = "1.2.840.113549.2.10"; break; case "SHA-512": result = "1.2.840.113549.2.11"; break; default: } break; case "DH": result = "1.2.840.113549.1.9.16.3.5"; break; case "SHA-1": result = "1.3.14.3.2.26"; break; case "SHA-256": result = "2.16.840.1.101.3.4.2.1"; break; case "SHA-384": result = "2.16.840.1.101.3.4.2.2"; break; case "SHA-512": result = "2.16.840.1.101.3.4.2.3"; break; case "CONCAT": break; case "HKDF": break; case "PBKDF2": result = "1.2.840.113549.1.5.12"; break; //#region Special case - OIDs for ECC curves case "P-256": result = "1.2.840.10045.3.1.7"; break; case "P-384": result = "1.3.132.0.34"; break; case "P-521": result = "1.3.132.0.35"; break; //#endregion default: } if (!result && safety) { throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`); } return result; } public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams { let result: type.CryptoEngineAlgorithmParams = { algorithm: {}, usages: [] }; switch (algorithmName.toUpperCase()) { case "RSAES-PKCS1-V1_5": case "RSASSA-PKCS1-V1_5": switch (operation.toLowerCase()) { case "generatekey": result = { algorithm: { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: "SHA-256" } }, usages: ["sign", "verify"] }; break; case "verify": case "sign": case "importkey": result = { algorithm: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }, usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only }; break; case "exportkey": default: return { algorithm: { name: "RSASSA-PKCS1-v1_5" }, usages: [] }; } break; case "RSA-PSS": switch (operation.toLowerCase()) { case "sign": case "verify": result = { algorithm: { name: "RSA-PSS", hash: { name: "SHA-1" }, saltLength: 20 }, usages: ["sign", "verify"] }; break; case "generatekey": result = { algorithm: { name: "RSA-PSS", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: "SHA-1" } }, usages: ["sign", "verify"] }; break; case "importkey": result = { algorithm: { name: "RSA-PSS", hash: { name: "SHA-1" } }, usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only }; break; case "exportkey": default: return { algorithm: { name: "RSA-PSS" }, usages: [] }; } break; case "RSA-OAEP": switch (operation.toLowerCase()) { case "encrypt": case "decrypt": result = { algorithm: { name: "RSA-OAEP" }, usages: ["encrypt", "decrypt"] }; break; case "generatekey": result = { algorithm: { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: "SHA-256" } }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; case "importkey": result = { algorithm: { name: "RSA-OAEP", hash: { name: "SHA-256" } }, usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8" }; break; case "exportkey": default: return { algorithm: { name: "RSA-OAEP" }, usages: [] }; } break; case "ECDSA": switch (operation.toLowerCase()) { case "generatekey": result = { algorithm: { name: "ECDSA", namedCurve: "P-256" }, usages: ["sign", "verify"] }; break; case "importkey": result = { algorithm: { name: "ECDSA", namedCurve: "P-256" }, usages: ["verify"] // "sign" for "pkcs8" }; break; case "verify": case "sign": result = { algorithm: { name: "ECDSA", hash: { name: "SHA-256" } }, usages: ["sign"] }; break; default: return { algorithm: { name: "ECDSA" }, usages: [] }; } break; case "ECDH": switch (operation.toLowerCase()) { case "exportkey": case "importkey": case "generatekey": result = { algorithm: { name: "ECDH", namedCurve: "P-256" }, usages: ["deriveKey", "deriveBits"] }; break; case "derivekey": case "derivebits": result = { algorithm: { name: "ECDH", namedCurve: "P-256", public: [] // Must be a "publicKey" }, usages: ["encrypt", "decrypt"] }; break; default: return { algorithm: { name: "ECDH" }, usages: [] }; } break; case "AES-CTR": switch (operation.toLowerCase()) { case "importkey": case "exportkey": case "generatekey": result = { algorithm: { name: "AES-CTR", length: 256 }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; case "decrypt": case "encrypt": result = { algorithm: { name: "AES-CTR", counter: new Uint8Array(16), length: 10 }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; default: return { algorithm: { name: "AES-CTR" }, usages: [] }; } break; case "AES-CBC": switch (operation.toLowerCase()) { case "importkey": case "exportkey": case "generatekey": result = { algorithm: { name: "AES-CBC", length: 256 }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; case "decrypt": case "encrypt": result = { algorithm: { name: "AES-CBC", iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; default: return { algorithm: { name: "AES-CBC" }, usages: [] }; } break; case "AES-GCM": switch (operation.toLowerCase()) { case "importkey": case "exportkey": case "generatekey": result = { algorithm: { name: "AES-GCM", length: 256 }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; case "decrypt": case "encrypt": result = { algorithm: { name: "AES-GCM", iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step }, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"] }; break; default: return { algorithm: { name: "AES-GCM" }, usages: [] }; } break; case "AES-KW": switch (operation.toLowerCase()) { case "importkey": case "exportkey": case "generatekey": case "wrapkey": case "unwrapkey": result = { algorithm: { name: "AES-KW", length: 256 }, usages: ["wrapKey", "unwrapKey"] }; break; default: return { algorithm: { name: "AES-KW" }, usages: [] }; } break; case "HMAC": switch (operation.toLowerCase()) { case "sign": case "verify": result = { algorithm: { name: "HMAC" }, usages: ["sign", "verify"] }; break; case "importkey": case "exportkey": case "generatekey": result = { algorithm: { name: "HMAC", length: 32, hash: { name: "SHA-256" } }, usages: ["sign", "verify"] }; break; default: return { algorithm: { name: "HMAC" }, usages: [] }; } break; case "HKDF": switch (operation.toLowerCase()) { case "derivekey": result = { algorithm: { name: "HKDF", hash: "SHA-256", salt: new Uint8Array([]), info: new Uint8Array([]) }, usages: ["encrypt", "decrypt"] }; break; default: return { algorithm: { name: "HKDF" }, usages: [] }; } break; case "PBKDF2": switch (operation.toLowerCase()) { case "derivekey": result = { algorithm: { name: "PBKDF2", hash: { name: "SHA-256" }, salt: new Uint8Array([]), iterations: 10000 }, usages: ["encrypt", "decrypt"] }; break; default: return { algorithm: { name: "PBKDF2" }, usages: [] }; } break; default: } return result; } /** * Getting hash algorithm by signature algorithm * @param signatureAlgorithm Signature algorithm */ // TODO use safety getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string { let result = EMPTY_STRING; switch (signatureAlgorithm.algorithmId) { case "1.2.840.10045.4.1": // ecdsa-with-SHA1 case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1 result = "SHA-1"; break; case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256 case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256 result = "SHA-256"; break; case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384 case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384 result = "SHA-384"; break; case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512 case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512 result = "SHA-512"; break; case "1.2.840.113549.1.1.10": // RSA-PSS { try { const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); if (params.hashAlgorithm) { const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId); if ("name" in algorithm) { result = algorithm.name; } else { return EMPTY_STRING; } } else result = "SHA-1"; } catch { // nothing } } break; default: } return result; } public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise { //#region Check for input parameters ParameterError.assert(parameters, "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm", "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType"); const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm"); const pbkdf2OID = this.getOIDByAlgorithm({ name: "PBKDF2" }, true, "PBKDF2"); const hmacOID = this.getOIDByAlgorithm({ name: "HMAC", hash: { name: parameters.hmacHashAlgorithm } } as Algorithm, true, "hmacHashAlgorithm"); //#endregion //#region Initial variables // TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer? const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long const ivView = new Uint8Array(ivBuffer); this.getRandomValues(ivView); const saltBuffer = new ArrayBuffer(64); const saltView = new Uint8Array(saltBuffer); this.getRandomValues(saltView); const contentView = new Uint8Array(parameters.contentToEncrypt); const pbkdf2Params = new PBKDF2Params({ salt: new asn1js.OctetString({ valueHex: saltBuffer }), iterationCount: parameters.iterationCount, prf: new AlgorithmIdentifier({ algorithmId: hmacOID, algorithmParams: new asn1js.Null() }) }); //#endregion //#region Derive PBKDF2 key from "password" buffer const passwordView = new Uint8Array(parameters.password); const pbkdfKey = await this.importKey("raw", passwordView, "PBKDF2", false, ["deriveKey"]); //#endregion //#region Derive key for "contentEncryptionAlgorithm" const derivedKey = await this.deriveKey({ name: "PBKDF2", hash: { name: parameters.hmacHashAlgorithm }, salt: saltView, iterations: parameters.iterationCount }, pbkdfKey, parameters.contentEncryptionAlgorithm, false, ["encrypt"]); //#endregion //#region Encrypt content // TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM) const encryptedData = await this.encrypt( { name: parameters.contentEncryptionAlgorithm.name, iv: ivView }, derivedKey, contentView); //#endregion //#region Store all parameters in EncryptedData object const pbes2Parameters = new PBES2Params({ keyDerivationFunc: new AlgorithmIdentifier({ algorithmId: pbkdf2OID, algorithmParams: pbkdf2Params.toSchema() }), encryptionScheme: new AlgorithmIdentifier({ algorithmId: contentEncryptionOID, algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer }) }) }); return new EncryptedContentInfo({ contentType: parameters.contentType, contentEncryptionAlgorithm: new AlgorithmIdentifier({ algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2 algorithmParams: pbes2Parameters.toSchema() }), encryptedContent: new asn1js.OctetString({ valueHex: encryptedData }) }); //#endregion } /** * Decrypt data stored in "EncryptedContentInfo" object using parameters * @param parameters */ public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise { //#region Check for input parameters ParameterError.assert(parameters, "password", "encryptedContentInfo"); if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2 throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`); //#endregion //#region Initial variables let pbes2Parameters: PBES2Params; try { pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams }); } catch { throw new Error("Incorrectly encoded \"pbes2Parameters\""); } let pbkdf2Params; try { pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams }); } catch { throw new Error("Incorrectly encoded \"pbkdf2Params\""); } const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true); const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex; const ivView = new Uint8Array(ivBuffer); const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex; const saltView = new Uint8Array(saltBuffer); const iterationCount = pbkdf2Params.iterationCount; let hmacHashAlgorithm = "SHA-1"; if (pbkdf2Params.prf) { const algorithm = this.getAlgorithmByOID(pbkdf2Params.prf.algorithmId, true); hmacHashAlgorithm = algorithm.hash.name; } //#endregion //#region Derive PBKDF2 key from "password" buffer const pbkdfKey = await this.importKey("raw", parameters.password, "PBKDF2", false, ["deriveKey"]); //#endregion //#region Derive key for "contentEncryptionAlgorithm" const result = await this.deriveKey( { name: "PBKDF2", hash: { name: hmacHashAlgorithm }, salt: saltView, iterations: iterationCount }, pbkdfKey, contentEncryptionAlgorithm as any, false, ["decrypt"]); //#endregion //#region Decrypt internal content using derived key //#region Create correct data block for decryption const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent(); //#endregion return this.decrypt({ name: contentEncryptionAlgorithm.name, iv: ivView }, result, dataBuffer); //#endregion } public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise { //#region Check for input parameters if ((parameters instanceof Object) === false) throw new Error("Parameters must have type \"Object\""); ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp"); //#endregion //#region Choose correct length for HMAC key let length: number; switch (parameters.hashAlgorithm.toLowerCase()) { case "sha-1": length = 160; break; case "sha-256": length = 256; break; case "sha-384": length = 384; break; case "sha-512": length = 512; break; default: throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); } //#endregion //#region Initial variables const hmacAlgorithm = { name: "HMAC", length, hash: { name: parameters.hashAlgorithm } }; //#endregion //#region Create PKCS#12 key for integrity checking const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); //#endregion //#region Import HMAC key const hmacKey = await this.importKey("raw", new Uint8Array(pkcsKey), hmacAlgorithm, false, ["sign"]); //#endregion //#region Make signed HMAC value return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp)); //#endregion } public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise { //#region Check for input parameters ParameterError.assert(parameters, "password", "hashAlgorithm", "salt", "iterationCount", "contentToVerify", "signatureToVerify"); //#endregion //#region Choose correct length for HMAC key let length = 0; switch (parameters.hashAlgorithm.toLowerCase()) { case "sha-1": length = 160; break; case "sha-256": length = 256; break; case "sha-384": length = 384; break; case "sha-512": length = 512; break; default: throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`); } //#endregion //#region Initial variables const hmacAlgorithm = { name: "HMAC", length, hash: { name: parameters.hashAlgorithm } }; //#endregion //#region Create PKCS#12 key for integrity checking const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount); //#endregion //#region Import HMAC key const hmacKey = await this.importKey("raw", new Uint8Array(pkcsKey), hmacAlgorithm, false, ["verify"]); //#endregion //#region Make signed HMAC value return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)); //#endregion } public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise { // Check hashing algorithm this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); // Initial variables const signatureAlgorithm = new AlgorithmIdentifier(); //#region Get "default parameters" for the current algorithm const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign"); if (!Object.keys(parameters.algorithm).length) { throw new Error("Parameter 'algorithm' is empty"); } // Use the hash from the privateKey.algorithm.hash.name for keys with hash algorithms (like RSA) const algorithm = parameters.algorithm as any; // TODO remove `as any` if ("hash" in privateKey.algorithm && privateKey.algorithm.hash && (privateKey.algorithm.hash as Algorithm).name) { algorithm.hash.name = (privateKey.algorithm.hash as Algorithm).name; } else { algorithm.hash.name = hashAlgorithm; } //#endregion //#region Fill internal structures based on "privateKey" and "hashAlgorithm" switch (privateKey.algorithm.name.toUpperCase()) { case "RSASSA-PKCS1-V1_5": case "ECDSA": signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true); break; case "RSA-PSS": { //#region Set "saltLength" as the length (in octets) of the hash function result switch (algorithm.hash.name.toUpperCase()) { case "SHA-256": algorithm.saltLength = 32; break; case "SHA-384": algorithm.saltLength = 48; break; case "SHA-512": algorithm.saltLength = 64; break; default: } //#endregion //#region Fill "RSASSA_PSS_params" object const paramsObject: Partial = {}; if (algorithm.hash.name.toUpperCase() !== "SHA-1") { const hashAlgorithmOID = this.getOIDByAlgorithm({ name: algorithm.hash.name }, true, "hashAlgorithm"); paramsObject.hashAlgorithm = new AlgorithmIdentifier({ algorithmId: hashAlgorithmOID, algorithmParams: new asn1js.Null() }); paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({ algorithmId: "1.2.840.113549.1.1.8", // MGF1 algorithmParams: paramsObject.hashAlgorithm.toSchema() }); } if (algorithm.saltLength !== 20) paramsObject.saltLength = algorithm.saltLength; const pssParameters = new RSASSAPSSParams(paramsObject); //#endregion //#region Automatically set signature algorithm signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10"; signatureAlgorithm.algorithmParams = pssParameters.toSchema(); //#endregion } break; default: throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`); } //#endregion return { signatureAlgorithm, parameters }; } public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise { const signature = await this.sign(parameters.algorithm, privateKey, data); //#region Special case for ECDSA algorithm if (parameters.algorithm.name === "ECDSA") { return common.createCMSECDSASignature(signature); } //#endregion return signature; } public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams { const parameters = {} as any; //#region Find signer's hashing algorithm const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); if (shaAlgorithm === EMPTY_STRING) throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); //#endregion //#region Get information about public key algorithm and default parameters for import let algorithmId: string; if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") algorithmId = signatureAlgorithm.algorithmId; else algorithmId = publicKeyInfo.algorithm.algorithmId; const algorithmObject = this.getAlgorithmByOID(algorithmId, true); parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); if ("hash" in parameters.algorithm.algorithm) parameters.algorithm.algorithm.hash.name = shaAlgorithm; //#region Special case for ECDSA if (algorithmObject.name === "ECDSA") { //#region Get information about named curve const publicKeyAlgorithm = publicKeyInfo.algorithm; if (!publicKeyAlgorithm.algorithmParams) { throw new Error("Algorithm parameters for ECDSA public key are missed"); } const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams; if ("idBlock" in publicKeyAlgorithm.algorithmParams) { if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) { throw new Error("Incorrect type for ECDSA public key parameters"); } } const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true); //#endregion parameters.algorithm.algorithm.namedCurve = curveObject.name; } //#endregion //#endregion return parameters; } public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise { if (!parameters) { parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm); } const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false); return this.importKey("spki", publicKeyInfoBuffer, parameters.algorithm.algorithm as Algorithm, true, parameters.algorithm.usages ); } public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise { //#region Find signer's hashing algorithm let publicKey: CryptoKey; if (!shaAlgorithm) { shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm); if (!shaAlgorithm) throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); //#region Import public key publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm); //#endregion } else { const parameters = {} as type.CryptoEnginePublicKeyParams; //#region Get information about public key algorithm and default parameters for import let algorithmId; if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10") algorithmId = signatureAlgorithm.algorithmId; else algorithmId = publicKeyInfo.algorithm.algorithmId; const algorithmObject = this.getAlgorithmByOID(algorithmId, true); parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey"); if ("hash" in parameters.algorithm.algorithm) (parameters.algorithm.algorithm as any).hash.name = shaAlgorithm; //#region Special case for ECDSA if (algorithmObject.name === "ECDSA") { //#region Get information about named curve let algorithmParamsChecked = false; if (("algorithmParams" in publicKeyInfo.algorithm) === true) { if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) { if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6)) algorithmParamsChecked = true; } } if (algorithmParamsChecked === false) { throw new Error("Incorrect type for ECDSA public key parameters"); } const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true); //#endregion (parameters.algorithm.algorithm as any).namedCurve = curveObject.name; } //#endregion //#endregion //#region Import public key publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!! //#endregion } //#endregion //#region Verify signature //#region Get default algorithm parameters for verification const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify"); if ("hash" in algorithm.algorithm) (algorithm.algorithm as any).hash.name = shaAlgorithm; //#endregion //#region Special case for ECDSA signatures let signatureValue: Uint8Array | ArrayBuffer = signature.valueBlock.valueHexView; if (publicKey.algorithm.name === "ECDSA") { const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve); if (!namedCurve) { throw new Error("Unsupported named curve in use"); } const asn1 = asn1js.fromBER(signatureValue); AsnError.assert(asn1, "Signature value"); signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size); } //#endregion //#region Special case for RSA-PSS if (publicKey.algorithm.name === "RSA-PSS") { const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams }); if ("saltLength" in pssParameters) (algorithm.algorithm as any).saltLength = pssParameters.saltLength; else (algorithm.algorithm as any).saltLength = 20; let hashAlgo = "SHA-1"; if ("hashAlgorithm" in pssParameters) { const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true); hashAlgo = hashAlgorithm.name; } (algorithm.algorithm as any).hash.name = hashAlgo; } //#endregion return this.verify((algorithm.algorithm as any), publicKey, signatureValue as BufferSource, data, ); //#endregion } }