All files / src/protocol SegmentedVerifier.ts

93.75% Statements 30/32
93.75% Branches 15/16
100% Functions 3/3
92.59% Lines 25/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89                                9x     9x 5x 3x 1x     8x       8x   8x                   3x 3x   3x                 3x 3x 1x 1x             2x 4x 4x     4x   4x 4x 1x 1x                 3x 2x          
import { createI18nKey } from "./ProtocolValidator.js";
import { ISegmentedSRI, EngineErrorCode } from "../types.js";
import { EngineError } from "../errors/EngineError.js";
 
/**
 * 2026 Zenith Tier: 分割された SRI ハッシュの検証を行うユーティリティ。
 * 巨大なファイルのダウンロード中にインクリメンタルな検証を可能にします。
 */
export class SegmentedVerifier {
  /**
   * チャンク(セグメント)ごとのハッシュを検証します。
   */
  public static async verifySegment(
    data: Uint8Array,
    expectedHash: string,
  ): Promise<boolean> {
    const [algo, expectedBase64] = expectedHash.split("-") as [string, string];
 
    let webCryptoAlgo: string;
    if (algo === "sha256") webCryptoAlgo = "SHA-256";
    else if (algo === "sha384") webCryptoAlgo = "SHA-384";
    else if (algo === "sha512") webCryptoAlgo = "SHA-512";
    else return false;
 
    // 2026: Ensure we are passing a compatible buffer to subtle crypto
    const digest = await crypto.subtle.digest(
      webCryptoAlgo,
      data as BufferSource,
    );
    const actualBase64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
 
    return actualBase64 === expectedBase64;
  }
 
  /**
   * ストリーム読み込み中にインクリメンタルに検証を行います。
   */
  public static async assertSegmented(
    fullData: ArrayBuffer,
    segmentedSri: ISegmentedSRI,
  ): Promise<void> {
    const { segmentSize, hashes } = segmentedSri;
    const bytes = new Uint8Array(fullData);
 
    Iif (!Number.isInteger(segmentSize) || segmentSize <= 0) {
      const i18nKey = createI18nKey("engine.errors.sriMismatch");
      throw new EngineError({
        code: EngineErrorCode.SRI_MISMATCH,
        message: "Segmented SRI verification failed: invalid segmentSize.",
        i18nKey,
      });
    }
 
    const expectedSegments = Math.ceil(bytes.length / segmentSize);
    if (hashes.length !== expectedSegments) {
      const i18nKey = createI18nKey("engine.errors.sriMismatch");
      throw new EngineError({
        code: EngineErrorCode.SRI_MISMATCH,
        message: `Segmented SRI verification failed: expected ${expectedSegments} hashes, got ${hashes.length}.`,
        i18nKey,
      });
    }
 
    for (let i = 0; i < expectedSegments; i++) {
      const start = i * segmentSize;
      const end = Math.min(start + segmentSize, bytes.length);
 
      // 2026 Zenith Tier: subarray() を使用して物理的なメモリコピーを回避
      const segment = bytes.subarray(start, end);
 
      const isValid = await this.verifySegment(segment, hashes[i]!);
      if (!isValid) {
        const i18nKey = createI18nKey("engine.errors.sriMismatch");
        throw new EngineError({
          code: EngineErrorCode.SRI_MISMATCH,
          message: `Segmented SRI verification failed at segment ${i}.`,
          i18nKey,
        });
      }
 
      // 2026 Zenith Tier: 大規模検証中のメインスレッド・ブロッキングを防止
      // 5セグメントごとにイベントループへ制御を戻す
      if (i % 5 === 0) {
        await new Promise((resolve) => setTimeout(resolve, 0));
      }
    }
  }
}