{"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,kBAAkB;AAGlF,MAAI,eAAe,WAAW,EAC5B,OAAM,IAAI,WAAW,8DAA8D;EAIrF,MAAM,oBAAoB,kBAAkB,qBAAqB,eAAe;AAGhF,OAAK,MAAM,YAAY,kBAErB,KADoB,kBAAkB,sBAAsB,MAAM,SAAS,IAAI,KAC3D,SAAS,IAC3B;AAIJ,QAAM,IAAI,WACR,gGACD;;;;;;;;CASH,OAAe,uBAAuB,UAAuD;EAC3F,MAAM,SAA8C,EAAE;EACtD,MAAM,kBAAkB;GAAC;GAAU;GAAU;GAAS;EAGtD,MAAM,QAAQ,SAAS,MAAM,CAAC,MAAM,MAAM;AAE1C,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,CAAC,KAAM;GAGX,MAAM,CAAC,uBAAuB,KAAK,MAAM,IAAI;GAG7C,MAAM,QAAQ,oBAAoB,MAAM,IAAI;AAC5C,OAAI,MAAM,SAAS,EAAG;GAEtB,MAAM,YAAY,MAAM,GAAG,aAAa;GACxC,MAAM,cAAc,MAAM,MAAM,EAAE,CAAC,KAAK,IAAI;AAG5C,OAAI,gBAAgB,SAAS,UAAU,CACrC,QAAO,KAAK;IAAE,KAAK;IAAW,KAAK;IAAa,CAAC;;AAIrD,SAAO;;;;;;;;CAST,OAAe,qBACb,aACqC;AACrC,MAAI,YAAY,WAAW,EACzB,QAAO,EAAE;EAIX,MAAM,oBAA4C;GAChD,QAAQ;GACR,QAAQ;GACR,QAAQ;GACT;EAED,IAAI,YAAiD;EACrD,MAAM,SAA8C,EAAE;AAEtD,OAAK,MAAM,QAAQ,aAAa;AAC9B,OAAI,CAAC,WAAW;AACd,gBAAY;AACZ,WAAO,KAAK,KAAK;AACjB;;GAGF,MAAM,eAAe,kBAAkB,UAAU;GACjD,MAAM,WAAW,kBAAkB,KAAK;AAExC,OAAI,WAAW,cAAc;AAE3B,gBAAY;AACZ,WAAO,SAAS;AAChB,WAAO,KAAK,KAAK;cACR,aAAa,aAEtB,QAAO,KAAK,KAAK;;AAKrB,SAAO;;;;;;;;;CAUT,OAAe,sBAAsB,OAAmB,WAA2B;EACjF,IAAI;AAEJ,UAAQ,WAAR;GACE,KAAK;AACH,iBAAa,OAAO,KAAK,OAAO,UAAU;AAC1C;GACF,KAAK;AACH,iBAAa,OAAO,KAAK,OAAO,UAAU;AAC1C;GACF,KAAK;AACH,iBAAa,OAAO,KAAK,OAAO,UAAU;AAC1C;GACF,QACE,OAAM,IAAI,WAAW,+BAA+B,YAAY;;AAGpE,SAAO,kBAAkB,SAAS,WAAW"}