@freedomofpress/sigstore-browser 0.1.15 → 0.1.17
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/bundle.d.ts +2 -3
- package/dist/bundle.js.map +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js.map +1 -1
- package/dist/dsse.d.ts +0 -1
- package/dist/dsse.js +4 -9
- package/dist/dsse.js.map +1 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +1 -2
- package/dist/interfaces.js +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/oid.d.ts +0 -1
- package/dist/oid.js.map +1 -1
- package/dist/policy.d.ts +0 -1
- package/dist/policy.js +1 -1
- package/dist/policy.js.map +1 -1
- package/dist/rfc3161/error.d.ts +0 -1
- package/dist/rfc3161/error.js.map +1 -1
- package/dist/rfc3161/index.d.ts +0 -1
- package/dist/rfc3161/index.js.map +1 -1
- package/dist/rfc3161/timestamp.d.ts +0 -1
- package/dist/rfc3161/timestamp.js +3 -3
- package/dist/rfc3161/timestamp.js.map +1 -1
- package/dist/rfc3161/tstinfo.d.ts +0 -1
- package/dist/rfc3161/tstinfo.js +2 -3
- package/dist/rfc3161/tstinfo.js.map +1 -1
- package/dist/sigstore.d.ts +2 -3
- package/dist/sigstore.js +11 -14
- package/dist/sigstore.js.map +1 -1
- package/dist/timestamp/tsa.d.ts +1 -2
- package/dist/timestamp/tsa.js +1 -1
- package/dist/timestamp/tsa.js.map +1 -1
- package/dist/tlog/body.d.ts +0 -1
- package/dist/tlog/body.js +1 -1
- package/dist/tlog/body.js.map +1 -1
- package/dist/tlog/checkpoint.d.ts +0 -1
- package/dist/tlog/checkpoint.js.map +1 -1
- package/dist/tlog/dsse.d.ts +0 -1
- package/dist/tlog/dsse.js +6 -4
- package/dist/tlog/dsse.js.map +1 -1
- package/dist/tlog/hashedrekord.d.ts +0 -1
- package/dist/tlog/hashedrekord.js.map +1 -1
- package/dist/tlog/intoto.d.ts +0 -1
- package/dist/tlog/intoto.js +2 -3
- package/dist/tlog/intoto.js.map +1 -1
- package/dist/tlog/merkle.d.ts +0 -1
- package/dist/tlog/merkle.js +8 -102
- package/dist/tlog/merkle.js.map +1 -1
- package/dist/trust/tuf-root.d.ts +0 -1
- package/dist/trust/tuf-root.js.map +1 -1
- package/dist/trust/tuf.d.ts +0 -1
- package/dist/trust/tuf.js +2 -5
- package/dist/trust/tuf.js.map +1 -1
- package/dist/x509/cert.d.ts +0 -1
- package/dist/x509/cert.js +2 -2
- package/dist/x509/cert.js.map +1 -1
- package/dist/x509/chain.d.ts +0 -1
- package/dist/x509/chain.js +2 -2
- package/dist/x509/chain.js.map +1 -1
- package/dist/x509/ext.d.ts +0 -1
- package/dist/x509/ext.js.map +1 -1
- package/dist/x509/index.d.ts +1 -2
- package/dist/x509/index.js +1 -1
- package/dist/x509/index.js.map +1 -1
- package/dist/x509/sct.d.ts +0 -1
- package/dist/x509/sct.js.map +1 -1
- package/package.json +16 -9
- package/.github/workflows/conformance.yml +0 -29
- package/.github/workflows/publish.yml +0 -47
- package/dist/bundle.d.ts.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/dsse.d.ts.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/interfaces.d.ts.map +0 -1
- package/dist/oid.d.ts.map +0 -1
- package/dist/policy.d.ts.map +0 -1
- package/dist/rfc3161/error.d.ts.map +0 -1
- package/dist/rfc3161/index.d.ts.map +0 -1
- package/dist/rfc3161/timestamp.d.ts.map +0 -1
- package/dist/rfc3161/tstinfo.d.ts.map +0 -1
- package/dist/sigstore.d.ts.map +0 -1
- package/dist/timestamp/tsa.d.ts.map +0 -1
- package/dist/tlog/body.d.ts.map +0 -1
- package/dist/tlog/checkpoint.d.ts.map +0 -1
- package/dist/tlog/dsse.d.ts.map +0 -1
- package/dist/tlog/hashedrekord.d.ts.map +0 -1
- package/dist/tlog/intoto.d.ts.map +0 -1
- package/dist/tlog/merkle.d.ts.map +0 -1
- package/dist/trust/tuf-root.d.ts.map +0 -1
- package/dist/trust/tuf.d.ts.map +0 -1
- package/dist/x509/cert.d.ts.map +0 -1
- package/dist/x509/chain.d.ts.map +0 -1
- package/dist/x509/ext.d.ts.map +0 -1
- package/dist/x509/index.d.ts.map +0 -1
- package/dist/x509/sct.d.ts.map +0 -1
- package/eslint.config.mjs +0 -31
- package/src/bundle.ts +0 -186
- package/src/cli.ts +0 -290
- package/src/dsse.ts +0 -49
- package/src/ed25519.browser.test.ts +0 -76
- package/src/errors.ts +0 -82
- package/src/index.browser.test.ts +0 -301
- package/src/index.ts +0 -93
- package/src/interfaces.ts +0 -150
- package/src/oid.ts +0 -34
- package/src/policy.ts +0 -476
- package/src/rfc3161/error.ts +0 -16
- package/src/rfc3161/index.ts +0 -17
- package/src/rfc3161/timestamp.ts +0 -268
- package/src/rfc3161/tstinfo.ts +0 -78
- package/src/security.browser.test.ts +0 -249
- package/src/sigstore.ts +0 -605
- package/src/timestamp/tsa.ts +0 -212
- package/src/tlog/body.ts +0 -84
- package/src/tlog/checkpoint.ts +0 -154
- package/src/tlog/dsse.ts +0 -153
- package/src/tlog/hashedrekord.ts +0 -136
- package/src/tlog/intoto.ts +0 -109
- package/src/tlog/merkle.ts +0 -152
- package/src/trust/README.md +0 -38
- package/src/trust/tuf-root.ts +0 -3
- package/src/trust/tuf.ts +0 -233
- package/src/verify.browser.test.ts +0 -281
- package/src/x509/cert.ts +0 -569
- package/src/x509/chain.ts +0 -212
- package/src/x509/ext.ts +0 -356
- package/src/x509/index.ts +0 -19
- package/src/x509/sct.ts +0 -156
- package/test/fixtures/rekor2.ts +0 -14
- package/test/fixtures/sigstore.ts +0 -13
- package/tsconfig.json +0 -18
- package/vite.config.playwright.ts +0 -14
- package/vite.config.ts +0 -18
package/src/cli.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createHash, webcrypto } from "node:crypto";
|
|
3
|
-
import { access, readFile } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
base64ToUint8Array,
|
|
8
|
-
hexToUint8Array,
|
|
9
|
-
Uint8ArrayToHex,
|
|
10
|
-
Uint8ArrayToString,
|
|
11
|
-
} from "@freedomofpress/crypto-browser";
|
|
12
|
-
import { SigstoreBundle } from "./bundle.js";
|
|
13
|
-
import { TrustedRoot } from "./interfaces.js";
|
|
14
|
-
import { SigstoreVerifier } from "./sigstore.js";
|
|
15
|
-
import { TrustedRootProvider } from "./trust/tuf.js";
|
|
16
|
-
|
|
17
|
-
// Ensure the global Web Crypto implementation is available when running under Node.js
|
|
18
|
-
if (typeof globalThis.crypto === "undefined") {
|
|
19
|
-
globalThis.crypto = webcrypto as unknown as Crypto;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface CLIOptions {
|
|
23
|
-
bundlePath: string;
|
|
24
|
-
certificateIdentity: string;
|
|
25
|
-
certificateOidcIssuer: string;
|
|
26
|
-
trustedRootPath: string | undefined;
|
|
27
|
-
artifactInput: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type ArtifactInput =
|
|
31
|
-
| { type: "file"; path: string; data: Uint8Array; digestHex: string }
|
|
32
|
-
| { type: "digest"; digestHex: string };
|
|
33
|
-
|
|
34
|
-
const USAGE = `Usage: sigstore-browser verify-bundle [--staging] --bundle FILE --certificate-identity IDENTITY --certificate-oidc-issuer URL [--trusted-root FILE] FILE_OR_DIGEST`;
|
|
35
|
-
|
|
36
|
-
function printUsage(): void {
|
|
37
|
-
console.error(USAGE);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function parseArgs(argv: string[]): CLIOptions {
|
|
41
|
-
const options: Partial<CLIOptions> = {};
|
|
42
|
-
let command: string | undefined;
|
|
43
|
-
let artifactInput: string | undefined;
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < argv.length; i++) {
|
|
46
|
-
const current = argv[i];
|
|
47
|
-
|
|
48
|
-
if (current === "--help" || current === "-h") {
|
|
49
|
-
printUsage();
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (current.startsWith("--")) {
|
|
54
|
-
const [name, providedValue] = current.split("=", 2);
|
|
55
|
-
|
|
56
|
-
switch (name) {
|
|
57
|
-
case "--bundle": {
|
|
58
|
-
const value = providedValue ?? argv[++i];
|
|
59
|
-
if (!value) {
|
|
60
|
-
throw new Error("Missing value for --bundle");
|
|
61
|
-
}
|
|
62
|
-
options.bundlePath = value;
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
case "--certificate-identity": {
|
|
66
|
-
const value = providedValue ?? argv[++i];
|
|
67
|
-
if (!value) {
|
|
68
|
-
throw new Error("Missing value for --certificate-identity");
|
|
69
|
-
}
|
|
70
|
-
options.certificateIdentity = value;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
case "--certificate-oidc-issuer": {
|
|
74
|
-
const value = providedValue ?? argv[++i];
|
|
75
|
-
if (!value) {
|
|
76
|
-
throw new Error("Missing value for --certificate-oidc-issuer");
|
|
77
|
-
}
|
|
78
|
-
options.certificateOidcIssuer = value;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
case "--trusted-root": {
|
|
82
|
-
const value = providedValue ?? argv[++i];
|
|
83
|
-
if (!value) {
|
|
84
|
-
throw new Error("Missing value for --trusted-root");
|
|
85
|
-
}
|
|
86
|
-
options.trustedRootPath = value;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
default:
|
|
90
|
-
throw new Error(`Unknown option: ${name}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!command) {
|
|
97
|
-
command = current;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!artifactInput) {
|
|
102
|
-
artifactInput = current;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
throw new Error(`Unexpected argument: ${current}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (command !== "verify-bundle") {
|
|
110
|
-
throw new Error("No command provided. Expected 'verify-bundle'.");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!artifactInput) {
|
|
114
|
-
throw new Error("Missing FILE_OR_DIGEST argument.");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!options.bundlePath) {
|
|
118
|
-
throw new Error("Missing required option --bundle");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!options.certificateIdentity) {
|
|
122
|
-
throw new Error("Missing required option --certificate-identity");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!options.certificateOidcIssuer) {
|
|
126
|
-
throw new Error("Missing required option --certificate-oidc-issuer");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
bundlePath: options.bundlePath,
|
|
131
|
-
certificateIdentity: options.certificateIdentity,
|
|
132
|
-
certificateOidcIssuer: options.certificateOidcIssuer,
|
|
133
|
-
trustedRootPath: options.trustedRootPath,
|
|
134
|
-
artifactInput,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
139
|
-
try {
|
|
140
|
-
await access(filePath);
|
|
141
|
-
return true;
|
|
142
|
-
} catch {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function resolveArtifact(input: string): Promise<ArtifactInput> {
|
|
148
|
-
const digestPrefix = "sha256:";
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
input.startsWith(digestPrefix) &&
|
|
152
|
-
input.length === digestPrefix.length + 64 &&
|
|
153
|
-
/^[0-9a-fA-F]+$/.test(input.slice(digestPrefix.length)) &&
|
|
154
|
-
!(await fileExists(input))
|
|
155
|
-
) {
|
|
156
|
-
return {
|
|
157
|
-
type: "digest",
|
|
158
|
-
digestHex: input.slice(digestPrefix.length).toLowerCase(),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const resolvedPath = path.resolve(input);
|
|
163
|
-
const artifactBuffer = await readFile(resolvedPath);
|
|
164
|
-
const digestHex = createHash("sha256").update(artifactBuffer).digest("hex");
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
type: "file",
|
|
168
|
-
path: resolvedPath,
|
|
169
|
-
data: new Uint8Array(artifactBuffer),
|
|
170
|
-
digestHex,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function loadTrustedRoot(
|
|
175
|
-
pathInput: string | undefined,
|
|
176
|
-
): Promise<TrustedRoot> {
|
|
177
|
-
if (!pathInput) {
|
|
178
|
-
// Use TUF to fetch the latest trusted root (matches sigstore-js behavior)
|
|
179
|
-
const tufProvider = new TrustedRootProvider({
|
|
180
|
-
metadataUrl: "https://tuf-repo-cdn.sigstore.dev/",
|
|
181
|
-
targetBaseUrl: "https://tuf-repo-cdn.sigstore.dev/targets/",
|
|
182
|
-
});
|
|
183
|
-
return await tufProvider.getTrustedRoot();
|
|
184
|
-
}
|
|
185
|
-
const resolvedPath = path.resolve(pathInput);
|
|
186
|
-
const raw = await readFile(resolvedPath, "utf8");
|
|
187
|
-
return JSON.parse(raw) as TrustedRoot;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function loadBundle(bundlePath: string): Promise<SigstoreBundle> {
|
|
191
|
-
const resolved = path.resolve(bundlePath);
|
|
192
|
-
const raw = await readFile(resolved, "utf8");
|
|
193
|
-
return JSON.parse(raw) as SigstoreBundle;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function verifyBundle(options: CLIOptions): Promise<void> {
|
|
197
|
-
const [trustedRoot, bundle, artifact] = await Promise.all([
|
|
198
|
-
loadTrustedRoot(options.trustedRootPath),
|
|
199
|
-
loadBundle(options.bundlePath),
|
|
200
|
-
resolveArtifact(options.artifactInput),
|
|
201
|
-
]);
|
|
202
|
-
|
|
203
|
-
const verifier = new SigstoreVerifier();
|
|
204
|
-
await verifier.loadSigstoreRoot(trustedRoot);
|
|
205
|
-
|
|
206
|
-
// Extract and verify the artifact digest from the bundle
|
|
207
|
-
let bundleDigestHex: string;
|
|
208
|
-
let bundleDigestBytes: Uint8Array;
|
|
209
|
-
|
|
210
|
-
if (bundle.messageSignature) {
|
|
211
|
-
// Regular bundle: digest is in messageSignature
|
|
212
|
-
if (bundle.messageSignature.messageDigest.algorithm !== "SHA2_256") {
|
|
213
|
-
throw new Error(
|
|
214
|
-
`Unsupported message digest algorithm: ${bundle.messageSignature.messageDigest.algorithm}`,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
bundleDigestBytes = base64ToUint8Array(
|
|
219
|
-
bundle.messageSignature.messageDigest.digest,
|
|
220
|
-
);
|
|
221
|
-
bundleDigestHex = Uint8ArrayToHex(bundleDigestBytes).toLowerCase();
|
|
222
|
-
} else if (bundle.dsseEnvelope) {
|
|
223
|
-
// DSSE bundle: digest is in the in-toto statement payload
|
|
224
|
-
const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
|
|
225
|
-
const payload = JSON.parse(Uint8ArrayToString(payloadBytes));
|
|
226
|
-
|
|
227
|
-
if (!payload.subject || payload.subject.length === 0) {
|
|
228
|
-
throw new Error("DSSE payload has no subject");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Find matching subject by comparing artifact digest with all subjects
|
|
232
|
-
let matchedSubject = null;
|
|
233
|
-
for (const subject of payload.subject) {
|
|
234
|
-
const subjectDigest = subject.digest?.["sha256"];
|
|
235
|
-
if (subjectDigest && artifact.digestHex === subjectDigest.toLowerCase()) {
|
|
236
|
-
matchedSubject = subject;
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!matchedSubject) {
|
|
242
|
-
throw new Error(
|
|
243
|
-
`Artifact digest ${artifact.digestHex} does not match any subject in DSSE payload`
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
bundleDigestHex = matchedSubject.digest["sha256"].toLowerCase();
|
|
248
|
-
|
|
249
|
-
// Convert hex digest to bytes for verification target
|
|
250
|
-
bundleDigestBytes = hexToUint8Array(bundleDigestHex);
|
|
251
|
-
} else {
|
|
252
|
-
throw new Error("Bundle does not contain a message signature or DSSE envelope");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// For non-DSSE bundles, verify the digest matches
|
|
256
|
-
if (bundle.messageSignature && artifact.digestHex !== bundleDigestHex) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
"Artifact digest does not match the digest embedded in the bundle.",
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const verificationTarget =
|
|
263
|
-
artifact.type === "file" ? artifact.data : bundleDigestBytes;
|
|
264
|
-
const isDigestOnly = artifact.type === "digest";
|
|
265
|
-
|
|
266
|
-
await verifier.verifyArtifact(
|
|
267
|
-
options.certificateIdentity,
|
|
268
|
-
options.certificateOidcIssuer,
|
|
269
|
-
bundle,
|
|
270
|
-
verificationTarget,
|
|
271
|
-
isDigestOnly,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function main(): Promise<void> {
|
|
276
|
-
try {
|
|
277
|
-
const options = parseArgs(process.argv.slice(2));
|
|
278
|
-
await verifyBundle(options);
|
|
279
|
-
console.log("Bundle verification succeeded.");
|
|
280
|
-
} catch (error) {
|
|
281
|
-
if (error instanceof Error) {
|
|
282
|
-
console.error(error.message);
|
|
283
|
-
} else {
|
|
284
|
-
console.error(error);
|
|
285
|
-
}
|
|
286
|
-
process.exitCode = 1;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
await main();
|
package/src/dsse.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2023 The Sigstore Authors.
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
const PAE_PREFIX = "DSSEv1";
|
|
17
|
-
|
|
18
|
-
// DSSE Pre-Authentication Encoding
|
|
19
|
-
export function preAuthEncoding(
|
|
20
|
-
payloadType: string,
|
|
21
|
-
payload: Uint8Array,
|
|
22
|
-
): Uint8Array {
|
|
23
|
-
const prefix = [
|
|
24
|
-
PAE_PREFIX,
|
|
25
|
-
payloadType.length,
|
|
26
|
-
payloadType,
|
|
27
|
-
payload.length,
|
|
28
|
-
"",
|
|
29
|
-
].join(" ");
|
|
30
|
-
|
|
31
|
-
// DSSE spec requires ASCII encoding for the prefix
|
|
32
|
-
// TextEncoder will throw if the string contains non-ASCII characters
|
|
33
|
-
const encoder = new TextEncoder();
|
|
34
|
-
const prefixBuffer = encoder.encode(prefix);
|
|
35
|
-
|
|
36
|
-
// Verify all characters are ASCII (< 128)
|
|
37
|
-
for (let i = 0; i < prefixBuffer.length; i++) {
|
|
38
|
-
if (prefixBuffer[i] > 127) {
|
|
39
|
-
throw new Error(`Invalid non-ASCII character in PAE prefix at position ${i}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Concatenate prefix and payload
|
|
44
|
-
const combinedArray = new Uint8Array(prefixBuffer.length + payload.length);
|
|
45
|
-
|
|
46
|
-
combinedArray.set(prefixBuffer, 0);
|
|
47
|
-
combinedArray.set(payload, prefixBuffer.length);
|
|
48
|
-
return combinedArray;
|
|
49
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
// Replace crypto.subtle with a wrapper whose Ed25519 operations throw NotSupportedError, as browsers without native
|
|
4
|
-
// Ed25519 do. It must be in place before crypto-browser is first imported, because it probes once and caches the result.
|
|
5
|
-
const real = globalThis.crypto.subtle;
|
|
6
|
-
const noEd25519 = new Proxy(real, {
|
|
7
|
-
get(target, prop) {
|
|
8
|
-
const value = Reflect.get(target, prop) as unknown;
|
|
9
|
-
if (typeof value !== "function") return value;
|
|
10
|
-
return (...args: unknown[]) => {
|
|
11
|
-
const alg = (prop === "importKey" ? args[2] : args[0]) as { name?: string } | undefined;
|
|
12
|
-
if (alg?.name === "Ed25519") {
|
|
13
|
-
return Promise.reject(new DOMException("Unrecognized name.", "NotSupportedError"));
|
|
14
|
-
}
|
|
15
|
-
return (value as (...a: unknown[]) => unknown).apply(target, args);
|
|
16
|
-
};
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
Object.defineProperty(globalThis.crypto, "subtle", { value: noEd25519, configurable: true });
|
|
20
|
-
|
|
21
|
-
describe("Fix 7: Ed25519 log keys without native WebCrypto support", () => {
|
|
22
|
-
it("imports trusted root Ed25519 keys through the fallback and verifies a checkpoint", async () => {
|
|
23
|
-
await expect(crypto.subtle.generateKey({ name: "Ed25519" }, false, ["sign", "verify"])).rejects.toThrow();
|
|
24
|
-
|
|
25
|
-
const { SigstoreVerifier } = await import("./sigstore.js");
|
|
26
|
-
const { verifyCheckpoint } = await import("./tlog/checkpoint.js");
|
|
27
|
-
const { Uint8ArrayToBase64 } = await import("@freedomofpress/crypto-browser");
|
|
28
|
-
const { ed25519 } = await import("@noble/curves/ed25519.js");
|
|
29
|
-
const { trustRoot } = await import("../test/fixtures/sigstore.js");
|
|
30
|
-
|
|
31
|
-
// The real trusted root carries an SPKI-encoded Ed25519 key for Rekor v2.
|
|
32
|
-
const keys = await new SigstoreVerifier().loadLog(trustRoot().tlogs);
|
|
33
|
-
const rekorV2 = keys.find((k) => k.publicKey.algorithm.name === "Ed25519");
|
|
34
|
-
expect((rekorV2?.publicKey as unknown as { bytes: Uint8Array }).bytes).toHaveLength(32);
|
|
35
|
-
|
|
36
|
-
// A synthetic log whose key we control, signing a checkpoint note.
|
|
37
|
-
const priv = ed25519.utils.randomSecretKey();
|
|
38
|
-
const pub = ed25519.getPublicKey(priv);
|
|
39
|
-
const spki = new Uint8Array([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, ...pub]);
|
|
40
|
-
const logId = new Uint8Array(await crypto.subtle.digest("SHA-256", spki));
|
|
41
|
-
const [log] = await new SigstoreVerifier().loadLog([
|
|
42
|
-
{
|
|
43
|
-
baseUrl: "https://log.test",
|
|
44
|
-
hashAlgorithm: "SHA2_256",
|
|
45
|
-
publicKey: { rawBytes: Uint8ArrayToBase64(spki), keyDetails: "PKIX_ED25519", validFor: { start: "2025-01-01T00:00:00Z" } },
|
|
46
|
-
logId: { keyId: Uint8ArrayToBase64(logId) },
|
|
47
|
-
},
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
const rootHash = Uint8ArrayToBase64(new Uint8Array(32));
|
|
51
|
-
const note = `log.test\n7\n${rootHash}\n`;
|
|
52
|
-
const sig = ed25519.sign(new TextEncoder().encode(note), priv);
|
|
53
|
-
const sigLine = Uint8ArrayToBase64(new Uint8Array([...logId.subarray(0, 4), ...sig]));
|
|
54
|
-
const proof = (n: string) => ({
|
|
55
|
-
logIndex: "0",
|
|
56
|
-
hashes: [],
|
|
57
|
-
treeSize: "7",
|
|
58
|
-
rootHash,
|
|
59
|
-
checkpoint: { envelope: `${n}\n— log.test ${sigLine}\n` },
|
|
60
|
-
});
|
|
61
|
-
const entry = (n = note) => ({
|
|
62
|
-
logIndex: "0",
|
|
63
|
-
logId: { keyId: Uint8ArrayToBase64(logId) },
|
|
64
|
-
kindVersion: { kind: "dsse", version: "0.0.2" },
|
|
65
|
-
integratedTime: null,
|
|
66
|
-
canonicalizedBody: "",
|
|
67
|
-
inclusionProof: proof(n),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await expect(verifyCheckpoint(entry(), log)).resolves.toBeUndefined();
|
|
71
|
-
await expect(verifyCheckpoint(entry(`log.test\n8\n${rootHash}\n`), log)).rejects.toThrow("Invalid checkpoint signature");
|
|
72
|
-
const bad = entry();
|
|
73
|
-
bad.inclusionProof.treeSize = "8";
|
|
74
|
-
await expect(verifyCheckpoint(bad, log)).rejects.toThrow("does not match inclusion proof");
|
|
75
|
-
});
|
|
76
|
-
});
|
package/src/errors.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2023 The Sigstore Authors.
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Base class for all verification errors
|
|
19
|
-
* Provides structured error handling with error codes
|
|
20
|
-
*/
|
|
21
|
-
export class VerificationError extends Error {
|
|
22
|
-
constructor(public code: string, message: string) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = 'VerificationError';
|
|
25
|
-
Object.setPrototypeOf(this, VerificationError.prototype);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Error thrown when timestamp verification fails
|
|
31
|
-
*/
|
|
32
|
-
export class TimestampError extends VerificationError {
|
|
33
|
-
constructor(message: string) {
|
|
34
|
-
super('TIMESTAMP_ERROR', message);
|
|
35
|
-
this.name = 'TimestampError';
|
|
36
|
-
Object.setPrototypeOf(this, TimestampError.prototype);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Error thrown when certificate verification fails
|
|
42
|
-
*/
|
|
43
|
-
export class CertificateError extends VerificationError {
|
|
44
|
-
constructor(message: string) {
|
|
45
|
-
super('CERTIFICATE_ERROR', message);
|
|
46
|
-
this.name = 'CertificateError';
|
|
47
|
-
Object.setPrototypeOf(this, CertificateError.prototype);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Error thrown when transparency log verification fails
|
|
53
|
-
*/
|
|
54
|
-
export class TLogError extends VerificationError {
|
|
55
|
-
constructor(message: string) {
|
|
56
|
-
super('TLOG_ERROR', message);
|
|
57
|
-
this.name = 'TLogError';
|
|
58
|
-
Object.setPrototypeOf(this, TLogError.prototype);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Error thrown when signature verification fails
|
|
64
|
-
*/
|
|
65
|
-
export class SignatureError extends VerificationError {
|
|
66
|
-
constructor(message: string) {
|
|
67
|
-
super('SIGNATURE_ERROR', message);
|
|
68
|
-
this.name = 'SignatureError';
|
|
69
|
-
Object.setPrototypeOf(this, SignatureError.prototype);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Error thrown when policy verification fails
|
|
75
|
-
*/
|
|
76
|
-
export class PolicyError extends VerificationError {
|
|
77
|
-
constructor(message: string) {
|
|
78
|
-
super('POLICY_ERROR', message);
|
|
79
|
-
this.name = 'PolicyError';
|
|
80
|
-
Object.setPrototypeOf(this, PolicyError.prototype);
|
|
81
|
-
}
|
|
82
|
-
}
|