@enactprotocol/trust 2.0.0

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/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @enactprotocol/trust
2
+
3
+ Sigstore integration, attestations, and verification for Enact.
4
+
5
+ ## Overview
6
+
7
+ This package provides:
8
+ - Sigstore integration for keyless signing and verification
9
+ - Attestation generation and validation (in-toto, SLSA)
10
+ - Bundle signature verification
11
+ - Certificate chain validation
12
+ - Trust policy enforcement
13
+
14
+ ## Status
15
+
16
+ Currently in Phase 1 (scaffolding). Full implementation will be completed in Phase 2.
17
+
18
+ ## Development
19
+
20
+ ```bash
21
+ # Build
22
+ bun run build
23
+
24
+ # Test
25
+ bun test
26
+
27
+ # Type check
28
+ bun run typecheck
29
+ ```
30
+
31
+ ## Planned Features (Phase 2)
32
+
33
+ - [ ] Hash utilities with streaming support
34
+ - [ ] Sigstore keyless signing via OIDC
35
+ - [ ] Bundle verification with certificate chain validation
36
+ - [ ] In-toto attestation format support
37
+ - [ ] SLSA provenance support
38
+ - [ ] Comprehensive test coverage
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@enactprotocol/trust",
3
+ "version": "2.0.0",
4
+ "description": "Sigstore integration, attestations, and verification for Enact",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc --build",
16
+ "clean": "rm -rf dist",
17
+ "test": "bun test",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "open": "^11.0.0",
22
+ "openid-client": "5",
23
+ "sigstore": "^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.10.1",
27
+ "typescript": "^5.7.2"
28
+ },
29
+ "keywords": ["cryptography", "sigstore", "attestation", "verification"],
30
+ "author": "Enact Protocol",
31
+ "license": "Apache-2.0"
32
+ }
package/src/hash.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Hash utilities for content integrity verification
3
+ */
4
+
5
+ import { type Hash, createHash } from "node:crypto";
6
+ import { createReadStream, statSync } from "node:fs";
7
+ import type { FileHashOptions, HashAlgorithm, HashResult } from "./types";
8
+
9
+ /**
10
+ * Hash a string or buffer using the specified algorithm
11
+ *
12
+ * @param content - The content to hash
13
+ * @param algorithm - The hash algorithm to use (default: sha256)
14
+ * @returns Hash result with algorithm and digest
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const result = hashContent("hello world");
19
+ * console.log(result.digest); // "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
20
+ * ```
21
+ */
22
+ export function hashContent(
23
+ content: string | Buffer,
24
+ algorithm: HashAlgorithm = "sha256"
25
+ ): HashResult {
26
+ const hash: Hash = createHash(algorithm);
27
+ hash.update(content);
28
+ const digest = hash.digest("hex");
29
+
30
+ return {
31
+ algorithm,
32
+ digest,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Hash a buffer directly
38
+ *
39
+ * @param buffer - The buffer to hash
40
+ * @param algorithm - The hash algorithm to use (default: sha256)
41
+ * @returns Hash result with algorithm and digest
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const buffer = Buffer.from("hello world");
46
+ * const result = hashBuffer(buffer);
47
+ * ```
48
+ */
49
+ export function hashBuffer(buffer: Buffer, algorithm: HashAlgorithm = "sha256"): HashResult {
50
+ return hashContent(buffer, algorithm);
51
+ }
52
+
53
+ /**
54
+ * Hash a file using streaming to support large files
55
+ *
56
+ * @param filePath - Path to the file to hash
57
+ * @param options - Hashing options including algorithm and progress callback
58
+ * @returns Promise resolving to hash result
59
+ *
60
+ * @throws Error if file doesn't exist or cannot be read
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const result = await hashFile("/path/to/file.txt", {
65
+ * algorithm: "sha256",
66
+ * onProgress: (read, total) => {
67
+ * console.log(`${(read / total * 100).toFixed(1)}% complete`);
68
+ * }
69
+ * });
70
+ * ```
71
+ */
72
+ export async function hashFile(
73
+ filePath: string,
74
+ options: FileHashOptions = {}
75
+ ): Promise<HashResult> {
76
+ const { algorithm = "sha256", onProgress } = options;
77
+
78
+ // Validate file exists and get size
79
+ let fileSize: number;
80
+ try {
81
+ const stats = statSync(filePath);
82
+ if (!stats.isFile()) {
83
+ throw new Error(`Path is not a file: ${filePath}`);
84
+ }
85
+ fileSize = stats.size;
86
+ } catch (error) {
87
+ if (error instanceof Error) {
88
+ throw new Error(`Failed to access file: ${error.message}`);
89
+ }
90
+ throw error;
91
+ }
92
+
93
+ return new Promise((resolve, reject) => {
94
+ const hash: Hash = createHash(algorithm);
95
+ const stream = createReadStream(filePath);
96
+ let bytesRead = 0;
97
+
98
+ stream.on("data", (chunk: string | Buffer) => {
99
+ hash.update(chunk);
100
+ const chunkSize = typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
101
+ bytesRead += chunkSize;
102
+
103
+ if (onProgress) {
104
+ onProgress(bytesRead, fileSize);
105
+ }
106
+ });
107
+
108
+ stream.on("end", () => {
109
+ const digest = hash.digest("hex");
110
+ resolve({
111
+ algorithm,
112
+ digest,
113
+ });
114
+ });
115
+
116
+ stream.on("error", (error: Error) => {
117
+ reject(new Error(`Failed to read file: ${error.message}`));
118
+ });
119
+ });
120
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @enactprotocol/trust
3
+ *
4
+ * Sigstore integration, attestation generation/verification,
5
+ * and trust system for Enact.
6
+ */
7
+
8
+ export const version = "0.1.0";
9
+
10
+ // Hash utilities
11
+ export { hashContent, hashBuffer, hashFile } from "./hash";
12
+
13
+ // Key management
14
+ export { generateKeyPair, isValidPEMKey, getKeyTypeFromPEM } from "./keys";
15
+
16
+ // Sigstore integration (attestation signing, verification, trust policies)
17
+ export * from "./sigstore";
18
+
19
+ // TypeScript types for hash/key operations
20
+ export type {
21
+ HashAlgorithm,
22
+ HashResult,
23
+ FileHashOptions,
24
+ KeyType,
25
+ KeyFormat,
26
+ KeyPair,
27
+ KeyGenerationOptions,
28
+ } from "./types";
package/src/keys.ts ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Key management utilities for cryptographic operations
3
+ */
4
+
5
+ import { generateKeyPairSync } from "node:crypto";
6
+ import type { KeyGenerationOptions, KeyPair, KeyType } from "./types";
7
+
8
+ /**
9
+ * Generate a new cryptographic key pair
10
+ *
11
+ * @param options - Key generation options
12
+ * @returns Generated key pair with public and private keys
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // Generate RSA key pair
17
+ * const rsaKeys = generateKeyPair({
18
+ * type: "rsa",
19
+ * modulusLength: 2048
20
+ * });
21
+ *
22
+ * // Generate Ed25519 key pair
23
+ * const ed25519Keys = generateKeyPair({
24
+ * type: "ed25519"
25
+ * });
26
+ * ```
27
+ */
28
+ export function generateKeyPair(options: KeyGenerationOptions): KeyPair {
29
+ const { type, format = "pem", modulusLength = 2048, passphrase } = options;
30
+
31
+ const keyPairOptions = getKeyPairOptions(type, modulusLength, format, passphrase);
32
+ const nodeKeyType = getNodeKeyType(type);
33
+
34
+ // TypeScript has very strict overloads for generateKeyPairSync
35
+ // We use any here as we've validated the types through our own KeyType
36
+ // biome-ignore lint/suspicious/noExplicitAny: Node.js crypto API has complex overloads
37
+ const { publicKey, privateKey } = generateKeyPairSync(nodeKeyType as any, keyPairOptions as any);
38
+
39
+ return {
40
+ publicKey: publicKey.toString(),
41
+ privateKey: privateKey.toString(),
42
+ type,
43
+ format,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Convert our KeyType to Node.js crypto key type
49
+ */
50
+ function getNodeKeyType(type: KeyType): "rsa" | "ed25519" | "ec" {
51
+ switch (type) {
52
+ case "rsa":
53
+ return "rsa";
54
+ case "ed25519":
55
+ return "ed25519";
56
+ case "ecdsa":
57
+ return "ec";
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get key pair generation options for Node.js crypto
63
+ */
64
+ function getKeyPairOptions(
65
+ type: KeyType,
66
+ modulusLength: number,
67
+ format: "pem" | "der" | "jwk",
68
+ passphrase?: string
69
+ ): Parameters<typeof generateKeyPairSync>[1] {
70
+ const baseOptions: Record<string, unknown> = {
71
+ publicKeyEncoding: {
72
+ type: "spki",
73
+ format,
74
+ },
75
+ privateKeyEncoding: {
76
+ type: "pkcs8",
77
+ format,
78
+ ...(passphrase && {
79
+ cipher: "aes-256-cbc",
80
+ passphrase,
81
+ }),
82
+ },
83
+ };
84
+
85
+ // Add type-specific options
86
+ if (type === "rsa") {
87
+ return {
88
+ ...baseOptions,
89
+ modulusLength,
90
+ };
91
+ }
92
+
93
+ if (type === "ecdsa") {
94
+ return {
95
+ ...baseOptions,
96
+ namedCurve: "prime256v1", // Also known as secp256r1 or P-256
97
+ };
98
+ }
99
+
100
+ // Ed25519 doesn't need additional options
101
+ return baseOptions;
102
+ }
103
+
104
+ /**
105
+ * Validate a PEM-formatted key
106
+ *
107
+ * @param key - The key to validate (public or private)
108
+ * @param expectedType - Expected key type (public or private)
109
+ * @returns True if key is valid PEM format
110
+ */
111
+ export function isValidPEMKey(
112
+ key: string,
113
+ expectedType: "public" | "private" = "private"
114
+ ): boolean {
115
+ if (expectedType === "public") {
116
+ return key.includes("-----BEGIN PUBLIC KEY-----") && key.includes("-----END PUBLIC KEY-----");
117
+ }
118
+
119
+ return (
120
+ (key.includes("-----BEGIN PRIVATE KEY-----") && key.includes("-----END PRIVATE KEY-----")) ||
121
+ (key.includes("-----BEGIN ENCRYPTED PRIVATE KEY-----") &&
122
+ key.includes("-----END ENCRYPTED PRIVATE KEY-----"))
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Parse key type from a PEM key string
128
+ *
129
+ * @param key - PEM-formatted key string
130
+ * @returns Detected key type or undefined if cannot be determined
131
+ */
132
+ export function getKeyTypeFromPEM(key: string): KeyType | undefined {
133
+ // Check if it's a valid PEM key first
134
+ if (!isValidPEMKey(key, "private") && !isValidPEMKey(key, "public")) {
135
+ return undefined;
136
+ }
137
+
138
+ // Check for explicit algorithm markers
139
+ if (key.includes("RSA")) {
140
+ return "rsa";
141
+ }
142
+
143
+ // Length-based heuristic (RSA keys are much longer)
144
+ // RSA 2048: ~1700 chars, RSA 4096: ~3200 chars
145
+ // Ed25519: ~120 chars
146
+ // ECDSA P-256: ~200-300 chars
147
+ if (key.length > 1000) {
148
+ return "rsa";
149
+ }
150
+
151
+ if (key.length < 200) {
152
+ return "ed25519";
153
+ }
154
+
155
+ // ECDSA falls somewhere in between
156
+ return "ecdsa";
157
+ }