@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.
Files changed (132) hide show
  1. package/dist/bundle.d.ts +2 -3
  2. package/dist/bundle.js.map +1 -1
  3. package/dist/cli.d.ts +0 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/dsse.d.ts +0 -1
  6. package/dist/dsse.js.map +1 -1
  7. package/dist/errors.d.ts +0 -1
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +9 -10
  10. package/dist/index.js +5 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/interfaces.d.ts +1 -2
  13. package/dist/interfaces.js +1 -1
  14. package/dist/interfaces.js.map +1 -1
  15. package/dist/oid.d.ts +0 -1
  16. package/dist/oid.js.map +1 -1
  17. package/dist/policy.d.ts +0 -1
  18. package/dist/policy.js +1 -1
  19. package/dist/policy.js.map +1 -1
  20. package/dist/rfc3161/error.d.ts +0 -1
  21. package/dist/rfc3161/error.js.map +1 -1
  22. package/dist/rfc3161/index.d.ts +0 -1
  23. package/dist/rfc3161/index.js.map +1 -1
  24. package/dist/rfc3161/timestamp.d.ts +0 -1
  25. package/dist/rfc3161/timestamp.js.map +1 -1
  26. package/dist/rfc3161/tstinfo.d.ts +0 -1
  27. package/dist/rfc3161/tstinfo.js.map +1 -1
  28. package/dist/sigstore.d.ts +2 -3
  29. package/dist/sigstore.js +9 -9
  30. package/dist/sigstore.js.map +1 -1
  31. package/dist/timestamp/tsa.d.ts +1 -2
  32. package/dist/timestamp/tsa.js +1 -1
  33. package/dist/timestamp/tsa.js.map +1 -1
  34. package/dist/tlog/body.d.ts +0 -1
  35. package/dist/tlog/body.js +1 -1
  36. package/dist/tlog/body.js.map +1 -1
  37. package/dist/tlog/checkpoint.d.ts +0 -1
  38. package/dist/tlog/checkpoint.js.map +1 -1
  39. package/dist/tlog/dsse.d.ts +0 -1
  40. package/dist/tlog/dsse.js +3 -0
  41. package/dist/tlog/dsse.js.map +1 -1
  42. package/dist/tlog/hashedrekord.d.ts +0 -1
  43. package/dist/tlog/hashedrekord.js.map +1 -1
  44. package/dist/tlog/intoto.d.ts +0 -1
  45. package/dist/tlog/intoto.js.map +1 -1
  46. package/dist/tlog/merkle.d.ts +0 -1
  47. package/dist/tlog/merkle.js.map +1 -1
  48. package/dist/trust/tuf-root.d.ts +0 -1
  49. package/dist/trust/tuf-root.js.map +1 -1
  50. package/dist/trust/tuf.d.ts +0 -1
  51. package/dist/trust/tuf.js.map +1 -1
  52. package/dist/x509/cert.d.ts +0 -1
  53. package/dist/x509/cert.js +1 -1
  54. package/dist/x509/cert.js.map +1 -1
  55. package/dist/x509/chain.d.ts +0 -1
  56. package/dist/x509/chain.js +2 -2
  57. package/dist/x509/chain.js.map +1 -1
  58. package/dist/x509/ext.d.ts +0 -1
  59. package/dist/x509/ext.js.map +1 -1
  60. package/dist/x509/index.d.ts +1 -2
  61. package/dist/x509/index.js +1 -1
  62. package/dist/x509/index.js.map +1 -1
  63. package/dist/x509/sct.d.ts +0 -1
  64. package/dist/x509/sct.js.map +1 -1
  65. package/package.json +15 -8
  66. package/.github/workflows/conformance.yml +0 -29
  67. package/.github/workflows/publish.yml +0 -47
  68. package/dist/bundle.d.ts.map +0 -1
  69. package/dist/cli.d.ts.map +0 -1
  70. package/dist/dsse.d.ts.map +0 -1
  71. package/dist/errors.d.ts.map +0 -1
  72. package/dist/index.d.ts.map +0 -1
  73. package/dist/interfaces.d.ts.map +0 -1
  74. package/dist/oid.d.ts.map +0 -1
  75. package/dist/policy.d.ts.map +0 -1
  76. package/dist/rfc3161/error.d.ts.map +0 -1
  77. package/dist/rfc3161/index.d.ts.map +0 -1
  78. package/dist/rfc3161/timestamp.d.ts.map +0 -1
  79. package/dist/rfc3161/tstinfo.d.ts.map +0 -1
  80. package/dist/sigstore.d.ts.map +0 -1
  81. package/dist/timestamp/tsa.d.ts.map +0 -1
  82. package/dist/tlog/body.d.ts.map +0 -1
  83. package/dist/tlog/checkpoint.d.ts.map +0 -1
  84. package/dist/tlog/dsse.d.ts.map +0 -1
  85. package/dist/tlog/hashedrekord.d.ts.map +0 -1
  86. package/dist/tlog/intoto.d.ts.map +0 -1
  87. package/dist/tlog/merkle.d.ts.map +0 -1
  88. package/dist/trust/tuf-root.d.ts.map +0 -1
  89. package/dist/trust/tuf.d.ts.map +0 -1
  90. package/dist/x509/cert.d.ts.map +0 -1
  91. package/dist/x509/chain.d.ts.map +0 -1
  92. package/dist/x509/ext.d.ts.map +0 -1
  93. package/dist/x509/index.d.ts.map +0 -1
  94. package/dist/x509/sct.d.ts.map +0 -1
  95. package/eslint.config.mjs +0 -31
  96. package/src/bundle.ts +0 -186
  97. package/src/cli.ts +0 -290
  98. package/src/dsse.ts +0 -49
  99. package/src/ed25519.browser.test.ts +0 -76
  100. package/src/errors.ts +0 -82
  101. package/src/index.browser.test.ts +0 -301
  102. package/src/index.ts +0 -93
  103. package/src/interfaces.ts +0 -150
  104. package/src/oid.ts +0 -34
  105. package/src/policy.ts +0 -476
  106. package/src/rfc3161/error.ts +0 -16
  107. package/src/rfc3161/index.ts +0 -17
  108. package/src/rfc3161/timestamp.ts +0 -268
  109. package/src/rfc3161/tstinfo.ts +0 -78
  110. package/src/security.browser.test.ts +0 -249
  111. package/src/sigstore.ts +0 -605
  112. package/src/timestamp/tsa.ts +0 -212
  113. package/src/tlog/body.ts +0 -84
  114. package/src/tlog/checkpoint.ts +0 -154
  115. package/src/tlog/dsse.ts +0 -153
  116. package/src/tlog/hashedrekord.ts +0 -136
  117. package/src/tlog/intoto.ts +0 -109
  118. package/src/tlog/merkle.ts +0 -152
  119. package/src/trust/README.md +0 -38
  120. package/src/trust/tuf-root.ts +0 -3
  121. package/src/trust/tuf.ts +0 -233
  122. package/src/verify.browser.test.ts +0 -281
  123. package/src/x509/cert.ts +0 -569
  124. package/src/x509/chain.ts +0 -212
  125. package/src/x509/ext.ts +0 -356
  126. package/src/x509/index.ts +0 -19
  127. package/src/x509/sct.ts +0 -156
  128. package/test/fixtures/rekor2.ts +0 -14
  129. package/test/fixtures/sigstore.ts +0 -13
  130. package/tsconfig.json +0 -18
  131. package/vite.config.playwright.ts +0 -14
  132. package/vite.config.ts +0 -18
