@xemahq/xema 0.26.0 → 0.28.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 (230) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/distribution-assets.manifest.json +1 -2
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/dev.js +1 -0
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/down.d.ts.map +1 -1
  8. package/dist/commands/down.js +7 -1
  9. package/dist/commands/down.js.map +1 -1
  10. package/dist/commands/edition.d.ts +3 -0
  11. package/dist/commands/edition.d.ts.map +1 -0
  12. package/dist/commands/edition.js +46 -0
  13. package/dist/commands/edition.js.map +1 -0
  14. package/dist/commands/init.js +2 -2
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/serve.js +1 -0
  17. package/dist/commands/serve.js.map +1 -1
  18. package/dist/commands/up.js +1 -0
  19. package/dist/commands/up.js.map +1 -1
  20. package/dist/dev/env-defaults.d.ts.map +1 -1
  21. package/dist/dev/env-defaults.js +5 -2
  22. package/dist/dev/env-defaults.js.map +1 -1
  23. package/dist/dev/infra-preflight.d.ts +3 -2
  24. package/dist/dev/infra-preflight.d.ts.map +1 -1
  25. package/dist/dev/infra-preflight.js +18 -8
  26. package/dist/dev/infra-preflight.js.map +1 -1
  27. package/dist/edition/verify-image-evidence.bundle.mjs +54283 -0
  28. package/dist/edition/verify-image-evidence.bundle.provenance.json +2877 -0
  29. package/dist/runtime/config/schema.d.ts +1 -0
  30. package/dist/runtime/config/schema.d.ts.map +1 -1
  31. package/dist/runtime/custody/local-custody.d.ts +12 -0
  32. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  33. package/dist/runtime/custody/local-custody.js +22 -0
  34. package/dist/runtime/custody/local-custody.js.map +1 -0
  35. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  36. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  37. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  38. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  39. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  40. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  41. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  42. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  43. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  44. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  45. package/dist/runtime/custody/openbao-http-client.js +154 -0
  46. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  47. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  48. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  49. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  50. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  51. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  52. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  53. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  54. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  55. package/dist/runtime/edition/run-entry.d.ts +9 -0
  56. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  57. package/dist/runtime/edition/run-entry.js +36 -0
  58. package/dist/runtime/edition/run-entry.js.map +1 -0
  59. package/dist/runtime/edition/verb-map.d.ts +60 -0
  60. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  61. package/dist/runtime/edition/verb-map.js +146 -0
  62. package/dist/runtime/edition/verb-map.js.map +1 -0
  63. package/dist/runtime/env-seeder.d.ts +2 -0
  64. package/dist/runtime/env-seeder.d.ts.map +1 -1
  65. package/dist/runtime/env-seeder.js +18 -3
  66. package/dist/runtime/env-seeder.js.map +1 -1
  67. package/dist/runtime/launch.d.ts +2 -1
  68. package/dist/runtime/launch.d.ts.map +1 -1
  69. package/dist/runtime/launch.js +31 -7
  70. package/dist/runtime/launch.js.map +1 -1
  71. package/dist/runtime/paths.d.ts +1 -2
  72. package/dist/runtime/paths.d.ts.map +1 -1
  73. package/dist/runtime/paths.js +0 -10
  74. package/dist/runtime/paths.js.map +1 -1
  75. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  76. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  77. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  78. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  79. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  80. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  81. package/dist/runtime/runner/chart-preflight.js +30 -69
  82. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  83. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  84. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  85. package/dist/runtime/runner/chart-pull.js +96 -0
  86. package/dist/runtime/runner/chart-pull.js.map +1 -0
  87. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  88. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  89. package/dist/runtime/runner/chart-resolver.js +16 -62
  90. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  91. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  92. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  93. package/dist/runtime/runner/chart-set-lock.js +55 -99
  94. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  95. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  96. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  97. package/dist/runtime/runner/helm-cli.js +9 -7
  98. package/dist/runtime/runner/helm-cli.js.map +1 -1
  99. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  100. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  101. package/dist/runtime/runner/helm-runner.js +18 -16
  102. package/dist/runtime/runner/helm-runner.js.map +1 -1
  103. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  104. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  105. package/dist/runtime/sources/kinds/git.js +8 -5
  106. package/dist/runtime/sources/kinds/git.js.map +1 -1
  107. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  108. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  109. package/dist/runtime/sources/manifest-walk.js +8 -3
  110. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  111. package/dist/runtime/sources/schema.d.ts +3 -0
  112. package/dist/runtime/sources/schema.d.ts.map +1 -1
  113. package/dist/runtime/sources/schema.js +6 -1
  114. package/dist/runtime/sources/schema.js.map +1 -1
  115. package/dist/runtime/supervisor.d.ts +2 -0
  116. package/dist/runtime/supervisor.d.ts.map +1 -1
  117. package/dist/runtime/supervisor.js +1 -0
  118. package/dist/runtime/supervisor.js.map +1 -1
  119. package/edition/README.md +313 -0
  120. package/edition/assemble-producer-input.mjs +1251 -0
  121. package/edition/authorization-base.mjs +347 -0
  122. package/edition/biome-content.mjs +268 -0
  123. package/edition/build-biome-index.mjs +175 -0
  124. package/edition/build-platform-service-index.mjs +288 -0
  125. package/edition/build-runtime-image-source-index.mjs +821 -0
  126. package/edition/built-verifier.mjs +55 -0
  127. package/edition/catalogue-edition.mjs +49 -0
  128. package/edition/catalogue-fetch.mjs +64 -0
  129. package/edition/catalogue.mjs +135 -0
  130. package/edition/chart-set-source-commit.mjs +92 -0
  131. package/edition/check-committed-image-evidence.mjs +383 -0
  132. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  133. package/edition/comment-blanker.mjs +75 -0
  134. package/edition/connector-source-authority.mjs +60 -0
  135. package/edition/customer-definition.mjs +183 -0
  136. package/edition/customer-flags.mjs +7 -0
  137. package/edition/develop-customer-edition.mjs +99 -0
  138. package/edition/develop-edition-resolve.mjs +65 -0
  139. package/edition/develop-edition.mjs +274 -0
  140. package/edition/develop-freeze.mjs +173 -0
  141. package/edition/develop-image-coordinate.mjs +54 -0
  142. package/edition/develop-seal.mjs +77 -0
  143. package/edition/develop-snapshot-inputs.mjs +104 -0
  144. package/edition/dist-check.mjs +986 -0
  145. package/edition/distribution-evidence-set.mjs +987 -0
  146. package/edition/distribution-paths.mjs +191 -0
  147. package/edition/edition-artifact.mjs +284 -0
  148. package/edition/edition-biome-manifests.mjs +85 -0
  149. package/edition/edition-channel.mjs +6 -0
  150. package/edition/edition-chart-set.mjs +93 -0
  151. package/edition/edition-config.mjs +205 -0
  152. package/edition/edition-constants.mjs +57 -0
  153. package/edition/edition-contract.json +30 -0
  154. package/edition/edition-list.mjs +35 -0
  155. package/edition/edition-locks.mjs +69 -0
  156. package/edition/edition-protocol.mjs +228 -0
  157. package/edition/edition-workspace.mjs +187 -0
  158. package/edition/evidence-checkout.mjs +439 -0
  159. package/edition/evidence-grammars.mjs +16 -0
  160. package/edition/evidence-trust-pin.mjs +304 -0
  161. package/edition/evidence-trust-policy.mjs +294 -0
  162. package/edition/external-source-walk.mjs +463 -0
  163. package/edition/first-party-source-ids.mjs +59 -0
  164. package/edition/fleet-coordinate-finality.mjs +350 -0
  165. package/edition/git-source-auth.mjs +52 -0
  166. package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
  167. package/edition/image-evidence-replay-state.mjs +242 -0
  168. package/edition/image-evidence.mjs +1233 -0
  169. package/edition/kernel-contract-authority.mjs +443 -0
  170. package/edition/lib.mjs +3021 -0
  171. package/edition/no-follow-file.mjs +94 -0
  172. package/edition/oci-evidence-verbs.mjs +392 -0
  173. package/edition/oci-image-source-evidence.mjs +239 -0
  174. package/edition/oras.mjs +648 -0
  175. package/edition/pin-source.mjs +227 -0
  176. package/edition/plan-image-evidence-refresh.mjs +408 -0
  177. package/edition/platform-lock-readability.mjs +892 -0
  178. package/edition/platform-service-class.mjs +18 -0
  179. package/edition/platform-service-source-authority.mjs +105 -0
  180. package/edition/positive-closure.mjs +162 -0
  181. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  182. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  183. package/edition/produce-image-source-evidence.mjs +765 -0
  184. package/edition/producer-evidence-bundle.mjs +157 -0
  185. package/edition/registry-credentials.mjs +193 -0
  186. package/edition/release-catalogue.mjs +39 -0
  187. package/edition/release-channel-guard.mjs +109 -0
  188. package/edition/release-edition-artifact.mjs +157 -0
  189. package/edition/remedy-command.mjs +89 -0
  190. package/edition/repin-evidence-trust.mjs +101 -0
  191. package/edition/required-resource-provider.mjs +58 -0
  192. package/edition/resolve-chart-set.mjs +415 -0
  193. package/edition/resolve-customer-edition.mjs +288 -0
  194. package/edition/resolve-distribution.mjs +2318 -0
  195. package/edition/resolve-edition.mjs +186 -0
  196. package/edition/resolve-images.mjs +1606 -0
  197. package/edition/seal-image-digests.mjs +855 -0
  198. package/edition/sealed-evidence-accept.mjs +82 -0
  199. package/edition/source-authority.mjs +725 -0
  200. package/edition/source-closure.mjs +116 -0
  201. package/edition/strict-json.mjs +121 -0
  202. package/edition/string-order.mjs +5 -0
  203. package/edition/trust-policy-arming.mjs +120 -0
  204. package/edition/trusted-executable.mjs +166 -0
  205. package/edition/verifier-binding.mjs +45 -0
  206. package/edition/verifier-files.mjs +8 -0
  207. package/edition/verifier-kernel-schemas.mjs +22 -0
  208. package/edition/verifier-provenance.mjs +64 -0
  209. package/edition/verify-image-evidence.mjs +458 -0
  210. package/edition/verify-image-platforms.mjs +495 -0
  211. package/edition/why.mjs +56 -0
  212. package/edition/workspace-sections.mjs +195 -0
  213. package/examples/06-biome-sources.yaml +1 -0
  214. package/package.json +14 -6
  215. package/dist/commands/dist.d.ts +0 -16
  216. package/dist/commands/dist.d.ts.map +0 -1
  217. package/dist/commands/dist.js +0 -182
  218. package/dist/commands/dist.js.map +0 -1
  219. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  220. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  221. package/dist/runtime/distribution/available-biome.js +0 -156
  222. package/dist/runtime/distribution/available-biome.js.map +0 -1
  223. package/dist/runtime/distribution/compose.d.ts +0 -23
  224. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  225. package/dist/runtime/distribution/compose.js +0 -48
  226. package/dist/runtime/distribution/compose.js.map +0 -1
  227. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  228. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  229. package/dist/runtime/runner/chart-trust.js +0 -270
  230. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,458 @@
