@enactprotocol/api 2.0.0 → 2.0.1
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/attestations.d.ts +253 -0
- package/dist/attestations.d.ts.map +1 -0
- package/dist/attestations.js +326 -0
- package/dist/attestations.js.map +1 -0
- package/dist/auth.d.ts +169 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +196 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +251 -0
- package/dist/client.js.map +1 -0
- package/dist/download.d.ts +172 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +154 -0
- package/dist/download.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/publish.d.ts +177 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +174 -0
- package/dist/publish.js.map +1 -0
- package/dist/search.d.ts +76 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +66 -0
- package/dist/search.js.map +1 -0
- package/dist/trust.d.ts +123 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +152 -0
- package/dist/trust.js.map +1 -0
- package/dist/types.d.ts +421 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +84 -0
- package/dist/utils.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attestation management (v2)
|
|
3
|
+
* Functions for managing auditor attestations
|
|
4
|
+
*/
|
|
5
|
+
import type { OIDCIdentity, SigstoreBundle } from "@enactprotocol/trust";
|
|
6
|
+
import type { EnactApiClient } from "./client";
|
|
7
|
+
import type { Attestation } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Verified auditor info with full identity details
|
|
10
|
+
*/
|
|
11
|
+
export interface VerifiedAuditor {
|
|
12
|
+
/** Email from attestation */
|
|
13
|
+
email: string;
|
|
14
|
+
/** Full identity from verified certificate (may be undefined if not extractable) */
|
|
15
|
+
identity: OIDCIdentity | undefined;
|
|
16
|
+
/** Provider:identity format (e.g., github:keithagroves) */
|
|
17
|
+
providerIdentity: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attestation list response
|
|
21
|
+
*/
|
|
22
|
+
export interface AttestationListResponse {
|
|
23
|
+
attestations: Attestation[];
|
|
24
|
+
total: number;
|
|
25
|
+
limit: number;
|
|
26
|
+
offset: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get all attestations for a tool version (v2)
|
|
30
|
+
*
|
|
31
|
+
* @param client - API client instance
|
|
32
|
+
* @param name - Tool name
|
|
33
|
+
* @param version - Tool version
|
|
34
|
+
* @param options - Pagination options
|
|
35
|
+
* @returns List of attestations with pagination info
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const result = await getAttestations(client, "alice/utils/greeter", "1.2.0", {
|
|
40
|
+
* limit: 10,
|
|
41
|
+
* offset: 0
|
|
42
|
+
* });
|
|
43
|
+
* console.log(`Found ${result.total} attestations`);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function getAttestations(client: EnactApiClient, name: string, version: string, options?: {
|
|
47
|
+
limit?: number | undefined;
|
|
48
|
+
offset?: number | undefined;
|
|
49
|
+
}): Promise<AttestationListResponse>;
|
|
50
|
+
/**
|
|
51
|
+
* Submit an attestation for a tool version (v2)
|
|
52
|
+
*
|
|
53
|
+
* The server will verify the Sigstore bundle against the public Sigstore
|
|
54
|
+
* infrastructure (Rekor + Fulcio) before accepting.
|
|
55
|
+
*
|
|
56
|
+
* @param client - API client instance (must be authenticated)
|
|
57
|
+
* @param name - Tool name
|
|
58
|
+
* @param version - Tool version
|
|
59
|
+
* @param sigstoreBundle - Complete Sigstore bundle
|
|
60
|
+
* @returns Attestation response with verification result
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const result = await submitAttestation(client, "alice/utils/greeter", "1.2.0", {
|
|
65
|
+
* "$schema": "https://sigstore.dev/bundle/v1",
|
|
66
|
+
* "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
|
67
|
+
* "verificationMaterial": { ... },
|
|
68
|
+
* "messageSignature": { ... }
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* if (result.verification.verified) {
|
|
72
|
+
* console.log("Attestation verified and recorded!");
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function submitAttestation(client: EnactApiClient, name: string, version: string, sigstoreBundle: Record<string, unknown>): Promise<{
|
|
77
|
+
auditor: string;
|
|
78
|
+
auditorProvider: string;
|
|
79
|
+
signedAt: Date;
|
|
80
|
+
rekorLogId: string;
|
|
81
|
+
rekorLogIndex?: number | undefined;
|
|
82
|
+
verification: {
|
|
83
|
+
verified: boolean;
|
|
84
|
+
verifiedAt: Date;
|
|
85
|
+
rekorVerified: boolean;
|
|
86
|
+
certificateVerified: boolean;
|
|
87
|
+
signatureVerified: boolean;
|
|
88
|
+
};
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Revoke an attestation (v2)
|
|
92
|
+
*
|
|
93
|
+
* Only the original auditor can revoke their attestation.
|
|
94
|
+
*
|
|
95
|
+
* @param client - API client instance (must be authenticated)
|
|
96
|
+
* @param name - Tool name
|
|
97
|
+
* @param version - Tool version
|
|
98
|
+
* @param auditorEmail - Email of the auditor (from Sigstore certificate)
|
|
99
|
+
* @returns Revocation confirmation
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* const result = await revokeAttestation(
|
|
104
|
+
* client,
|
|
105
|
+
* "alice/utils/greeter",
|
|
106
|
+
* "1.2.0",
|
|
107
|
+
* "security@example.com"
|
|
108
|
+
* );
|
|
109
|
+
* console.log(`Revoked at ${result.revokedAt}`);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function revokeAttestation(client: EnactApiClient, name: string, version: string, auditorEmail: string): Promise<{
|
|
113
|
+
auditor: string;
|
|
114
|
+
revoked: true;
|
|
115
|
+
revokedAt: Date;
|
|
116
|
+
}>;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a tool version has attestations from specific auditors
|
|
119
|
+
*
|
|
120
|
+
* @param client - API client instance
|
|
121
|
+
* @param name - Tool name
|
|
122
|
+
* @param version - Tool version
|
|
123
|
+
* @param trustedAuditors - List of trusted auditor emails
|
|
124
|
+
* @returns True if at least one trusted auditor has attested
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const isTrusted = await hasAttestation(
|
|
129
|
+
* client,
|
|
130
|
+
* "alice/utils/greeter",
|
|
131
|
+
* "1.2.0",
|
|
132
|
+
* ["security@example.com", "bob@github.com"]
|
|
133
|
+
* );
|
|
134
|
+
*
|
|
135
|
+
* if (isTrusted) {
|
|
136
|
+
* console.log("Tool is trusted!");
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function hasAttestation(client: EnactApiClient, name: string, version: string, trustedAuditors: string[]): Promise<boolean>;
|
|
141
|
+
/**
|
|
142
|
+
* Get the full Sigstore bundle for a specific attestation
|
|
143
|
+
*
|
|
144
|
+
* This fetches the complete Sigstore bundle needed for local verification.
|
|
145
|
+
* Never trust the registry's verification status - always verify locally.
|
|
146
|
+
*
|
|
147
|
+
* @param client - API client instance
|
|
148
|
+
* @param name - Tool name
|
|
149
|
+
* @param version - Tool version
|
|
150
|
+
* @param auditor - Auditor email
|
|
151
|
+
* @returns Complete Sigstore bundle
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const bundle = await getAttestationBundle(
|
|
156
|
+
* client,
|
|
157
|
+
* "alice/utils/greeter",
|
|
158
|
+
* "1.2.0",
|
|
159
|
+
* "security@example.com"
|
|
160
|
+
* );
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export declare function getAttestationBundle(client: EnactApiClient, name: string, version: string, auditor: string): Promise<SigstoreBundle>;
|
|
164
|
+
/**
|
|
165
|
+
* Verify an attestation locally using Sigstore (never trust registry)
|
|
166
|
+
*
|
|
167
|
+
* This performs cryptographic verification against Rekor transparency log,
|
|
168
|
+
* Fulcio certificate authority, and validates the signature. The registry's
|
|
169
|
+
* verification status is NEVER trusted - we always verify locally.
|
|
170
|
+
*
|
|
171
|
+
* @param client - API client instance
|
|
172
|
+
* @param name - Tool name
|
|
173
|
+
* @param version - Tool version
|
|
174
|
+
* @param attestation - Attestation metadata from registry
|
|
175
|
+
* @param bundleHash - Bundle hash to verify against (sha256:...)
|
|
176
|
+
* @returns True if attestation is cryptographically valid
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* const attestations = await getAttestations(client, "alice/utils/greeter", "1.2.0");
|
|
181
|
+
* const attestation = attestations.attestations[0];
|
|
182
|
+
*
|
|
183
|
+
* const isValid = await verifyAttestationLocally(
|
|
184
|
+
* client,
|
|
185
|
+
* "alice/utils/greeter",
|
|
186
|
+
* "1.2.0",
|
|
187
|
+
* attestation,
|
|
188
|
+
* "sha256:abc123..."
|
|
189
|
+
* );
|
|
190
|
+
*
|
|
191
|
+
* if (isValid) {
|
|
192
|
+
* console.log("Attestation cryptographically verified!");
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export declare function verifyAttestationLocally(client: EnactApiClient, name: string, version: string, attestation: Attestation, bundleHash: string): Promise<boolean>;
|
|
197
|
+
/**
|
|
198
|
+
* Verify all attestations for a tool and return verified auditors
|
|
199
|
+
*
|
|
200
|
+
* This verifies all attestations locally and returns only those that pass
|
|
201
|
+
* cryptographic verification. Never trusts the registry's verification status.
|
|
202
|
+
*
|
|
203
|
+
* @param client - API client instance
|
|
204
|
+
* @param name - Tool name
|
|
205
|
+
* @param version - Tool version
|
|
206
|
+
* @param bundleHash - Bundle hash to verify against
|
|
207
|
+
* @returns Array of verified auditor emails
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const verifiedAuditors = await verifyAllAttestations(
|
|
212
|
+
* client,
|
|
213
|
+
* "alice/utils/greeter",
|
|
214
|
+
* "1.2.0",
|
|
215
|
+
* "sha256:abc123..."
|
|
216
|
+
* );
|
|
217
|
+
*
|
|
218
|
+
* console.log(`Verified auditors: ${verifiedAuditors.join(", ")}`);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export declare function verifyAllAttestations(client: EnactApiClient, name: string, version: string, bundleHash: string): Promise<VerifiedAuditor[]>;
|
|
222
|
+
/**
|
|
223
|
+
* Check if a tool has a trusted attestation (with local verification)
|
|
224
|
+
*
|
|
225
|
+
* This checks if at least one attestation exists from a trusted auditor
|
|
226
|
+
* AND verifies it locally. Never trusts the registry's verification status.
|
|
227
|
+
*
|
|
228
|
+
* @param client - API client instance
|
|
229
|
+
* @param name - Tool name
|
|
230
|
+
* @param version - Tool version
|
|
231
|
+
* @param bundleHash - Bundle hash to verify against
|
|
232
|
+
* @param trustedAuditors - List of trusted auditor emails
|
|
233
|
+
* @returns True if at least one trusted auditor's attestation is verified
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const isTrusted = await hasTrustedAttestation(
|
|
238
|
+
* client,
|
|
239
|
+
* "alice/utils/greeter",
|
|
240
|
+
* "1.2.0",
|
|
241
|
+
* "sha256:abc123...",
|
|
242
|
+
* ["security@example.com", "bob@github.com"]
|
|
243
|
+
* );
|
|
244
|
+
*
|
|
245
|
+
* if (isTrusted) {
|
|
246
|
+
* console.log("Tool is trusted and verified!");
|
|
247
|
+
* } else {
|
|
248
|
+
* console.log("No trusted attestations found - use 'enact inspect' to review");
|
|
249
|
+
* }
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
export declare function hasTrustedAttestation(client: EnactApiClient, name: string, version: string, bundleHash: string, trustedAuditors: string[]): Promise<boolean>;
|
|
253
|
+
//# sourceMappingURL=attestations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestations.d.ts","sourceRoot":"","sources":["../src/attestations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attestation management (v2)
|
|
3
|
+
* Functions for managing auditor attestations
|
|
4
|
+
*/
|
|
5
|
+
import { extractIdentityFromBundle, verifyBundle } from "@enactprotocol/trust";
|
|
6
|
+
import { emailToProviderIdentity } from "./utils";
|
|
7
|
+
/**
|
|
8
|
+
* Get all attestations for a tool version (v2)
|
|
9
|
+
*
|
|
10
|
+
* @param client - API client instance
|
|
11
|
+
* @param name - Tool name
|
|
12
|
+
* @param version - Tool version
|
|
13
|
+
* @param options - Pagination options
|
|
14
|
+
* @returns List of attestations with pagination info
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const result = await getAttestations(client, "alice/utils/greeter", "1.2.0", {
|
|
19
|
+
* limit: 10,
|
|
20
|
+
* offset: 0
|
|
21
|
+
* });
|
|
22
|
+
* console.log(`Found ${result.total} attestations`);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export async function getAttestations(client, name, version, options) {
|
|
26
|
+
const params = new URLSearchParams();
|
|
27
|
+
if (options?.limit !== undefined) {
|
|
28
|
+
params.set("limit", String(Math.min(options.limit, 100)));
|
|
29
|
+
}
|
|
30
|
+
if (options?.offset !== undefined) {
|
|
31
|
+
params.set("offset", String(options.offset));
|
|
32
|
+
}
|
|
33
|
+
const queryString = params.toString();
|
|
34
|
+
const path = `/tools/${name}/versions/${version}/attestations${queryString ? `?${queryString}` : ""}`;
|
|
35
|
+
const response = await client.get(path);
|
|
36
|
+
return response.data;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Submit an attestation for a tool version (v2)
|
|
40
|
+
*
|
|
41
|
+
* The server will verify the Sigstore bundle against the public Sigstore
|
|
42
|
+
* infrastructure (Rekor + Fulcio) before accepting.
|
|
43
|
+
*
|
|
44
|
+
* @param client - API client instance (must be authenticated)
|
|
45
|
+
* @param name - Tool name
|
|
46
|
+
* @param version - Tool version
|
|
47
|
+
* @param sigstoreBundle - Complete Sigstore bundle
|
|
48
|
+
* @returns Attestation response with verification result
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const result = await submitAttestation(client, "alice/utils/greeter", "1.2.0", {
|
|
53
|
+
* "$schema": "https://sigstore.dev/bundle/v1",
|
|
54
|
+
* "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
|
|
55
|
+
* "verificationMaterial": { ... },
|
|
56
|
+
* "messageSignature": { ... }
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* if (result.verification.verified) {
|
|
60
|
+
* console.log("Attestation verified and recorded!");
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export async function submitAttestation(client, name, version, sigstoreBundle) {
|
|
65
|
+
const response = await client.post(`/tools/${name}/versions/${version}/attestations`, {
|
|
66
|
+
bundle: sigstoreBundle,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
auditor: response.data.auditor,
|
|
70
|
+
auditorProvider: response.data.auditor_provider,
|
|
71
|
+
signedAt: new Date(response.data.signed_at),
|
|
72
|
+
rekorLogId: response.data.rekor_log_id,
|
|
73
|
+
rekorLogIndex: response.data.rekor_log_index,
|
|
74
|
+
verification: {
|
|
75
|
+
verified: response.data.verification.verified,
|
|
76
|
+
verifiedAt: new Date(response.data.verification.verified_at),
|
|
77
|
+
rekorVerified: response.data.verification.rekor_verified,
|
|
78
|
+
certificateVerified: response.data.verification.certificate_verified,
|
|
79
|
+
signatureVerified: response.data.verification.signature_verified,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Revoke an attestation (v2)
|
|
85
|
+
*
|
|
86
|
+
* Only the original auditor can revoke their attestation.
|
|
87
|
+
*
|
|
88
|
+
* @param client - API client instance (must be authenticated)
|
|
89
|
+
* @param name - Tool name
|
|
90
|
+
* @param version - Tool version
|
|
91
|
+
* @param auditorEmail - Email of the auditor (from Sigstore certificate)
|
|
92
|
+
* @returns Revocation confirmation
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const result = await revokeAttestation(
|
|
97
|
+
* client,
|
|
98
|
+
* "alice/utils/greeter",
|
|
99
|
+
* "1.2.0",
|
|
100
|
+
* "security@example.com"
|
|
101
|
+
* );
|
|
102
|
+
* console.log(`Revoked at ${result.revokedAt}`);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export async function revokeAttestation(client, name, version, auditorEmail) {
|
|
106
|
+
const encodedEmail = encodeURIComponent(auditorEmail);
|
|
107
|
+
const response = await client.delete(`/tools/${name}/versions/${version}/attestations?auditor=${encodedEmail}`);
|
|
108
|
+
return {
|
|
109
|
+
auditor: response.data.auditor,
|
|
110
|
+
revoked: response.data.revoked,
|
|
111
|
+
revokedAt: new Date(response.data.revoked_at),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if a tool version has attestations from specific auditors
|
|
116
|
+
*
|
|
117
|
+
* @param client - API client instance
|
|
118
|
+
* @param name - Tool name
|
|
119
|
+
* @param version - Tool version
|
|
120
|
+
* @param trustedAuditors - List of trusted auditor emails
|
|
121
|
+
* @returns True if at least one trusted auditor has attested
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const isTrusted = await hasAttestation(
|
|
126
|
+
* client,
|
|
127
|
+
* "alice/utils/greeter",
|
|
128
|
+
* "1.2.0",
|
|
129
|
+
* ["security@example.com", "bob@github.com"]
|
|
130
|
+
* );
|
|
131
|
+
*
|
|
132
|
+
* if (isTrusted) {
|
|
133
|
+
* console.log("Tool is trusted!");
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export async function hasAttestation(client, name, version, trustedAuditors) {
|
|
138
|
+
const result = await getAttestations(client, name, version);
|
|
139
|
+
return result.attestations.some((attestation) => trustedAuditors.includes(attestation.auditor) && attestation.verification?.verified === true);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the full Sigstore bundle for a specific attestation
|
|
143
|
+
*
|
|
144
|
+
* This fetches the complete Sigstore bundle needed for local verification.
|
|
145
|
+
* Never trust the registry's verification status - always verify locally.
|
|
146
|
+
*
|
|
147
|
+
* @param client - API client instance
|
|
148
|
+
* @param name - Tool name
|
|
149
|
+
* @param version - Tool version
|
|
150
|
+
* @param auditor - Auditor email
|
|
151
|
+
* @returns Complete Sigstore bundle
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const bundle = await getAttestationBundle(
|
|
156
|
+
* client,
|
|
157
|
+
* "alice/utils/greeter",
|
|
158
|
+
* "1.2.0",
|
|
159
|
+
* "security@example.com"
|
|
160
|
+
* );
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export async function getAttestationBundle(client, name, version, auditor) {
|
|
164
|
+
const encodedAuditor = encodeURIComponent(auditor);
|
|
165
|
+
const response = await client.get(`/tools/${name}/versions/${version}/trust/attestations/${encodedAuditor}`);
|
|
166
|
+
return response.data;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Verify an attestation locally using Sigstore (never trust registry)
|
|
170
|
+
*
|
|
171
|
+
* This performs cryptographic verification against Rekor transparency log,
|
|
172
|
+
* Fulcio certificate authority, and validates the signature. The registry's
|
|
173
|
+
* verification status is NEVER trusted - we always verify locally.
|
|
174
|
+
*
|
|
175
|
+
* @param client - API client instance
|
|
176
|
+
* @param name - Tool name
|
|
177
|
+
* @param version - Tool version
|
|
178
|
+
* @param attestation - Attestation metadata from registry
|
|
179
|
+
* @param bundleHash - Bundle hash to verify against (sha256:...)
|
|
180
|
+
* @returns True if attestation is cryptographically valid
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const attestations = await getAttestations(client, "alice/utils/greeter", "1.2.0");
|
|
185
|
+
* const attestation = attestations.attestations[0];
|
|
186
|
+
*
|
|
187
|
+
* const isValid = await verifyAttestationLocally(
|
|
188
|
+
* client,
|
|
189
|
+
* "alice/utils/greeter",
|
|
190
|
+
* "1.2.0",
|
|
191
|
+
* attestation,
|
|
192
|
+
* "sha256:abc123..."
|
|
193
|
+
* );
|
|
194
|
+
*
|
|
195
|
+
* if (isValid) {
|
|
196
|
+
* console.log("Attestation cryptographically verified!");
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export async function verifyAttestationLocally(client, name, version, attestation, bundleHash) {
|
|
201
|
+
try {
|
|
202
|
+
// Fetch the full Sigstore bundle from registry
|
|
203
|
+
const bundle = await getAttestationBundle(client, name, version, attestation.auditor);
|
|
204
|
+
// Convert bundle hash to Buffer for verification
|
|
205
|
+
const hashWithoutPrefix = bundleHash.replace("sha256:", "");
|
|
206
|
+
const artifactHash = Buffer.from(hashWithoutPrefix, "hex");
|
|
207
|
+
// Verify using @enactprotocol/trust package (checks Rekor, Fulcio, signatures)
|
|
208
|
+
const result = await verifyBundle(bundle, artifactHash, {
|
|
209
|
+
expectedIdentity: {
|
|
210
|
+
subjectAlternativeName: attestation.auditor,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
return result.verified;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
// Verification failed - log error and return false
|
|
217
|
+
console.error(`Attestation verification failed for ${attestation.auditor}:`, error);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Verify all attestations for a tool and return verified auditors
|
|
223
|
+
*
|
|
224
|
+
* This verifies all attestations locally and returns only those that pass
|
|
225
|
+
* cryptographic verification. Never trusts the registry's verification status.
|
|
226
|
+
*
|
|
227
|
+
* @param client - API client instance
|
|
228
|
+
* @param name - Tool name
|
|
229
|
+
* @param version - Tool version
|
|
230
|
+
* @param bundleHash - Bundle hash to verify against
|
|
231
|
+
* @returns Array of verified auditor emails
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const verifiedAuditors = await verifyAllAttestations(
|
|
236
|
+
* client,
|
|
237
|
+
* "alice/utils/greeter",
|
|
238
|
+
* "1.2.0",
|
|
239
|
+
* "sha256:abc123..."
|
|
240
|
+
* );
|
|
241
|
+
*
|
|
242
|
+
* console.log(`Verified auditors: ${verifiedAuditors.join(", ")}`);
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export async function verifyAllAttestations(client, name, version, bundleHash) {
|
|
246
|
+
const { attestations } = await getAttestations(client, name, version);
|
|
247
|
+
// Verify all attestations in parallel
|
|
248
|
+
const verificationResults = await Promise.all(attestations.map(async (attestation) => {
|
|
249
|
+
try {
|
|
250
|
+
// Fetch the full Sigstore bundle
|
|
251
|
+
const bundle = await getAttestationBundle(client, name, version, attestation.auditor);
|
|
252
|
+
// Convert bundle hash to Buffer for verification
|
|
253
|
+
const hashWithoutPrefix = bundleHash.replace("sha256:", "");
|
|
254
|
+
const artifactHash = Buffer.from(hashWithoutPrefix, "hex");
|
|
255
|
+
// Verify the bundle
|
|
256
|
+
const result = await verifyBundle(bundle, artifactHash, {
|
|
257
|
+
expectedIdentity: {
|
|
258
|
+
subjectAlternativeName: attestation.auditor,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
if (result.verified) {
|
|
262
|
+
// Extract full identity from the verified bundle
|
|
263
|
+
const identity = extractIdentityFromBundle(bundle);
|
|
264
|
+
// Build provider:identity format using issuer info
|
|
265
|
+
const providerIdentity = emailToProviderIdentity(attestation.auditor, identity?.issuer, identity?.username);
|
|
266
|
+
return {
|
|
267
|
+
auditor: attestation.auditor,
|
|
268
|
+
identity,
|
|
269
|
+
providerIdentity,
|
|
270
|
+
isValid: true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { auditor: attestation.auditor, isValid: false };
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return { auditor: attestation.auditor, isValid: false };
|
|
277
|
+
}
|
|
278
|
+
}));
|
|
279
|
+
// Return only verified auditors with full identity info
|
|
280
|
+
return verificationResults
|
|
281
|
+
.filter((result) => result.isValid)
|
|
282
|
+
.map((result) => ({
|
|
283
|
+
email: result.auditor,
|
|
284
|
+
identity: result.identity,
|
|
285
|
+
providerIdentity: result.providerIdentity,
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Check if a tool has a trusted attestation (with local verification)
|
|
290
|
+
*
|
|
291
|
+
* This checks if at least one attestation exists from a trusted auditor
|
|
292
|
+
* AND verifies it locally. Never trusts the registry's verification status.
|
|
293
|
+
*
|
|
294
|
+
* @param client - API client instance
|
|
295
|
+
* @param name - Tool name
|
|
296
|
+
* @param version - Tool version
|
|
297
|
+
* @param bundleHash - Bundle hash to verify against
|
|
298
|
+
* @param trustedAuditors - List of trusted auditor emails
|
|
299
|
+
* @returns True if at least one trusted auditor's attestation is verified
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* const isTrusted = await hasTrustedAttestation(
|
|
304
|
+
* client,
|
|
305
|
+
* "alice/utils/greeter",
|
|
306
|
+
* "1.2.0",
|
|
307
|
+
* "sha256:abc123...",
|
|
308
|
+
* ["security@example.com", "bob@github.com"]
|
|
309
|
+
* );
|
|
310
|
+
*
|
|
311
|
+
* if (isTrusted) {
|
|
312
|
+
* console.log("Tool is trusted and verified!");
|
|
313
|
+
* } else {
|
|
314
|
+
* console.log("No trusted attestations found - use 'enact inspect' to review");
|
|
315
|
+
* }
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
export async function hasTrustedAttestation(client, name, version, bundleHash, trustedAuditors) {
|
|
319
|
+
const verifiedAuditors = await verifyAllAttestations(client, name, version, bundleHash);
|
|
320
|
+
// Check if any verified auditor's providerIdentity matches a trusted auditor
|
|
321
|
+
// providerIdentity is in format "github:username" or "github:email@domain.com"
|
|
322
|
+
return verifiedAuditors.some((auditor) => {
|
|
323
|
+
return trustedAuditors.includes(auditor.providerIdentity);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=attestations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestations.js","sourceRoot":"","sources":["../src/attestations.ts"],"names":[],"mappings":"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"}
|