import { createMessage, decryptKey, generateKey, PrivateKey, sign, } from "npm:openpgp@^6.1.1"; import { passphrase } from "./test_data.ts"; import { MaybeIterable } from "../../src/utils/iterator.ts"; import { afterEach, beforeEach } from "@std/testing/bdd"; import { Stub, stub } from "@std/testing/mock"; export async function generateKeyPair( name: string, options?: Partial[0]>, ): ReturnType { const key = await generateKey({ type: "ecc", userIDs: [{ name, email: `${ name.toLowerCase().replaceAll(/\s/g, "") }@localhost.localdomain`, }], passphrase, format: "object", ...options, }); const privateKey = await decryptKey({ privateKey: key.privateKey, passphrase, }); return { ...key, privateKey }; } export function generateKeyPairWithSubkey( name: string, ): ReturnType { return generateKeyPair(name, { curve: "nistP256", subkeys: [{ type: "ecc", curve: "nistP256", sign: true }], }); } export async function createDetachedSignature( data: Uint8Array, signingKeys: MaybeIterable, ): Promise> { const message = await createMessage({ binary: data }); const signature = await sign({ message, signingKeys: Symbol.iterator in signingKeys ? Iterator.from(signingKeys).toArray() : signingKeys, detached: true, format: "object", }); return signature.write() as Uint8Array; } export function corruptData(data: Uint8Array): Uint8Array { const corrupted = new Uint8Array(data); if (corrupted.length > 0) { corrupted[0] += 1; corrupted[0] %= 1 << 8; } return corrupted; } export function corruptSignatureFormat( signature: Uint8Array, ): Uint8Array { const corrupted = new Uint8Array(signature); if (corrupted.length > 0) { // Strategy 1: Change the packet tag byte // The first byte contains the tag and format information. // Changing the lower 6 bits (new format tag) or higher bits (packet format) // can easily break parsing. Let's try flipping a bit. // corrupted[0] = corrupted[0] ^ 0x01; // Flip the last bit // Strategy 2 (Alternative - more drastic): Truncate the signature // return corrupted.slice(0, corrupted.length / 2); // Cut off half the signature // Strategy 3 (Alternative - modify length field): // This is more complex as length encoding varies, but for typical new format // packets, length information is in the bytes immediately following the tag. // Modifying these can cause the parser to misinterpret the packet length. // Example (simplified, might need adjustment based on actual encoding): if (corrupted.length > 2) { corrupted[1] = (corrupted[1] + 10) % 256; } } return corrupted; } const inMemoryFiles = new Map< string, { text?: string; bytes?: Uint8Array } >(); export function startMockFs(): void { inMemoryFiles.clear(); beforeEach(() => { Deno.readTextFile = stub( Deno, "readTextFile", async (path: string | URL) => { const url = new URL(path).href; const content = inMemoryFiles.get(url)?.text; if (content === undefined) { throw new Deno.errors.NotFound(`File not found: ${url}`); } return await Promise.resolve(content); }, ); Deno.readFile = stub(Deno, "readFile", async (path: string | URL) => { const url = new URL(path).href; const content = inMemoryFiles.get(url)?.bytes; if (content === undefined) { throw new Deno.errors.NotFound(`File not found: ${url}`); } return await Promise.resolve(content); }); inMemoryFiles.clear(); }); afterEach(() => { (Deno.readTextFile as Stub).restore(); (Deno.readFile as Stub).restore(); }); } export function createInMemoryFile( url: URL, content: string | Uint8Array, ): URL { inMemoryFiles.set( url.href, typeof content === "string" ? { text: content } : { bytes: content }, ); return url; }