@attest-it/core 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UWYR7JNE.js → chunk-CEE7ONNG.js} +20 -25
- package/dist/chunk-CEE7ONNG.js.map +1 -0
- package/dist/core-alpha.d.ts +33 -36
- package/dist/core-beta.d.ts +33 -36
- package/dist/core-public.d.ts +33 -36
- package/dist/core-unstripped.d.ts +33 -36
- package/dist/{crypto-ITLMIMRJ.js → crypto-VAXWUGKL.js} +3 -3
- package/dist/{crypto-ITLMIMRJ.js.map → crypto-VAXWUGKL.js.map} +1 -1
- package/dist/index.cjs +21 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -36
- package/dist/index.d.ts +36 -37
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-UWYR7JNE.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,8 +17,6 @@ interface AttestItSettings {
|
|
|
17
17
|
attestationsPath: string;
|
|
18
18
|
/** Default command to execute for attestation (can be overridden per suite) */
|
|
19
19
|
defaultCommand?: string;
|
|
20
|
-
/** Cryptographic algorithm to use for signatures */
|
|
21
|
-
algorithm: 'ed25519' | 'rsa';
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
22
|
* Suite definition from the configuration file.
|
|
@@ -111,24 +109,21 @@ interface SuiteVerificationResult {
|
|
|
111
109
|
*/
|
|
112
110
|
declare const configSchema: z.ZodObject<{
|
|
113
111
|
settings: z.ZodDefault<z.ZodObject<{
|
|
114
|
-
algorithm: z.ZodDefault<z.ZodEnum<["ed25519", "rsa"]>>;
|
|
115
112
|
attestationsPath: z.ZodDefault<z.ZodString>;
|
|
116
113
|
defaultCommand: z.ZodOptional<z.ZodString>;
|
|
117
114
|
maxAgeDays: z.ZodDefault<z.ZodNumber>;
|
|
118
115
|
publicKeyPath: z.ZodDefault<z.ZodString>;
|
|
119
|
-
}, "
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
publicKeyPath?: string | undefined;
|
|
131
|
-
}>>;
|
|
116
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
117
|
+
attestationsPath: z.ZodDefault<z.ZodString>;
|
|
118
|
+
defaultCommand: z.ZodOptional<z.ZodString>;
|
|
119
|
+
maxAgeDays: z.ZodDefault<z.ZodNumber>;
|
|
120
|
+
publicKeyPath: z.ZodDefault<z.ZodString>;
|
|
121
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
122
|
+
attestationsPath: z.ZodDefault<z.ZodString>;
|
|
123
|
+
defaultCommand: z.ZodOptional<z.ZodString>;
|
|
124
|
+
maxAgeDays: z.ZodDefault<z.ZodNumber>;
|
|
125
|
+
publicKeyPath: z.ZodDefault<z.ZodString>;
|
|
126
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
132
127
|
suites: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
133
128
|
command: z.ZodOptional<z.ZodString>;
|
|
134
129
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -168,12 +163,11 @@ declare const configSchema: z.ZodObject<{
|
|
|
168
163
|
version: z.ZodLiteral<1>;
|
|
169
164
|
}, "strict", z.ZodTypeAny, {
|
|
170
165
|
settings: {
|
|
171
|
-
algorithm: "ed25519" | "rsa";
|
|
172
166
|
attestationsPath: string;
|
|
173
167
|
defaultCommand?: string | undefined;
|
|
174
168
|
maxAgeDays: number;
|
|
175
169
|
publicKeyPath: string;
|
|
176
|
-
};
|
|
170
|
+
} & { [k: string]: unknown };
|
|
177
171
|
suites: Record<string, {
|
|
178
172
|
command?: string | undefined;
|
|
179
173
|
description?: string | undefined;
|
|
@@ -184,13 +178,12 @@ declare const configSchema: z.ZodObject<{
|
|
|
184
178
|
}>;
|
|
185
179
|
version: 1;
|
|
186
180
|
}, {
|
|
187
|
-
settings?: {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} | undefined;
|
|
181
|
+
settings?: undefined | z.objectInputType<{
|
|
182
|
+
attestationsPath: z.ZodDefault<z.ZodString>;
|
|
183
|
+
defaultCommand: z.ZodOptional<z.ZodString>;
|
|
184
|
+
maxAgeDays: z.ZodDefault<z.ZodNumber>;
|
|
185
|
+
publicKeyPath: z.ZodDefault<z.ZodString>;
|
|
186
|
+
}, z.ZodTypeAny, "passthrough">;
|
|
194
187
|
suites: Record<string, {
|
|
195
188
|
command?: string | undefined;
|
|
196
189
|
description?: string | undefined;
|
|
@@ -523,15 +516,11 @@ declare class SignatureInvalidError extends Error {
|
|
|
523
516
|
*
|
|
524
517
|
* @remarks
|
|
525
518
|
* This module provides cryptographic operations using OpenSSL for key management
|
|
526
|
-
* and signature verification. It
|
|
519
|
+
* and signature verification. It uses RSA-2048 with SHA-256 for signatures,
|
|
520
|
+
* which is universally supported across all OpenSSL and LibreSSL versions.
|
|
527
521
|
*
|
|
528
522
|
* @packageDocumentation
|
|
529
523
|
*/
|
|
530
|
-
/**
|
|
531
|
-
* Supported signature algorithms.
|
|
532
|
-
* @public
|
|
533
|
-
*/
|
|
534
|
-
type Algorithm = 'ed25519' | 'rsa';
|
|
535
524
|
/**
|
|
536
525
|
* Paths to a generated keypair.
|
|
537
526
|
* @public
|
|
@@ -547,8 +536,6 @@ interface KeyPaths {
|
|
|
547
536
|
* @public
|
|
548
537
|
*/
|
|
549
538
|
interface KeygenOptions {
|
|
550
|
-
/** Algorithm to use (default: ed25519) */
|
|
551
|
-
algorithm?: Algorithm;
|
|
552
539
|
/** Path for private key (default: OS-specific config dir) */
|
|
553
540
|
privatePath?: string;
|
|
554
541
|
/** Path for public key (default: repo root) */
|
|
@@ -598,7 +585,11 @@ declare function getDefaultPrivateKeyPath(): string;
|
|
|
598
585
|
*/
|
|
599
586
|
declare function getDefaultPublicKeyPath(): string;
|
|
600
587
|
/**
|
|
601
|
-
* Generate a new keypair using OpenSSL.
|
|
588
|
+
* Generate a new RSA-2048 keypair using OpenSSL.
|
|
589
|
+
*
|
|
590
|
+
* RSA-2048 with SHA-256 is used because it's universally supported across
|
|
591
|
+
* all OpenSSL and LibreSSL versions, including older macOS systems.
|
|
592
|
+
*
|
|
602
593
|
* @param options - Generation options
|
|
603
594
|
* @returns Paths to generated keys
|
|
604
595
|
* @throws Error if OpenSSL fails or keys exist without force
|
|
@@ -606,7 +597,11 @@ declare function getDefaultPublicKeyPath(): string;
|
|
|
606
597
|
*/
|
|
607
598
|
declare function generateKeyPair(options?: KeygenOptions): Promise<KeyPaths>;
|
|
608
599
|
/**
|
|
609
|
-
* Sign data using
|
|
600
|
+
* Sign data using an RSA private key with SHA-256.
|
|
601
|
+
*
|
|
602
|
+
* Uses `openssl dgst -sha256 -sign` which is universally supported across
|
|
603
|
+
* all OpenSSL and LibreSSL versions.
|
|
604
|
+
*
|
|
610
605
|
* @param options - Signing options
|
|
611
606
|
* @returns Base64-encoded signature
|
|
612
607
|
* @throws Error if signing fails
|
|
@@ -614,7 +609,11 @@ declare function generateKeyPair(options?: KeygenOptions): Promise<KeyPaths>;
|
|
|
614
609
|
*/
|
|
615
610
|
declare function sign(options: SignOptions): Promise<string>;
|
|
616
611
|
/**
|
|
617
|
-
* Verify a signature using
|
|
612
|
+
* Verify a signature using an RSA public key with SHA-256.
|
|
613
|
+
*
|
|
614
|
+
* Uses `openssl dgst -sha256 -verify` which is universally supported across
|
|
615
|
+
* all OpenSSL and LibreSSL versions.
|
|
616
|
+
*
|
|
618
617
|
* @param options - Verification options
|
|
619
618
|
* @returns true if signature is valid
|
|
620
619
|
* @throws Error if verification fails (not just invalid signature)
|
|
@@ -688,4 +687,4 @@ declare function verifyAttestations(options: VerifyOptions): Promise<VerifyResul
|
|
|
688
687
|
*/
|
|
689
688
|
declare const version = "0.0.0";
|
|
690
689
|
|
|
691
|
-
export { type
|
|
690
|
+
export { type AttestItConfig, type AttestItSettings, type Attestation, type AttestationsFile, type Config, ConfigNotFoundError, ConfigValidationError, type VerifyOptions$1 as CryptoVerifyOptions, type FingerprintOptions, type FingerprintResult, type KeyPaths, type KeygenOptions, type ReadSignedAttestationsOptions, type SignOptions, SignatureInvalidError, type SuiteConfig, type SuiteVerificationResult, type VerificationStatus, type VerifyOptions, type VerifyResult, type WriteSignedAttestationsOptions, canonicalizeAttestations, checkOpenSSL, computeFingerprint, computeFingerprintSync, createAttestation, findAttestation, findConfigPath, generateKeyPair, getDefaultPrivateKeyPath, getDefaultPublicKeyPath, listPackageFiles, loadConfig, loadConfigSync, readAndVerifyAttestations, readAttestations, readAttestationsSync, removeAttestation, resolveConfigPaths, setKeyPermissions, sign, toAttestItConfig, upsertAttestation, verify, verifyAttestations, version, writeAttestations, writeAttestationsSync, writeSignedAttestations };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { checkOpenSSL, generateKeyPair, getDefaultPrivateKeyPath, getDefaultPublicKeyPath, setKeyPermissions, sign, verify } from './chunk-
|
|
1
|
+
export { checkOpenSSL, generateKeyPair, getDefaultPrivateKeyPath, getDefaultPublicKeyPath, setKeyPermissions, sign, verify } from './chunk-CEE7ONNG.js';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { readFile } from 'fs/promises';
|
|
@@ -15,9 +15,9 @@ var settingsSchema = z.object({
|
|
|
15
15
|
maxAgeDays: z.number().int().positive().default(30),
|
|
16
16
|
publicKeyPath: z.string().default(".attest-it/pubkey.pem"),
|
|
17
17
|
attestationsPath: z.string().default(".attest-it/attestations.json"),
|
|
18
|
-
defaultCommand: z.string().optional()
|
|
19
|
-
|
|
20
|
-
}).
|
|
18
|
+
defaultCommand: z.string().optional()
|
|
19
|
+
// Note: algorithm field was removed - RSA is the only supported algorithm
|
|
20
|
+
}).passthrough();
|
|
21
21
|
var suiteSchema = z.object({
|
|
22
22
|
description: z.string().optional(),
|
|
23
23
|
packages: z.array(z.string().min(1, "Package path cannot be empty")).min(1, "At least one package pattern is required"),
|
|
@@ -150,7 +150,6 @@ function toAttestItConfig(config) {
|
|
|
150
150
|
maxAgeDays: config.settings.maxAgeDays,
|
|
151
151
|
publicKeyPath: config.settings.publicKeyPath,
|
|
152
152
|
attestationsPath: config.settings.attestationsPath,
|
|
153
|
-
algorithm: config.settings.algorithm,
|
|
154
153
|
...config.settings.defaultCommand !== void 0 && {
|
|
155
154
|
defaultCommand: config.settings.defaultCommand
|
|
156
155
|
}
|
|
@@ -467,7 +466,7 @@ function createAttestation(params) {
|
|
|
467
466
|
return attestation;
|
|
468
467
|
}
|
|
469
468
|
async function writeSignedAttestations(options) {
|
|
470
|
-
const { sign: sign2 } = await import('./crypto-
|
|
469
|
+
const { sign: sign2 } = await import('./crypto-VAXWUGKL.js');
|
|
471
470
|
const canonical = canonicalizeAttestations(options.attestations);
|
|
472
471
|
const signature = await sign2({
|
|
473
472
|
privateKeyPath: options.privateKeyPath,
|
|
@@ -476,7 +475,7 @@ async function writeSignedAttestations(options) {
|
|
|
476
475
|
await writeAttestations(options.filePath, options.attestations, signature);
|
|
477
476
|
}
|
|
478
477
|
async function readAndVerifyAttestations(options) {
|
|
479
|
-
const { verify: verify2 } = await import('./crypto-
|
|
478
|
+
const { verify: verify2 } = await import('./crypto-VAXWUGKL.js');
|
|
480
479
|
const file = await readAttestations(options.filePath);
|
|
481
480
|
if (!file) {
|
|
482
481
|
throw new Error(`Attestations file not found: ${options.filePath}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/fingerprint.ts","../src/attestation.ts","../src/verify.ts","../src/index.ts"],"names":["parseYaml","resolve","hash","z","fs2","path2","sign","verify","fs3","path3"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration loading and validation for attest-it.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport { parse as parseYaml } from 'yaml'\nimport { z } from 'zod'\n\n/**\n * Zod schema for settings with defaults applied.\n */\nconst settingsSchema = z\n .object({\n maxAgeDays: z.number().int().positive().default(30),\n publicKeyPath: z.string().default('.attest-it/pubkey.pem'),\n attestationsPath: z.string().default('.attest-it/attestations.json'),\n defaultCommand: z.string().optional(),\n algorithm: z.enum(['ed25519', 'rsa']).default('ed25519'),\n })\n .strict()\n\n/**\n * Zod schema for a suite configuration.\n */\nconst suiteSchema = z\n .object({\n description: z.string().optional(),\n packages: z\n .array(z.string().min(1, 'Package path cannot be empty'))\n .min(1, 'At least one package pattern is required'),\n files: z.array(z.string().min(1, 'File path cannot be empty')).optional(),\n ignore: z.array(z.string().min(1, 'Ignore pattern cannot be empty')).optional(),\n command: z.string().optional(),\n invalidates: z.array(z.string().min(1, 'Invalidated suite name cannot be empty')).optional(),\n })\n .strict()\n\n/**\n * Zod schema for the full configuration file.\n */\nconst configSchema = z\n .object({\n version: z.literal(1),\n settings: settingsSchema.default({}),\n suites: z.record(z.string(), suiteSchema).refine((suites) => Object.keys(suites).length >= 1, {\n message: 'At least one suite must be defined',\n }),\n })\n .strict()\n\n/**\n * Type inference from Zod schema (should match AttestItConfig).\n * This is the same as AttestItConfig but with defaults applied.\n * @public\n */\nexport type Config = z.infer<typeof configSchema>\n\n/**\n * Error thrown when configuration is invalid.\n * @public\n */\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly issues: z.ZodIssue[],\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Error thrown when configuration file cannot be found.\n * @public\n */\nexport class ConfigNotFoundError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigNotFoundError'\n }\n}\n\n/**\n * Parse configuration content from a string.\n *\n * @param content - The configuration file content\n * @param format - The format of the content ('yaml' or 'json')\n * @returns Parsed and validated configuration\n * @throws {ConfigValidationError} If validation fails\n */\nfunction parseConfigContent(content: string, format: 'yaml' | 'json'): Config {\n let rawConfig: unknown\n\n try {\n if (format === 'yaml') {\n rawConfig = parseYaml(content)\n } else {\n rawConfig = JSON.parse(content)\n }\n } catch (error) {\n throw new ConfigValidationError(\n `Failed to parse ${format.toUpperCase()}: ${error instanceof Error ? error.message : String(error)}`,\n [],\n )\n }\n\n const result = configSchema.safeParse(rawConfig)\n\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed:\\n' +\n result.error.issues\n .map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n'),\n result.error.issues,\n )\n }\n\n return result.data\n}\n\n/**\n * Determine the format of a config file from its extension.\n *\n * @param filePath - Path to the config file\n * @returns 'yaml' or 'json'\n */\nfunction getConfigFormat(filePath: string): 'yaml' | 'json' {\n const ext = filePath.toLowerCase()\n if (ext.endsWith('.yaml') || ext.endsWith('.yml')) {\n return 'yaml'\n }\n if (ext.endsWith('.json')) {\n return 'json'\n }\n // Default to yaml for extensionless files\n return 'yaml'\n}\n\n/**\n * Find the configuration file in default locations.\n *\n * Searches in this order:\n * 1. .attest-it/config.yaml\n * 2. .attest-it/config.yml\n * 3. .attest-it/config.json\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to the config file, or null if not found\n * @public\n */\nexport function findConfigPath(startDir: string = process.cwd()): string | null {\n const configDir = join(startDir, '.attest-it')\n const candidates = ['config.yaml', 'config.yml', 'config.json']\n\n for (const candidate of candidates) {\n const configPath = join(configDir, candidate)\n try {\n readFileSync(configPath, 'utf8')\n return configPath\n } catch {\n // File doesn't exist or can't be read, try next candidate\n continue\n }\n }\n\n return null\n}\n\n/**\n * Load and validate configuration from a file (async).\n *\n * @param configPath - Optional path to config file. If not provided, searches default locations.\n * @returns Validated configuration object\n * @throws {@link ConfigNotFoundError} If config file cannot be found\n * @throws {@link ConfigValidationError} If validation fails\n * @public\n */\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const resolvedPath = configPath ?? findConfigPath()\n\n if (!resolvedPath) {\n throw new ConfigNotFoundError(\n 'Configuration file not found. Expected .attest-it/config.yaml, .attest-it/config.yml, or .attest-it/config.json',\n )\n }\n\n try {\n const content = await readFile(resolvedPath, 'utf8')\n const format = getConfigFormat(resolvedPath)\n return parseConfigContent(content, format)\n } catch (error) {\n if (error instanceof ConfigValidationError) {\n throw error\n }\n throw new ConfigNotFoundError(\n `Failed to read configuration file at ${resolvedPath}: ${String(error)}`,\n )\n }\n}\n\n/**\n * Load and validate configuration from a file (sync).\n *\n * @param configPath - Optional path to config file. If not provided, searches default locations.\n * @returns Validated configuration object\n * @throws {@link ConfigNotFoundError} If config file cannot be found\n * @throws {@link ConfigValidationError} If validation fails\n * @public\n */\nexport function loadConfigSync(configPath?: string): Config {\n const resolvedPath = configPath ?? findConfigPath()\n\n if (!resolvedPath) {\n throw new ConfigNotFoundError(\n 'Configuration file not found. Expected .attest-it/config.yaml, .attest-it/config.yml, or .attest-it/config.json',\n )\n }\n\n try {\n const content = readFileSync(resolvedPath, 'utf8')\n const format = getConfigFormat(resolvedPath)\n return parseConfigContent(content, format)\n } catch (error) {\n if (error instanceof ConfigValidationError) {\n throw error\n }\n throw new ConfigNotFoundError(\n `Failed to read configuration file at ${resolvedPath}: ${String(error)}`,\n )\n }\n}\n\n/**\n * Resolve relative paths in the configuration against the repository root.\n *\n * This converts relative paths in settings.publicKeyPath and settings.attestationsPath\n * to absolute paths relative to the repository root.\n *\n * @param config - The configuration object\n * @param repoRoot - Absolute path to the repository root\n * @returns Configuration with resolved absolute paths\n * @public\n */\nexport function resolveConfigPaths(config: Config, repoRoot: string): Config {\n return {\n ...config,\n settings: {\n ...config.settings,\n publicKeyPath: resolve(repoRoot, config.settings.publicKeyPath),\n attestationsPath: resolve(repoRoot, config.settings.attestationsPath),\n },\n }\n}\n\n/**\n * Convert Zod-validated Config to AttestItConfig by removing undefined values.\n *\n * The Config type (from Zod) has optional fields as `T | undefined`,\n * while AttestItConfig has optional fields as `T?` (can be absent, not undefined).\n *\n * This adapter removes any undefined values to match the AttestItConfig interface\n * that the core functions expect.\n *\n * @param config - The Zod-validated configuration from loadConfig()\n * @returns Configuration compatible with AttestItConfig\n * @public\n */\nexport function toAttestItConfig(config: Config): import('./types.js').AttestItConfig {\n return {\n version: config.version,\n settings: {\n maxAgeDays: config.settings.maxAgeDays,\n publicKeyPath: config.settings.publicKeyPath,\n attestationsPath: config.settings.attestationsPath,\n algorithm: config.settings.algorithm,\n ...(config.settings.defaultCommand !== undefined && {\n defaultCommand: config.settings.defaultCommand,\n }),\n },\n suites: Object.fromEntries(\n Object.entries(config.suites).map(([name, suite]) => [\n name,\n {\n packages: suite.packages,\n ...(suite.description !== undefined && { description: suite.description }),\n ...(suite.files !== undefined && { files: suite.files }),\n ...(suite.ignore !== undefined && { ignore: suite.ignore }),\n ...(suite.command !== undefined && { command: suite.command }),\n ...(suite.invalidates !== undefined && { invalidates: suite.invalidates }),\n },\n ]),\n ),\n }\n}\n","import * as crypto from 'node:crypto'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { glob, globSync } from 'tinyglobby'\n\n/**\n * Threshold for streaming large files instead of reading into memory.\n * Files larger than this will be hashed via streaming to avoid memory issues.\n */\nconst LARGE_FILE_THRESHOLD = 50 * 1024 * 1024 // 50MB\n\n/**\n * Options for computing a package fingerprint.\n * @public\n */\nexport interface FingerprintOptions {\n /** Package directories to include */\n packages: string[]\n /** Glob patterns to exclude from fingerprint */\n ignore?: string[]\n /** Base directory for resolving paths */\n baseDir?: string\n}\n\n/**\n * Result of computing a package fingerprint.\n * @public\n */\nexport interface FingerprintResult {\n /** The fingerprint in \"sha256:...\" format */\n fingerprint: string\n /** List of files included in fingerprint calculation */\n files: string[]\n /** Number of files processed */\n fileCount: number\n}\n\n/**\n * Internal representation of a file hash for fingerprint computation.\n */\ninterface FileHashInput {\n /** The normalized relative path of the file */\n relativePath: string\n /** The computed hash of the file content */\n hash: Buffer\n}\n\n/**\n * Sort files lexicographically (locale-independent).\n */\nfunction sortFiles(files: string[]): string[] {\n return [...files].sort((a, b) => {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n })\n}\n\n/**\n * Normalize path separators to forward slashes.\n */\nfunction normalizePath(filePath: string): string {\n return filePath.split(path.sep).join('/')\n}\n\n/**\n * Compute final fingerprint from file hashes.\n */\nfunction computeFinalFingerprint(fileHashes: FileHashInput[]): string {\n // Sort by relative path\n const sorted = [...fileHashes].sort((a, b) => {\n if (a.relativePath < b.relativePath) return -1\n if (a.relativePath > b.relativePath) return 1\n return 0\n })\n\n // Concatenate all file hashes\n const hashes = sorted.map((input) => input.hash)\n const concatenated = Buffer.concat(hashes)\n\n // Compute final hash\n const finalHash = crypto.createHash('sha256').update(concatenated).digest()\n return `sha256:${finalHash.toString('hex')}`\n}\n\n/**\n * Hash a file's content using streaming for large files (async).\n * For files larger than LARGE_FILE_THRESHOLD, uses streaming to avoid memory issues.\n */\nasync function hashFileAsync(\n realPath: string,\n normalizedPath: string,\n stats: fs.Stats,\n): Promise<Buffer> {\n if (stats.size > LARGE_FILE_THRESHOLD) {\n // Stream large files to avoid memory issues\n return new Promise((resolve, reject) => {\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n\n const stream = fs.createReadStream(realPath)\n stream.on('data', (chunk: string | Buffer) => {\n hash.update(chunk)\n })\n stream.on('end', () => {\n resolve(hash.digest())\n })\n stream.on('error', reject)\n })\n }\n\n // Read small files into memory (faster than streaming)\n const content = await fs.promises.readFile(realPath)\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n hash.update(content)\n return hash.digest()\n}\n\n/**\n * Hash a file's content synchronously.\n * Note: Cannot stream synchronously, so large files are read into memory.\n */\nfunction hashFileSync(realPath: string, normalizedPath: string): Buffer {\n const content = fs.readFileSync(realPath)\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n hash.update(content)\n return hash.digest()\n}\n\n/**\n * Validate fingerprint options and return base directory.\n */\nfunction validateOptions(options: FingerprintOptions): string {\n if (options.packages.length === 0) {\n throw new Error('packages array must not be empty')\n }\n\n const baseDir = options.baseDir ?? process.cwd()\n\n // Verify all package paths exist\n for (const pkg of options.packages) {\n const pkgPath = path.resolve(baseDir, pkg)\n if (!fs.existsSync(pkgPath)) {\n throw new Error(`Package path does not exist: ${pkgPath}`)\n }\n }\n\n return baseDir\n}\n\n/**\n * Compute a deterministic fingerprint for a set of packages (async).\n *\n * Algorithm:\n * 1. List all files in packages (respecting ignore globs)\n * 2. Sort files lexicographically by relative path\n * 3. For each file: compute SHA256(relativePath + \"\\0\" + content)\n * 4. Concatenate all file hashes in sorted order\n * 5. Compute final SHA256 of concatenated hashes\n * 6. Return \"sha256:\" + hex(fingerprint)\n *\n * @param options - Configuration for fingerprint computation\n * @returns Result containing the fingerprint hash and list of files processed\n * @throws Error if packages array is empty or if package paths don't exist\n * @public\n */\nexport async function computeFingerprint(options: FingerprintOptions): Promise<FingerprintResult> {\n const baseDir = validateOptions(options)\n\n // List all files in packages\n const files = await listPackageFiles(options.packages, options.ignore, baseDir)\n\n // Sort files lexicographically\n const sortedFiles = sortFiles(files)\n\n // Track visited files to handle multiple symlinks pointing to the same file\n // Key: realpath, Value: file hash\n const fileHashCache = new Map<string, Buffer>()\n\n // Compute individual file hashes\n const fileHashInputs: FileHashInput[] = []\n for (const file of sortedFiles) {\n const filePath = path.resolve(baseDir, file)\n\n // Handle symlinks\n let realPath = filePath\n let stats = await fs.promises.lstat(filePath)\n\n if (stats.isSymbolicLink()) {\n try {\n realPath = await fs.promises.realpath(filePath)\n } catch {\n // Skip broken symlinks\n continue\n }\n\n // Get stats of the target\n try {\n stats = await fs.promises.stat(realPath)\n } catch {\n // Skip broken symlinks\n continue\n }\n }\n\n // Skip if not a file (e.g., directories)\n if (!stats.isFile()) {\n continue\n }\n\n // Normalize path separators to forward slashes\n const normalizedPath = normalizePath(file)\n\n // Check if we've already hashed this file (via symlinks)\n let hash: Buffer\n const cachedHash = fileHashCache.get(realPath)\n if (cachedHash !== undefined) {\n // Reuse cached hash for files we've already seen (via symlinks)\n hash = cachedHash\n } else {\n // Hash the file content\n hash = await hashFileAsync(realPath, normalizedPath, stats)\n fileHashCache.set(realPath, hash)\n }\n\n fileHashInputs.push({ relativePath: normalizedPath, hash })\n }\n\n // Compute final fingerprint\n const fingerprint = computeFinalFingerprint(fileHashInputs)\n\n return {\n fingerprint,\n files: sortedFiles,\n fileCount: sortedFiles.length,\n }\n}\n\n/**\n * Compute a deterministic fingerprint for a set of packages (sync).\n *\n * @param options - Configuration for fingerprint computation\n * @returns Result containing the fingerprint hash and list of files processed\n * @throws Error if packages array is empty or if package paths don't exist\n * @public\n * @see {@link computeFingerprint} for the async version\n */\nexport function computeFingerprintSync(options: FingerprintOptions): FingerprintResult {\n const baseDir = validateOptions(options)\n\n // List all files in packages (sync version)\n const files = listPackageFilesSync(options.packages, options.ignore, baseDir)\n\n // Sort files lexicographically\n const sortedFiles = sortFiles(files)\n\n // Track visited files to handle multiple symlinks pointing to the same file\n // Key: realpath, Value: file hash\n const fileHashCache = new Map<string, Buffer>()\n\n // Compute individual file hashes\n const fileHashInputs: FileHashInput[] = []\n for (const file of sortedFiles) {\n const filePath = path.resolve(baseDir, file)\n\n // Handle symlinks\n let realPath = filePath\n let stats = fs.lstatSync(filePath)\n\n if (stats.isSymbolicLink()) {\n try {\n realPath = fs.realpathSync(filePath)\n } catch {\n // Skip broken symlinks\n continue\n }\n\n // Get stats of the target\n try {\n stats = fs.statSync(realPath)\n } catch {\n // Skip broken symlinks\n continue\n }\n }\n\n // Skip if not a file (e.g., directories)\n if (!stats.isFile()) {\n continue\n }\n\n // Normalize path separators to forward slashes\n const normalizedPath = normalizePath(file)\n\n // Check if we've already hashed this file (via symlinks)\n let hash: Buffer\n const cachedHash = fileHashCache.get(realPath)\n if (cachedHash !== undefined) {\n // Reuse cached hash for files we've already seen (via symlinks)\n hash = cachedHash\n } else {\n // Hash the file content (sync version cannot stream)\n hash = hashFileSync(realPath, normalizedPath)\n fileHashCache.set(realPath, hash)\n }\n\n fileHashInputs.push({ relativePath: normalizedPath, hash })\n }\n\n // Compute final fingerprint\n const fingerprint = computeFinalFingerprint(fileHashInputs)\n\n return {\n fingerprint,\n files: sortedFiles,\n fileCount: sortedFiles.length,\n }\n}\n\n/**\n * List files in packages, respecting ignore patterns (async).\n *\n * @param packages - Array of package directory paths\n * @param ignore - Optional glob patterns to exclude\n * @param baseDir - Base directory for resolving paths (defaults to cwd)\n * @returns Array of relative file paths\n * @public\n */\nexport async function listPackageFiles(\n packages: string[],\n ignore: string[] = [],\n baseDir: string = process.cwd(),\n): Promise<string[]> {\n const allFiles: string[] = []\n\n for (const pkg of packages) {\n // Build glob patterns for this package\n const patterns = [`${pkg}/**/*`]\n\n // Use tinyglobby to find files\n const files = await glob(patterns, {\n cwd: baseDir,\n ignore,\n onlyFiles: true,\n dot: true, // Include dotfiles\n absolute: false, // Return relative paths\n })\n\n allFiles.push(...files)\n }\n\n return allFiles\n}\n\n/**\n * Synchronous version of listPackageFiles\n */\nfunction listPackageFilesSync(\n packages: string[],\n ignore: string[] = [],\n baseDir: string = process.cwd(),\n): string[] {\n const allFiles: string[] = []\n\n for (const pkg of packages) {\n // Build glob patterns for this package\n const patterns = [`${pkg}/**/*`]\n\n // Use tinyglobby to find files (sync version)\n const files = globSync(patterns, {\n cwd: baseDir,\n ignore,\n onlyFiles: true,\n dot: true, // Include dotfiles\n absolute: false, // Return relative paths\n })\n\n allFiles.push(...files)\n }\n\n return allFiles\n}\n","/**\n * Attestation file I/O module with JSON canonicalization.\n */\n\nimport * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport * as canonicalizeNamespace from 'canonicalize'\nimport { z } from 'zod'\nimport type { Attestation, AttestationsFile } from './types.js'\n\n/**\n * Extract the serialize function from the canonicalize CommonJS module.\n *\n * Context: The canonicalize package uses `module.exports = function(...)`, which is\n * a CommonJS pattern. With `esModuleInterop: false` (required for library code to\n * avoid leaking tsconfig options to consumers), TypeScript treats the namespace import\n * as the module object itself, not as an object with a default export.\n *\n * The package's type definitions declare `export default function serialize(...)`,\n * which TypeScript interprets as `{ default: function }` when esModuleInterop is off.\n * However, at runtime with NodeNext module resolution, the actual value is just the\n * function itself.\n *\n * This type assertion is safe because:\n * 1. We verified the runtime export structure of the canonicalize module\n * 2. The type matches the published @types signature\n * 3. This is the standard pattern for importing CommonJS modules in strict ESM\n */\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\nconst canonicalize = canonicalizeNamespace as unknown as {\n default: (input: unknown) => string | undefined\n}\nconst serialize = canonicalize.default\n\n// Zod schema for attestation validation\nconst attestationSchema = z.object({\n suite: z.string().min(1),\n fingerprint: z.string().regex(/^sha256:[a-f0-9]{64}$/),\n attestedAt: z.string().datetime(),\n attestedBy: z.string().min(1),\n command: z.string().min(1),\n exitCode: z.literal(0),\n})\n\nconst attestationsFileSchema = z.object({\n schemaVersion: z.literal('1'),\n attestations: z.array(attestationSchema),\n signature: z.string(), // Will be validated by crypto module\n})\n\n/**\n * Type guard to check if an error is a Node.js file system error with code.\n * We need to disable the type assertion rule here because TypeScript doesn't\n * provide a way to narrow an object type after checking for a property without\n * using type assertions or indexed access. This is a safe assertion because we\n * check for the property existence and type before using it.\n */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n if (error === null || typeof error !== 'object') {\n return false\n }\n if (!('code' in error)) {\n return false\n }\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const errorObj = error as Record<string, unknown>\n return typeof errorObj.code === 'string'\n}\n\n/**\n * Read attestations file from disk (async).\n *\n * @param filePath - Absolute path to the attestations JSON file\n * @returns Parsed attestations file, or null if the file doesn't exist\n * @throws Error on parse or validation errors\n * @public\n */\nexport async function readAttestations(filePath: string): Promise<AttestationsFile | null> {\n try {\n const content = await fs.promises.readFile(filePath, 'utf-8')\n const parsed: unknown = JSON.parse(content)\n // Zod validates and returns the correct type\n return attestationsFileSchema.parse(parsed)\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return null\n }\n throw error\n }\n}\n\n/**\n * Read attestations file from disk (sync).\n *\n * @param filePath - Absolute path to the attestations JSON file\n * @returns Parsed attestations file, or null if the file doesn't exist\n * @throws Error on parse or validation errors\n * @public\n */\nexport function readAttestationsSync(filePath: string): AttestationsFile | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const parsed: unknown = JSON.parse(content)\n // Zod validates and returns the correct type\n return attestationsFileSchema.parse(parsed)\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return null\n }\n throw error\n }\n}\n\n/**\n * Write attestations file to disk (async).\n *\n * Creates parent directories if needed. The signature should be computed\n * separately and passed in.\n *\n * @param filePath - Absolute path to write the attestations file\n * @param attestations - Array of attestation entries\n * @param signature - Cryptographic signature of the attestations\n * @throws Error on validation or write errors\n * @public\n */\nexport async function writeAttestations(\n filePath: string,\n attestations: Attestation[],\n signature: string,\n): Promise<void> {\n const fileContent: AttestationsFile = {\n schemaVersion: '1',\n attestations,\n signature,\n }\n\n // Validate before writing\n attestationsFileSchema.parse(fileContent)\n\n // Create parent directories if needed\n const dir = path.dirname(filePath)\n await fs.promises.mkdir(dir, { recursive: true })\n\n // Write with pretty formatting for readability\n const json = JSON.stringify(fileContent, null, 2)\n await fs.promises.writeFile(filePath, json, 'utf-8')\n}\n\n/**\n * Write attestations file to disk (sync).\n *\n * Creates parent directories if needed. The signature should be computed\n * separately and passed in.\n *\n * @param filePath - Absolute path to write the attestations file\n * @param attestations - Array of attestation entries\n * @param signature - Cryptographic signature of the attestations\n * @throws Error on validation or write errors\n * @public\n */\nexport function writeAttestationsSync(\n filePath: string,\n attestations: Attestation[],\n signature: string,\n): void {\n const fileContent: AttestationsFile = {\n schemaVersion: '1',\n attestations,\n signature,\n }\n\n // Validate before writing\n attestationsFileSchema.parse(fileContent)\n\n // Create parent directories if needed\n const dir = path.dirname(filePath)\n fs.mkdirSync(dir, { recursive: true })\n\n // Write with pretty formatting for readability\n const json = JSON.stringify(fileContent, null, 2)\n fs.writeFileSync(filePath, json, 'utf-8')\n}\n\n/**\n * Find an attestation for a specific suite.\n *\n * @param attestations - Attestations file containing all attestations\n * @param suite - Name of the suite to find\n * @returns The attestation if found, undefined otherwise\n * @public\n */\nexport function findAttestation(\n attestations: AttestationsFile,\n suite: string,\n): Attestation | undefined {\n return attestations.attestations.find((a) => a.suite === suite)\n}\n\n/**\n * Add or update an attestation for a suite.\n *\n * This is an immutable operation that returns a new array.\n *\n * @param attestations - Current array of attestations\n * @param newAttestation - Attestation to add or update\n * @returns New attestations array with the upserted attestation\n * @throws Error if the new attestation fails validation\n * @public\n */\nexport function upsertAttestation(\n attestations: Attestation[],\n newAttestation: Attestation,\n): Attestation[] {\n // Validate the new attestation\n attestationSchema.parse(newAttestation)\n\n const existingIndex = attestations.findIndex((a) => a.suite === newAttestation.suite)\n\n if (existingIndex === -1) {\n // Add new attestation\n return [...attestations, newAttestation]\n } else {\n // Update existing attestation\n const updated = [...attestations]\n // eslint-disable-next-line security/detect-object-injection -- False positive: existingIndex is a safe number from findIndex\n updated[existingIndex] = newAttestation\n return updated\n }\n}\n\n/**\n * Remove attestations for a suite.\n *\n * This is an immutable operation that returns a new array.\n *\n * @param attestations - Current array of attestations\n * @param suite - Name of the suite to remove\n * @returns New attestations array without the specified suite\n * @public\n */\nexport function removeAttestation(attestations: Attestation[], suite: string): Attestation[] {\n return attestations.filter((a) => a.suite !== suite)\n}\n\n/**\n * Compute canonical JSON representation for signing.\n *\n * Implements RFC 8785 JSON Canonicalization Scheme. The canonicalize package provides:\n * 1. Keys sorted lexicographically (Unicode code point order)\n * 2. No whitespace between tokens\n * 3. No trailing commas\n * 4. Strings escaped using \\uXXXX for control characters\n * 5. Numbers: no leading zeros, no +, use lowercase 'e' for exponent\n * 6. UTF-8 encoding\n *\n * @param attestations - Array of attestations to canonicalize\n * @returns Canonical JSON string representation\n * @throws Error if canonicalization fails\n * @public\n */\nexport function canonicalizeAttestations(attestations: Attestation[]): string {\n const canonical = serialize(attestations)\n if (canonical === undefined) {\n throw new Error('Failed to canonicalize attestations')\n }\n return canonical\n}\n\n/**\n * Create a new attestation entry.\n *\n * @param params - Parameters for creating the attestation\n * @param params.suite - Name of the test suite\n * @param params.fingerprint - Fingerprint of the packages in sha256 format\n * @param params.command - Command that was executed\n * @param params.attestedBy - Optional username (defaults to current OS user)\n * @returns Validated attestation object\n * @throws Error if attestation validation fails\n * @public\n */\nexport function createAttestation(params: {\n suite: string\n fingerprint: string\n command: string\n attestedBy?: string\n}): Attestation {\n const attestation: Attestation = {\n suite: params.suite,\n fingerprint: params.fingerprint,\n attestedAt: new Date().toISOString(),\n attestedBy: params.attestedBy ?? os.userInfo().username,\n command: params.command,\n exitCode: 0,\n }\n\n // Validate before returning\n attestationSchema.parse(attestation)\n\n return attestation\n}\n\n/**\n * Options for writing signed attestations.\n * @public\n */\nexport interface WriteSignedAttestationsOptions {\n /** Path to write the attestations file */\n filePath: string\n /** Array of attestations to write */\n attestations: Attestation[]\n /** Path to the private key for signing */\n privateKeyPath: string\n}\n\n/**\n * Options for reading and verifying signed attestations.\n * @public\n */\nexport interface ReadSignedAttestationsOptions {\n /** Path to read the attestations file from */\n filePath: string\n /** Path to the public key for verification */\n publicKeyPath: string\n}\n\n/**\n * Write attestations with a cryptographic signature.\n *\n * This function canonicalizes the attestations, signs them with the private key,\n * and writes the attestations file with the signature.\n *\n * @param options - Options for writing signed attestations\n * @throws Error if signing or writing fails\n * @public\n */\nexport async function writeSignedAttestations(\n options: WriteSignedAttestationsOptions,\n): Promise<void> {\n // Import sign function here to avoid circular dependency\n const { sign } = await import('./crypto.js')\n\n const canonical = canonicalizeAttestations(options.attestations)\n const signature = await sign({\n privateKeyPath: options.privateKeyPath,\n data: canonical,\n })\n await writeAttestations(options.filePath, options.attestations, signature)\n}\n\n/**\n * Read attestations and verify the signature.\n *\n * This function reads the attestations file, canonicalizes the attestations,\n * and verifies the signature using the public key. It throws an error if the\n * file doesn't exist or if signature verification fails.\n *\n * @param options - Options for reading and verifying attestations\n * @returns The attestations file if signature is valid\n * @throws Error if attestations file not found\n * @throws SignatureInvalidError if signature verification fails\n * @public\n */\nexport async function readAndVerifyAttestations(\n options: ReadSignedAttestationsOptions,\n): Promise<AttestationsFile> {\n // Import verify function here to avoid circular dependency\n const { verify } = await import('./crypto.js')\n\n const file = await readAttestations(options.filePath)\n if (!file) {\n throw new Error(`Attestations file not found: ${options.filePath}`)\n }\n\n const canonical = canonicalizeAttestations(file.attestations)\n const isValid = await verify({\n publicKeyPath: options.publicKeyPath,\n data: canonical,\n signature: file.signature,\n })\n\n if (!isValid) {\n throw new SignatureInvalidError(options.filePath)\n }\n\n return file\n}\n\n/**\n * Error thrown when signature verification fails.\n * @public\n */\nexport class SignatureInvalidError extends Error {\n /**\n * Create a new SignatureInvalidError.\n * @param filePath - Path to the file that failed verification\n */\n constructor(filePath: string) {\n super(`Signature verification failed for: ${filePath}`)\n this.name = 'SignatureInvalidError'\n }\n}\n","/**\n * Verification logic for attestations.\n * @packageDocumentation\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n AttestItConfig,\n Attestation,\n AttestationsFile,\n SuiteVerificationResult,\n} from './types.js'\nimport { computeFingerprint } from './fingerprint.js'\nimport { readAndVerifyAttestations, SignatureInvalidError } from './attestation.js'\n\n/**\n * Options for verifying attestations.\n * @public\n */\nexport interface VerifyOptions {\n /** Configuration object */\n config: AttestItConfig\n /** Repository root directory (defaults to process.cwd()) */\n repoRoot?: string\n}\n\n/**\n * Result of verifying all attestations.\n * @public\n */\nexport interface VerifyResult {\n /** Overall success - true if all attestations are valid */\n success: boolean\n /** Whether the attestations file signature is valid */\n signatureValid: boolean\n /** Verification results for each suite */\n suites: SuiteVerificationResult[]\n /** Error messages encountered during verification */\n errors: string[]\n}\n\n/**\n * Verify all attestations against current code state.\n *\n * Verification algorithm:\n * 1. Load and verify attestations file signature\n * 2. For each suite in config:\n * a. Compute current fingerprint\n * b. Find matching attestation\n * c. Compare fingerprints\n * d. Check age\n * 3. Check invalidation chains\n * 4. Return aggregated results\n *\n * @param options - Verification options\n * @returns Verification result with status for each suite\n * @public\n */\nexport async function verifyAttestations(options: VerifyOptions): Promise<VerifyResult> {\n const { config, repoRoot = process.cwd() } = options\n const errors: string[] = []\n const suiteResults: SuiteVerificationResult[] = []\n let signatureValid = true\n let attestationsFile: AttestationsFile | null = null\n\n // Resolve paths\n const attestationsPath = resolvePath(config.settings.attestationsPath, repoRoot)\n const publicKeyPath = resolvePath(config.settings.publicKeyPath, repoRoot)\n\n // Step 1: Load and verify attestations\n try {\n if (!fs.existsSync(attestationsPath)) {\n // No attestations file - all suites need attestation\n attestationsFile = null\n } else if (!fs.existsSync(publicKeyPath)) {\n errors.push(`Public key not found: ${publicKeyPath}`)\n signatureValid = false\n } else {\n attestationsFile = await readAndVerifyAttestations({\n filePath: attestationsPath,\n publicKeyPath,\n })\n }\n } catch (err) {\n if (err instanceof SignatureInvalidError) {\n signatureValid = false\n errors.push(err.message)\n } else if (err instanceof Error) {\n errors.push(err.message)\n }\n }\n\n const attestations = attestationsFile?.attestations ?? []\n\n // Step 2: Check each suite\n for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {\n const result = await verifySuite({\n suiteName,\n suiteConfig,\n attestations,\n maxAgeDays: config.settings.maxAgeDays,\n repoRoot,\n })\n suiteResults.push(result)\n }\n\n // Step 3: Check invalidation chains\n checkInvalidationChains(config, suiteResults)\n\n // Step 4: Aggregate results\n const allValid =\n signatureValid && suiteResults.every((r) => r.status === 'VALID') && errors.length === 0\n\n return {\n success: allValid,\n signatureValid,\n suites: suiteResults,\n errors,\n }\n}\n\n/**\n * Options for verifying a single suite.\n * @internal\n */\ninterface VerifySuiteOptions {\n /** Name of the suite */\n suiteName: string\n /** Suite configuration */\n suiteConfig: { packages: string[]; ignore?: string[] }\n /** All attestations from the attestations file */\n attestations: Attestation[]\n /** Maximum age in days before attestation expires */\n maxAgeDays: number\n /** Repository root directory */\n repoRoot: string\n}\n\n/**\n * Verify a single suite's attestation.\n * @internal\n */\nasync function verifySuite(options: VerifySuiteOptions): Promise<SuiteVerificationResult> {\n const { suiteName, suiteConfig, attestations, maxAgeDays, repoRoot } = options\n\n // Compute current fingerprint\n const fingerprintOptions = {\n packages: suiteConfig.packages.map((p) => resolvePath(p, repoRoot)),\n baseDir: repoRoot,\n ...(suiteConfig.ignore && { ignore: suiteConfig.ignore }),\n }\n const fingerprintResult = await computeFingerprint(fingerprintOptions)\n\n // Find attestation for this suite\n const attestation = attestations.find((a) => a.suite === suiteName)\n\n // No attestation found\n if (!attestation) {\n return {\n suite: suiteName,\n status: 'NEEDS_ATTESTATION',\n fingerprint: fingerprintResult.fingerprint,\n message: 'No attestation found for this suite',\n }\n }\n\n // Check fingerprint\n if (attestation.fingerprint !== fingerprintResult.fingerprint) {\n return {\n suite: suiteName,\n status: 'FINGERPRINT_CHANGED',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n message: `Fingerprint changed from ${attestation.fingerprint.slice(0, 20)}... to ${fingerprintResult.fingerprint.slice(0, 20)}...`,\n }\n }\n\n // Check age\n const attestedAt = new Date(attestation.attestedAt)\n const ageMs = Date.now() - attestedAt.getTime()\n const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24))\n\n if (ageDays > maxAgeDays) {\n return {\n suite: suiteName,\n status: 'EXPIRED',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n age: ageDays,\n message: `Attestation expired (${String(ageDays)} days old, max ${String(maxAgeDays)} days)`,\n }\n }\n\n // All checks passed\n return {\n suite: suiteName,\n status: 'VALID',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n age: ageDays,\n }\n}\n\n/**\n * Check invalidation chains.\n *\n * If suite A invalidates suite B, and A's attestation is newer than B's,\n * then B should be marked as INVALIDATED_BY_PARENT.\n *\n * @param config - Full configuration\n * @param results - Array of suite verification results to mutate\n * @internal\n */\nfunction checkInvalidationChains(config: AttestItConfig, results: SuiteVerificationResult[]): void {\n for (const [parentName, parentConfig] of Object.entries(config.suites)) {\n const invalidates = parentConfig.invalidates ?? []\n const parentResult = results.find((r) => r.suite === parentName)\n\n if (!parentResult?.attestation) continue\n\n const parentTime = new Date(parentResult.attestation.attestedAt).getTime()\n\n for (const childName of invalidates) {\n const childResult = results.find((r) => r.suite === childName)\n if (!childResult?.attestation) continue\n\n const childTime = new Date(childResult.attestation.attestedAt).getTime()\n\n // If parent was attested AFTER child, child is invalidated\n if (parentTime > childTime && childResult.status === 'VALID') {\n childResult.status = 'INVALIDATED_BY_PARENT'\n childResult.message = `Invalidated by ${parentName} (attested later)`\n }\n }\n }\n}\n\n/**\n * Resolve a path relative to a base directory.\n * @param relativePath - Path that may be relative or absolute\n * @param baseDir - Base directory for resolving relative paths\n * @returns Absolute path\n * @internal\n */\nfunction resolvePath(relativePath: string, baseDir: string): string {\n if (path.isAbsolute(relativePath)) {\n return relativePath\n }\n return path.join(baseDir, relativePath)\n}\n","/**\n * @attest-it/core\n *\n * Core functionality for the attest-it testing framework.\n * @packageDocumentation\n */\n\n/**\n * Package version\n * @public\n */\nexport const version = '0.0.0'\n\n// Types\nexport type {\n AttestItSettings,\n SuiteConfig,\n AttestItConfig,\n Attestation,\n AttestationsFile,\n VerificationStatus,\n SuiteVerificationResult,\n} from './types.js'\n\n// Config\nexport {\n loadConfig,\n loadConfigSync,\n findConfigPath,\n resolveConfigPaths,\n toAttestItConfig,\n ConfigValidationError,\n ConfigNotFoundError,\n type Config,\n} from './config.js'\n\n// Fingerprinting\nexport { computeFingerprint, computeFingerprintSync, listPackageFiles } from './fingerprint.js'\nexport type { FingerprintOptions, FingerprintResult } from './fingerprint.js'\n\n// Attestations\nexport {\n readAttestations,\n readAttestationsSync,\n writeAttestations,\n writeAttestationsSync,\n findAttestation,\n upsertAttestation,\n removeAttestation,\n canonicalizeAttestations,\n createAttestation,\n writeSignedAttestations,\n readAndVerifyAttestations,\n SignatureInvalidError,\n} from './attestation.js'\nexport type {\n WriteSignedAttestationsOptions,\n ReadSignedAttestationsOptions,\n} from './attestation.js'\n\n// Cryptography\nexport {\n checkOpenSSL,\n getDefaultPrivateKeyPath,\n getDefaultPublicKeyPath,\n generateKeyPair,\n sign,\n verify,\n setKeyPermissions,\n} from './crypto.js'\nexport type {\n Algorithm,\n KeyPaths,\n KeygenOptions,\n SignOptions,\n VerifyOptions as CryptoVerifyOptions,\n} from './crypto.js'\n\n// Verification\nexport { verifyAttestations } from './verify.js'\nexport type { VerifyOptions, VerifyResult } from './verify.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/fingerprint.ts","../src/attestation.ts","../src/verify.ts","../src/index.ts"],"names":["parseYaml","resolve","hash","z","fs2","path2","sign","verify","fs3","path3"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration loading and validation for attest-it.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport { parse as parseYaml } from 'yaml'\nimport { z } from 'zod'\n\n/**\n * Zod schema for settings with defaults applied.\n */\nconst settingsSchema = z\n .object({\n maxAgeDays: z.number().int().positive().default(30),\n publicKeyPath: z.string().default('.attest-it/pubkey.pem'),\n attestationsPath: z.string().default('.attest-it/attestations.json'),\n defaultCommand: z.string().optional(),\n // Note: algorithm field was removed - RSA is the only supported algorithm\n })\n .passthrough()\n\n/**\n * Zod schema for a suite configuration.\n */\nconst suiteSchema = z\n .object({\n description: z.string().optional(),\n packages: z\n .array(z.string().min(1, 'Package path cannot be empty'))\n .min(1, 'At least one package pattern is required'),\n files: z.array(z.string().min(1, 'File path cannot be empty')).optional(),\n ignore: z.array(z.string().min(1, 'Ignore pattern cannot be empty')).optional(),\n command: z.string().optional(),\n invalidates: z.array(z.string().min(1, 'Invalidated suite name cannot be empty')).optional(),\n })\n .strict()\n\n/**\n * Zod schema for the full configuration file.\n */\nconst configSchema = z\n .object({\n version: z.literal(1),\n settings: settingsSchema.default({}),\n suites: z.record(z.string(), suiteSchema).refine((suites) => Object.keys(suites).length >= 1, {\n message: 'At least one suite must be defined',\n }),\n })\n .strict()\n\n/**\n * Type inference from Zod schema (should match AttestItConfig).\n * This is the same as AttestItConfig but with defaults applied.\n * @public\n */\nexport type Config = z.infer<typeof configSchema>\n\n/**\n * Error thrown when configuration is invalid.\n * @public\n */\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly issues: z.ZodIssue[],\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Error thrown when configuration file cannot be found.\n * @public\n */\nexport class ConfigNotFoundError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigNotFoundError'\n }\n}\n\n/**\n * Parse configuration content from a string.\n *\n * @param content - The configuration file content\n * @param format - The format of the content ('yaml' or 'json')\n * @returns Parsed and validated configuration\n * @throws {ConfigValidationError} If validation fails\n */\nfunction parseConfigContent(content: string, format: 'yaml' | 'json'): Config {\n let rawConfig: unknown\n\n try {\n if (format === 'yaml') {\n rawConfig = parseYaml(content)\n } else {\n rawConfig = JSON.parse(content)\n }\n } catch (error) {\n throw new ConfigValidationError(\n `Failed to parse ${format.toUpperCase()}: ${error instanceof Error ? error.message : String(error)}`,\n [],\n )\n }\n\n const result = configSchema.safeParse(rawConfig)\n\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed:\\n' +\n result.error.issues\n .map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n'),\n result.error.issues,\n )\n }\n\n return result.data\n}\n\n/**\n * Determine the format of a config file from its extension.\n *\n * @param filePath - Path to the config file\n * @returns 'yaml' or 'json'\n */\nfunction getConfigFormat(filePath: string): 'yaml' | 'json' {\n const ext = filePath.toLowerCase()\n if (ext.endsWith('.yaml') || ext.endsWith('.yml')) {\n return 'yaml'\n }\n if (ext.endsWith('.json')) {\n return 'json'\n }\n // Default to yaml for extensionless files\n return 'yaml'\n}\n\n/**\n * Find the configuration file in default locations.\n *\n * Searches in this order:\n * 1. .attest-it/config.yaml\n * 2. .attest-it/config.yml\n * 3. .attest-it/config.json\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to the config file, or null if not found\n * @public\n */\nexport function findConfigPath(startDir: string = process.cwd()): string | null {\n const configDir = join(startDir, '.attest-it')\n const candidates = ['config.yaml', 'config.yml', 'config.json']\n\n for (const candidate of candidates) {\n const configPath = join(configDir, candidate)\n try {\n readFileSync(configPath, 'utf8')\n return configPath\n } catch {\n // File doesn't exist or can't be read, try next candidate\n continue\n }\n }\n\n return null\n}\n\n/**\n * Load and validate configuration from a file (async).\n *\n * @param configPath - Optional path to config file. If not provided, searches default locations.\n * @returns Validated configuration object\n * @throws {@link ConfigNotFoundError} If config file cannot be found\n * @throws {@link ConfigValidationError} If validation fails\n * @public\n */\nexport async function loadConfig(configPath?: string): Promise<Config> {\n const resolvedPath = configPath ?? findConfigPath()\n\n if (!resolvedPath) {\n throw new ConfigNotFoundError(\n 'Configuration file not found. Expected .attest-it/config.yaml, .attest-it/config.yml, or .attest-it/config.json',\n )\n }\n\n try {\n const content = await readFile(resolvedPath, 'utf8')\n const format = getConfigFormat(resolvedPath)\n return parseConfigContent(content, format)\n } catch (error) {\n if (error instanceof ConfigValidationError) {\n throw error\n }\n throw new ConfigNotFoundError(\n `Failed to read configuration file at ${resolvedPath}: ${String(error)}`,\n )\n }\n}\n\n/**\n * Load and validate configuration from a file (sync).\n *\n * @param configPath - Optional path to config file. If not provided, searches default locations.\n * @returns Validated configuration object\n * @throws {@link ConfigNotFoundError} If config file cannot be found\n * @throws {@link ConfigValidationError} If validation fails\n * @public\n */\nexport function loadConfigSync(configPath?: string): Config {\n const resolvedPath = configPath ?? findConfigPath()\n\n if (!resolvedPath) {\n throw new ConfigNotFoundError(\n 'Configuration file not found. Expected .attest-it/config.yaml, .attest-it/config.yml, or .attest-it/config.json',\n )\n }\n\n try {\n const content = readFileSync(resolvedPath, 'utf8')\n const format = getConfigFormat(resolvedPath)\n return parseConfigContent(content, format)\n } catch (error) {\n if (error instanceof ConfigValidationError) {\n throw error\n }\n throw new ConfigNotFoundError(\n `Failed to read configuration file at ${resolvedPath}: ${String(error)}`,\n )\n }\n}\n\n/**\n * Resolve relative paths in the configuration against the repository root.\n *\n * This converts relative paths in settings.publicKeyPath and settings.attestationsPath\n * to absolute paths relative to the repository root.\n *\n * @param config - The configuration object\n * @param repoRoot - Absolute path to the repository root\n * @returns Configuration with resolved absolute paths\n * @public\n */\nexport function resolveConfigPaths(config: Config, repoRoot: string): Config {\n return {\n ...config,\n settings: {\n ...config.settings,\n publicKeyPath: resolve(repoRoot, config.settings.publicKeyPath),\n attestationsPath: resolve(repoRoot, config.settings.attestationsPath),\n },\n }\n}\n\n/**\n * Convert Zod-validated Config to AttestItConfig by removing undefined values.\n *\n * The Config type (from Zod) has optional fields as `T | undefined`,\n * while AttestItConfig has optional fields as `T?` (can be absent, not undefined).\n *\n * This adapter removes any undefined values to match the AttestItConfig interface\n * that the core functions expect.\n *\n * @param config - The Zod-validated configuration from loadConfig()\n * @returns Configuration compatible with AttestItConfig\n * @public\n */\nexport function toAttestItConfig(config: Config): import('./types.js').AttestItConfig {\n return {\n version: config.version,\n settings: {\n maxAgeDays: config.settings.maxAgeDays,\n publicKeyPath: config.settings.publicKeyPath,\n attestationsPath: config.settings.attestationsPath,\n ...(config.settings.defaultCommand !== undefined && {\n defaultCommand: config.settings.defaultCommand,\n }),\n },\n suites: Object.fromEntries(\n Object.entries(config.suites).map(([name, suite]) => [\n name,\n {\n packages: suite.packages,\n ...(suite.description !== undefined && { description: suite.description }),\n ...(suite.files !== undefined && { files: suite.files }),\n ...(suite.ignore !== undefined && { ignore: suite.ignore }),\n ...(suite.command !== undefined && { command: suite.command }),\n ...(suite.invalidates !== undefined && { invalidates: suite.invalidates }),\n },\n ]),\n ),\n }\n}\n","import * as crypto from 'node:crypto'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { glob, globSync } from 'tinyglobby'\n\n/**\n * Threshold for streaming large files instead of reading into memory.\n * Files larger than this will be hashed via streaming to avoid memory issues.\n */\nconst LARGE_FILE_THRESHOLD = 50 * 1024 * 1024 // 50MB\n\n/**\n * Options for computing a package fingerprint.\n * @public\n */\nexport interface FingerprintOptions {\n /** Package directories to include */\n packages: string[]\n /** Glob patterns to exclude from fingerprint */\n ignore?: string[]\n /** Base directory for resolving paths */\n baseDir?: string\n}\n\n/**\n * Result of computing a package fingerprint.\n * @public\n */\nexport interface FingerprintResult {\n /** The fingerprint in \"sha256:...\" format */\n fingerprint: string\n /** List of files included in fingerprint calculation */\n files: string[]\n /** Number of files processed */\n fileCount: number\n}\n\n/**\n * Internal representation of a file hash for fingerprint computation.\n */\ninterface FileHashInput {\n /** The normalized relative path of the file */\n relativePath: string\n /** The computed hash of the file content */\n hash: Buffer\n}\n\n/**\n * Sort files lexicographically (locale-independent).\n */\nfunction sortFiles(files: string[]): string[] {\n return [...files].sort((a, b) => {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n })\n}\n\n/**\n * Normalize path separators to forward slashes.\n */\nfunction normalizePath(filePath: string): string {\n return filePath.split(path.sep).join('/')\n}\n\n/**\n * Compute final fingerprint from file hashes.\n */\nfunction computeFinalFingerprint(fileHashes: FileHashInput[]): string {\n // Sort by relative path\n const sorted = [...fileHashes].sort((a, b) => {\n if (a.relativePath < b.relativePath) return -1\n if (a.relativePath > b.relativePath) return 1\n return 0\n })\n\n // Concatenate all file hashes\n const hashes = sorted.map((input) => input.hash)\n const concatenated = Buffer.concat(hashes)\n\n // Compute final hash\n const finalHash = crypto.createHash('sha256').update(concatenated).digest()\n return `sha256:${finalHash.toString('hex')}`\n}\n\n/**\n * Hash a file's content using streaming for large files (async).\n * For files larger than LARGE_FILE_THRESHOLD, uses streaming to avoid memory issues.\n */\nasync function hashFileAsync(\n realPath: string,\n normalizedPath: string,\n stats: fs.Stats,\n): Promise<Buffer> {\n if (stats.size > LARGE_FILE_THRESHOLD) {\n // Stream large files to avoid memory issues\n return new Promise((resolve, reject) => {\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n\n const stream = fs.createReadStream(realPath)\n stream.on('data', (chunk: string | Buffer) => {\n hash.update(chunk)\n })\n stream.on('end', () => {\n resolve(hash.digest())\n })\n stream.on('error', reject)\n })\n }\n\n // Read small files into memory (faster than streaming)\n const content = await fs.promises.readFile(realPath)\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n hash.update(content)\n return hash.digest()\n}\n\n/**\n * Hash a file's content synchronously.\n * Note: Cannot stream synchronously, so large files are read into memory.\n */\nfunction hashFileSync(realPath: string, normalizedPath: string): Buffer {\n const content = fs.readFileSync(realPath)\n const hash = crypto.createHash('sha256')\n hash.update(normalizedPath)\n hash.update('\\0')\n hash.update(content)\n return hash.digest()\n}\n\n/**\n * Validate fingerprint options and return base directory.\n */\nfunction validateOptions(options: FingerprintOptions): string {\n if (options.packages.length === 0) {\n throw new Error('packages array must not be empty')\n }\n\n const baseDir = options.baseDir ?? process.cwd()\n\n // Verify all package paths exist\n for (const pkg of options.packages) {\n const pkgPath = path.resolve(baseDir, pkg)\n if (!fs.existsSync(pkgPath)) {\n throw new Error(`Package path does not exist: ${pkgPath}`)\n }\n }\n\n return baseDir\n}\n\n/**\n * Compute a deterministic fingerprint for a set of packages (async).\n *\n * Algorithm:\n * 1. List all files in packages (respecting ignore globs)\n * 2. Sort files lexicographically by relative path\n * 3. For each file: compute SHA256(relativePath + \"\\0\" + content)\n * 4. Concatenate all file hashes in sorted order\n * 5. Compute final SHA256 of concatenated hashes\n * 6. Return \"sha256:\" + hex(fingerprint)\n *\n * @param options - Configuration for fingerprint computation\n * @returns Result containing the fingerprint hash and list of files processed\n * @throws Error if packages array is empty or if package paths don't exist\n * @public\n */\nexport async function computeFingerprint(options: FingerprintOptions): Promise<FingerprintResult> {\n const baseDir = validateOptions(options)\n\n // List all files in packages\n const files = await listPackageFiles(options.packages, options.ignore, baseDir)\n\n // Sort files lexicographically\n const sortedFiles = sortFiles(files)\n\n // Track visited files to handle multiple symlinks pointing to the same file\n // Key: realpath, Value: file hash\n const fileHashCache = new Map<string, Buffer>()\n\n // Compute individual file hashes\n const fileHashInputs: FileHashInput[] = []\n for (const file of sortedFiles) {\n const filePath = path.resolve(baseDir, file)\n\n // Handle symlinks\n let realPath = filePath\n let stats = await fs.promises.lstat(filePath)\n\n if (stats.isSymbolicLink()) {\n try {\n realPath = await fs.promises.realpath(filePath)\n } catch {\n // Skip broken symlinks\n continue\n }\n\n // Get stats of the target\n try {\n stats = await fs.promises.stat(realPath)\n } catch {\n // Skip broken symlinks\n continue\n }\n }\n\n // Skip if not a file (e.g., directories)\n if (!stats.isFile()) {\n continue\n }\n\n // Normalize path separators to forward slashes\n const normalizedPath = normalizePath(file)\n\n // Check if we've already hashed this file (via symlinks)\n let hash: Buffer\n const cachedHash = fileHashCache.get(realPath)\n if (cachedHash !== undefined) {\n // Reuse cached hash for files we've already seen (via symlinks)\n hash = cachedHash\n } else {\n // Hash the file content\n hash = await hashFileAsync(realPath, normalizedPath, stats)\n fileHashCache.set(realPath, hash)\n }\n\n fileHashInputs.push({ relativePath: normalizedPath, hash })\n }\n\n // Compute final fingerprint\n const fingerprint = computeFinalFingerprint(fileHashInputs)\n\n return {\n fingerprint,\n files: sortedFiles,\n fileCount: sortedFiles.length,\n }\n}\n\n/**\n * Compute a deterministic fingerprint for a set of packages (sync).\n *\n * @param options - Configuration for fingerprint computation\n * @returns Result containing the fingerprint hash and list of files processed\n * @throws Error if packages array is empty or if package paths don't exist\n * @public\n * @see {@link computeFingerprint} for the async version\n */\nexport function computeFingerprintSync(options: FingerprintOptions): FingerprintResult {\n const baseDir = validateOptions(options)\n\n // List all files in packages (sync version)\n const files = listPackageFilesSync(options.packages, options.ignore, baseDir)\n\n // Sort files lexicographically\n const sortedFiles = sortFiles(files)\n\n // Track visited files to handle multiple symlinks pointing to the same file\n // Key: realpath, Value: file hash\n const fileHashCache = new Map<string, Buffer>()\n\n // Compute individual file hashes\n const fileHashInputs: FileHashInput[] = []\n for (const file of sortedFiles) {\n const filePath = path.resolve(baseDir, file)\n\n // Handle symlinks\n let realPath = filePath\n let stats = fs.lstatSync(filePath)\n\n if (stats.isSymbolicLink()) {\n try {\n realPath = fs.realpathSync(filePath)\n } catch {\n // Skip broken symlinks\n continue\n }\n\n // Get stats of the target\n try {\n stats = fs.statSync(realPath)\n } catch {\n // Skip broken symlinks\n continue\n }\n }\n\n // Skip if not a file (e.g., directories)\n if (!stats.isFile()) {\n continue\n }\n\n // Normalize path separators to forward slashes\n const normalizedPath = normalizePath(file)\n\n // Check if we've already hashed this file (via symlinks)\n let hash: Buffer\n const cachedHash = fileHashCache.get(realPath)\n if (cachedHash !== undefined) {\n // Reuse cached hash for files we've already seen (via symlinks)\n hash = cachedHash\n } else {\n // Hash the file content (sync version cannot stream)\n hash = hashFileSync(realPath, normalizedPath)\n fileHashCache.set(realPath, hash)\n }\n\n fileHashInputs.push({ relativePath: normalizedPath, hash })\n }\n\n // Compute final fingerprint\n const fingerprint = computeFinalFingerprint(fileHashInputs)\n\n return {\n fingerprint,\n files: sortedFiles,\n fileCount: sortedFiles.length,\n }\n}\n\n/**\n * List files in packages, respecting ignore patterns (async).\n *\n * @param packages - Array of package directory paths\n * @param ignore - Optional glob patterns to exclude\n * @param baseDir - Base directory for resolving paths (defaults to cwd)\n * @returns Array of relative file paths\n * @public\n */\nexport async function listPackageFiles(\n packages: string[],\n ignore: string[] = [],\n baseDir: string = process.cwd(),\n): Promise<string[]> {\n const allFiles: string[] = []\n\n for (const pkg of packages) {\n // Build glob patterns for this package\n const patterns = [`${pkg}/**/*`]\n\n // Use tinyglobby to find files\n const files = await glob(patterns, {\n cwd: baseDir,\n ignore,\n onlyFiles: true,\n dot: true, // Include dotfiles\n absolute: false, // Return relative paths\n })\n\n allFiles.push(...files)\n }\n\n return allFiles\n}\n\n/**\n * Synchronous version of listPackageFiles\n */\nfunction listPackageFilesSync(\n packages: string[],\n ignore: string[] = [],\n baseDir: string = process.cwd(),\n): string[] {\n const allFiles: string[] = []\n\n for (const pkg of packages) {\n // Build glob patterns for this package\n const patterns = [`${pkg}/**/*`]\n\n // Use tinyglobby to find files (sync version)\n const files = globSync(patterns, {\n cwd: baseDir,\n ignore,\n onlyFiles: true,\n dot: true, // Include dotfiles\n absolute: false, // Return relative paths\n })\n\n allFiles.push(...files)\n }\n\n return allFiles\n}\n","/**\n * Attestation file I/O module with JSON canonicalization.\n */\n\nimport * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport * as canonicalizeNamespace from 'canonicalize'\nimport { z } from 'zod'\nimport type { Attestation, AttestationsFile } from './types.js'\n\n/**\n * Extract the serialize function from the canonicalize CommonJS module.\n *\n * Context: The canonicalize package uses `module.exports = function(...)`, which is\n * a CommonJS pattern. With `esModuleInterop: false` (required for library code to\n * avoid leaking tsconfig options to consumers), TypeScript treats the namespace import\n * as the module object itself, not as an object with a default export.\n *\n * The package's type definitions declare `export default function serialize(...)`,\n * which TypeScript interprets as `{ default: function }` when esModuleInterop is off.\n * However, at runtime with NodeNext module resolution, the actual value is just the\n * function itself.\n *\n * This type assertion is safe because:\n * 1. We verified the runtime export structure of the canonicalize module\n * 2. The type matches the published @types signature\n * 3. This is the standard pattern for importing CommonJS modules in strict ESM\n */\n// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\nconst canonicalize = canonicalizeNamespace as unknown as {\n default: (input: unknown) => string | undefined\n}\nconst serialize = canonicalize.default\n\n// Zod schema for attestation validation\nconst attestationSchema = z.object({\n suite: z.string().min(1),\n fingerprint: z.string().regex(/^sha256:[a-f0-9]{64}$/),\n attestedAt: z.string().datetime(),\n attestedBy: z.string().min(1),\n command: z.string().min(1),\n exitCode: z.literal(0),\n})\n\nconst attestationsFileSchema = z.object({\n schemaVersion: z.literal('1'),\n attestations: z.array(attestationSchema),\n signature: z.string(), // Will be validated by crypto module\n})\n\n/**\n * Type guard to check if an error is a Node.js file system error with code.\n * We need to disable the type assertion rule here because TypeScript doesn't\n * provide a way to narrow an object type after checking for a property without\n * using type assertions or indexed access. This is a safe assertion because we\n * check for the property existence and type before using it.\n */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n if (error === null || typeof error !== 'object') {\n return false\n }\n if (!('code' in error)) {\n return false\n }\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const errorObj = error as Record<string, unknown>\n return typeof errorObj.code === 'string'\n}\n\n/**\n * Read attestations file from disk (async).\n *\n * @param filePath - Absolute path to the attestations JSON file\n * @returns Parsed attestations file, or null if the file doesn't exist\n * @throws Error on parse or validation errors\n * @public\n */\nexport async function readAttestations(filePath: string): Promise<AttestationsFile | null> {\n try {\n const content = await fs.promises.readFile(filePath, 'utf-8')\n const parsed: unknown = JSON.parse(content)\n // Zod validates and returns the correct type\n return attestationsFileSchema.parse(parsed)\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return null\n }\n throw error\n }\n}\n\n/**\n * Read attestations file from disk (sync).\n *\n * @param filePath - Absolute path to the attestations JSON file\n * @returns Parsed attestations file, or null if the file doesn't exist\n * @throws Error on parse or validation errors\n * @public\n */\nexport function readAttestationsSync(filePath: string): AttestationsFile | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const parsed: unknown = JSON.parse(content)\n // Zod validates and returns the correct type\n return attestationsFileSchema.parse(parsed)\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return null\n }\n throw error\n }\n}\n\n/**\n * Write attestations file to disk (async).\n *\n * Creates parent directories if needed. The signature should be computed\n * separately and passed in.\n *\n * @param filePath - Absolute path to write the attestations file\n * @param attestations - Array of attestation entries\n * @param signature - Cryptographic signature of the attestations\n * @throws Error on validation or write errors\n * @public\n */\nexport async function writeAttestations(\n filePath: string,\n attestations: Attestation[],\n signature: string,\n): Promise<void> {\n const fileContent: AttestationsFile = {\n schemaVersion: '1',\n attestations,\n signature,\n }\n\n // Validate before writing\n attestationsFileSchema.parse(fileContent)\n\n // Create parent directories if needed\n const dir = path.dirname(filePath)\n await fs.promises.mkdir(dir, { recursive: true })\n\n // Write with pretty formatting for readability\n const json = JSON.stringify(fileContent, null, 2)\n await fs.promises.writeFile(filePath, json, 'utf-8')\n}\n\n/**\n * Write attestations file to disk (sync).\n *\n * Creates parent directories if needed. The signature should be computed\n * separately and passed in.\n *\n * @param filePath - Absolute path to write the attestations file\n * @param attestations - Array of attestation entries\n * @param signature - Cryptographic signature of the attestations\n * @throws Error on validation or write errors\n * @public\n */\nexport function writeAttestationsSync(\n filePath: string,\n attestations: Attestation[],\n signature: string,\n): void {\n const fileContent: AttestationsFile = {\n schemaVersion: '1',\n attestations,\n signature,\n }\n\n // Validate before writing\n attestationsFileSchema.parse(fileContent)\n\n // Create parent directories if needed\n const dir = path.dirname(filePath)\n fs.mkdirSync(dir, { recursive: true })\n\n // Write with pretty formatting for readability\n const json = JSON.stringify(fileContent, null, 2)\n fs.writeFileSync(filePath, json, 'utf-8')\n}\n\n/**\n * Find an attestation for a specific suite.\n *\n * @param attestations - Attestations file containing all attestations\n * @param suite - Name of the suite to find\n * @returns The attestation if found, undefined otherwise\n * @public\n */\nexport function findAttestation(\n attestations: AttestationsFile,\n suite: string,\n): Attestation | undefined {\n return attestations.attestations.find((a) => a.suite === suite)\n}\n\n/**\n * Add or update an attestation for a suite.\n *\n * This is an immutable operation that returns a new array.\n *\n * @param attestations - Current array of attestations\n * @param newAttestation - Attestation to add or update\n * @returns New attestations array with the upserted attestation\n * @throws Error if the new attestation fails validation\n * @public\n */\nexport function upsertAttestation(\n attestations: Attestation[],\n newAttestation: Attestation,\n): Attestation[] {\n // Validate the new attestation\n attestationSchema.parse(newAttestation)\n\n const existingIndex = attestations.findIndex((a) => a.suite === newAttestation.suite)\n\n if (existingIndex === -1) {\n // Add new attestation\n return [...attestations, newAttestation]\n } else {\n // Update existing attestation\n const updated = [...attestations]\n // eslint-disable-next-line security/detect-object-injection -- False positive: existingIndex is a safe number from findIndex\n updated[existingIndex] = newAttestation\n return updated\n }\n}\n\n/**\n * Remove attestations for a suite.\n *\n * This is an immutable operation that returns a new array.\n *\n * @param attestations - Current array of attestations\n * @param suite - Name of the suite to remove\n * @returns New attestations array without the specified suite\n * @public\n */\nexport function removeAttestation(attestations: Attestation[], suite: string): Attestation[] {\n return attestations.filter((a) => a.suite !== suite)\n}\n\n/**\n * Compute canonical JSON representation for signing.\n *\n * Implements RFC 8785 JSON Canonicalization Scheme. The canonicalize package provides:\n * 1. Keys sorted lexicographically (Unicode code point order)\n * 2. No whitespace between tokens\n * 3. No trailing commas\n * 4. Strings escaped using \\uXXXX for control characters\n * 5. Numbers: no leading zeros, no +, use lowercase 'e' for exponent\n * 6. UTF-8 encoding\n *\n * @param attestations - Array of attestations to canonicalize\n * @returns Canonical JSON string representation\n * @throws Error if canonicalization fails\n * @public\n */\nexport function canonicalizeAttestations(attestations: Attestation[]): string {\n const canonical = serialize(attestations)\n if (canonical === undefined) {\n throw new Error('Failed to canonicalize attestations')\n }\n return canonical\n}\n\n/**\n * Create a new attestation entry.\n *\n * @param params - Parameters for creating the attestation\n * @param params.suite - Name of the test suite\n * @param params.fingerprint - Fingerprint of the packages in sha256 format\n * @param params.command - Command that was executed\n * @param params.attestedBy - Optional username (defaults to current OS user)\n * @returns Validated attestation object\n * @throws Error if attestation validation fails\n * @public\n */\nexport function createAttestation(params: {\n suite: string\n fingerprint: string\n command: string\n attestedBy?: string\n}): Attestation {\n const attestation: Attestation = {\n suite: params.suite,\n fingerprint: params.fingerprint,\n attestedAt: new Date().toISOString(),\n attestedBy: params.attestedBy ?? os.userInfo().username,\n command: params.command,\n exitCode: 0,\n }\n\n // Validate before returning\n attestationSchema.parse(attestation)\n\n return attestation\n}\n\n/**\n * Options for writing signed attestations.\n * @public\n */\nexport interface WriteSignedAttestationsOptions {\n /** Path to write the attestations file */\n filePath: string\n /** Array of attestations to write */\n attestations: Attestation[]\n /** Path to the private key for signing */\n privateKeyPath: string\n}\n\n/**\n * Options for reading and verifying signed attestations.\n * @public\n */\nexport interface ReadSignedAttestationsOptions {\n /** Path to read the attestations file from */\n filePath: string\n /** Path to the public key for verification */\n publicKeyPath: string\n}\n\n/**\n * Write attestations with a cryptographic signature.\n *\n * This function canonicalizes the attestations, signs them with the private key,\n * and writes the attestations file with the signature.\n *\n * @param options - Options for writing signed attestations\n * @throws Error if signing or writing fails\n * @public\n */\nexport async function writeSignedAttestations(\n options: WriteSignedAttestationsOptions,\n): Promise<void> {\n // Import sign function here to avoid circular dependency\n const { sign } = await import('./crypto.js')\n\n const canonical = canonicalizeAttestations(options.attestations)\n const signature = await sign({\n privateKeyPath: options.privateKeyPath,\n data: canonical,\n })\n await writeAttestations(options.filePath, options.attestations, signature)\n}\n\n/**\n * Read attestations and verify the signature.\n *\n * This function reads the attestations file, canonicalizes the attestations,\n * and verifies the signature using the public key. It throws an error if the\n * file doesn't exist or if signature verification fails.\n *\n * @param options - Options for reading and verifying attestations\n * @returns The attestations file if signature is valid\n * @throws Error if attestations file not found\n * @throws SignatureInvalidError if signature verification fails\n * @public\n */\nexport async function readAndVerifyAttestations(\n options: ReadSignedAttestationsOptions,\n): Promise<AttestationsFile> {\n // Import verify function here to avoid circular dependency\n const { verify } = await import('./crypto.js')\n\n const file = await readAttestations(options.filePath)\n if (!file) {\n throw new Error(`Attestations file not found: ${options.filePath}`)\n }\n\n const canonical = canonicalizeAttestations(file.attestations)\n const isValid = await verify({\n publicKeyPath: options.publicKeyPath,\n data: canonical,\n signature: file.signature,\n })\n\n if (!isValid) {\n throw new SignatureInvalidError(options.filePath)\n }\n\n return file\n}\n\n/**\n * Error thrown when signature verification fails.\n * @public\n */\nexport class SignatureInvalidError extends Error {\n /**\n * Create a new SignatureInvalidError.\n * @param filePath - Path to the file that failed verification\n */\n constructor(filePath: string) {\n super(`Signature verification failed for: ${filePath}`)\n this.name = 'SignatureInvalidError'\n }\n}\n","/**\n * Verification logic for attestations.\n * @packageDocumentation\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n AttestItConfig,\n Attestation,\n AttestationsFile,\n SuiteVerificationResult,\n} from './types.js'\nimport { computeFingerprint } from './fingerprint.js'\nimport { readAndVerifyAttestations, SignatureInvalidError } from './attestation.js'\n\n/**\n * Options for verifying attestations.\n * @public\n */\nexport interface VerifyOptions {\n /** Configuration object */\n config: AttestItConfig\n /** Repository root directory (defaults to process.cwd()) */\n repoRoot?: string\n}\n\n/**\n * Result of verifying all attestations.\n * @public\n */\nexport interface VerifyResult {\n /** Overall success - true if all attestations are valid */\n success: boolean\n /** Whether the attestations file signature is valid */\n signatureValid: boolean\n /** Verification results for each suite */\n suites: SuiteVerificationResult[]\n /** Error messages encountered during verification */\n errors: string[]\n}\n\n/**\n * Verify all attestations against current code state.\n *\n * Verification algorithm:\n * 1. Load and verify attestations file signature\n * 2. For each suite in config:\n * a. Compute current fingerprint\n * b. Find matching attestation\n * c. Compare fingerprints\n * d. Check age\n * 3. Check invalidation chains\n * 4. Return aggregated results\n *\n * @param options - Verification options\n * @returns Verification result with status for each suite\n * @public\n */\nexport async function verifyAttestations(options: VerifyOptions): Promise<VerifyResult> {\n const { config, repoRoot = process.cwd() } = options\n const errors: string[] = []\n const suiteResults: SuiteVerificationResult[] = []\n let signatureValid = true\n let attestationsFile: AttestationsFile | null = null\n\n // Resolve paths\n const attestationsPath = resolvePath(config.settings.attestationsPath, repoRoot)\n const publicKeyPath = resolvePath(config.settings.publicKeyPath, repoRoot)\n\n // Step 1: Load and verify attestations\n try {\n if (!fs.existsSync(attestationsPath)) {\n // No attestations file - all suites need attestation\n attestationsFile = null\n } else if (!fs.existsSync(publicKeyPath)) {\n errors.push(`Public key not found: ${publicKeyPath}`)\n signatureValid = false\n } else {\n attestationsFile = await readAndVerifyAttestations({\n filePath: attestationsPath,\n publicKeyPath,\n })\n }\n } catch (err) {\n if (err instanceof SignatureInvalidError) {\n signatureValid = false\n errors.push(err.message)\n } else if (err instanceof Error) {\n errors.push(err.message)\n }\n }\n\n const attestations = attestationsFile?.attestations ?? []\n\n // Step 2: Check each suite\n for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {\n const result = await verifySuite({\n suiteName,\n suiteConfig,\n attestations,\n maxAgeDays: config.settings.maxAgeDays,\n repoRoot,\n })\n suiteResults.push(result)\n }\n\n // Step 3: Check invalidation chains\n checkInvalidationChains(config, suiteResults)\n\n // Step 4: Aggregate results\n const allValid =\n signatureValid && suiteResults.every((r) => r.status === 'VALID') && errors.length === 0\n\n return {\n success: allValid,\n signatureValid,\n suites: suiteResults,\n errors,\n }\n}\n\n/**\n * Options for verifying a single suite.\n * @internal\n */\ninterface VerifySuiteOptions {\n /** Name of the suite */\n suiteName: string\n /** Suite configuration */\n suiteConfig: { packages: string[]; ignore?: string[] }\n /** All attestations from the attestations file */\n attestations: Attestation[]\n /** Maximum age in days before attestation expires */\n maxAgeDays: number\n /** Repository root directory */\n repoRoot: string\n}\n\n/**\n * Verify a single suite's attestation.\n * @internal\n */\nasync function verifySuite(options: VerifySuiteOptions): Promise<SuiteVerificationResult> {\n const { suiteName, suiteConfig, attestations, maxAgeDays, repoRoot } = options\n\n // Compute current fingerprint\n const fingerprintOptions = {\n packages: suiteConfig.packages.map((p) => resolvePath(p, repoRoot)),\n baseDir: repoRoot,\n ...(suiteConfig.ignore && { ignore: suiteConfig.ignore }),\n }\n const fingerprintResult = await computeFingerprint(fingerprintOptions)\n\n // Find attestation for this suite\n const attestation = attestations.find((a) => a.suite === suiteName)\n\n // No attestation found\n if (!attestation) {\n return {\n suite: suiteName,\n status: 'NEEDS_ATTESTATION',\n fingerprint: fingerprintResult.fingerprint,\n message: 'No attestation found for this suite',\n }\n }\n\n // Check fingerprint\n if (attestation.fingerprint !== fingerprintResult.fingerprint) {\n return {\n suite: suiteName,\n status: 'FINGERPRINT_CHANGED',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n message: `Fingerprint changed from ${attestation.fingerprint.slice(0, 20)}... to ${fingerprintResult.fingerprint.slice(0, 20)}...`,\n }\n }\n\n // Check age\n const attestedAt = new Date(attestation.attestedAt)\n const ageMs = Date.now() - attestedAt.getTime()\n const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24))\n\n if (ageDays > maxAgeDays) {\n return {\n suite: suiteName,\n status: 'EXPIRED',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n age: ageDays,\n message: `Attestation expired (${String(ageDays)} days old, max ${String(maxAgeDays)} days)`,\n }\n }\n\n // All checks passed\n return {\n suite: suiteName,\n status: 'VALID',\n fingerprint: fingerprintResult.fingerprint,\n attestation,\n age: ageDays,\n }\n}\n\n/**\n * Check invalidation chains.\n *\n * If suite A invalidates suite B, and A's attestation is newer than B's,\n * then B should be marked as INVALIDATED_BY_PARENT.\n *\n * @param config - Full configuration\n * @param results - Array of suite verification results to mutate\n * @internal\n */\nfunction checkInvalidationChains(config: AttestItConfig, results: SuiteVerificationResult[]): void {\n for (const [parentName, parentConfig] of Object.entries(config.suites)) {\n const invalidates = parentConfig.invalidates ?? []\n const parentResult = results.find((r) => r.suite === parentName)\n\n if (!parentResult?.attestation) continue\n\n const parentTime = new Date(parentResult.attestation.attestedAt).getTime()\n\n for (const childName of invalidates) {\n const childResult = results.find((r) => r.suite === childName)\n if (!childResult?.attestation) continue\n\n const childTime = new Date(childResult.attestation.attestedAt).getTime()\n\n // If parent was attested AFTER child, child is invalidated\n if (parentTime > childTime && childResult.status === 'VALID') {\n childResult.status = 'INVALIDATED_BY_PARENT'\n childResult.message = `Invalidated by ${parentName} (attested later)`\n }\n }\n }\n}\n\n/**\n * Resolve a path relative to a base directory.\n * @param relativePath - Path that may be relative or absolute\n * @param baseDir - Base directory for resolving relative paths\n * @returns Absolute path\n * @internal\n */\nfunction resolvePath(relativePath: string, baseDir: string): string {\n if (path.isAbsolute(relativePath)) {\n return relativePath\n }\n return path.join(baseDir, relativePath)\n}\n","/**\n * @attest-it/core\n *\n * Core functionality for the attest-it testing framework.\n * @packageDocumentation\n */\n\n/**\n * Package version\n * @public\n */\nexport const version = '0.0.0'\n\n// Types\nexport type {\n AttestItSettings,\n SuiteConfig,\n AttestItConfig,\n Attestation,\n AttestationsFile,\n VerificationStatus,\n SuiteVerificationResult,\n} from './types.js'\n\n// Config\nexport {\n loadConfig,\n loadConfigSync,\n findConfigPath,\n resolveConfigPaths,\n toAttestItConfig,\n ConfigValidationError,\n ConfigNotFoundError,\n type Config,\n} from './config.js'\n\n// Fingerprinting\nexport { computeFingerprint, computeFingerprintSync, listPackageFiles } from './fingerprint.js'\nexport type { FingerprintOptions, FingerprintResult } from './fingerprint.js'\n\n// Attestations\nexport {\n readAttestations,\n readAttestationsSync,\n writeAttestations,\n writeAttestationsSync,\n findAttestation,\n upsertAttestation,\n removeAttestation,\n canonicalizeAttestations,\n createAttestation,\n writeSignedAttestations,\n readAndVerifyAttestations,\n SignatureInvalidError,\n} from './attestation.js'\nexport type {\n WriteSignedAttestationsOptions,\n ReadSignedAttestationsOptions,\n} from './attestation.js'\n\n// Cryptography\nexport {\n checkOpenSSL,\n getDefaultPrivateKeyPath,\n getDefaultPublicKeyPath,\n generateKeyPair,\n sign,\n verify,\n setKeyPermissions,\n} from './crypto.js'\nexport type {\n KeyPaths,\n KeygenOptions,\n SignOptions,\n VerifyOptions as CryptoVerifyOptions,\n} from './crypto.js'\n\n// Verification\nexport { verifyAttestations } from './verify.js'\nexport type { VerifyOptions, VerifyResult } from './verify.js'\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attest-it/core",
|
|
3
3
|
"description": "Core functionality for attest-it",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"author": "Mike North <michael.l.north@gmail.com>",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"canonicalize": "^2.0.0",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
],
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"main": "./dist/index.js",
|
|
39
|
+
"type": "module",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
39
41
|
"scripts": {
|
|
40
42
|
"build": "tsup && declaration-file-normalizer dist/index.d.ts",
|
|
41
43
|
"check": "pnpm run check:api-report && pnpm run check:eslint && pnpm run check:types",
|
|
@@ -45,7 +47,5 @@
|
|
|
45
47
|
"generate:api-report": "api-extractor run --local",
|
|
46
48
|
"test": "vitest run",
|
|
47
49
|
"test:watch": "vitest"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
"types": "./dist/index.d.ts"
|
|
51
|
-
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crypto.ts"],"names":[],"mappings":"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","file":"chunk-UWYR7JNE.js","sourcesContent":["/**\n * Cryptographic utilities for key generation, signing, and verification.\n *\n * @remarks\n * This module provides cryptographic operations using OpenSSL for key management\n * and signature verification. It supports Ed25519 and RSA algorithms.\n *\n * @packageDocumentation\n */\n\nimport { spawn } from 'node:child_process'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport * as os from 'node:os'\n\n/**\n * Supported signature algorithms.\n * @public\n */\nexport type Algorithm = 'ed25519' | 'rsa'\n\n/**\n * Paths to a generated keypair.\n * @public\n */\nexport interface KeyPaths {\n /** Path to the private key file */\n privatePath: string\n /** Path to the public key file */\n publicPath: string\n}\n\n/**\n * Options for key generation.\n * @public\n */\nexport interface KeygenOptions {\n /** Algorithm to use (default: ed25519) */\n algorithm?: Algorithm\n /** Path for private key (default: OS-specific config dir) */\n privatePath?: string\n /** Path for public key (default: repo root) */\n publicPath?: string\n /** Overwrite existing keys (default: false) */\n force?: boolean\n}\n\n/**\n * Options for signing data.\n * @public\n */\nexport interface SignOptions {\n /** Path to the private key file */\n privateKeyPath: string\n /** Data to sign (string or Buffer) */\n data: string | Buffer\n}\n\n/**\n * Options for verifying signatures.\n * @public\n */\nexport interface VerifyOptions {\n /** Path to the public key file */\n publicKeyPath: string\n /** Original data that was signed */\n data: string | Buffer\n /** Base64-encoded signature to verify */\n signature: string\n}\n\n/**\n * Result from spawning an OpenSSL process.\n * @internal\n */\ninterface SpawnResult {\n /** Process exit code */\n exitCode: number\n /** Standard output as Buffer */\n stdout: Buffer\n /** Standard error as string */\n stderr: string\n}\n\n/**\n * Run OpenSSL with the given arguments.\n * @param args - Command-line arguments for OpenSSL\n * @param stdin - Optional data to write to stdin\n * @returns Process result with exit code and outputs\n * @internal\n */\nasync function runOpenSSL(args: string[], stdin?: Buffer): Promise<SpawnResult> {\n return new Promise((resolve, reject) => {\n const child = spawn('openssl', args, {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n const stdoutChunks: Buffer[] = []\n let stderr = ''\n\n child.stdout.on('data', (chunk: Buffer) => {\n stdoutChunks.push(chunk)\n })\n\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString()\n })\n\n child.on('error', (err) => {\n reject(new Error(`Failed to spawn OpenSSL: ${err.message}`))\n })\n\n child.on('close', (code) => {\n resolve({\n exitCode: code ?? 1,\n stdout: Buffer.concat(stdoutChunks),\n stderr,\n })\n })\n\n if (stdin) {\n child.stdin.write(stdin)\n }\n child.stdin.end()\n })\n}\n\n/**\n * Check if OpenSSL is available and get version info.\n * @returns OpenSSL version string\n * @throws Error if OpenSSL is not available\n * @public\n */\nexport async function checkOpenSSL(): Promise<string> {\n const result = await runOpenSSL(['version'])\n\n if (result.exitCode !== 0) {\n throw new Error(`OpenSSL check failed: ${result.stderr}`)\n }\n\n return result.stdout.toString().trim()\n}\n\n/**\n * Cached result of OpenSSL availability check.\n * @internal\n */\nlet openSSLChecked = false\n\n/**\n * Ensure OpenSSL is available before performing cryptographic operations.\n * @throws Error with installation instructions if OpenSSL is not available\n * @internal\n */\nasync function ensureOpenSSLAvailable(): Promise<void> {\n if (openSSLChecked) {\n return\n }\n\n try {\n await checkOpenSSL()\n openSSLChecked = true\n } catch {\n throw new Error(\n 'OpenSSL is not installed or not in PATH. ' +\n 'Please install OpenSSL to use attest-it. ' +\n 'On macOS: brew install openssl. ' +\n 'On Ubuntu: apt-get install openssl',\n )\n }\n}\n\n/**\n * Get the default private key path based on OS.\n * - macOS/Linux: ~/.config/attest-it/private.pem\n * - Windows: %APPDATA%\\attest-it\\private.pem\n * @public\n */\nexport function getDefaultPrivateKeyPath(): string {\n const homeDir = os.homedir()\n\n if (process.platform === 'win32') {\n const appData = process.env.APPDATA ?? path.join(homeDir, 'AppData', 'Roaming')\n return path.join(appData, 'attest-it', 'private.pem')\n }\n\n return path.join(homeDir, '.config', 'attest-it', 'private.pem')\n}\n\n/**\n * Get the default public key path (in repo).\n * @public\n */\nexport function getDefaultPublicKeyPath(): string {\n return path.join(process.cwd(), 'attest-it-public.pem')\n}\n\n/**\n * Ensure a directory exists, creating it and parent directories if needed.\n * @param dirPath - Directory path to create\n * @internal\n */\nasync function ensureDir(dirPath: string): Promise<void> {\n try {\n await fs.mkdir(dirPath, { recursive: true })\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code !== 'EEXIST') {\n throw err\n }\n }\n}\n\n/**\n * Check if a file exists.\n * @param filePath - File path to check\n * @returns true if file exists\n * @internal\n */\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Clean up one or more files, ignoring errors if files don't exist.\n * @param paths - File paths to delete\n * @internal\n */\nasync function cleanupFiles(...paths: string[]): Promise<void> {\n for (const filePath of paths) {\n try {\n await fs.unlink(filePath)\n } catch {\n // Ignore cleanup errors - file may not exist\n }\n }\n}\n\n/**\n * Generate a new keypair using OpenSSL.\n * @param options - Generation options\n * @returns Paths to generated keys\n * @throws Error if OpenSSL fails or keys exist without force\n * @public\n */\nexport async function generateKeyPair(options: KeygenOptions = {}): Promise<KeyPaths> {\n // Ensure OpenSSL is available before proceeding\n await ensureOpenSSLAvailable()\n\n const {\n algorithm = 'ed25519',\n privatePath = getDefaultPrivateKeyPath(),\n publicPath = getDefaultPublicKeyPath(),\n force = false,\n } = options\n\n // Check if keys already exist\n const privateExists = await fileExists(privatePath)\n const publicExists = await fileExists(publicPath)\n\n if ((privateExists || publicExists) && !force) {\n const existing = [privateExists ? privatePath : null, publicExists ? publicPath : null].filter(\n Boolean,\n )\n throw new Error(\n `Key files already exist: ${existing.join(', ')}. Use force: true to overwrite.`,\n )\n }\n\n // Ensure parent directories exist\n await ensureDir(path.dirname(privatePath))\n await ensureDir(path.dirname(publicPath))\n\n try {\n // Generate private key\n const genArgs =\n algorithm === 'ed25519'\n ? ['genpkey', '-algorithm', 'Ed25519', '-out', privatePath]\n : ['genpkey', '-algorithm', 'RSA', '-pkeyopt', 'rsa_keygen_bits:2048', '-out', privatePath]\n\n const genResult = await runOpenSSL(genArgs)\n if (genResult.exitCode !== 0) {\n throw new Error(`Failed to generate private key: ${genResult.stderr}`)\n }\n\n // Set restrictive permissions on private key\n await setKeyPermissions(privatePath)\n\n // Extract public key\n const pubResult = await runOpenSSL(['pkey', '-in', privatePath, '-pubout', '-out', publicPath])\n\n if (pubResult.exitCode !== 0) {\n throw new Error(`Failed to extract public key: ${pubResult.stderr}`)\n }\n\n return {\n privatePath,\n publicPath,\n }\n } catch (err) {\n // Clean up both key files on any failure\n await cleanupFiles(privatePath, publicPath)\n throw err\n }\n}\n\n/**\n * Sign data using a private key.\n * @param options - Signing options\n * @returns Base64-encoded signature\n * @throws Error if signing fails\n * @public\n */\nexport async function sign(options: SignOptions): Promise<string> {\n // Ensure OpenSSL is available before proceeding\n await ensureOpenSSLAvailable()\n\n const { privateKeyPath, data } = options\n\n // Check if private key exists\n if (!(await fileExists(privateKeyPath))) {\n throw new Error(`Private key not found: ${privateKeyPath}`)\n }\n\n // Convert data to Buffer\n const dataBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data\n\n // OpenSSL pkeyutl cannot handle empty files, so we need to add a single byte\n // for empty data and document this limitation\n const processBuffer = dataBuffer.length === 0 ? Buffer.from([0x00]) : dataBuffer\n\n // Create temporary directory with OS-level uniqueness guarantees\n // This prevents TOCTOU race conditions that Math.random() would allow\n const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'attest-it-'))\n const dataFile = path.join(tmpDir, 'data.bin')\n const sigFile = path.join(tmpDir, 'sig.bin')\n\n try {\n // Write data to temp file\n await fs.writeFile(dataFile, processBuffer)\n\n // Sign the data\n const result = await runOpenSSL([\n 'pkeyutl',\n '-sign',\n '-inkey',\n privateKeyPath,\n '-in',\n dataFile,\n '-out',\n sigFile,\n ])\n\n if (result.exitCode !== 0) {\n throw new Error(`Failed to sign data: ${result.stderr}`)\n }\n\n // Read the signature\n const sigBuffer = await fs.readFile(sigFile)\n return sigBuffer.toString('base64')\n } finally {\n // Clean up temp directory and all files within it\n try {\n await fs.rm(tmpDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors - OS will eventually clean tmpdir\n }\n }\n}\n\n/**\n * Verify a signature using a public key.\n * @param options - Verification options\n * @returns true if signature is valid\n * @throws Error if verification fails (not just invalid signature)\n * @public\n */\nexport async function verify(options: VerifyOptions): Promise<boolean> {\n // Ensure OpenSSL is available before proceeding\n await ensureOpenSSLAvailable()\n\n const { publicKeyPath, data, signature } = options\n\n // Check if public key exists\n if (!(await fileExists(publicKeyPath))) {\n throw new Error(`Public key not found: ${publicKeyPath}`)\n }\n\n // Convert data to Buffer\n const dataBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data\n\n // OpenSSL pkeyutl cannot handle empty files, so we use the same workaround\n // as in sign() - add a single byte for empty data\n const processBuffer = dataBuffer.length === 0 ? Buffer.from([0x00]) : dataBuffer\n\n // Decode signature from base64\n const sigBuffer = Buffer.from(signature, 'base64')\n\n // Create temporary directory with OS-level uniqueness guarantees\n // This prevents TOCTOU race conditions that Math.random() would allow\n const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'attest-it-'))\n const dataFile = path.join(tmpDir, 'data.bin')\n const sigFile = path.join(tmpDir, 'sig.bin')\n\n try {\n // Write data and signature to temp files\n await fs.writeFile(dataFile, processBuffer)\n await fs.writeFile(sigFile, sigBuffer)\n\n // Verify the signature\n const result = await runOpenSSL([\n 'pkeyutl',\n '-verify',\n '-pubin',\n '-inkey',\n publicKeyPath,\n '-sigfile',\n sigFile,\n '-in',\n dataFile,\n ])\n\n // Exit code 0 means valid signature, 1 means invalid\n // Any other exit code or stderr output indicates an error\n if (result.exitCode !== 0 && result.exitCode !== 1) {\n throw new Error(`Verification error: ${result.stderr}`)\n }\n\n return result.exitCode === 0\n } finally {\n // Clean up temp directory and all files within it\n try {\n await fs.rm(tmpDir, { recursive: true, force: true })\n } catch {\n // Ignore cleanup errors - OS will eventually clean tmpdir\n }\n }\n}\n\n/**\n * Set restrictive permissions on a private key file.\n * @param keyPath - Path to the private key\n * @public\n */\nexport async function setKeyPermissions(keyPath: string): Promise<void> {\n // On Windows, use fs.chmod which has limited effect\n // On Unix, set to 0o600 (read/write for owner only)\n if (process.platform === 'win32') {\n // Windows doesn't support Unix-style permissions in the same way\n // But we still call chmod for consistency\n await fs.chmod(keyPath, 0o600)\n } else {\n await fs.chmod(keyPath, 0o600)\n }\n}\n"]}
|