@freedomofpress/sigstore-browser 0.1.0

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 (193) hide show
  1. package/.github/workflows/conformance.yml +29 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/README.md +10 -0
  4. package/dist/asn1/error.d.ts +5 -0
  5. package/dist/asn1/error.d.ts.map +1 -0
  6. package/dist/asn1/error.js +20 -0
  7. package/dist/asn1/error.js.map +1 -0
  8. package/dist/asn1/index.d.ts +2 -0
  9. package/dist/asn1/index.d.ts.map +1 -0
  10. package/dist/asn1/index.js +17 -0
  11. package/dist/asn1/index.js.map +1 -0
  12. package/dist/asn1/length.d.ts +4 -0
  13. package/dist/asn1/length.d.ts.map +1 -0
  14. package/dist/asn1/length.js +59 -0
  15. package/dist/asn1/length.js.map +1 -0
  16. package/dist/asn1/obj.d.ts +15 -0
  17. package/dist/asn1/obj.d.ts.map +1 -0
  18. package/dist/asn1/obj.js +153 -0
  19. package/dist/asn1/obj.js.map +1 -0
  20. package/dist/asn1/parse.d.ts +7 -0
  21. package/dist/asn1/parse.d.ts.map +1 -0
  22. package/dist/asn1/parse.js +118 -0
  23. package/dist/asn1/parse.js.map +1 -0
  24. package/dist/asn1/tag.d.ts +17 -0
  25. package/dist/asn1/tag.d.ts.map +1 -0
  26. package/dist/asn1/tag.js +86 -0
  27. package/dist/asn1/tag.js.map +1 -0
  28. package/dist/bundle.d.ts +87 -0
  29. package/dist/bundle.d.ts.map +1 -0
  30. package/dist/bundle.js +2 -0
  31. package/dist/bundle.js.map +1 -0
  32. package/dist/canonicalize.d.ts +2 -0
  33. package/dist/canonicalize.d.ts.map +1 -0
  34. package/dist/canonicalize.js +64 -0
  35. package/dist/canonicalize.js.map +1 -0
  36. package/dist/cli.d.ts +3 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +217 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/crypto.d.ts +5 -0
  41. package/dist/crypto.d.ts.map +1 -0
  42. package/dist/crypto.js +260 -0
  43. package/dist/crypto.js.map +1 -0
  44. package/dist/dsse.d.ts +2 -0
  45. package/dist/dsse.d.ts.map +1 -0
  46. package/dist/dsse.js +42 -0
  47. package/dist/dsse.js.map +1 -0
  48. package/dist/encoding.d.ts +17 -0
  49. package/dist/encoding.d.ts.map +1 -0
  50. package/dist/encoding.js +112 -0
  51. package/dist/encoding.js.map +1 -0
  52. package/dist/errors.d.ts +39 -0
  53. package/dist/errors.d.ts.map +1 -0
  54. package/dist/errors.js +79 -0
  55. package/dist/errors.js.map +1 -0
  56. package/dist/index.d.ts +8 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +5 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/interfaces.d.ts +104 -0
  61. package/dist/interfaces.d.ts.map +1 -0
  62. package/dist/interfaces.js +26 -0
  63. package/dist/interfaces.js.map +1 -0
  64. package/dist/oid.d.ts +5 -0
  65. package/dist/oid.d.ts.map +1 -0
  66. package/dist/oid.js +25 -0
  67. package/dist/oid.js.map +1 -0
  68. package/dist/pem.d.ts +3 -0
  69. package/dist/pem.d.ts.map +1 -0
  70. package/dist/pem.js +41 -0
  71. package/dist/pem.js.map +1 -0
  72. package/dist/rfc3161/error.d.ts +3 -0
  73. package/dist/rfc3161/error.d.ts.map +1 -0
  74. package/dist/rfc3161/error.js +18 -0
  75. package/dist/rfc3161/error.js.map +1 -0
  76. package/dist/rfc3161/index.d.ts +2 -0
  77. package/dist/rfc3161/index.d.ts.map +1 -0
  78. package/dist/rfc3161/index.js +17 -0
  79. package/dist/rfc3161/index.js.map +1 -0
  80. package/dist/rfc3161/timestamp.d.ts +36 -0
  81. package/dist/rfc3161/timestamp.d.ts.map +1 -0
  82. package/dist/rfc3161/timestamp.js +203 -0
  83. package/dist/rfc3161/timestamp.js.map +1 -0
  84. package/dist/rfc3161/tstinfo.d.ts +13 -0
  85. package/dist/rfc3161/tstinfo.d.ts.map +1 -0
  86. package/dist/rfc3161/tstinfo.js +47 -0
  87. package/dist/rfc3161/tstinfo.js.map +1 -0
  88. package/dist/sigstore.d.ts +33 -0
  89. package/dist/sigstore.d.ts.map +1 -0
  90. package/dist/sigstore.js +531 -0
  91. package/dist/sigstore.js.map +1 -0
  92. package/dist/stream.d.ts +22 -0
  93. package/dist/stream.d.ts.map +1 -0
  94. package/dist/stream.js +114 -0
  95. package/dist/stream.js.map +1 -0
  96. package/dist/timestamp/tsa.d.ts +24 -0
  97. package/dist/timestamp/tsa.d.ts.map +1 -0
  98. package/dist/timestamp/tsa.js +150 -0
  99. package/dist/timestamp/tsa.js.map +1 -0
  100. package/dist/tlog/body.d.ts +8 -0
  101. package/dist/tlog/body.d.ts.map +1 -0
  102. package/dist/tlog/body.js +45 -0
  103. package/dist/tlog/body.js.map +1 -0
  104. package/dist/tlog/checkpoint.d.ts +23 -0
  105. package/dist/tlog/checkpoint.d.ts.map +1 -0
  106. package/dist/tlog/checkpoint.js +187 -0
  107. package/dist/tlog/checkpoint.js.map +1 -0
  108. package/dist/tlog/dsse.d.ts +4 -0
  109. package/dist/tlog/dsse.d.ts.map +1 -0
  110. package/dist/tlog/dsse.js +85 -0
  111. package/dist/tlog/dsse.js.map +1 -0
  112. package/dist/tlog/hashedrekord.d.ts +4 -0
  113. package/dist/tlog/hashedrekord.d.ts.map +1 -0
  114. package/dist/tlog/hashedrekord.js +63 -0
  115. package/dist/tlog/hashedrekord.js.map +1 -0
  116. package/dist/tlog/intoto.d.ts +4 -0
  117. package/dist/tlog/intoto.d.ts.map +1 -0
  118. package/dist/tlog/intoto.js +50 -0
  119. package/dist/tlog/intoto.js.map +1 -0
  120. package/dist/tlog/merkle.d.ts +3 -0
  121. package/dist/tlog/merkle.d.ts.map +1 -0
  122. package/dist/tlog/merkle.js +112 -0
  123. package/dist/tlog/merkle.js.map +1 -0
  124. package/dist/trust/tuf-root.json +1 -0
  125. package/dist/trust/tuf.d.ts +95 -0
  126. package/dist/trust/tuf.d.ts.map +1 -0
  127. package/dist/trust/tuf.js +150 -0
  128. package/dist/trust/tuf.js.map +1 -0
  129. package/dist/x509/cert.d.ts +52 -0
  130. package/dist/x509/cert.d.ts.map +1 -0
  131. package/dist/x509/cert.js +256 -0
  132. package/dist/x509/cert.js.map +1 -0
  133. package/dist/x509/chain.d.ts +20 -0
  134. package/dist/x509/chain.d.ts.map +1 -0
  135. package/dist/x509/chain.js +141 -0
  136. package/dist/x509/chain.js.map +1 -0
  137. package/dist/x509/ext.d.ts +48 -0
  138. package/dist/x509/ext.d.ts.map +1 -0
  139. package/dist/x509/ext.js +164 -0
  140. package/dist/x509/ext.js.map +1 -0
  141. package/dist/x509/index.d.ts +4 -0
  142. package/dist/x509/index.d.ts.map +1 -0
  143. package/dist/x509/index.js +19 -0
  144. package/dist/x509/index.js.map +1 -0
  145. package/dist/x509/sct.d.ts +25 -0
  146. package/dist/x509/sct.d.ts.map +1 -0
  147. package/dist/x509/sct.js +123 -0
  148. package/dist/x509/sct.js.map +1 -0
  149. package/eslint.config.mjs +31 -0
  150. package/package.json +39 -0
  151. package/src/asn1/error.ts +18 -0
  152. package/src/asn1/index.ts +16 -0
  153. package/src/asn1/length.ts +71 -0
  154. package/src/asn1/obj.ts +184 -0
  155. package/src/asn1/parse.ts +146 -0
  156. package/src/asn1/tag.ts +105 -0
  157. package/src/bundle.ts +99 -0
  158. package/src/canonicalize.ts +61 -0
  159. package/src/cli.ts +285 -0
  160. package/src/crypto.ts +310 -0
  161. package/src/dsse.ts +49 -0
  162. package/src/encoding.ts +141 -0
  163. package/src/errors.ts +82 -0
  164. package/src/index.browser.test.ts +219 -0
  165. package/src/index.ts +14 -0
  166. package/src/interfaces.ts +118 -0
  167. package/src/oid.ts +28 -0
  168. package/src/pem.ts +48 -0
  169. package/src/rfc3161/error.ts +16 -0
  170. package/src/rfc3161/index.ts +17 -0
  171. package/src/rfc3161/timestamp.ts +270 -0
  172. package/src/rfc3161/tstinfo.ts +80 -0
  173. package/src/sigstore.ts +728 -0
  174. package/src/stream.ts +141 -0
  175. package/src/timestamp/tsa.ts +205 -0
  176. package/src/tlog/body.ts +65 -0
  177. package/src/tlog/checkpoint.ts +248 -0
  178. package/src/tlog/dsse.ts +147 -0
  179. package/src/tlog/hashedrekord.ts +130 -0
  180. package/src/tlog/intoto.ts +102 -0
  181. package/src/tlog/merkle.ts +152 -0
  182. package/src/trust/README.md +38 -0
  183. package/src/trust/tuf-root.json +1 -0
  184. package/src/trust/tuf.ts +223 -0
  185. package/src/types/tuf-browser.d.ts +13 -0
  186. package/src/x509/cert.ts +319 -0
  187. package/src/x509/chain.ts +212 -0
  188. package/src/x509/ext.ts +212 -0
  189. package/src/x509/index.ts +19 -0
  190. package/src/x509/sct.ts +154 -0
  191. package/tsconfig.json +18 -0
  192. package/vite.config.playwright.ts +26 -0
  193. package/vite.config.ts +18 -0
