Skip to content

Commit fd1ee6c

Browse files
authored
Fix issue where in rekorv2 timestamps would not be properly checked (#25)
1 parent c169bb8 commit fd1ee6c

2 files changed

Lines changed: 54 additions & 7 deletions

File tree

src/index.browser.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "vitest";
2-
import { SigstoreVerifier } from "./sigstore.js";
2+
import { SigstoreVerifier, assertRekorV2Timestamp } from "./sigstore.js";
33
import { X509Certificate } from "./x509/cert.js";
44

55
describe("Sigstore Browser Integration Tests", () => {
@@ -254,3 +254,37 @@ ewS+2T7Qz4oXaQMidPOjr1Q8WKqaKO4yCtC8cz4qVWi3lNqAcAGtonQMXUiflEWV
254254
expect(parsed.verificationMaterial.tlogEntries[0].logIndex).toBe("123");
255255
});
256256
});
257+
258+
describe("Rekor v2 observer-timestamp enforcement", () => {
259+
// Regression test: a Rekor-v2-shaped bundle (no integratedTime in the tlog
260+
// entry) must carry a signed RFC3161 timestamp. A previous guard only checked
261+
// that `timestampVerificationData` was present, which an empty object ({})
262+
// satisfies in JavaScript — letting a crafted bundle pass verification with
263+
// zero verified timestamps and the signing certificate's validity window left
264+
// completely unanchored in time.
265+
266+
it("rejects a missing timestampVerificationData", () => {
267+
expect(() => assertRekorV2Timestamp(undefined)).toThrow(
268+
"Rekor v2 bundles require a timestamp for verification.",
269+
);
270+
});
271+
272+
it("rejects an empty timestampVerificationData object ({})", () => {
273+
// This is the exact bypass the fix closes: {} is truthy.
274+
expect(() => assertRekorV2Timestamp({})).toThrow(
275+
"Rekor v2 bundles require a timestamp for verification.",
276+
);
277+
});
278+
279+
it("rejects timestampVerificationData with an empty rfc3161Timestamps array", () => {
280+
expect(() =>
281+
assertRekorV2Timestamp({ rfc3161Timestamps: [] }),
282+
).toThrow("Rekor v2 bundles require a timestamp for verification.");
283+
});
284+
285+
it("accepts timestampVerificationData with at least one RFC3161 timestamp", () => {
286+
expect(() =>
287+
assertRekorV2Timestamp({ rfc3161Timestamps: [{ signedTimestamp: "…" }] }),
288+
).not.toThrow();
289+
});
290+
});

src/sigstore.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ function getBundleVersion(mediaType: string): string {
6969
return "0.1";
7070
}
7171

72+
/**
73+
* Rekor v2 entries omit `integratedTime` and MUST carry a signed RFC3161
74+
* timestamp instead. Check for actual content, not just presence: an empty
75+
* `timestampVerificationData: {}` is truthy, so `if (!timestampVerificationData)`
76+
* is bypassable and leaves the cert validity window unanchored in time.
77+
*/
78+
export function assertRekorV2Timestamp(
79+
timestampData?: { rfc3161Timestamps?: readonly unknown[] },
80+
): void {
81+
if (!timestampData?.rfc3161Timestamps?.length) {
82+
throw new Error("Rekor v2 bundles require a timestamp for verification.");
83+
}
84+
}
85+
7286
export interface SigstoreVerifierOptions {
7387
tlogThreshold?: number;
7488
ctlogThreshold?: number;
@@ -496,12 +510,11 @@ export class SigstoreVerifier {
496510
);
497511
}
498512
} else {
499-
// Rekor v2 bundles (no integratedTime) require a timestamp for verification
500-
if (!bundle.verificationMaterial.timestampVerificationData) {
501-
throw new Error(
502-
"Rekor v2 bundles require a timestamp for verification.",
503-
);
504-
}
513+
// Rekor v2 bundles (no integratedTime) require a signed RFC3161 timestamp.
514+
// An empty timestampVerificationData object ({}) must NOT satisfy this.
515+
assertRekorV2Timestamp(
516+
bundle.verificationMaterial.timestampVerificationData,
517+
);
505518
}
506519

507520
// Verify that the certificate in the log matches the signing certificate

0 commit comments

Comments
 (0)