1
+ // The evidence verifier. This module is the ENTRY of the verifier bundle
2
+ // (`scripts/build-evidence-verifier.mjs` -> dist/edition/verify-image-evidence
3
+ // .bundle.mjs); `xema edition verify-evidence` runs that built file, never this
4
+ // source. Run as the bundle it refuses, before it trusts anything, unless:
5
+ //
6
+ // * its launch environment carries no NODE_OPTIONS/NODE_PATH/DYLD_*/LD_*/GIT_*;
7
+ // * its own file and provenance hash to the digests the data root PINS, and
8
+ // its install path and every ancestor are not group/world writable or
9
+ // foreign-owned;
10
+ // * the kernel `--kernel` states declares, as data, exactly the edition
11
+ // protocol compiled into the bundle, and this CLI supports it;
12
+ // * the data root and the editions root are clean git checkouts at an exact
13
+ // HEAD with every file read from them bound to its HEAD blob.
14
+ //
15
+ // usage: verify-image-evidence <distributionId> --evidence-root <dir>
16
+ // --descriptor <absolute-path> --state <absolute-path> --cosign <absolute-path> --git <absolute-path>
17
+ // [--evidence-set <relative-path>] --data-root <dir> --editions-root <dir>
18
+ // --kernel <dir>
19
+
20
+ import { readFileSync, realpathSync } from 'node:fs';
21
+ import { isAbsolute } from 'node:path';
22
+
23
+ import { assertCanonicalDistributionId } from './distribution-paths.mjs';
24
+ import { parseEditionDescriptor } from './edition-artifact.mjs';
25
+ import { EditionChannel } from './edition-channel.mjs';
26
+ import { editionWorkspaceConfig, resetEditionWorkspaceConfig } from './edition-config.mjs';
27
+ import { assertKernelSpeaksBundledProtocol } from './edition-protocol.mjs';
28
+ import {
29
+ WorkspaceRoot,
30
+ configureEditionWorkspaceFromArgv,
31
+ dataRoot,
32
+ editionsRoot,
33
+ isEntryModule,
34
+ kernelPackageDir,
35
+ } from './edition-workspace.mjs';
36
+ import {
37
+ assertExternalInput,
38
+ gradeTrustedCheckouts,
39
+ prepareTrustedGitBinary,
40
+ } from './evidence-checkout.mjs';
41
+ import {
42
+ RUNTIME_IMAGE_SOURCE_INDEX_FILE,
43
+ assertPinnedKernelContractAuthority,
44
+ assertPinnedPolicy,
45
+ assertPinnedVerifier,
46
+ readStatedKernelContractAuthority,
47
+ loadDeclaredTrustPolicy,
48
+ readEvidenceTrustPin,
49
+ trustPolicyDigest,
50
+ verifierDigests,
51
+ } from './evidence-trust-pin.mjs';
52
+ import {
53
+ assertEntitlementDescriptorStates,
54
+ entitlementEvidenceRoot,
55
+ parseEntitlementDescriptorDocument,
56
+ } from './image-delivery-entitlement-descriptor.mjs';
57
+ import { verifyAndAdvanceReplayState } from './image-evidence-replay-state.mjs';
58
+ import {
59
+ readCanonicalJsonFromRoot,
60
+ verifyDistributionImageEvidenceSet,
61
+ verifyImageDeliveryEntitlement,
62
+ } from './image-evidence.mjs';
63
+ import {
64
+ assertSanitizedOperatorEnvironment,
65
+ readRunningVerifier,
66
+ } from './verifier-binding.mjs';
67
+ import { loadVerifierKernelSchemas } from './verifier-kernel-schemas.mjs';
68
+
69
+ const USAGE =
70
+ 'usage: verify-image-evidence <distributionId> --evidence-root <dir> --descriptor <absolute-path> --state <absolute-path> ' +
71
+ '--cosign <absolute-path> --git <absolute-path> [--evidence-set <relative-path>] [--json] ' +
72
+ '[--entitlement <relative-path> --entitlement-workflow-sha <40-hex git sha> --customer <id> --mode <mode> [--target-registry <registry>]] ' +
73
+ '--data-root <dir> --editions-root <dir> --kernel <dir>';
74
+ // The edition channel -> the kernel's evidence channel, stated once and explicitly.
75
+ const EVIDENCE_CHANNEL_OF_EDITION_CHANNEL = Object.freeze({
76
+ [EditionChannel.Release]: 'release',
77
+ [EditionChannel.Develop]: 'develop',
78
+ });
79
+ const GIT_COMMIT_SHA = /^[a-f0-9]{40}$/;
80
+ const DATA_ROOT_FILES = Object.freeze(['edition-workspace.json', 'runtime-image-source-index.json']);
81
+
82
+ function requireOptionValue(args, index, flag) {
83
+ const value = args[index + 1];
84
+ if (value === undefined || value.startsWith('--')) {
85
+ throw new Error(`${flag} requires a value`);
86
+ }
87
+ return value;
88
+ }
89
+
90
+ function parseArgs(args) {
91
+ const [distributionId, ...withJson] = args;
92
+ const jsonFlags = withJson.filter((arg) => arg === '--json').length;
93
+ if (jsonFlags > 1) {throw new Error('--json was given twice');}
94
+ const rest = withJson.filter((arg) => arg !== '--json');
95
+ try {
96
+ assertCanonicalDistributionId(distributionId);
97
+ } catch {
98
+ throw new Error(USAGE);
99
+ }
100
+ const options = {
101
+ distributionId,
102
+ evidenceRoot: null,
103
+ evidenceSetPath: 'evidence-set.json',
104
+ json: jsonFlags === 1,
105
+ descriptorPath: null,
106
+ statePath: null,
107
+ cosignBinary: null,
108
+ gitBinary: null,
109
+ entitlementDescriptorPath: null,
110
+ entitlementWorkflowSha: null,
111
+ customerId: null,
112
+ mode: null,
113
+ targetRegistry: null,
114
+ };
115
+ const seen = new Set();
116
+ for (let index = 0; index < rest.length; index += 2) {
117
+ const flag = rest[index];
118
+ const value = requireOptionValue(rest, index, flag);
119
+ if (seen.has(flag)) {throw new Error(`${flag} was given twice`);}
120
+ seen.add(flag);
121
+ if (flag === '--evidence-root') {options.evidenceRoot = value;}
122
+ else if (flag === '--evidence-set') {options.evidenceSetPath = value;}
123
+ else if (flag === '--descriptor') {options.descriptorPath = value;}
124
+ else if (flag === '--state') {options.statePath = value;}
125
+ else if (flag === '--cosign') {options.cosignBinary = value;}
126
+ else if (flag === '--git') {options.gitBinary = value;}
127
+ else if (flag === '--entitlement') {options.entitlementDescriptorPath = value;}
128
+ else if (flag === '--entitlement-workflow-sha') {options.entitlementWorkflowSha = value;}
129
+ else if (flag === '--customer') {options.customerId = value;}
130
+ else if (flag === '--mode') {options.mode = value;}
131
+ else if (flag === '--target-registry') {options.targetRegistry = value;}
132
+ else {throw new Error(`unknown option "${flag}"`);}
133
+ }
134
+ if (!options.evidenceRoot) {
135
+ throw new Error('--evidence-root is required');
136
+ }
137
+ for (const [flag, value] of [
138
+ ['--descriptor', options.descriptorPath],
139
+ ['--state', options.statePath],
140
+ ['--cosign', options.cosignBinary],
141
+ ['--git', options.gitBinary],
142
+ ]) {
143
+ if (!value || !isAbsolute(value)) {
144
+ throw new Error(`${flag} is required and must be an absolute path`);
145
+ }
146
+ }
147
+ // An entitlement is verified whole or not at all: the descriptor names the
148
+ // signed statement, the workflow SHA is the exact commit its certificate is
149
+ // held to (stated by the caller, never read from the evidence), and the
150
+ // customer and mode are what it must be bound to.
151
+ const entitlementFlags = [
152
+ ['--entitlement', options.entitlementDescriptorPath],
153
+ ['--entitlement-workflow-sha', options.entitlementWorkflowSha],
154
+ ['--customer', options.customerId],
155
+ ['--mode', options.mode],
156
+ ];
157
+ const stated = entitlementFlags.filter(([, value]) => value !== null);
158
+ if (stated.length !== 0 && stated.length !== entitlementFlags.length) {
159
+ throw new Error(
160
+ `${entitlementFlags.map(([flag]) => flag).join(', ')} are given together or not at all`,
161
+ );
162
+ }
163
+ if (stated.length === 0 && options.targetRegistry !== null) {
164
+ throw new Error('--target-registry needs --entitlement, --entitlement-workflow-sha, --customer and --mode');
165
+ }
166
+ if (options.entitlementWorkflowSha !== null && !GIT_COMMIT_SHA.test(options.entitlementWorkflowSha)) {
167
+ throw new Error('--entitlement-workflow-sha must be a 40-character lowercase hexadecimal git commit SHA');
168
+ }
169
+ if (options.entitlementDescriptorPath !== null && isAbsolute(options.entitlementDescriptorPath)) {
170
+ throw new Error('--entitlement must be an evidence-root-relative path');
171
+ }
172
+ return options;
173
+ }
174
+
175
+ /**
176
+ * The channel the edition being verified was produced on: the descriptor's
177
+ * `provenance.channel`, mapped to the kernel's evidence channel. The descriptor
178
+ * must be the one of the requested distribution; an unmapped channel is refused.
179
+ */
180
+ function readEvidenceChannel({ descriptorPath, distributionId, schemas }) {
181
+ const descriptor = parseEditionDescriptor(JSON.parse(readFileSync(descriptorPath, 'utf8')));
182
+ if (descriptor.distributionId !== distributionId) {
183
+ throw new Error(
184
+ `--descriptor is the descriptor of "${descriptor.distributionId}", not of the requested distribution "${distributionId}"`,
185
+ );
186
+ }
187
+ const channel = EVIDENCE_CHANNEL_OF_EDITION_CHANNEL[descriptor.provenance.channel];
188
+ if (channel === undefined || !schemas.EVIDENCE_CHANNELS.includes(channel)) {
189
+ throw new Error(
190
+ `the descriptor channel "${descriptor.provenance.channel}" maps to no kernel evidence channel`,
191
+ );
192
+ }
193
+ return channel;
194
+ }
195
+
196
+ export const EDITION_EVIDENCE_VERIFICATION_SCHEMA = 'xema.edition-evidence-verification/v1';
197
+
198
+ /**
199
+ * What `--json` prints once the evidence is verified: the channel it was verified
200
+ * on, the release sequence it proved, and the statementDigest (the digest of the verified in-toto statement, not of the image) of every statement verified
201
+ * (producers by subject, then completeness, then the entitlement if one was asked for).
202
+ * `entitlementDescriptor` is the Sigstore evidence descriptor `entitlement.json` holds.
203
+ */
204
+ export function evidenceVerificationDocument({ distributionId, channel, evidenceSet, verified, entitlementDescriptor }) {
205
+ const producers = evidenceSet.producerEvidence
206
+ .map((producer) => {
207
+ const statement = verified.producerStatements.get(`${producer.subjectKind}\0${producer.subjectId}`);
208
+ return {
209
+ predicateType: statement.predicateType,
210
+ subject: statement.subject[0].name,
211
+ statementDigest: producer.evidence.statementDigest,
212
+ };
213
+ })
214
+ .sort((left, right) => (left.subject < right.subject ? -1 : left.subject > right.subject ? 1 : 0));
215
+ const statements = [
216
+ ...producers,
217
+ {
218
+ predicateType: verified.completenessStatement.predicateType,
219
+ subject: verified.completenessStatement.subject[0].name,
220
+ statementDigest: evidenceSet.completeness.statementDigest,
221
+ },
222
+ ];
223
+ if (verified.entitlementStatement !== null) {
224
+ statements.push({
225
+ predicateType: verified.entitlementStatement.predicateType,
226
+ subject: verified.entitlementStatement.subject[0].name,
227
+ statementDigest: entitlementDescriptor.statementDigest,
228
+ });
229
+ }
230
+ return {
231
+ schema: EDITION_EVIDENCE_VERIFICATION_SCHEMA,
232
+ distributionId,
233
+ channel,
234
+ releaseSequence: verified.completenessStatement.predicate.releaseSequence,
235
+ statements,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * The declared policy, held to the data-root pin of both the policy and THIS
241
+ * running verifier. Read from the working tree: it is read twice, before the
242
+ * checkouts are graded (the policy names the git binary that grades them) and
243
+ * after, and the two readings must be the same policy.
244
+ */
245
+ function readTrustAnchors({ schemas, running }) {
246
+ resetEditionWorkspaceConfig();
247
+ const declaration = editionWorkspaceConfig().evidenceTrustPolicy;
248
+ if (declaration.policy === null) {
249
+ throw new Error(
250
+ `${dataRoot()} declares no evidenceTrustPolicy in edition-workspace.json; there is no policy to verify against`,
251
+ );
252
+ }
253
+ const { policy, policyDigest } = loadDeclaredTrustPolicy({
254
+ dataRoot: dataRoot(),
255
+ declaration,
256
+ schemas,
257
+ });
258
+ const pin = readEvidenceTrustPin({ dataRoot: dataRoot(), declaration });
259
+ assertPinnedPolicy({ pin, policyDigest });
260
+ assertPinnedVerifier({ pin, actual: verifierDigests(running) });
261
+ // The authority the producer statements are held to is read from the data
262
+ // root's index downstream; it is the pinned one, or nothing is verified.
263
+ assertPinnedKernelContractAuthority({
264
+ pin,
265
+ authority: readStatedKernelContractAuthority({ dataRoot: dataRoot(), schemas }),
266
+ label: RUNTIME_IMAGE_SOURCE_INDEX_FILE,
267
+ });
268
+ return { declaration, policy, policyDigest };
269
+ }
270
+
271
+ /**
272
+ * Verify the evidence for one edition. `bundlePath` is the running bundle
273
+ * (`process.argv[1]` when run as one).
274
+ */
275
+ export async function verifyEvidence({ argv, env, bundlePath }) {
276
+ assertSanitizedOperatorEnvironment(env);
277
+ const options = parseArgs(
278
+ configureEditionWorkspaceFromArgv(argv, [
279
+ WorkspaceRoot.Data,
280
+ WorkspaceRoot.Editions,
281
+ WorkspaceRoot.Kernel,
282
+ ]),
283
+ );
284
+ const evidenceRoot = realpathSync(options.evidenceRoot);
285
+ const running = readRunningVerifier(bundlePath);
286
+ const { schemas, editionProtocol } = await loadVerifierKernelSchemas();
287
+ assertKernelSpeaksBundledProtocol({
288
+ kernelPackageDir: kernelPackageDir(),
289
+ bundled: editionProtocol,
290
+ });
291
+ const first = readTrustAnchors({ schemas, running });
292
+ const trustedGit = prepareTrustedGitBinary({
293
+ gitBinary: options.gitBinary,
294
+ policy: first.policy,
295
+ });
296
+ try {
297
+ const grades = [
298
+ {
299
+ root: dataRoot(),
300
+ label: 'data root',
301
+ requiredPaths: [
302
+ ...DATA_ROOT_FILES,
303
+ first.declaration.policy,
304
+ first.declaration.pin,
305
+ first.policy.trustedRootPath,
306
+ ],
307
+ },
308
+ {
309
+ root: editionsRoot(),
310
+ label: 'editions root',
311
+ requiredPaths: [
312
+ 'distribution.lock.json',
313
+ 'images.lock.json',
314
+ 'xema-distribution.json',
315
+ ].map((file) => `${options.distributionId}/${file}`),
316
+ },
317
+ ];
318
+ const graded = gradeTrustedCheckouts({ grades, trustedGit });
319
+ const repositoryRoots = new Set(
320
+ [...graded.values()].map((checkout) => checkout.repositoryRoot),
321
+ );
322
+ // Now that the files are bound to HEAD, the policy and pin read above must
323
+ // be the ones that were graded.
324
+ const anchors = readTrustAnchors({ schemas, running });
325
+ if (anchors.policyDigest !== first.policyDigest) {
326
+ throw new Error('the trust policy changed while the checkouts were graded');
327
+ }
328
+ assertExternalInput(repositoryRoots, evidenceRoot, 'evidence root');
329
+ assertExternalInput(repositoryRoots, options.statePath, 'replay state');
330
+ assertExternalInput(repositoryRoots, options.descriptorPath, 'edition descriptor');
331
+ const channel = readEvidenceChannel({
332
+ descriptorPath: options.descriptorPath,
333
+ distributionId: options.distributionId,
334
+ schemas,
335
+ });
336
+ const releaseSourceCommit = graded.get(realpathSync(editionsRoot())).commit;
337
+ const assertCheckoutsUnchanged = () =>
338
+ gradeTrustedCheckouts({ grades, trustedGit, expected: graded });
339
+ const evidenceSet = schemas.parseDistributionImageEvidenceSet(
340
+ readCanonicalJsonFromRoot(
341
+ evidenceRoot,
342
+ options.evidenceSetPath,
343
+ 'distribution image evidence set',
344
+ ),
345
+ );
346
+ const policyDigest = trustPolicyDigest(anchors.policy);
347
+ const { verified } = await verifyAndAdvanceReplayState({
348
+ statePath: options.statePath,
349
+ distributionId: options.distributionId,
350
+ trustPolicyDigest: policyDigest,
351
+ parseReplayState: schemas.parseDistributionImageReplayState,
352
+ verify: async () => {
353
+ assertCheckoutsUnchanged();
354
+ const result = await verifyDistributionImageEvidenceSet({
355
+ distributionId: options.distributionId,
356
+ channel,
357
+ dataRoot: dataRoot(),
358
+ editionsRoot: editionsRoot(),
359
+ evidenceRoot,
360
+ evidenceSet,
361
+ policy: anchors.policy,
362
+ policyRoot: dataRoot(),
363
+ now: new Date(),
364
+ expectedReleaseSourceCommit: releaseSourceCommit,
365
+ cosignBinary: options.cosignBinary,
366
+ schemas,
367
+ });
368
+ const entitlement =
369
+ options.entitlementDescriptorPath === null
370
+ ? null
371
+ : parseEntitlementDescriptorDocument(
372
+ readCanonicalJsonFromRoot(
373
+ evidenceRoot,
374
+ options.entitlementDescriptorPath,
375
+ 'delivery entitlement descriptor',
376
+ ),
377
+ schemas,
378
+ );
379
+ if (entitlement !== null) {
380
+ assertEntitlementDescriptorStates(entitlement, {
381
+ distributionId: options.distributionId,
382
+ customerId: options.customerId,
383
+ mode: options.mode,
384
+ targetRegistry: options.targetRegistry,
385
+ distributionStatementDigest: result.completenessStatementDigest,
386
+ });
387
+ }
388
+ const entitlementDescriptor = entitlement === null ? null : entitlement.evidence;
389
+ const entitlementStatement =
390
+ entitlementDescriptor === null
391
+ ? null
392
+ : await verifyImageDeliveryEntitlement({
393
+ evidenceRoot: entitlementEvidenceRoot(evidenceRoot, options.entitlementDescriptorPath),
394
+ descriptor: entitlementDescriptor,
395
+ policy: anchors.policy,
396
+ policyRoot: dataRoot(),
397
+ completenessStatementDigest: result.completenessStatementDigest,
398
+ customerId: options.customerId,
399
+ distributionId: options.distributionId,
400
+ channel,
401
+ mode: options.mode,
402
+ targetRegistry: options.targetRegistry,
403
+ now: new Date(),
404
+ // The entitlement predicate carries no producer run: the commit
405
+ // its certificate is held to is the caller's statement.
406
+ expectedWorkflowSha: options.entitlementWorkflowSha,
407
+ // A nonce spent by a producer or completeness statement of
408
+ // this release cannot be reused by its entitlement.
409
+ consumedNonces: result.verifiedNonces,
410
+ cosignBinary: options.cosignBinary,
411
+ schemas,
412
+ });
413
+ assertCheckoutsUnchanged();
414
+ return { ...result, entitlementStatement, entitlementDescriptor };
415
+ },
416
+ });
417
+ // With --json the human text goes to stderr and stdout carries the one document.
418
+ (options.json ? process.stderr : process.stdout).write(
419
+ `verified ${verified.producerStatements.size} image producers and distribution completeness for ` +
420
+ `"${options.distributionId}" at release sequence ` +
421
+ `${verified.completenessStatement.predicate.releaseSequence}\n` +
422
+ `edition read from ${editionsRoot()} at editions checkout commit ${releaseSourceCommit}\n` +
423
+ (verified.entitlementStatement === null
424
+ ? ''
425
+ : `verified the delivery entitlement of "${options.customerId}" (${options.mode})\n`),
426
+ );
427
+ if (options.json) {
428
+ process.stdout.write(
429
+ `${JSON.stringify(
430
+ evidenceVerificationDocument({
431
+ distributionId: options.distributionId,
432
+ channel,
433
+ evidenceSet,
434
+ verified,
435
+ entitlementDescriptor: verified.entitlementDescriptor,
436
+ }),
437
+ null,
438
+ 2,
439
+ )}\n`,
440
+ );
441
+ }
442
+ } finally {
443
+ trustedGit.dispose();
444
+ }
445
+ }
446
+
447
+ if (isEntryModule(import.meta.url)) {
448
+ await verifyEvidence({
449
+ argv: process.argv.slice(2),
450
+ env: process.env,
451
+ bundlePath: process.argv[1],
452
+ }).catch((error) => {
453
+ // A named refusal (e.g. EditionProtocolMismatchError) is printed with its name.
454
+ const named = error.name && error.name !== 'Error' ? `${error.name}: ` : '';
455
+ console.error(`verify-image-evidence: ${named}${error.message}`);
456
+ process.exitCode = 1;
457
+ });
458
+ }