{"version":3,"file":"IntegrityVerifier.mjs","names":[],"sources":["../../src/utils/IntegrityVerifier.ts"],"sourcesContent":["import { Hasher } from '../crypto/hashes/Hasher'\nimport { CredoError } from '../error'\nimport { TypedArrayEncoder } from './TypedArrayEncoder'\n\n/**\n * Verifies Subresource Integrity (SRI) metadata according to W3C specification.\n *\n * This class implements the verification logic for integrity metadata strings\n * as defined in the W3C Subresource Integrity specification.\n *\n * @see https://www.w3.org/TR/SRI/\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: no explanation\nexport class IntegrityVerifier {\n  /**\n   * Verifies the integrity of raw data against provided integrity metadata.\n   *\n   * @param data - The data to verify\n   * @param integrityMetadata - The integrity metadata string (e.g., \"sha256-...\")\n   * @throws {CredoError} if verification fails\n   */\n  public static verifyIntegrity(data: Uint8Array, integrityMetadata: string): void {\n    const parsedMetadata = IntegrityVerifier.parseIntegrityMetadata(integrityMetadata)\n\n    // If metadata is empty, no verification is required\n    if (parsedMetadata.length === 0) {\n      throw new CredoError(`Integrity check failed. Parsed integrity metadata is empty.`)\n    }\n\n    // Get the strongest algorithm's metadata\n    const strongestMetadata = IntegrityVerifier.getStrongestMetadata(parsedMetadata)\n\n    // Try to match any of the strongest hashes\n    for (const metadata of strongestMetadata) {\n      const actualValue = IntegrityVerifier.applyAlgorithmToBytes(data, metadata.alg)\n      if (actualValue === metadata.val) {\n        return\n      }\n    }\n\n    throw new CredoError(\n      `Integrity check failed. None of the provided hashes match the computed hash for the response.`\n    )\n  }\n\n  /**\n   * Parses integrity metadata string into structured format.\n   *\n   * @param metadata - The integrity metadata string\n   * @returns Array of parsed metadata objects\n   */\n  private static parseIntegrityMetadata(metadata: string): Array<{ alg: string; val: string }> {\n    const result: Array<{ alg: string; val: string }> = []\n    const validAlgorithms = ['sha256', 'sha384', 'sha512']\n\n    // Split by whitespace\n    const items = metadata.trim().split(/\\s+/)\n\n    for (const item of items) {\n      if (!item) continue\n\n      // Remove options (anything after '?')\n      const [algorithmExpression] = item.split('?')\n\n      // Split algorithm and base64 value\n      const parts = algorithmExpression.split('-')\n      if (parts.length < 2) continue\n\n      const algorithm = parts[0].toLowerCase()\n      const base64Value = parts.slice(1).join('-') // Rejoin in case base64 contains hyphens\n\n      // Only include supported algorithms\n      if (validAlgorithms.includes(algorithm)) {\n        result.push({ alg: algorithm, val: base64Value })\n      }\n    }\n\n    return result\n  }\n\n  /**\n   * Returns the metadata for the strongest algorithm(s) in the set.\n   *\n   * @param metadataSet - Array of parsed metadata\n   * @returns Array containing only the strongest algorithm's metadata\n   */\n  private static getStrongestMetadata(\n    metadataSet: Array<{ alg: string; val: string }>\n  ): Array<{ alg: string; val: string }> {\n    if (metadataSet.length === 0) {\n      return []\n    }\n\n    // Algorithm priority (higher = stronger)\n    const algorithmPriority: Record<string, number> = {\n      sha256: 0,\n      sha384: 1,\n      sha512: 2,\n    }\n\n    let strongest: { alg: string; val: string } | null = null\n    const result: Array<{ alg: string; val: string }> = []\n\n    for (const item of metadataSet) {\n      if (!strongest) {\n        strongest = item\n        result.push(item)\n        continue\n      }\n\n      const currentIndex = algorithmPriority[strongest.alg]\n      const newIndex = algorithmPriority[item.alg]\n\n      if (newIndex > currentIndex) {\n        // Found a stronger algorithm, replace all\n        strongest = item\n        result.length = 0\n        result.push(item)\n      } else if (newIndex === currentIndex) {\n        // Same strength, add to results\n        result.push(item)\n      }\n      // If newIndex < currentIndex, ignore (weaker algorithm)\n    }\n\n    return result\n  }\n\n  /**\n   * Applies the specified hash algorithm to the given bytes.\n   *\n   * @param bytes - The bytes to hash\n   * @param algorithm - The hash algorithm name\n   * @returns Base64-encoded hash value\n   */\n  private static applyAlgorithmToBytes(bytes: Uint8Array, algorithm: string): string {\n    let hashResult: Uint8Array\n\n    switch (algorithm) {\n      case 'sha256':\n        hashResult = Hasher.hash(bytes, 'sha-256')\n        break\n      case 'sha384':\n        hashResult = Hasher.hash(bytes, 'sha-384')\n        break\n      case 'sha512':\n        hashResult = Hasher.hash(bytes, 'sha-512')\n        break\n      default:\n        throw new CredoError(`Unsupported hash algorithm: ${algorithm}`)\n    }\n\n    return TypedArrayEncoder.toBase64(hashResult)\n  }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,oBAAb,MAAa,kBAAkB;;;;;;;;CAQ7B,OAAc,gBAAgB,MAAkB,mBAAiC;EAC/E,MAAM,iBAAiB,kBAAkB,uBAAuB,iBAAiB;EAGjF,IAAI,eAAe,WAAW,GAC5B,MAAM,IAAI,WAAW,6DAA6D;EAIpF,MAAM,oBAAoB,kBAAkB,qBAAqB,cAAc;EAG/E,KAAK,MAAM,YAAY,mBAErB,IADoB,kBAAkB,sBAAsB,MAAM,SAAS,GAC7D,MAAM,SAAS,KAC3B;EAIJ,MAAM,IAAI,WACR,+FACF;CACF;;;;;;;CAQA,OAAe,uBAAuB,UAAuD;EAC3F,MAAM,SAA8C,CAAC;EACrD,MAAM,kBAAkB;GAAC;GAAU;GAAU;EAAQ;EAGrD,MAAM,QAAQ,SAAS,KAAK,CAAC,CAAC,MAAM,KAAK;EAEzC,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,CAAC,MAAM;GAGX,MAAM,CAAC,uBAAuB,KAAK,MAAM,GAAG;GAG5C,MAAM,QAAQ,oBAAoB,MAAM,GAAG;GAC3C,IAAI,MAAM,SAAS,GAAG;GAEtB,MAAM,YAAY,MAAM,EAAE,CAAC,YAAY;GACvC,MAAM,cAAc,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;GAG3C,IAAI,gBAAgB,SAAS,SAAS,GACpC,OAAO,KAAK;IAAE,KAAK;IAAW,KAAK;GAAY,CAAC;EAEpD;EAEA,OAAO;CACT;;;;;;;CAQA,OAAe,qBACb,aACqC;EACrC,IAAI,YAAY,WAAW,GACzB,OAAO,CAAC;EAIV,MAAM,oBAA4C;GAChD,QAAQ;GACR,QAAQ;GACR,QAAQ;EACV;EAEA,IAAI,YAAiD;EACrD,MAAM,SAA8C,CAAC;EAErD,KAAK,MAAM,QAAQ,aAAa;GAC9B,IAAI,CAAC,WAAW;IACd,YAAY;IACZ,OAAO,KAAK,IAAI;IAChB;GACF;GAEA,MAAM,eAAe,kBAAkB,UAAU;GACjD,MAAM,WAAW,kBAAkB,KAAK;GAExC,IAAI,WAAW,cAAc;IAE3B,YAAY;IACZ,OAAO,SAAS;IAChB,OAAO,KAAK,IAAI;GAClB,OAAO,IAAI,aAAa,cAEtB,OAAO,KAAK,IAAI;EAGpB;EAEA,OAAO;CACT;;;;;;;;CASA,OAAe,sBAAsB,OAAmB,WAA2B;EACjF,IAAI;EAEJ,QAAQ,WAAR;GACE,KAAK;IACH,aAAa,OAAO,KAAK,OAAO,SAAS;IACzC;GACF,KAAK;IACH,aAAa,OAAO,KAAK,OAAO,SAAS;IACzC;GACF,KAAK;IACH,aAAa,OAAO,KAAK,OAAO,SAAS;IACzC;GACF,SACE,MAAM,IAAI,WAAW,+BAA+B,WAAW;EACnE;EAEA,OAAO,kBAAkB,SAAS,UAAU;CAC9C;AACF"}