@freedomofpress/sigstore-browser 0.1.15 → 0.1.16
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.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.map +1 -1
- package/dist/rfc3161/tstinfo.d.ts +0 -1
- package/dist/rfc3161/tstinfo.js.map +1 -1
- package/dist/sigstore.d.ts +2 -3
- package/dist/sigstore.js +9 -9
- 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 +3 -0
- 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.map +1 -1
- package/dist/tlog/merkle.d.ts +0 -1
- 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.map +1 -1
- package/dist/x509/cert.d.ts +0 -1
- package/dist/x509/cert.js +1 -1
- 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 +15 -8
- 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
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { base64ToUint8Array, hexToUint8Array, Uint8ArrayToBase64, Uint8ArrayToHex } from "@freedomofpress/crypto-browser";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
|
|
4
|
-
import * as rekor2 from "../test/fixtures/rekor2.js";
|
|
5
|
-
import { bundle, digestHex, identity, issuer, trustRoot } from "../test/fixtures/sigstore.js";
|
|
6
|
-
import type { SigstoreBundle, TLogEntry } from "./bundle.js";
|
|
7
|
-
import type { TrustedRoot } from "./interfaces.js";
|
|
8
|
-
import { AllOf, AnyOf, Identity, OIDCIssuer, OIDCIssuerV2 } from "./policy.js";
|
|
9
|
-
import { RFC3161Timestamp } from "./rfc3161/index.js";
|
|
10
|
-
import { SigstoreVerifier, type SigstoreVerifierOptions } from "./sigstore.js";
|
|
11
|
-
import { verifyTLogBody } from "./tlog/body.js";
|
|
12
|
-
import { X509Certificate } from "./x509/cert.js";
|
|
13
|
-
|
|
14
|
-
const digest = hexToUint8Array(digestHex);
|
|
15
|
-
const policy = new AllOf([new Identity({ identity }), new AnyOf([new OIDCIssuerV2(issuer), new OIDCIssuer(issuer)])]);
|
|
16
|
-
const signingCertB64 = () => bundle().verificationMaterial.certificate?.rawBytes ?? "";
|
|
17
|
-
const signingCert = () => X509Certificate.parse(base64ToUint8Array(signingCertB64()));
|
|
18
|
-
const integratedTime = new Date(Number(bundle().verificationMaterial.tlogEntries[0].integratedTime) * 1000);
|
|
19
|
-
const dayAfterSigning = new Date(integratedTime.getTime() + 86_400_000).toISOString();
|
|
20
|
-
|
|
21
|
-
async function verifier(opts: SigstoreVerifierOptions = {}, root: TrustedRoot = trustRoot()) {
|
|
22
|
-
const v = new SigstoreVerifier(opts);
|
|
23
|
-
await v.loadSigstoreRoot(root);
|
|
24
|
-
return v;
|
|
25
|
-
}
|
|
26
|
-
const verify = async (b: SigstoreBundle, opts: SigstoreVerifierOptions = {}, root?: TrustedRoot) =>
|
|
27
|
-
(await verifier(opts, root)).verifyArtifact(identity, issuer, b, digest, true);
|
|
28
|
-
|
|
29
|
-
// Wraps an empty OCTET STRING in `depth` SEQUENCEs with minimal DER lengths.
|
|
30
|
-
function nested(depth: number): Uint8Array {
|
|
31
|
-
let buf = new Uint8Array([0x04, 0x00]);
|
|
32
|
-
for (let i = 0; i < depth; i++) {
|
|
33
|
-
const len = buf.length;
|
|
34
|
-
const hdr = len < 128 ? [0x30, len] : len < 256 ? [0x30, 0x81, len] : [0x30, 0x82, len >> 8, len & 0xff];
|
|
35
|
-
const next = new Uint8Array(hdr.length + len);
|
|
36
|
-
next.set(hdr);
|
|
37
|
-
next.set(buf, hdr.length);
|
|
38
|
-
buf = next;
|
|
39
|
-
}
|
|
40
|
-
return buf;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe("Baseline", () => {
|
|
44
|
-
it("verifies the fixture bundle with verifyArtifact and verifyDsse", async () => {
|
|
45
|
-
await expect(verify(bundle())).resolves.toBe(true);
|
|
46
|
-
const result = await (await verifier()).verifyDsse(bundle(), policy);
|
|
47
|
-
expect(result.payloadType).toBe("application/vnd.in-toto+json");
|
|
48
|
-
expect(new TextDecoder().decode(result.payload)).toContain(digestHex);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("rejects a wrong identity and a wrong artifact digest", async () => {
|
|
52
|
-
const v = await verifier();
|
|
53
|
-
await expect(v.verifyArtifact("someone@else", issuer, bundle(), digest, true)).rejects.toThrow();
|
|
54
|
-
await expect(v.verifyArtifact(identity, issuer, bundle(), new Uint8Array(32), true)).rejects.toThrow("does not match any subject");
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe("Transparency log entries and threshold", () => {
|
|
59
|
-
const withExtra = (make: (e: TLogEntry, i: number) => TLogEntry, count: number) => {
|
|
60
|
-
const b = bundle();
|
|
61
|
-
const [first] = b.verificationMaterial.tlogEntries;
|
|
62
|
-
for (let i = 1; i <= count; i++) b.verificationMaterial.tlogEntries.push(make(structuredClone(first), i));
|
|
63
|
-
return b;
|
|
64
|
-
};
|
|
65
|
-
const proofless = (e: TLogEntry, i: number) => {
|
|
66
|
-
e.logIndex = String(Number(e.logIndex) + i);
|
|
67
|
-
delete e.inclusionProof;
|
|
68
|
-
delete e.inclusionPromise;
|
|
69
|
-
return e;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
it("counts distinct verified log operators against the threshold", async () => {
|
|
73
|
-
await expect(verify(bundle(), { tlogThreshold: 2 })).rejects.toThrow("Not enough verified transparency logs: 1 < 2");
|
|
74
|
-
await expect(verify(withExtra(proofless, 1), { tlogThreshold: 2 })).rejects.toThrow("requires an inclusion proof");
|
|
75
|
-
const unknown = (e: TLogEntry, i: number) => proofless({ ...e, logId: { keyId: Uint8ArrayToBase64(new Uint8Array(32).fill(i)) } }, i);
|
|
76
|
-
await expect(verify(withExtra(unknown, 3), { tlogThreshold: 4 })).rejects.toThrow("Not enough verified transparency logs: 1 < 4");
|
|
77
|
-
const b = bundle();
|
|
78
|
-
b.verificationMaterial.tlogEntries[0].logId = { keyId: Uint8ArrayToBase64(new Uint8Array(32)) };
|
|
79
|
-
await expect(verify(b)).rejects.toThrow("Not enough verified transparency logs: 0 < 1");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("verifies the SET of every entry, so a copied entry with a forged logIndex fails", async () => {
|
|
83
|
-
const forged = (e: TLogEntry, i: number) => ({ ...e, logIndex: String(Number(e.logIndex) + i) });
|
|
84
|
-
await expect(verify(withExtra(forged, 1), { tlogThreshold: 2 })).rejects.toThrow("inclusion promise");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("groups logs by operator and never infers independence from missing metadata", async () => {
|
|
88
|
-
const root = trustRoot();
|
|
89
|
-
expect(root.tlogs.every((t) => t.operator === undefined)).toBe(true);
|
|
90
|
-
const keys = await (await verifier()).loadLog(root.tlogs);
|
|
91
|
-
expect(new Set(keys.map((k) => k.operator)).size).toBe(1);
|
|
92
|
-
root.tlogs.forEach((t, i) => (t.operator = `operator-${i}`));
|
|
93
|
-
expect(new Set((await (await verifier()).loadLog(root.tlogs)).map((k) => k.operator)).size).toBe(2);
|
|
94
|
-
await expect(verify(bundle(), {}, root)).resolves.toBe(true);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("requires the Rekor key to be valid at the integrated time", async () => {
|
|
98
|
-
const expired = trustRoot();
|
|
99
|
-
expired.tlogs[0].publicKey.validFor.end = new Date(integratedTime.getTime() - 1000).toISOString();
|
|
100
|
-
await expect(verify(bundle(), {}, expired)).rejects.toThrow("Rekor key was not valid at the integrated time");
|
|
101
|
-
const future = trustRoot();
|
|
102
|
-
future.tlogs[0].publicKey.validFor.start = dayAfterSigning;
|
|
103
|
-
await expect(verify(bundle(), {}, future)).rejects.toThrow("Rekor key was not valid at the integrated time");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("rejects more than 32 entries", async () => {
|
|
107
|
-
await expect(verify(withExtra((e) => e, 32))).rejects.toThrow("Invalid bundle: tlogEntries");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe("Logged certificate binding per entry kind and version", () => {
|
|
112
|
-
const pem = (b64: string) => Uint8ArrayToBase64(new TextEncoder().encode(`-----BEGIN CERTIFICATE-----\n${b64}\n-----END CERTIFICATE-----\n`));
|
|
113
|
-
const otherDer = trustRoot().certificateAuthorities[1].certChain.certificates[0].rawBytes;
|
|
114
|
-
const otherPem = pem(otherDer);
|
|
115
|
-
const ownPem = pem(signingCertB64());
|
|
116
|
-
const env = bundle().dsseEnvelope ?? { payload: "", payloadType: "", signatures: [{ sig: "" }] };
|
|
117
|
-
const sig = env.signatures[0].sig;
|
|
118
|
-
const hashHex = () =>
|
|
119
|
-
crypto.subtle.digest("SHA-256", base64ToUint8Array(env.payload) as BufferSource).then((h) => Uint8ArrayToHex(new Uint8Array(h)));
|
|
120
|
-
const entry = (kind: string, version: string, spec: unknown): TLogEntry => ({
|
|
121
|
-
logIndex: "1",
|
|
122
|
-
logId: { keyId: trustRoot().tlogs[0].logId.keyId },
|
|
123
|
-
kindVersion: { kind, version },
|
|
124
|
-
integratedTime: "1",
|
|
125
|
-
canonicalizedBody: Uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify({ apiVersion: version, kind, spec }))),
|
|
126
|
-
});
|
|
127
|
-
const expectBinding = async (ok: TLogEntry, wrong: TLogEntry, missing: TLogEntry, b: SigstoreBundle) => {
|
|
128
|
-
await expect(verifyTLogBody(ok, b, signingCert())).resolves.toBeUndefined();
|
|
129
|
-
await expect(verifyTLogBody(wrong, b, signingCert())).rejects.toThrow("does not match the signing certificate");
|
|
130
|
-
await expect(verifyTLogBody(missing, b, signingCert())).rejects.toThrow("does not record the signing certificate");
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
it("dsse 0.0.1 and 0.0.2", async () => {
|
|
134
|
-
const h = await hashHex();
|
|
135
|
-
const v1 = (verifier?: string) => entry("dsse", "0.0.1", { payloadHash: { algorithm: "sha256", value: h }, signatures: [{ signature: sig, verifier }] });
|
|
136
|
-
const v2 = (rawBytes?: string) =>
|
|
137
|
-
entry("dsse", "0.0.2", {
|
|
138
|
-
dsseV002: {
|
|
139
|
-
payloadHash: { algorithm: "SHA2_256", digest: Uint8ArrayToBase64(hexToUint8Array(h)) },
|
|
140
|
-
signatures: [{ content: sig, verifier: rawBytes ? { x509Certificate: { rawBytes } } : undefined }],
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
await expectBinding(v1(ownPem), v1(otherPem), v1(undefined), bundle());
|
|
144
|
-
await expectBinding(v2(signingCertB64()), v2(otherDer), v2(undefined), bundle());
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("intoto 0.0.2", async () => {
|
|
148
|
-
const h = await hashHex();
|
|
149
|
-
const doubleB64 = Uint8ArrayToBase64(new TextEncoder().encode(sig));
|
|
150
|
-
const e = (publicKey?: string) =>
|
|
151
|
-
entry("intoto", "0.0.2", {
|
|
152
|
-
content: { envelope: { payload: "", payloadType: "", signatures: [{ sig: doubleB64, publicKey }] }, payloadHash: { algorithm: "sha256", value: h } },
|
|
153
|
-
});
|
|
154
|
-
await expectBinding(e(ownPem), e(otherPem), e(undefined), bundle());
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("hashedrekord 0.0.1 and 0.0.2", async () => {
|
|
158
|
-
const b = bundle() as unknown as Record<string, unknown>;
|
|
159
|
-
delete b.dsseEnvelope;
|
|
160
|
-
b.messageSignature = { messageDigest: { algorithm: "SHA2_256", digest: "AA==" }, signature: sig };
|
|
161
|
-
const hr = b as unknown as SigstoreBundle;
|
|
162
|
-
const v1 = (content?: string) =>
|
|
163
|
-
entry("hashedrekord", "0.0.1", { data: { hash: { algorithm: "sha256", value: "00" } }, signature: { content: sig, publicKey: { content } } });
|
|
164
|
-
const v2 = (rawBytes?: string) =>
|
|
165
|
-
entry("hashedrekord", "0.0.2", {
|
|
166
|
-
hashedRekordV002: { data: { algorithm: "SHA2_256", digest: "AA==" }, signature: { content: sig, verifier: rawBytes ? { x509Certificate: { rawBytes } } : undefined } },
|
|
167
|
-
});
|
|
168
|
-
await expectBinding(v1(ownPem), v1(otherPem), v1(undefined), hr);
|
|
169
|
-
await expectBinding(v2(signingCertB64()), v2(otherDer), v2(undefined), hr);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe("Bundle media type and structure", () => {
|
|
174
|
-
const proofless = (mediaType: string) => {
|
|
175
|
-
const b = bundle();
|
|
176
|
-
b.mediaType = mediaType;
|
|
177
|
-
delete b.verificationMaterial.tlogEntries[0].inclusionProof;
|
|
178
|
-
return b;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
it("accepts only bundle versions 0.1 to 0.3", async () => {
|
|
182
|
-
for (const mt of ["application/x-garbage", "APPLICATION/VND.DEV.SIGSTORE.BUNDLE.V0.3+JSON", "", "application/vnd.dev.sigstore.bundle.v0.4+json", "application/vnd.dev.sigstore.bundle.v1.0+json"]) {
|
|
183
|
-
await expect(verify(proofless(mt))).rejects.toThrow("Unsupported bundle media type");
|
|
184
|
-
}
|
|
185
|
-
await expect(verify(proofless("application/vnd.dev.sigstore.bundle.v0.3+json"))).rejects.toThrow("requires an inclusion proof");
|
|
186
|
-
await expect(verify(proofless("application/vnd.dev.sigstore.bundle+json;version=0.1"))).resolves.toBe(true);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("rejects a bundle carrying both a message signature and a DSSE envelope", async () => {
|
|
190
|
-
const b = bundle() as unknown as Record<string, unknown>;
|
|
191
|
-
b.messageSignature = { messageDigest: { algorithm: "SHA2_256", digest: "AA==" }, signature: "AA==" };
|
|
192
|
-
await expect(verify(b as unknown as SigstoreBundle)).rejects.toThrow("exactly one of messageSignature or dsseEnvelope");
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("rejects DSSE payload types other than in-toto and non-dsse entries in verifyDsse", async () => {
|
|
196
|
-
const b = bundle();
|
|
197
|
-
if (b.dsseEnvelope) b.dsseEnvelope.payloadType = "application/json";
|
|
198
|
-
await expect(verify(b)).rejects.toThrow("Unsupported DSSE payload type");
|
|
199
|
-
const c = bundle();
|
|
200
|
-
c.verificationMaterial.tlogEntries[0].kindVersion.kind = "hashedrekord";
|
|
201
|
-
await expect((await verifier()).verifyDsse(c, policy)).rejects.toThrow(/kind\/version mismatch|Expected entry type dsse/);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe("Strict DER from crypto-browser", () => {
|
|
206
|
-
const withCert = (der: Uint8Array) => {
|
|
207
|
-
const b = bundle();
|
|
208
|
-
b.verificationMaterial.certificate = { rawBytes: Uint8ArrayToBase64(der) };
|
|
209
|
-
return b;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
it("rejects trailing bytes, BER lengths and deep nesting in certificates and timestamps", async () => {
|
|
213
|
-
const der = base64ToUint8Array(signingCertB64());
|
|
214
|
-
await expect(verify(withCert(new Uint8Array([...der, 0xde, 0xad])))).rejects.toThrow("trailing bytes");
|
|
215
|
-
await expect(verify(withCert(new Uint8Array([0x30, 0x83, 0x00, ...der.subarray(2)])))).rejects.toThrow("non-minimal length");
|
|
216
|
-
const deep = nested(8000);
|
|
217
|
-
await expect(verify(withCert(deep))).rejects.toThrow("nesting too deep");
|
|
218
|
-
expect(() => RFC3161Timestamp.parse(deep)).toThrow("nesting too deep");
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("Observer timestamps and historical trust material", () => {
|
|
223
|
-
it("verifies against a CA or Rekor key that was retired after the signature was made", async () => {
|
|
224
|
-
const ca = trustRoot();
|
|
225
|
-
ca.certificateAuthorities[1].validFor.end = dayAfterSigning;
|
|
226
|
-
await expect(verify(bundle(), {}, ca)).resolves.toBe(true);
|
|
227
|
-
const log = trustRoot();
|
|
228
|
-
log.tlogs[0].publicKey.validFor.end = dayAfterSigning;
|
|
229
|
-
await expect(verify(bundle(), {}, log)).resolves.toBe(true);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("checks the CA validity window at the observer time", async () => {
|
|
233
|
-
const root = trustRoot();
|
|
234
|
-
root.certificateAuthorities[1].validFor.start = dayAfterSigning;
|
|
235
|
-
await expect(verify(bundle(), {}, root)).rejects.toThrow("No valid CAs found");
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("requires at least one verified observer timestamp", async () => {
|
|
239
|
-
// Without a SET the integrated time is unauthenticated, and there is no TSA timestamp either.
|
|
240
|
-
const b = bundle();
|
|
241
|
-
delete b.verificationMaterial.tlogEntries[0].inclusionPromise;
|
|
242
|
-
await expect(verify(b)).rejects.toThrow("No verified observer timestamp");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("loads all trust material regardless of the wall clock", async () => {
|
|
246
|
-
const root = trustRoot();
|
|
247
|
-
expect(root.certificateAuthorities.some((ca) => ca.validFor.end && new Date(ca.validFor.end) < new Date())).toBe(true);
|
|
248
|
-
const v = await verifier({}, root);
|
|
249
|
-
expect(v.loadCA(root.certificateAuthorities)).toHaveLength(root.certificateAuthorities.length);
|
|
250
|
-
expect(await v.loadLog(root.tlogs)).toHaveLength(root.tlogs.length);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe("Rekor v2 bundle with RFC 3161 timestamp", () => {
|
|
255
|
-
const r2digest = base64ToUint8Array(rekor2.digestB64);
|
|
256
|
-
const verifyR2 = async (b: SigstoreBundle, data = r2digest, opts: SigstoreVerifierOptions = {}) =>
|
|
257
|
-
(await verifier(opts, rekor2.trustRoot())).verifyArtifact(rekor2.identity, rekor2.issuer, b, data, true);
|
|
258
|
-
|
|
259
|
-
it("verifies hashedrekord 0.0.2, an Ed25519 checkpoint and a real TSA response", async () => {
|
|
260
|
-
await expect(verifyR2(rekor2.bundle())).resolves.toBe(true);
|
|
261
|
-
await expect(verifyR2(rekor2.bundle(), r2digest, { tsaThreshold: 1 })).resolves.toBe(true);
|
|
262
|
-
await expect(verifyR2(rekor2.bundle(), r2digest, { tsaThreshold: 2 })).rejects.toThrow("Not enough verified TSA operators: 1 < 2");
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("needs the timestamp as its only observer time and matches the message digest to the artifact", async () => {
|
|
266
|
-
const b = rekor2.bundle();
|
|
267
|
-
delete b.verificationMaterial.timestampVerificationData;
|
|
268
|
-
await expect(verifyR2(b)).rejects.toThrow("No verified observer timestamp");
|
|
269
|
-
await expect(verifyR2(rekor2.bundle(), new Uint8Array(32))).rejects.toThrow("does not match the bundle message digest");
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("rejects a tampered timestamp and a swapped signing certificate", async () => {
|
|
273
|
-
const b = rekor2.bundle();
|
|
274
|
-
const ts = b.verificationMaterial.timestampVerificationData?.rfc3161Timestamps[0];
|
|
275
|
-
if (ts) ts.signedTimestamp = Uint8ArrayToBase64(new Uint8Array([...base64ToUint8Array(ts.signedTimestamp), 0]));
|
|
276
|
-
await expect(verifyR2(b)).rejects.toThrow("trailing bytes");
|
|
277
|
-
const c = rekor2.bundle();
|
|
278
|
-
c.verificationMaterial.certificate = bundle().verificationMaterial.certificate;
|
|
279
|
-
await expect(verifyR2(c)).rejects.toThrow();
|
|
280
|
-
});
|
|
281
|
-
});
|