@@ -1,136 +0,0 @@
1
- /*
2
- * HashedRekord transparency log entry verification
3
- *
4
- * Based on sigstore-js:
5
- * https://github.com/sigstore/sigstore-js/blob/main/packages/verify/src/tlog/hashedrekord.ts
6
- *
7
- * Key differences from sigstore-js:
8
- * - Browser-compatible: uses Uint8Array instead of Buffer for binary data
9
- * - Direct bundle field comparison instead of SignatureContent abstraction
10
- * - Adds verifyHashedRekordV002Body() - new functionality (reference only supports v0.0.1)
11
- */
12
-
13
- import { base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
14
- import type { SigstoreBundle } from "../bundle.js";
15
- import type { X509Certificate } from "../x509/cert.js";
16
- import { assertLoggedCertificate, type RekorEntry } from "./body.js";
17
-
18
- interface HashedRekordSpec {
19
- signature: {
20
- content: string;
21
- publicKey: {
22
- content: string;
23
- };
24
- };
25
- data: {
26
- hash: {
27
- algorithm: string;
28
- value: string;
29
- };
30
- };
31
- }
32
-
33
- interface HashedRekordEntry extends RekorEntry {
34
- apiVersion: "0.0.1" | "0.0.2";
35
- kind: "hashedrekord";
36
- spec: HashedRekordSpec | HashedRekordV002Spec;
37
- }
38
-
39
- interface HashedRekordV002Spec {
40
- hashedRekordV002: {
41
- signature: {
42
- content: string;
43
- verifier?: {
44
- x509Certificate?: {
45
- rawBytes: string;
46
- };
47
- };
48
- };
49
- data: {
50
- algorithm: string;
51
- digest: string;
52
- };
53
- };
54
- }
55
-
56
- export async function verifyHashedRekordBody(
57
- entry: RekorEntry,
58
- bundle: SigstoreBundle,
59
- cert: X509Certificate,
60
- ): Promise<void> {
61
- const hashedRekordEntry = entry as HashedRekordEntry;
62
-
63
- switch (hashedRekordEntry.apiVersion) {
64
- case "0.0.1":
65
- return verifyHashedRekordV001Body(hashedRekordEntry, bundle, cert);
66
- case "0.0.2":
67
- return verifyHashedRekordV002Body(hashedRekordEntry, bundle, cert);
68
- default:
69
- throw new Error(
70
- `Unsupported hashedrekord version: ${hashedRekordEntry.apiVersion}`
71
- );
72
- }
73
- }
74
-
75
- function verifyHashedRekordV001Body(
76
- entry: HashedRekordEntry,
77
- bundle: SigstoreBundle,
78
- cert: X509Certificate,
79
- ): void {
80
- const spec = entry.spec as HashedRekordSpec;
81
- if (!bundle.messageSignature) {
82
- throw new Error("Bundle missing messageSignature for hashedrekord entry");
83
- }
84
- assertLoggedCertificate(cert, spec.signature?.publicKey?.content, true);
85
-
86
- const tlogSig = spec.signature.content || "";
87
- const tlogSigBytes = base64ToUint8Array(tlogSig);
88
- const bundleSigBytes = base64ToUint8Array(bundle.messageSignature.signature);
89
-
90
- if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
91
- throw new Error("Signature mismatch between TLog entry and bundle");
92
- }
93
-
94
- const tlogDigest = spec.data.hash?.value || "";
95
- const tlogDigestBytes = hexToUint8Array(tlogDigest);
96
- const bundleDigestBytes = base64ToUint8Array(
97
- bundle.messageSignature.messageDigest.digest
98
- );
99
-
100
- if (!uint8ArrayEqual(tlogDigestBytes, bundleDigestBytes)) {
101
- throw new Error("Digest mismatch between TLog entry and bundle");
102
- }
103
- }
104
-
105
- // New functionality for HashedRekord v0.0.2 (not in sigstore-js reference, which only supports v0.0.1)
106
- function verifyHashedRekordV002Body(
107
- entry: HashedRekordEntry,
108
- bundle: SigstoreBundle,
109
- cert: X509Certificate,
110
- ): void {
111
- const spec = (entry.spec as HashedRekordV002Spec).hashedRekordV002;
112
- if (!bundle.messageSignature) {
113
- throw new Error("Bundle missing messageSignature for hashedrekord v0.0.2 entry");
114
- }
115
- assertLoggedCertificate(cert, spec?.signature?.verifier?.x509Certificate?.rawBytes, false);
116
-
117
- // NOTE: HashedRekord v0.0.2 uses single base64 encoding (unlike intoto which is double-encoded)
118
- // Verified against Sigstore conformance test suite bundles
119
- const tlogSig = spec.signature.content || "";
120
- const tlogSigBytes = base64ToUint8Array(tlogSig);
121
- const bundleSigBytes = base64ToUint8Array(bundle.messageSignature.signature);
122
-
123
- if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
124
- throw new Error("Signature mismatch between TLog entry and bundle (v0.0.2)");
125
- }
126
-
127
- const tlogDigest = spec.data.digest || "";
128
- const tlogDigestBytes = base64ToUint8Array(tlogDigest);
129
- const bundleDigestBytes = base64ToUint8Array(
130
- bundle.messageSignature.messageDigest.digest
131
- );
132
-
133
- if (!uint8ArrayEqual(tlogDigestBytes, bundleDigestBytes)) {
134
- throw new Error("Digest mismatch between TLog entry and bundle (v0.0.2)");
135
- }
136
- }
@@ -1,109 +0,0 @@
1
- /*
2
- * Intoto transparency log entry verification
3
- *
4
- * Based on sigstore-js:
5
- * https://github.com/sigstore/sigstore-js/blob/main/packages/verify/src/tlog/intoto.ts
6
- *
7
- * Key differences from sigstore-js:
8
- * - Browser-compatible: uses Uint8Array instead of Buffer for binary data
9
- * - Direct bundle field comparison instead of SignatureContent abstraction
10
- * - Uses crypto.subtle.digest for hash computation instead of Node.js crypto
11
- */
12
-
13
- import { base64Decode, base64ToUint8Array, hexToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
14
- import { getHashAlgorithm } from "../interfaces.js";
15
- import type { SigstoreBundle } from "../bundle.js";
16
- import type { X509Certificate } from "../x509/cert.js";
17
- import { assertLoggedCertificate, type RekorEntry } from "./body.js";
18
-
19
- interface IntotoEnvelope {
20
- payload: string;
21
- payloadType: string;
22
- signatures: Array<{
23
- keyid?: string;
24
- sig: string;
25
- publicKey?: string;
26
- }>;
27
- }
28
-
29
- interface IntotoSpec {
30
- content: {
31
- envelope: IntotoEnvelope;
32
- hash?: {
33
- algorithm: string;
34
- value: string;
35
- };
36
- payloadHash?: {
37
- algorithm: string;
38
- value: string;
39
- };
40
- };
41
- publicKey?: string;
42
- }
43
-
44
- interface IntotoEntry extends RekorEntry {
45
- apiVersion: "0.0.2";
46
- kind: "intoto";
47
- spec: IntotoSpec;
48
- }
49
-
50
- export async function verifyIntotoBody(
51
- entry: RekorEntry,
52
- bundle: SigstoreBundle,
53
- cert: X509Certificate,
54
- ): Promise<void> {
55
- const intotoEntry = entry as IntotoEntry;
56
-
57
- if (intotoEntry.apiVersion !== "0.0.2") {
58
- throw new Error(
59
- `Unsupported intoto version: ${intotoEntry.apiVersion}`
60
- );
61
- }
62
-
63
- if (!bundle.dsseEnvelope) {
64
- throw new Error("Bundle missing dsseEnvelope for intoto entry");
65
- }
66
-
67
- const tlogEnvelope = intotoEntry.spec.content.envelope;
68
-
69
- if (!tlogEnvelope.signatures || tlogEnvelope.signatures.length !== 1) {
70
- throw new Error("Intoto entry must have exactly one signature");
71
- }
72
- assertLoggedCertificate(cert, tlogEnvelope.signatures[0].publicKey, true);
73
-
74
- // The signature in the intoto tlog entry is double-base64-encoded:
75
- // 1. Raw signature bytes are base64-encoded (normal)
76
- // 2. That base64 string is base64-encoded again (for storage in JSON)
77
- // So we decode twice: base64 -> string -> base64 -> bytes
78
- const tlogSigBase64 = tlogEnvelope.signatures[0].sig;
79
- const tlogSigDecoded = base64Decode(tlogSigBase64); // First decode: base64 -> UTF-8 string (which contains base64)
80
- const tlogSigBytes = base64ToUint8Array(tlogSigDecoded); // Second decode: base64 string -> raw bytes
81
-
82
- if (bundle.dsseEnvelope.signatures.length === 0) {
83
- throw new Error("Bundle DSSE envelope missing signatures");
84
- }
85
-
86
- const bundleSigBytes = base64ToUint8Array(bundle.dsseEnvelope.signatures[0].sig);
87
-
88
- if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
89
- throw new Error("Intoto signature mismatch between TLog entry and bundle");
90
- }
91
-
92
- if (intotoEntry.spec.content.payloadHash) {
93
- if (!intotoEntry.spec.content.payloadHash.algorithm) {
94
- throw new Error("Intoto entry missing payloadHash algorithm");
95
- }
96
-
97
- const hashAlg = getHashAlgorithm(intotoEntry.spec.content.payloadHash.algorithm);
98
- const tlogHashBytes = hexToUint8Array(intotoEntry.spec.content.payloadHash.value);
99
-
100
- const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
101
- const bundleHashBytes = new Uint8Array(
102
- await crypto.subtle.digest(hashAlg, payloadBytes as BufferSource)
103
- );
104
-
105
- if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
106
- throw new Error("Intoto payload hash mismatch between TLog entry and bundle");
107
- }
108
- }
109
- }
@@ -1,152 +0,0 @@
1
- /*
2
- Adapted from sigstore-js for browser compatibility:
3
- https://github.com/sigstore/sigstore-js/blob/main/packages/verify/src/timestamp/merkle.ts
4
-
5
- Key differences:
6
- - Uses Uint8Array instead of Buffer
7
- - Uses async Web Crypto API (crypto.subtle.digest) instead of sync Node.js crypto
8
- - Base64 decoding via custom base64ToUint8Array instead of Buffer.from
9
- - uint8ArrayEqual from crypto-browser instead of Node.js crypto.timingSafeEqual
10
- */
11
- import { base64ToUint8Array, uint8ArrayEqual } from "@freedomofpress/crypto-browser";
12
- import { HashAlgorithms } from "../interfaces.js";
13
- import type { TLogEntry } from "../bundle.js";
14
-
15
- const RFC6962_LEAF_HASH_PREFIX = new Uint8Array([0x00]);
16
- const RFC6962_NODE_HASH_PREFIX = new Uint8Array([0x01]);
17
-
18
- export async function verifyMerkleInclusion(
19
- entry: TLogEntry
20
- ): Promise<void> {
21
- if (!entry.inclusionProof) {
22
- throw new Error("Missing inclusion proof");
23
- }
24
-
25
- const inclusionProof = entry.inclusionProof;
26
- const logIndex = BigInt(inclusionProof.logIndex);
27
- const treeSize = BigInt(inclusionProof.treeSize);
28
-
29
- if (logIndex < 0n || logIndex >= treeSize) {
30
- throw new Error(`Invalid log index: ${logIndex}`);
31
- }
32
-
33
- // Figure out which subset of hashes corresponds to the inner and border
34
- // nodes
35
- const { inner, border } = decompInclProof(logIndex, treeSize);
36
-
37
- if (inclusionProof.hashes.length !== inner + border) {
38
- throw new Error("Invalid hash count in inclusion proof");
39
- }
40
-
41
- const innerHashes = inclusionProof.hashes
42
- .slice(0, inner)
43
- .map((h) => base64ToUint8Array(h));
44
- const borderHashes = inclusionProof.hashes
45
- .slice(inner)
46
- .map((h) => base64ToUint8Array(h));
47
-
48
- // The entry's hash is the leaf hash
49
- const leafHash = await hashLeaf(base64ToUint8Array(entry.canonicalizedBody));
50
-
51
- // Chain the hashes belonging to the inner and border portions
52
- const calculatedHash = await chainBorderRight(
53
- await chainInner(leafHash, innerHashes, logIndex),
54
- borderHashes
55
- );
56
-
57
- const rootHash = base64ToUint8Array(inclusionProof.rootHash);
58
-
59
- // Calculated hash should match the root hash in the inclusion proof
60
- if (!uint8ArrayEqual(calculatedHash, rootHash)) {
61
- throw new Error("Calculated root hash does not match inclusion proof");
62
- }
63
- }
64
-
65
- // Breaks down inclusion proof for a leaf at the specified index in a tree of
66
- // the specified size. The split point is where paths to the index leaf and
67
- // the (size - 1) leaf diverge. Returns lengths of the bottom and upper proof
68
- // parts.
69
- function decompInclProof(
70
- index: bigint,
71
- size: bigint
72
- ): { inner: number; border: number } {
73
- const inner = innerProofSize(index, size);
74
- const border = onesCount(index >> BigInt(inner));
75
- return { inner, border };
76
- }
77
-
78
- // Computes a subtree hash for a node on or below the tree's right border.
79
- // Assumes the provided proof hashes are ordered from lower to higher levels
80
- // and seed is the initial hash of the node specified by the index.
81
- async function chainInner(
82
- seed: Uint8Array,
83
- hashes: Uint8Array[],
84
- index: bigint
85
- ): Promise<Uint8Array> {
86
- let acc = seed;
87
- for (let i = 0; i < hashes.length; i++) {
88
- const h = hashes[i];
89
- if ((index >> BigInt(i)) & BigInt(1)) {
90
- acc = await hashChildren(h, acc);
91
- } else {
92
- acc = await hashChildren(acc, h);
93
- }
94
- }
95
- return acc;
96
- }
97
-
98
- // Computes a subtree hash for nodes along the tree's right border.
99
- async function chainBorderRight(
100
- seed: Uint8Array,
101
- hashes: Uint8Array[]
102
- ): Promise<Uint8Array> {
103
- let acc = seed;
104
- for (const h of hashes) {
105
- acc = await hashChildren(h, acc);
106
- }
107
- return acc;
108
- }
109
-
110
- function innerProofSize(index: bigint, size: bigint): number {
111
- return bitLength(index ^ (size - BigInt(1)));
112
- }
113
-
114
- // Counts the number of ones in the binary representation of the given number.
115
- // https://en.wikipedia.org/wiki/Hamming_weight
116
- function onesCount(num: bigint): number {
117
- return num.toString(2).split("1").length - 1;
118
- }
119
-
120
- // Returns the number of bits necessary to represent an integer in binary.
121
- function bitLength(n: bigint): number {
122
- if (n === 0n) {
123
- return 0;
124
- }
125
- return n.toString(2).length;
126
- }
127
-
128
- // Hashing logic according to RFC6962.
129
- // https://datatracker.ietf.org/doc/html/rfc6962#section-2
130
- async function hashChildren(
131
- left: Uint8Array,
132
- right: Uint8Array
133
- ): Promise<Uint8Array> {
134
- const data = new Uint8Array(
135
- RFC6962_NODE_HASH_PREFIX.length + left.length + right.length
136
- );
137
- data.set(RFC6962_NODE_HASH_PREFIX, 0);
138
- data.set(left, RFC6962_NODE_HASH_PREFIX.length);
139
- data.set(right, RFC6962_NODE_HASH_PREFIX.length + left.length);
140
-
141
- const hash = await crypto.subtle.digest(HashAlgorithms.SHA256, data as BufferSource);
142
- return new Uint8Array(hash);
143
- }
144
-
145
- async function hashLeaf(leaf: Uint8Array): Promise<Uint8Array> {
146
- const data = new Uint8Array(RFC6962_LEAF_HASH_PREFIX.length + leaf.length);
147
- data.set(RFC6962_LEAF_HASH_PREFIX, 0);
148
- data.set(leaf, RFC6962_LEAF_HASH_PREFIX.length);
149
-
150
- const hash = await crypto.subtle.digest(HashAlgorithms.SHA256, data as BufferSource);
151
- return new Uint8Array(hash);
152
- }
@@ -1,38 +0,0 @@
1
- # Trust Material
2
-
3
- This directory contains trust material for Sigstore verification.
4
-
5
- ## tuf-root.json
6
-
7
- **Source**: Extracted from [sigstore-js/packages/tuf/seeds.json](https://github.com/sigstore/sigstore-js/blob/main/packages/tuf/seeds.json)
8
-
9
- **Purpose**: Bootstrap TUF (The Update Framework) client with a pinned root of trust for the Sigstore TUF repository.
10
-
11
- **Content**: Base64-encoded TUF `root.json` metadata for `https://tuf-repo-cdn.sigstore.dev`
12
- - Type: TUF root metadata (version 13)
13
- - Expires: 2026-01-22
14
- - Contains: 5 root signing keys with threshold signature requirement
15
-
16
- **Security**: This file prevents MITM attacks during TUF initialization by embedding the initial root metadata instead of fetching it from the network. The TUF client uses this to verify all subsequent TUF metadata updates.
17
-
18
- **Updates**: Should be updated when sigstore-js updates its embedded TUF root. Check the sigstore-js repository for the latest version.
19
-
20
- ## Trust Material Flow
21
-
22
- 1. **TUF Bootstrap**: `tuf-root.json` → TUF client initialization
23
- 2. **TUF Metadata Updates**: TUF client fetches and verifies latest metadata
24
- 3. **Sigstore Trust Material**: TUF delivers `trusted_root.json` (CAs, CT logs, TLogs, TSAs)
25
- 4. **Verification**: Sigstore verifier uses the trust material to verify signatures
26
-
27
- This matches the sigstore-js architecture: TUF provides always up-to-date trust material rather than using static embedded files that can become outdated.
28
-
29
- ## Why No Static `trusted_root.json`?
30
-
31
- Unlike some implementations, we do **not** embed a static `trusted_root.json` file. Here's why:
32
-
33
- - **Outdated Material**: Static trust roots become outdated (e.g., expired certificates, new CAs, rotated keys)
34
- - **Reviewer Issues**: Previous embedded files had incorrect `validFor` dates and mismatched certificate subjects
35
- - **TUF Purpose**: TUF exists specifically to deliver fresh, verified trust material - using it is the correct approach
36
- - **Matches Reference**: sigstore-js uses TUF exclusively, with no static fallback
37
-
38
- The CLI always uses TUF to fetch `trusted_root.json`, ensuring correctness and freshness.
@@ -1,3 +0,0 @@
1
- // Embedded TUF root metadata (base64-encoded 1.root.json)
2
- // This is the root of trust for bootstrapping the TUF client.
3
- export default "ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiIgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGJiZGRkNDY0ZjgwNjZjZWI4OGJhNzg3Mzc1YzEyY2Q2MzMwNjgwZTA4YzI5MTA3MDNlNjUzOGM3MWNjNzlhZDIwMjIwNTE5MGIwNmU0NTM3ZmU5NjFiM2VmODFmZTY4ZWRjZDAwODljMTlmOTE5YWZlZDQyM2I5YWFmZDcwMDY0MTE1MyIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDQwMjIwNjkzMDZjZDUyNTdmNzMyYTc0MGMxYWZlNjBhOGU0MzNjNWRlNThlYWZlYWRiZTk5YzMzNmM5YzcxZDE5OGNmODAyMjAwZDc3Mzk1M2FlN2RiYzQ4ZDNlNWJhZDlhNmY2NGJhZmZmMTk2YjdlMmFkNGE1MmExOTUxOTM2N2Q0N2RjMDQyIgogIH0sCiAgewogICAia2V5aWQiOiAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICJzaWciOiAiMzA0NDAyMjA0ZDIxYTJlYzgwZGY2NmU2MWY2ZmUyOTEyOTUxZGM0N2RmODM2MDM2ZjhjMGFiMTA4MTZkMzc1ZTcxZGJmNzllMDIyMDU0N2FkY2UxYWZkZjA0ZTY3OTRlZmEyMDNkZDUyNjRjNmY3ZTBlZjc4ZTU3ZmU5MzRiMGQyNmNiOTk0ZWVjNzYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICJhNjg3ZTViZjRmYWI4MmIwZWU1OGQ0NmUwNWM5NTM1MTQ1YTJjOWFmYjQ1OGY0M2Q0MmI0NWNhMGZkY2UyYTcwIiwKICAgInNpZyI6ICIzMDQ1MDIyMDYwODI2NDk2NTU3MTQ0ZWIxNjQ5ODkzZWQ1ZjZmNGVhNTQ1MzZmZWIwY2E4MmY4Yjg5YWU2NDFiZTM5NzQzZTUwMjIxMDBhZDcxMThiNWU5ZDQ4MzczMjYyMDZlNDEyZmM2ZGEyOTk5OTI1ZDExMDMyOGE3YzE2NmIwNmM2MjQzMzZjOTNmIgogIH0sCiAgewogICAia2V5aWQiOiAiMTgzZTY0ZjM3NjcwZGMxM2NhMGQyODk5NWEzMDUzZjM3NDA5NTRkZGNlNDQzMjFhNDFlNDY1MzRjZjQ0ZTYzMiIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGQ4MTc5NDM5YzJlNzNlYjBjMTczM2FiZWU3ZmFmODMyZGNhZWE3MjYzZWRjYjQ5MTk4OTFjM2EyNDdmMDU5MjMwMjIxMDBlMWE0MzdlMDc5N2U4MDNmOWI3MmRjOWQyZDkyMTU1YjBhMjI3MGMyNGVmZGQ1ZjRiM2E1ZDhmMGIwZjQzMWE3IgogIH0KIF0sCiAic2lnbmVkIjogewogICJfdHlwZSI6ICJyb290IiwKICAiY29uc2lzdGVudF9zbmFwc2hvdCI6IHRydWUsCiAgImV4cGlyZXMiOiAiMjAyNi0wMS0yMlQxMzowNTo1OVoiLAogICJrZXlzIjogewogICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVdSaUdyNStqKzNKNVNzSCtadHI1bkUySDJ3TzdcbkJWK25PM3M5M2dMY2ExOHFUT3pIWTFvV3lBR0R5a01Tc0dUVUJTdDlEK0FuMEtmS3NEMm1mU000MlE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1vbmxpbmUtdXJpIjogImdjcGttczpwcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wL2NyeXB0b0tleVZlcnNpb25zLzEiCiAgIH0sCiAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIjogewogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNeHBQT0pDSVo1b3RHNDEwNmZHSnNlRVFpM1Y5XG5wa01ZUTR1eVY5VGoxTTdXSFhJeUxHK2prZnZ1RzBnbFExSlpiUlpaQlYzZ0FSNHNvamRHSElTZW93PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGxhbmNlIgogICB9LAogICAiMjJmNGNhZWM2ZDhlNmY5NTU1YWY2NmIzZDRjM2NiMDZhM2JiMjNmZGM3ZTM5YzkxNmM2MWY0NjJlNmY1MmIwNiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXpCelZPbUhDUG9qTVZMU0kzNjRXaWlWOE5QckRcbjZJZ1J4Vmxpc2t6L3YreTNKRVI1bWNWR2NPTmxpRGNXTUM1SjJsZkhtalBOUGhiNEg3eG04THpmU0E9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAc2FudGlhZ290b3JyZXMiCiAgIH0sCiAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaW5pa1NzQVFtWWtOZUg1ZVlxL0NuSXpMYWFjT1xueGxTYWF3UURPd3FLeS90Q3F4cTV4eFBTSmMyMUs0V0loczlHeU9rS2Z6dWVZM0dJTHpjTUpaNGNXdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBib2JjYWxsYXdheSIKICAgfSwKICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUwZ2hyaDkyTHcxWXIzaWRHVjVXcUN0TURCOEN4XG4rRDhoZEM0dzJaTE5JcGxWUm9WR0xza1lhM2doZU15T2ppSjhrUGkxNWFRMi8vN1Arb2o3VXZKUEd3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGpvc2h1YWdsIgogICB9LAogICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUVYc3ozU1pYRmI4ak1WNDJqNnBKbHlqYmpSOEtcbk4zQndvY2V4cTZMTUliNXFzV0tPUXZMTjE2TlVlZkxjNEhzd09vdW1Sc1ZWYWFqU3BRUzZmb2JrUnc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAbW5tNjc4IgogICB9CiAgfSwKICAicm9sZXMiOiB7CiAgICJyb290IjogewogICAgImtleWlkcyI6IFsKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICAgIjE4M2U2NGYzNzY3MGRjMTNjYTBkMjg5OTVhMzA1M2YzNzQwOTU0ZGRjZTQ0MzIxYTQxZTQ2NTM0Y2Y0NGU2MzIiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInNuYXBzaG90IjogewogICAgImtleWlkcyI6IFsKICAgICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMSwKICAgICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMzY1MCwKICAgICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDM2NQogICB9LAogICAidGFyZ2V0cyI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiLAogICAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAzCiAgIH0sCiAgICJ0aW1lc3RhbXAiOiB7CiAgICAia2V5aWRzIjogWwogICAgICIwYzg3NDMyYzNiZjA5ZmQ5OTE4OWZkYzMyZmE1ZWFlZGY0ZTRhNWZhYzdiYWI3M2ZhMDRhMmUwZmM2NGFmNmY1IgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiA3LAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogNgogICB9CiAgfSwKICAic3BlY192ZXJzaW9uIjogIjEuMCIsCiAgInZlcnNpb24iOiAxMywKICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDE5NywKICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0NgogfQp9";
package/src/trust/tuf.ts DELETED
@@ -1,233 +0,0 @@
1
- /*
2
- * TUF (The Update Framework) integration for trusted root management
3
- *
4
- * Provides secure updates for Sigstore trusted root metadata using TUF.
5
- * Uses tuf-browser for browser-compatible TUF client functionality.
6
- *
7
- * Reference: https://github.com/freedomofpress/tuf-browser
8
- */
9
-
10
- import { Uint8ArrayToString } from "@freedomofpress/crypto-browser";
11
- import type { TUFClient } from "@freedomofpress/tuf-browser";
12
- import { TrustedRoot } from "../interfaces.js";
13
-
14
- /**
15
- * Options for TrustedRootProvider configuration
16
- */
17
- export interface TrustedRootProviderOptions {
18
- /**
19
- * TUF repository URL for metadata
20
- * Default: Sigstore production TUF repository
21
- */
22
- metadataUrl?: string;
23
-
24
- /**
25
- * Target base URL for fetching target files
26
- * If not specified, uses the same as metadataUrl
27
- */
28
- targetBaseUrl?: string;
29
-
30
- /**
31
- * Initial root metadata (1.root.json content)
32
- * If not provided, will use embedded default
33
- */
34
- initialRoot?: string;
35
-
36
- /**
37
- * Namespace for TUF cache storage
38
- * Default: 'tuf-cache'
39
- */
40
- namespace?: string;
41
-
42
- /**
43
- * Name of the trusted root target file
44
- * Default: 'trusted_root.json'
45
- */
46
- trustedRootTarget?: string;
47
-
48
- /**
49
- * Cache TTL in milliseconds
50
- * Default: 1 hour (3600000 ms)
51
- */
52
- cacheTTL?: number;
53
-
54
- /**
55
- * Disable persistent caching (use in-memory only)
56
- * Default: false
57
- */
58
- disableCache?: boolean;
59
- }
60
-
61
- /**
62
- * Default configuration for Sigstore production TUF repository
63
- */
64
- const DEFAULT_CONFIG = {
65
- metadataUrl: 'https://tuf-repo-cdn.sigstore.dev/',
66
- targetBaseUrl: 'https://tuf-repo-cdn.sigstore.dev/targets/',
67
- namespace: 'tuf-cache',
68
- trustedRootTarget: 'trusted_root.json',
69
- cacheTTL: 3600000, // 1 hour
70
- };
71
-
72
- /**
73
- * Provides Sigstore trusted root via TUF for secure updates
74
- *
75
- * This class manages fetching and caching of Sigstore trusted root metadata
76
- * using The Update Framework (TUF) for secure, verified updates.
77
- *
78
- * Example usage:
79
- * ```typescript
80
- * const provider = new TrustedRootProvider();
81
- * const trustedRoot = await provider.getTrustedRoot();
82
- * ```
83
- */
84
- export class TrustedRootProvider {
85
- private metadataUrl: string;
86
- private targetBaseUrl?: string;
87
- private initialRoot?: string;
88
- private namespace: string;
89
- private trustedRootTarget: string;
90
- private cacheTTL: number;
91
- private disableCache: boolean;
92
-
93
- private tufClient?: TUFClient;
94
- private cachedRoot?: TrustedRoot;
95
- private cacheTimestamp?: number;
96
-
97
- constructor(options: TrustedRootProviderOptions = {}) {
98
- const metadataUrl = options.metadataUrl || DEFAULT_CONFIG.metadataUrl;
99
- this.metadataUrl = metadataUrl.endsWith('/') ? metadataUrl : `${metadataUrl}/`;
100
- const targetBaseUrl = options.targetBaseUrl || DEFAULT_CONFIG.targetBaseUrl;
101
- this.targetBaseUrl = targetBaseUrl.endsWith('/') ? targetBaseUrl : `${targetBaseUrl}/`;
102
- this.initialRoot = options.initialRoot;
103
- this.namespace = options.namespace || DEFAULT_CONFIG.namespace;
104
- this.trustedRootTarget = options.trustedRootTarget || DEFAULT_CONFIG.trustedRootTarget;
105
- this.cacheTTL = options.cacheTTL ?? DEFAULT_CONFIG.cacheTTL;
106
- this.disableCache = options.disableCache ?? false;
107
- }
108
-
109
- /**
110
- * Initialize the TUF client
111
- * Lazy initialization to avoid loading TUF client until needed
112
- */
113
- private async initTUFClient(): Promise<void> {
114
- if (this.tufClient) {
115
- return;
116
- }
117
-
118
- try {
119
- const { TUFClient } = await import('@freedomofpress/tuf-browser');
120
-
121
- // Get initial root metadata
122
- const rootMetadata = this.initialRoot || await this.getDefaultRoot();
123
-
124
- this.tufClient = new TUFClient(
125
- this.metadataUrl,
126
- rootMetadata,
127
- this.namespace,
128
- this.targetBaseUrl,
129
- { disableCache: this.disableCache }
130
- );
131
- } catch (error) {
132
- throw new Error(
133
- `Failed to initialize TUF client: ${error instanceof Error ? error.message : String(error)}`
134
- );
135
- }
136
- }
137
-
138
- /**
139
- * Get the default embedded root metadata
140
- * Returns the TUF root.json that bootstraps the TUF client
141
- */
142
- private async getDefaultRoot(): Promise<string> {
143
- // Import the embedded TUF root metadata (base64-encoded 1.root.json as string)
144
- // This is the root of trust for TUF, not to be confused with the Sigstore trusted root
145
- const { default: tufRootBase64 } = await import('./tuf-root.js');
146
-
147
- // Decode from base64 to get the actual root.json content
148
- const decoder = new TextDecoder();
149
- const rootBytes = Uint8Array.from(atob(tufRootBase64), c => c.charCodeAt(0));
150
- return decoder.decode(rootBytes);
151
- }
152
-
153
- /**
154
- * Check if cached trusted root is still valid
155
- */
156
- private isCacheValid(): boolean {
157
- if (!this.cachedRoot || !this.cacheTimestamp) {
158
- return false;
159
- }
160
-
161
- const now = Date.now();
162
- return (now - this.cacheTimestamp) < this.cacheTTL;
163
- }
164
-
165
- /**
166
- * Get the Sigstore trusted root metadata
167
- * Uses TUF to securely fetch and verify the trusted root
168
- *
169
- * @returns Promise<TrustedRoot> The verified trusted root metadata
170
- * @throws Error if TUF verification fails or root cannot be fetched
171
- */
172
- async getTrustedRoot(): Promise<TrustedRoot> {
173
- // Return cached root if still valid
174
- if (this.isCacheValid() && this.cachedRoot) {
175
- return this.cachedRoot;
176
- }
177
-
178
- // Initialize TUF client if needed
179
- await this.initTUFClient();
180
-
181
- if (!this.tufClient) {
182
- throw new Error('TUF client not initialized');
183
- }
184
-
185
- try {
186
- // Update TUF metadata (root, timestamp, snapshot, targets)
187
- // This must be called before getTarget() to populate the metadata cache
188
- await this.tufClient.updateTUF();
189
-
190
- // Fetch the trusted root target via TUF
191
- // TUF will handle all verification (signatures, rollback protection, etc.)
192
- const trustedRootBuffer = await this.tufClient.getTarget(this.trustedRootTarget);
193
-
194
- // Parse the trusted root JSON
195
- const trustedRootJson = Uint8ArrayToString(new Uint8Array(trustedRootBuffer));
196
- const trustedRoot = JSON.parse(trustedRootJson) as TrustedRoot;
197
-
198
- // Cache the result
199
- this.cachedRoot = trustedRoot;
200
- this.cacheTimestamp = Date.now();
201
-
202
- return trustedRoot;
203
- } catch (error) {
204
- throw new Error(
205
- `Failed to fetch trusted root via TUF: ${error instanceof Error ? error.message : String(error)}`
206
- );
207
- }
208
- }
209
-
210
- /**
211
- * Manually refresh the trusted root from TUF
212
- * Bypasses cache and forces a fresh fetch
213
- *
214
- * @returns Promise<TrustedRoot> The updated trusted root metadata
215
- */
216
- async refreshTrustedRoot(): Promise<TrustedRoot> {
217
- // Clear cache
218
- this.cachedRoot = undefined;
219
- this.cacheTimestamp = undefined;
220
-
221
- // Fetch fresh root
222
- return await this.getTrustedRoot();
223
- }
224
-
225
- /**
226
- * Clear the cached trusted root
227
- * Next call to getTrustedRoot() will fetch fresh data
228
- */
229
- clearCache(): void {
230
- this.cachedRoot = undefined;
231
- this.cacheTimestamp = undefined;
232
- }
233
- }