@@ -0,0 +1,147 @@
1
+ /*
2
+ * DSSE transparency log entry verification
3
+ *
4
+ * Based on sigstore-js:
5
+ * https://github.com/sigstore/sigstore-js/blob/main/packages/verify/src/tlog/dsse.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
+ * - Adds verifyDSSE002Body() - new functionality (reference only supports v0.0.1)
12
+ */
13
+
14
+ import { base64ToUint8Array, hexToUint8Array, toArrayBuffer, uint8ArrayEqual } from "../encoding.js";
15
+ import { HashAlgorithms } from "../interfaces.js";
16
+ import type { SigstoreBundle } from "../bundle.js";
17
+ import type { RekorEntry } from "./body.js";
18
+
19
+ interface DSSESpec {
20
+ signatures?: Array<{
21
+ signature: string;
22
+ verifier?: string;
23
+ keyid?: string;
24
+ }>;
25
+ payloadHash?: {
26
+ algorithm: string;
27
+ value: string;
28
+ };
29
+ envelopeHash?: {
30
+ algorithm: string;
31
+ value: string;
32
+ };
33
+ }
34
+
35
+ interface DSSEEntry extends RekorEntry {
36
+ apiVersion: "0.0.1" | "0.0.2";
37
+ kind: "dsse";
38
+ spec: DSSESpec;
39
+ }
40
+
41
+ export async function verifyDSSEBody(
42
+ entry: RekorEntry,
43
+ bundle: SigstoreBundle
44
+ ): Promise<void> {
45
+ const dsseEntry = entry as DSSEEntry;
46
+
47
+ switch (dsseEntry.apiVersion) {
48
+ case "0.0.1":
49
+ return verifyDSSE001Body(dsseEntry, bundle);
50
+ case "0.0.2":
51
+ return verifyDSSE002Body(dsseEntry, bundle);
52
+ default:
53
+ throw new Error(
54
+ `Unsupported dsse version: ${dsseEntry.apiVersion}`
55
+ );
56
+ }
57
+ }
58
+
59
+ async function verifyDSSE001Body(
60
+ entry: DSSEEntry,
61
+ bundle: SigstoreBundle
62
+ ): Promise<void> {
63
+ if (!bundle.dsseEnvelope) {
64
+ throw new Error("Bundle missing dsseEnvelope for DSSE entry");
65
+ }
66
+
67
+ if (!entry.spec.signatures || entry.spec.signatures.length !== 1) {
68
+ throw new Error("DSSE entry must have exactly one signature");
69
+ }
70
+
71
+ const tlogSig = entry.spec.signatures[0].signature;
72
+ const tlogSigBytes = base64ToUint8Array(tlogSig);
73
+
74
+ if (bundle.dsseEnvelope.signatures.length === 0) {
75
+ throw new Error("Bundle DSSE envelope missing signatures");
76
+ }
77
+
78
+ const bundleSigBytes = base64ToUint8Array(bundle.dsseEnvelope.signatures[0].sig);
79
+
80
+ if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
81
+ throw new Error("DSSE signature mismatch between TLog entry and bundle");
82
+ }
83
+
84
+ const tlogHash = entry.spec.payloadHash?.value || "";
85
+ if (!tlogHash) {
86
+ throw new Error("DSSE entry missing payloadHash");
87
+ }
88
+
89
+ const tlogHashBytes = hexToUint8Array(tlogHash);
90
+
91
+ const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
92
+ const bundleHashBytes = new Uint8Array(
93
+ await crypto.subtle.digest(HashAlgorithms.SHA256, toArrayBuffer(payloadBytes))
94
+ );
95
+
96
+ if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
97
+ throw new Error("DSSE payload hash mismatch between TLog entry and bundle");
98
+ }
99
+ }
100
+
101
+ // New functionality for DSSE v0.0.2 (not in sigstore-js reference, which only supports v0.0.1)
102
+ async function verifyDSSE002Body(
103
+ entry: DSSEEntry,
104
+ bundle: SigstoreBundle
105
+ ): Promise<void> {
106
+ if (!bundle.dsseEnvelope) {
107
+ throw new Error("Bundle missing dsseEnvelope for DSSE v0.0.2 entry");
108
+ }
109
+
110
+ const spec = (entry.spec as any).dsseV002;
111
+ if (!spec) {
112
+ throw new Error("DSSE v0.0.2 entry missing dsseV002 spec");
113
+ }
114
+
115
+ if (!spec.signatures || spec.signatures.length !== 1) {
116
+ throw new Error("DSSE v0.0.2 entry must have exactly one signature");
117
+ }
118
+
119
+ const tlogSig = spec.signatures[0].content;
120
+ const tlogSigBytes = base64ToUint8Array(tlogSig);
121
+
122
+ if (bundle.dsseEnvelope.signatures.length === 0) {
123
+ throw new Error("Bundle DSSE envelope missing signatures");
124
+ }
125
+
126
+ const bundleSigBytes = base64ToUint8Array(bundle.dsseEnvelope.signatures[0].sig);
127
+
128
+ if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
129
+ throw new Error("DSSE signature mismatch between TLog entry and bundle (v0.0.2)");
130
+ }
131
+
132
+ const tlogHash = spec.payloadHash?.digest || "";
133
+ if (!tlogHash) {
134
+ throw new Error("DSSE v0.0.2 entry missing payloadHash");
135
+ }
136
+
137
+ const tlogHashBytes = base64ToUint8Array(tlogHash);
138
+
139
+ const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
140
+ const bundleHashBytes = new Uint8Array(
141
+ await crypto.subtle.digest(HashAlgorithms.SHA256, toArrayBuffer(payloadBytes))
142
+ );
143
+
144
+ if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
145
+ throw new Error("DSSE payload hash mismatch between TLog entry and bundle (v0.0.2)");
146
+ }
147
+ }
@@ -0,0 +1,130 @@
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 "../encoding.js";
14
+ import type { SigstoreBundle } from "../bundle.js";
15
+ import type { RekorEntry } from "./body.js";
16
+
17
+ interface HashedRekordSpec {
18
+ signature: {
19
+ content: string;
20
+ publicKey: {
21
+ content: string;
22
+ };
23
+ };
24
+ data: {
25
+ hash: {
26
+ algorithm: string;
27
+ value: string;
28
+ };
29
+ };
30
+ }
31
+
32
+ interface HashedRekordEntry extends RekorEntry {
33
+ apiVersion: "0.0.1" | "0.0.2";
34
+ kind: "hashedrekord";
35
+ spec: HashedRekordSpec | HashedRekordV002Spec;
36
+ }
37
+
38
+ interface HashedRekordV002Spec {
39
+ hashedRekordV002: {
40
+ signature: {
41
+ content: string;
42
+ verifier: {
43
+ x509Certificate: {
44
+ rawBytes: string;
45
+ };
46
+ };
47
+ };
48
+ data: {
49
+ algorithm: string;
50
+ digest: string;
51
+ };
52
+ };
53
+ }
54
+
55
+ export async function verifyHashedRekordBody(
56
+ entry: RekorEntry,
57
+ bundle: SigstoreBundle
58
+ ): Promise<void> {
59
+ const hashedRekordEntry = entry as HashedRekordEntry;
60
+
61
+ switch (hashedRekordEntry.apiVersion) {
62
+ case "0.0.1":
63
+ return verifyHashedRekordV001Body(hashedRekordEntry, bundle);
64
+ case "0.0.2":
65
+ return verifyHashedRekordV002Body(hashedRekordEntry, bundle);
66
+ default:
67
+ throw new Error(
68
+ `Unsupported hashedrekord version: ${hashedRekordEntry.apiVersion}`
69
+ );
70
+ }
71
+ }
72
+
73
+ function verifyHashedRekordV001Body(
74
+ entry: HashedRekordEntry,
75
+ bundle: SigstoreBundle
76
+ ): void {
77
+ const spec = entry.spec as HashedRekordSpec;
78
+ if (!bundle.messageSignature) {
79
+ throw new Error("Bundle missing messageSignature for hashedrekord entry");
80
+ }
81
+
82
+ const tlogSig = spec.signature.content || "";
83
+ const tlogSigBytes = base64ToUint8Array(tlogSig);
84
+ const bundleSigBytes = base64ToUint8Array(bundle.messageSignature.signature);
85
+
86
+ if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
87
+ throw new Error("Signature mismatch between TLog entry and bundle");
88
+ }
89
+
90
+ const tlogDigest = spec.data.hash?.value || "";
91
+ const tlogDigestBytes = hexToUint8Array(tlogDigest);
92
+ const bundleDigestBytes = base64ToUint8Array(
93
+ bundle.messageSignature.messageDigest.digest
94
+ );
95
+
96
+ if (!uint8ArrayEqual(tlogDigestBytes, bundleDigestBytes)) {
97
+ throw new Error("Digest mismatch between TLog entry and bundle");
98
+ }
99
+ }
100
+
101
+ // New functionality for HashedRekord v0.0.2 (not in sigstore-js reference, which only supports v0.0.1)
102
+ function verifyHashedRekordV002Body(
103
+ entry: HashedRekordEntry,
104
+ bundle: SigstoreBundle
105
+ ): void {
106
+ const spec = (entry.spec as HashedRekordV002Spec).hashedRekordV002;
107
+ if (!bundle.messageSignature) {
108
+ throw new Error("Bundle missing messageSignature for hashedrekord v0.0.2 entry");
109
+ }
110
+
111
+ // NOTE: HashedRekord v0.0.2 uses single base64 encoding (unlike intoto which is double-encoded)
112
+ // Verified against Sigstore conformance test suite bundles
113
+ const tlogSig = spec.signature.content || "";
114
+ const tlogSigBytes = base64ToUint8Array(tlogSig);
115
+ const bundleSigBytes = base64ToUint8Array(bundle.messageSignature.signature);
116
+
117
+ if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
118
+ throw new Error("Signature mismatch between TLog entry and bundle (v0.0.2)");
119
+ }
120
+
121
+ const tlogDigest = spec.data.digest || "";
122
+ const tlogDigestBytes = base64ToUint8Array(tlogDigest);
123
+ const bundleDigestBytes = base64ToUint8Array(
124
+ bundle.messageSignature.messageDigest.digest
125
+ );
126
+
127
+ if (!uint8ArrayEqual(tlogDigestBytes, bundleDigestBytes)) {
128
+ throw new Error("Digest mismatch between TLog entry and bundle (v0.0.2)");
129
+ }
130
+ }
@@ -0,0 +1,102 @@
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, toArrayBuffer, uint8ArrayEqual } from "../encoding.js";
14
+ import { HashAlgorithms } from "../interfaces.js";
15
+ import type { SigstoreBundle } from "../bundle.js";
16
+ import type { RekorEntry } from "./body.js";
17
+
18
+ interface IntotoEnvelope {
19
+ payload: string;
20
+ payloadType: string;
21
+ signatures: Array<{
22
+ keyid?: string;
23
+ sig: string;
24
+ publicKey?: string;
25
+ }>;
26
+ }
27
+
28
+ interface IntotoSpec {
29
+ content: {
30
+ envelope: IntotoEnvelope;
31
+ hash?: {
32
+ algorithm: string;
33
+ value: string;
34
+ };
35
+ payloadHash?: {
36
+ algorithm: string;
37
+ value: string;
38
+ };
39
+ };
40
+ publicKey?: string;
41
+ }
42
+
43
+ interface IntotoEntry extends RekorEntry {
44
+ apiVersion: "0.0.2";
45
+ kind: "intoto";
46
+ spec: IntotoSpec;
47
+ }
48
+
49
+ export async function verifyIntotoBody(
50
+ entry: RekorEntry,
51
+ bundle: SigstoreBundle
52
+ ): Promise<void> {
53
+ const intotoEntry = entry as IntotoEntry;
54
+
55
+ if (intotoEntry.apiVersion !== "0.0.2") {
56
+ throw new Error(
57
+ `Unsupported intoto version: ${intotoEntry.apiVersion}`
58
+ );
59
+ }
60
+
61
+ if (!bundle.dsseEnvelope) {
62
+ throw new Error("Bundle missing dsseEnvelope for intoto entry");
63
+ }
64
+
65
+ const tlogEnvelope = intotoEntry.spec.content.envelope;
66
+
67
+ if (!tlogEnvelope.signatures || tlogEnvelope.signatures.length !== 1) {
68
+ throw new Error("Intoto entry must have exactly one signature");
69
+ }
70
+
71
+ // The signature in the intoto tlog entry is double-base64-encoded:
72
+ // 1. Raw signature bytes are base64-encoded (normal)
73
+ // 2. That base64 string is base64-encoded again (for storage in JSON)
74
+ // So we decode twice: base64 -> string -> base64 -> bytes
75
+ const tlogSigBase64 = tlogEnvelope.signatures[0].sig;
76
+ const tlogSigDecoded = base64Decode(tlogSigBase64); // First decode: base64 -> UTF-8 string (which contains base64)
77
+ const tlogSigBytes = base64ToUint8Array(tlogSigDecoded); // Second decode: base64 string -> raw bytes
78
+
79
+ if (bundle.dsseEnvelope.signatures.length === 0) {
80
+ throw new Error("Bundle DSSE envelope missing signatures");
81
+ }
82
+
83
+ const bundleSigBytes = base64ToUint8Array(bundle.dsseEnvelope.signatures[0].sig);
84
+
85
+ if (!uint8ArrayEqual(tlogSigBytes, bundleSigBytes)) {
86
+ throw new Error("Intoto signature mismatch between TLog entry and bundle");
87
+ }
88
+
89
+ if (intotoEntry.spec.content.payloadHash) {
90
+ const tlogHash = intotoEntry.spec.content.payloadHash.value;
91
+ const tlogHashBytes = hexToUint8Array(tlogHash);
92
+
93
+ const payloadBytes = base64ToUint8Array(bundle.dsseEnvelope.payload);
94
+ const bundleHashBytes = new Uint8Array(
95
+ await crypto.subtle.digest(HashAlgorithms.SHA256, toArrayBuffer(payloadBytes))
96
+ );
97
+
98
+ if (!uint8ArrayEqual(tlogHashBytes, bundleHashBytes)) {
99
+ throw new Error("Intoto payload hash mismatch between TLog entry and bundle");
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,152 @@
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
+ - Custom uint8ArrayEqual instead of crypto.bufferEqual
10
+ */
11
+ import { base64ToUint8Array, toArrayBuffer, uint8ArrayEqual } from "../encoding.js";
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, toArrayBuffer(data));
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, toArrayBuffer(data));
151
+ return new Uint8Array(hash);
152
+ }
@@ -0,0 +1,38 @@
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.
@@ -0,0 +1 @@
1
+ "ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiIgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGJiZGRkNDY0ZjgwNjZjZWI4OGJhNzg3Mzc1YzEyY2Q2MzMwNjgwZTA4YzI5MTA3MDNlNjUzOGM3MWNjNzlhZDIwMjIwNTE5MGIwNmU0NTM3ZmU5NjFiM2VmODFmZTY4ZWRjZDAwODljMTlmOTE5YWZlZDQyM2I5YWFmZDcwMDY0MTE1MyIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDQwMjIwNjkzMDZjZDUyNTdmNzMyYTc0MGMxYWZlNjBhOGU0MzNjNWRlNThlYWZlYWRiZTk5YzMzNmM5YzcxZDE5OGNmODAyMjAwZDc3Mzk1M2FlN2RiYzQ4ZDNlNWJhZDlhNmY2NGJhZmZmMTk2YjdlMmFkNGE1MmExOTUxOTM2N2Q0N2RjMDQyIgogIH0sCiAgewogICAia2V5aWQiOiAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICJzaWciOiAiMzA0NDAyMjA0ZDIxYTJlYzgwZGY2NmU2MWY2ZmUyOTEyOTUxZGM0N2RmODM2MDM2ZjhjMGFiMTA4MTZkMzc1ZTcxZGJmNzllMDIyMDU0N2FkY2UxYWZkZjA0ZTY3OTRlZmEyMDNkZDUyNjRjNmY3ZTBlZjc4ZTU3ZmU5MzRiMGQyNmNiOTk0ZWVjNzYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICJhNjg3ZTViZjRmYWI4MmIwZWU1OGQ0NmUwNWM5NTM1MTQ1YTJjOWFmYjQ1OGY0M2Q0MmI0NWNhMGZkY2UyYTcwIiwKICAgInNpZyI6ICIzMDQ1MDIyMDYwODI2NDk2NTU3MTQ0ZWIxNjQ5ODkzZWQ1ZjZmNGVhNTQ1MzZmZWIwY2E4MmY4Yjg5YWU2NDFiZTM5NzQzZTUwMjIxMDBhZDcxMThiNWU5ZDQ4MzczMjYyMDZlNDEyZmM2ZGEyOTk5OTI1ZDExMDMyOGE3YzE2NmIwNmM2MjQzMzZjOTNmIgogIH0sCiAgewogICAia2V5aWQiOiAiMTgzZTY0ZjM3NjcwZGMxM2NhMGQyODk5NWEzMDUzZjM3NDA5NTRkZGNlNDQzMjFhNDFlNDY1MzRjZjQ0ZTYzMiIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGQ4MTc5NDM5YzJlNzNlYjBjMTczM2FiZWU3ZmFmODMyZGNhZWE3MjYzZWRjYjQ5MTk4OTFjM2EyNDdmMDU5MjMwMjIxMDBlMWE0MzdlMDc5N2U4MDNmOWI3MmRjOWQyZDkyMTU1YjBhMjI3MGMyNGVmZGQ1ZjRiM2E1ZDhmMGIwZjQzMWE3IgogIH0KIF0sCiAic2lnbmVkIjogewogICJfdHlwZSI6ICJyb290IiwKICAiY29uc2lzdGVudF9zbmFwc2hvdCI6IHRydWUsCiAgImV4cGlyZXMiOiAiMjAyNi0wMS0yMlQxMzowNTo1OVoiLAogICJrZXlzIjogewogICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVdSaUdyNStqKzNKNVNzSCtadHI1bkUySDJ3TzdcbkJWK25PM3M5M2dMY2ExOHFUT3pIWTFvV3lBR0R5a01Tc0dUVUJTdDlEK0FuMEtmS3NEMm1mU000MlE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1vbmxpbmUtdXJpIjogImdjcGttczpwcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wL2NyeXB0b0tleVZlcnNpb25zLzEiCiAgIH0sCiAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIjogewogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNeHBQT0pDSVo1b3RHNDEwNmZHSnNlRVFpM1Y5XG5wa01ZUTR1eVY5VGoxTTdXSFhJeUxHK2prZnZ1RzBnbFExSlpiUlpaQlYzZ0FSNHNvamRHSElTZW93PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGxhbmNlIgogICB9LAogICAiMjJmNGNhZWM2ZDhlNmY5NTU1YWY2NmIzZDRjM2NiMDZhM2JiMjNmZGM3ZTM5YzkxNmM2MWY0NjJlNmY1MmIwNiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXpCelZPbUhDUG9qTVZMU0kzNjRXaWlWOE5QckRcbjZJZ1J4Vmxpc2t6L3YreTNKRVI1bWNWR2NPTmxpRGNXTUM1SjJsZkhtalBOUGhiNEg3eG04THpmU0E9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAc2FudGlhZ290b3JyZXMiCiAgIH0sCiAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaW5pa1NzQVFtWWtOZUg1ZVlxL0NuSXpMYWFjT1xueGxTYWF3UURPd3FLeS90Q3F4cTV4eFBTSmMyMUs0V0loczlHeU9rS2Z6dWVZM0dJTHpjTUpaNGNXdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBib2JjYWxsYXdheSIKICAgfSwKICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUwZ2hyaDkyTHcxWXIzaWRHVjVXcUN0TURCOEN4XG4rRDhoZEM0dzJaTE5JcGxWUm9WR0xza1lhM2doZU15T2ppSjhrUGkxNWFRMi8vN1Arb2o3VXZKUEd3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGpvc2h1YWdsIgogICB9LAogICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUVYc3ozU1pYRmI4ak1WNDJqNnBKbHlqYmpSOEtcbk4zQndvY2V4cTZMTUliNXFzV0tPUXZMTjE2TlVlZkxjNEhzd09vdW1Sc1ZWYWFqU3BRUzZmb2JrUnc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAbW5tNjc4IgogICB9CiAgfSwKICAicm9sZXMiOiB7CiAgICJyb290IjogewogICAgImtleWlkcyI6IFsKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICAgIjE4M2U2NGYzNzY3MGRjMTNjYTBkMjg5OTVhMzA1M2YzNzQwOTU0ZGRjZTQ0MzIxYTQxZTQ2NTM0Y2Y0NGU2MzIiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInNuYXBzaG90IjogewogICAgImtleWlkcyI6IFsKICAgICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMSwKICAgICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMzY1MCwKICAgICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDM2NQogICB9LAogICAidGFyZ2V0cyI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiLAogICAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAzCiAgIH0sCiAgICJ0aW1lc3RhbXAiOiB7CiAgICAia2V5aWRzIjogWwogICAgICIwYzg3NDMyYzNiZjA5ZmQ5OTE4OWZkYzMyZmE1ZWFlZGY0ZTRhNWZhYzdiYWI3M2ZhMDRhMmUwZmM2NGFmNmY1IgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiA3LAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogNgogICB9CiAgfSwKICAic3BlY192ZXJzaW9uIjogIjEuMCIsCiAgInZlcnNpb24iOiAxMywKICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDE5NywKICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0NgogfQp9"