@freedomofpress/sigstore-browser 0.1.6 → 0.1.8
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/LICENSE +201 -0
- package/README.md +106 -4
- package/dist/sigstore.d.ts +20 -0
- package/dist/sigstore.d.ts.map +1 -1
- package/dist/sigstore.js +82 -0
- package/dist/sigstore.js.map +1 -1
- package/package.json +2 -5
- package/src/sigstore.ts +118 -0
- package/vite.config.ts +3 -12
- package/dist/index-CRFtwdjM.js +0 -4528
- package/dist/index-CRFtwdjM.js.map +0 -1
- package/dist/sigstore.min.js +0 -6
- package/dist/sigstore.min.js.map +0 -1
- package/dist/tuf-D48cBq5i.js +0 -377
- package/dist/tuf-D48cBq5i.js.map +0 -1
- package/dist/tuf-root-D8m0y2I4.js +0 -5
- package/dist/tuf-root-D8m0y2I4.js.map +0 -1
package/src/sigstore.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { verifyCheckpoint } from "./tlog/checkpoint.js";
|
|
|
35
35
|
import { verifyTLogBody } from "./tlog/body.js";
|
|
36
36
|
import { verifyBundleTimestamp } from "./timestamp/tsa.js";
|
|
37
37
|
import { TrustedRootProvider } from "./trust/tuf.js";
|
|
38
|
+
import type { VerificationPolicy } from "./policy.js";
|
|
38
39
|
|
|
39
40
|
const MEDIA_TYPE_BASE = "application/vnd.dev.sigstore.bundle";
|
|
40
41
|
|
|
@@ -775,4 +776,121 @@ export class SigstoreVerifier {
|
|
|
775
776
|
|
|
776
777
|
return true;
|
|
777
778
|
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Verify a DSSE bundle using a verification policy.
|
|
782
|
+
* This matches sigstore-python's verify_dsse API.
|
|
783
|
+
*
|
|
784
|
+
* Reference: https://github.com/sigstore/sigstore-python/blob/main/sigstore/verify/verifier.py#L388
|
|
785
|
+
*
|
|
786
|
+
* Unlike verify_artifact which verifies an artifact against a bundle,
|
|
787
|
+
* this method verifies the DSSE envelope itself and returns the payload.
|
|
788
|
+
* The caller is responsible for checking that the payload matches their
|
|
789
|
+
* expected artifact (e.g., by checking subjects in an in-toto statement).
|
|
790
|
+
*
|
|
791
|
+
* @param bundle - The Sigstore bundle containing the DSSE envelope
|
|
792
|
+
* @param policy - A verification policy to apply to the signing certificate
|
|
793
|
+
* @returns The payload type and payload bytes from the verified envelope
|
|
794
|
+
*/
|
|
795
|
+
public async verifyDsse(
|
|
796
|
+
bundle: SigstoreBundle,
|
|
797
|
+
policy: VerificationPolicy,
|
|
798
|
+
): Promise<{ payloadType: string; payload: Uint8Array }> {
|
|
799
|
+
if (!this.root) {
|
|
800
|
+
throw new Error("Sigstore root is undefined");
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!bundle.dsseEnvelope) {
|
|
804
|
+
throw new Error("Bundle does not contain a DSSE envelope");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Extract certificate from bundle
|
|
808
|
+
const cert = bundle.verificationMaterial.certificate ||
|
|
809
|
+
bundle.verificationMaterial.x509CertificateChain?.certificates[0];
|
|
810
|
+
|
|
811
|
+
if (!cert) {
|
|
812
|
+
throw new Error("No certificate found in bundle");
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const signingCert = X509Certificate.parse(base64ToUint8Array(cert.rawBytes));
|
|
816
|
+
|
|
817
|
+
// (1) Verify certificate chain to trusted CA
|
|
818
|
+
const certPath = await this.verifyCertificateChain(
|
|
819
|
+
signingCert.notBefore,
|
|
820
|
+
signingCert,
|
|
821
|
+
this.root.certificateAuthorities
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
// (2) Verify SCTs
|
|
825
|
+
const issuerCert = certPath.length > 1 ? certPath[1] : certPath[0];
|
|
826
|
+
const verifiedSCTs = await this.verifySCT(signingCert, issuerCert, this.root.ctlogs);
|
|
827
|
+
if (verifiedSCTs.length < this.options.ctlogThreshold) {
|
|
828
|
+
throw new Error(
|
|
829
|
+
`Not enough valid SCTs: found ${verifiedSCTs.length}, required ${this.options.ctlogThreshold}`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// (3) Verify the signing certificate against the policy
|
|
834
|
+
policy.verify(signingCert);
|
|
835
|
+
|
|
836
|
+
// (4) Verify Rekor inclusion promise
|
|
837
|
+
if (!(await this.verifyInclusionPromise(signingCert, bundle, this.root.rekor))) {
|
|
838
|
+
throw new Error("Inclusion promise validation failed");
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// (5) Verify Rekor inclusion proof (Merkle tree verification)
|
|
842
|
+
await this.verifyInclusionProof(bundle);
|
|
843
|
+
|
|
844
|
+
// Validate envelope has exactly one signature (matches sigstore-python dsse._verify)
|
|
845
|
+
if (!bundle.dsseEnvelope.signatures || bundle.dsseEnvelope.signatures.length !== 1) {
|
|
846
|
+
throw new Error(
|
|
847
|
+
`DSSE envelope must have exactly 1 signature, got ${bundle.dsseEnvelope.signatures?.length ?? 0}`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// (6) TSA Timestamp verification
|
|
852
|
+
const signature = base64ToUint8Array(bundle.dsseEnvelope.signatures[0].sig);
|
|
853
|
+
const verifiedTimestamps = await verifyBundleTimestamp(
|
|
854
|
+
bundle.verificationMaterial.timestampVerificationData,
|
|
855
|
+
signature,
|
|
856
|
+
this.rawRoot?.timestampAuthorities || []
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
if (verifiedTimestamps.length < this.options.tsaThreshold) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
`Not enough verified TSA timestamps: ${verifiedTimestamps.length} < ${this.options.tsaThreshold}`
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
for (const verifiedTimestamp of verifiedTimestamps) {
|
|
866
|
+
if (!signingCert.validForDate(verifiedTimestamp)) {
|
|
867
|
+
throw new Error("Certificate was not valid at the time of timestamping");
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// (7) Verify the DSSE envelope signature
|
|
872
|
+
const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
|
|
873
|
+
const pae = preAuthEncoding(bundle.dsseEnvelope.payloadType, payloadBytes);
|
|
874
|
+
|
|
875
|
+
const publicKey = await signingCert.publicKeyObj;
|
|
876
|
+
const verified = await verifySignature(publicKey, pae, signature);
|
|
877
|
+
if (!verified) {
|
|
878
|
+
throw new Error("DSSE signature verification failed");
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// (8) Verify the consistency of the log entry's body against the bundle materials
|
|
882
|
+
// The entry MUST be of type "dsse" for DSSE verification
|
|
883
|
+
for (const entry of bundle.verificationMaterial.tlogEntries) {
|
|
884
|
+
if (entry.kindVersion.kind !== "dsse") {
|
|
885
|
+
throw new Error(`Expected entry type dsse, got ${entry.kindVersion.kind}`);
|
|
886
|
+
}
|
|
887
|
+
await verifyTLogBody(entry, bundle);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Return the verified payload
|
|
891
|
+
return {
|
|
892
|
+
payloadType: bundle.dsseEnvelope.payloadType,
|
|
893
|
+
payload: payloadBytes,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
778
896
|
}
|
package/vite.config.ts
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
/// <reference types="vitest" />
|
|
2
2
|
import { defineConfig } from "vite";
|
|
3
|
-
import { resolve } from "path";
|
|
4
3
|
|
|
5
4
|
export default defineConfig({
|
|
6
5
|
build: {
|
|
7
6
|
outDir: "dist",
|
|
8
7
|
target: "esnext",
|
|
9
|
-
minify: "esbuild",
|
|
10
8
|
lib: {
|
|
11
|
-
entry:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
fileName: (format) => (format === "iife" ? "sigstore.min.js" : "index.js"),
|
|
9
|
+
entry: "src/index.ts",
|
|
10
|
+
formats: ["es"],
|
|
11
|
+
fileName: "index",
|
|
15
12
|
},
|
|
16
|
-
rollupOptions: {
|
|
17
|
-
output: {
|
|
18
|
-
globals: {},
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
sourcemap: true,
|
|
22
13
|
},
|
|
23
14
|
test: {
|
|
24
15
|
globals: true,
|