@xemahq/xema 0.27.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.
- package/dist/dev/env-defaults.d.ts.map +1 -1
- package/dist/dev/env-defaults.js +0 -1
- package/dist/dev/env-defaults.js.map +1 -1
- package/dist/edition/verify-image-evidence.bundle.mjs +143 -39
- package/dist/edition/verify-image-evidence.bundle.provenance.json +45 -39
- package/edition/README.md +7 -9
- package/edition/edition-artifact.mjs +14 -0
- package/edition/edition-config.mjs +17 -19
- package/edition/edition-contract.json +11 -4
- package/edition/edition-workspace.mjs +12 -1
- package/edition/evidence-checkout.mjs +22 -1
- package/edition/evidence-trust-policy.mjs +116 -78
- package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
- package/edition/image-evidence.mjs +8 -0
- package/edition/lib.mjs +3 -0
- package/edition/oras.mjs +1 -1
- package/edition/produce-image-delivery-entitlement-evidence.mjs +12 -12
- package/edition/release-channel-guard.mjs +11 -1
- package/edition/release-edition-artifact.mjs +2 -3
- package/edition/verify-image-evidence.mjs +40 -12
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// `entitlement.json`: the file `xema edition evidence statement entitlement
|
|
2
|
+
// accept` writes beside the signed statement, and the ONE reader of it
|
|
3
|
+
// (`verify-evidence --entitlement`). Writer and reader share this module so the
|
|
4
|
+
// shape is stated once.
|
|
5
|
+
//
|
|
6
|
+
// Every field but `evidence` is an UNAUTHENTICATED convenience for the operator
|
|
7
|
+
// and for `verify-evidence`'s early refusals: the signed statement stays the
|
|
8
|
+
// authority (`validateEntitlementStatementBinding`). The file names no workflow
|
|
9
|
+
// SHA: the commit the entitlement's certificate is held to is stated by the
|
|
10
|
+
// caller of the verifier (`--entitlement-workflow-sha`), never by the evidence.
|
|
11
|
+
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
export const ENTITLEMENT_DESCRIPTOR_FILE = 'entitlement.json';
|
|
15
|
+
export const ENTITLEMENT_DESCRIPTOR_SCHEMA_VERSION = 2;
|
|
16
|
+
|
|
17
|
+
const ENTITLEMENT_DESCRIPTOR_KEYS = Object.freeze([
|
|
18
|
+
'schemaVersion',
|
|
19
|
+
'distributionId',
|
|
20
|
+
'customerId',
|
|
21
|
+
'mode',
|
|
22
|
+
'targetRegistry',
|
|
23
|
+
'idempotencyKey',
|
|
24
|
+
'distributionStatementDigest',
|
|
25
|
+
'evidence',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/** The document `accept` writes. `evidence` is the kernel Sigstore evidence descriptor of the signed statement. */
|
|
29
|
+
export function entitlementDescriptorDocument({
|
|
30
|
+
distributionId,
|
|
31
|
+
customerId,
|
|
32
|
+
mode,
|
|
33
|
+
targetRegistry,
|
|
34
|
+
idempotencyKey,
|
|
35
|
+
distributionStatementDigest,
|
|
36
|
+
evidence,
|
|
37
|
+
}) {
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: ENTITLEMENT_DESCRIPTOR_SCHEMA_VERSION,
|
|
40
|
+
distributionId,
|
|
41
|
+
customerId,
|
|
42
|
+
mode,
|
|
43
|
+
targetRegistry,
|
|
44
|
+
idempotencyKey,
|
|
45
|
+
distributionStatementDigest,
|
|
46
|
+
evidence,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse an `entitlement.json`: exactly the keys `accept` writes, at the one
|
|
52
|
+
* schema version, with `evidence` parsed by the kernel's Sigstore evidence
|
|
53
|
+
* descriptor schema. Any other shape is refused.
|
|
54
|
+
*/
|
|
55
|
+
export function parseEntitlementDescriptorDocument(raw, schemas) {
|
|
56
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
57
|
+
throw new Error(`${ENTITLEMENT_DESCRIPTOR_FILE} must be a JSON object`);
|
|
58
|
+
}
|
|
59
|
+
if (raw.schemaVersion !== ENTITLEMENT_DESCRIPTOR_SCHEMA_VERSION) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${ENTITLEMENT_DESCRIPTOR_FILE} has schemaVersion ${JSON.stringify(raw.schemaVersion)}; ` +
|
|
62
|
+
`this verifier reads schemaVersion ${ENTITLEMENT_DESCRIPTOR_SCHEMA_VERSION} only (accept the entitlement again with this CLI)`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const keys = Object.keys(raw).sort();
|
|
66
|
+
const expected = [...ENTITLEMENT_DESCRIPTOR_KEYS].sort();
|
|
67
|
+
if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`${ENTITLEMENT_DESCRIPTOR_FILE} must hold exactly the keys ${ENTITLEMENT_DESCRIPTOR_KEYS.join(', ')}; it holds ${keys.join(', ')}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
for (const key of ['distributionId', 'customerId', 'mode', 'idempotencyKey', 'distributionStatementDigest']) {
|
|
73
|
+
if (typeof raw[key] !== 'string' || raw[key] === '') {
|
|
74
|
+
throw new Error(`${ENTITLEMENT_DESCRIPTOR_FILE} "${key}" must be a non-empty string`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (raw.targetRegistry !== null && (typeof raw.targetRegistry !== 'string' || raw.targetRegistry === '')) {
|
|
78
|
+
throw new Error(`${ENTITLEMENT_DESCRIPTOR_FILE} "targetRegistry" must be null or a non-empty string`);
|
|
79
|
+
}
|
|
80
|
+
return { ...raw, evidence: schemas.SigstoreEvidenceDescriptorSchema.parse(raw.evidence) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Refuse an `entitlement.json` that states another request than the one being
|
|
85
|
+
* verified. `expected` holds the caller's flags and the completeness statement
|
|
86
|
+
* digest the verifier has just verified.
|
|
87
|
+
*/
|
|
88
|
+
export function assertEntitlementDescriptorStates(document, expected) {
|
|
89
|
+
for (const key of ['distributionId', 'customerId', 'mode', 'targetRegistry', 'distributionStatementDigest']) {
|
|
90
|
+
if (document[key] !== expected[key]) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`${ENTITLEMENT_DESCRIPTOR_FILE} states ${key} ${JSON.stringify(document[key])}, ` +
|
|
93
|
+
`not the ${JSON.stringify(expected[key])} this verification was asked for`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The evidence descriptor's paths are relative to the directory `accept` sealed
|
|
101
|
+
* (the one holding `entitlement.json`), which lies inside the evidence root the
|
|
102
|
+
* descriptor file itself was read from.
|
|
103
|
+
*/
|
|
104
|
+
export function entitlementEvidenceRoot(evidenceRoot, entitlementDescriptorPath) {
|
|
105
|
+
return dirname(join(evidenceRoot, entitlementDescriptorPath));
|
|
106
|
+
}
|
|
@@ -603,6 +603,14 @@ function assertEvidenceChannel(channel) {
|
|
|
603
603
|
* The signers a policy authorizes for one predicate on ONE channel. Producer
|
|
604
604
|
* evidence is further bound to the exact image repository the subject's
|
|
605
605
|
* imageRef names (no prefix match).
|
|
606
|
+
*
|
|
607
|
+
* A signer belongs to one channel: the kernel policy schema, which every policy
|
|
608
|
+
* this plane loads is parsed by, holds a row's `githubWorkflow.ref` to the ref
|
|
609
|
+
* of its channel (`EVIDENCE_CHANNEL_REFS`) and its `certificateIdentity` to
|
|
610
|
+
* that ref, so no signer can be authorized on two channels. The signature
|
|
611
|
+
* therefore authenticates the channel: an edition descriptor that states the
|
|
612
|
+
* other channel selects signers whose certificates the evidence does not carry,
|
|
613
|
+
* or none at all.
|
|
606
614
|
*/
|
|
607
615
|
function policyCandidates(policy, predicateType, channel, imageRepository) {
|
|
608
616
|
return policy.policies.filter(
|
package/edition/lib.mjs
CHANGED
|
@@ -722,6 +722,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
722
722
|
parseDistributionImageEvidenceSet,
|
|
723
723
|
parseDistributionImageReplayState,
|
|
724
724
|
parseSigstoreOfflineTrustPolicy,
|
|
725
|
+
SigstoreEvidenceDescriptorSchema,
|
|
725
726
|
parsePlatformServiceSourceIndex,
|
|
726
727
|
parseRuntimeImageSourceIndex,
|
|
727
728
|
resolveRuntimeImagePlatforms,
|
|
@@ -798,6 +799,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
798
799
|
typeof parseDistributionImageEvidenceSet !== 'function' ||
|
|
799
800
|
typeof parseDistributionImageReplayState !== 'function' ||
|
|
800
801
|
typeof parseSigstoreOfflineTrustPolicy !== 'function' ||
|
|
802
|
+
typeof SigstoreEvidenceDescriptorSchema?.parse !== 'function' ||
|
|
801
803
|
typeof parsePlatformServiceSourceIndex !== 'function' ||
|
|
802
804
|
typeof parseRuntimeImageSourceIndex !== 'function' ||
|
|
803
805
|
typeof resolveRuntimeImagePlatforms !== 'function' ||
|
|
@@ -892,6 +894,7 @@ export async function loadKernelDistributionSchemas() {
|
|
|
892
894
|
parseDistributionImageEvidenceSet,
|
|
893
895
|
parseDistributionImageReplayState,
|
|
894
896
|
parseSigstoreOfflineTrustPolicy,
|
|
897
|
+
SigstoreEvidenceDescriptorSchema,
|
|
895
898
|
parsePlatformServiceSourceIndex,
|
|
896
899
|
parseRuntimeImageSourceIndex,
|
|
897
900
|
resolveRuntimeImagePlatforms,
|
package/edition/oras.mjs
CHANGED
|
@@ -173,7 +173,7 @@ function assertOrasVersion(bin, home) {
|
|
|
173
173
|
/**
|
|
174
174
|
* The oras digests the configured data root trusts: `trustedOrasBinaryDigests`
|
|
175
175
|
* of its evidence-trust pin. `null` when the data root declares no evidence
|
|
176
|
-
* trust at all (`evidenceTrustPolicy:
|
|
176
|
+
* trust at all (`evidenceTrustPolicy: null`):
|
|
177
177
|
* such a data root has no reviewed pin to hold a binary to.
|
|
178
178
|
*/
|
|
179
179
|
export function configuredOrasTrust() {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
dataRoot,
|
|
29
29
|
isEntryModule,
|
|
30
30
|
} from './edition-workspace.mjs';
|
|
31
|
+
import { ENTITLEMENT_DESCRIPTOR_FILE, entitlementDescriptorDocument } from './image-delivery-entitlement-descriptor.mjs';
|
|
31
32
|
import { canonicalJsonBytes, readCanonicalJsonFromRoot } from './image-evidence.mjs';
|
|
32
33
|
import { serializeJson } from './lib.mjs';
|
|
33
34
|
import { githubWorkflowSigner } from './produce-distribution-completeness-evidence.mjs';
|
|
@@ -38,7 +39,6 @@ import {
|
|
|
38
39
|
} from './produce-image-source-evidence.mjs';
|
|
39
40
|
import { assertStagedExactly, reserveSealing, sealAccepted } from './sealed-evidence-accept.mjs';
|
|
40
41
|
|
|
41
|
-
const ENTITLEMENT_DESCRIPTOR_FILE = 'entitlement.json';
|
|
42
42
|
const ENTITLEMENT_STATEMENT_PATH = 'entitlement/statement.json';
|
|
43
43
|
const ENTITLEMENT_BUNDLE_PATH = 'entitlement/bundle.sigstore.json';
|
|
44
44
|
|
|
@@ -176,17 +176,17 @@ export function acceptImageDeliveryEntitlementEvidence(options) {
|
|
|
176
176
|
const descriptor = acceptSignedEvidenceStatement({ ...signing, sealingRoot: sealing, runCosign });
|
|
177
177
|
writeFileSync(
|
|
178
178
|
join(sealing, ENTITLEMENT_DESCRIPTOR_FILE),
|
|
179
|
-
canonicalJsonBytes(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
179
|
+
canonicalJsonBytes(
|
|
180
|
+
entitlementDescriptorDocument({
|
|
181
|
+
distributionId: options.distributionId,
|
|
182
|
+
customerId: options.customerId,
|
|
183
|
+
mode: options.mode,
|
|
184
|
+
targetRegistry: options.targetRegistry,
|
|
185
|
+
idempotencyKey: options.idempotencyKey,
|
|
186
|
+
distributionStatementDigest: completenessStatementDigest,
|
|
187
|
+
evidence: descriptor,
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
190
190
|
{ flag: 'wx', mode: 0o644 },
|
|
191
191
|
);
|
|
192
192
|
return { outputDirectory, statement, descriptor, completenessStatementDigest };
|
|
@@ -60,8 +60,18 @@ export function assertReleaseEditionTree(dir) {
|
|
|
60
60
|
*/
|
|
61
61
|
export const RELEASE_COMPANION_FILES = Object.freeze([EDITION_CHART_SET_FILE]);
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The release locks an edition directory holds (the distribution and the
|
|
65
|
+
* authorization lock). A directory holding either IS a release edition: a
|
|
66
|
+
* develop resolve writes nothing into an editions tree, so a lock committed
|
|
67
|
+
* there was written by the release resolve.
|
|
68
|
+
*/
|
|
69
|
+
export function heldReleaseLocks(dir) {
|
|
70
|
+
return [EditionLockFile.Distribution, EditionLockFile.Authorization].filter((file) => existsSync(join(dir, file)));
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
function assertReleaseLockSet(dir) {
|
|
64
|
-
const held =
|
|
74
|
+
const held = heldReleaseLocks(dir);
|
|
65
75
|
if (held.length === 0) {return;}
|
|
66
76
|
const missing = RELEASE_COMPANION_FILES.filter((file) => !existsSync(join(dir, file)));
|
|
67
77
|
if (missing.length > 0) {
|
|
@@ -131,10 +131,9 @@ async function run(argv, env) {
|
|
|
131
131
|
const target = editionTarget(id, options['--registry'], options['--coordinate']);
|
|
132
132
|
const workflow = options['--local'] ? LOCAL_WORKFLOW : workflowFromEnv(env);
|
|
133
133
|
const locks = await readCommittedEdition(id);
|
|
134
|
+
// Refuses (EvidenceTrustRequiredError) unless the policy is ARMED on the
|
|
135
|
+
// release channel: a release artifact is never written not-armed.
|
|
134
136
|
const evidenceTrustPolicy = await descriptorEvidenceTrust({ channel: EditionChannel.Release });
|
|
135
|
-
if (evidenceTrustPolicy === 'not-armed') {
|
|
136
|
-
console.warn('[edition-artifact] evidence trust policy: NOT ARMED — the descriptor records not-armed.');
|
|
137
|
-
}
|
|
138
137
|
const outDir = resolve(options['--out']);
|
|
139
138
|
writeEditionArtifact({
|
|
140
139
|
outDir,
|
|
@@ -49,6 +49,11 @@ import {
|
|
|
49
49
|
trustPolicyDigest,
|
|
50
50
|
verifierDigests,
|
|
51
51
|
} from './evidence-trust-pin.mjs';
|
|
52
|
+
import {
|
|
53
|
+
assertEntitlementDescriptorStates,
|
|
54
|
+
entitlementEvidenceRoot,
|
|
55
|
+
parseEntitlementDescriptorDocument,
|
|
56
|
+
} from './image-delivery-entitlement-descriptor.mjs';
|
|
52
57
|
import { verifyAndAdvanceReplayState } from './image-evidence-replay-state.mjs';
|
|
53
58
|
import {
|
|
54
59
|
readCanonicalJsonFromRoot,
|
|
@@ -64,13 +69,14 @@ import { loadVerifierKernelSchemas } from './verifier-kernel-schemas.mjs';
|
|
|
64
69
|
const USAGE =
|
|
65
70
|
'usage: verify-image-evidence <distributionId> --evidence-root <dir> --descriptor <absolute-path> --state <absolute-path> ' +
|
|
66
71
|
'--cosign <absolute-path> --git <absolute-path> [--evidence-set <relative-path>] [--json] ' +
|
|
67
|
-
'[--entitlement <relative-path> --customer <id> --mode <mode> [--target-registry <registry>]] ' +
|
|
72
|
+
'[--entitlement <relative-path> --entitlement-workflow-sha <40-hex git sha> --customer <id> --mode <mode> [--target-registry <registry>]] ' +
|
|
68
73
|
'--data-root <dir> --editions-root <dir> --kernel <dir>';
|
|
69
74
|
// The edition channel -> the kernel's evidence channel, stated once and explicitly.
|
|
70
75
|
const EVIDENCE_CHANNEL_OF_EDITION_CHANNEL = Object.freeze({
|
|
71
76
|
[EditionChannel.Release]: 'release',
|
|
72
77
|
[EditionChannel.Develop]: 'develop',
|
|
73
78
|
});
|
|
79
|
+
const GIT_COMMIT_SHA = /^[a-f0-9]{40}$/;
|
|
74
80
|
const DATA_ROOT_FILES = Object.freeze(['edition-workspace.json', 'runtime-image-source-index.json']);
|
|
75
81
|
|
|
76
82
|
function requireOptionValue(args, index, flag) {
|
|
@@ -101,6 +107,7 @@ function parseArgs(args) {
|
|
|
101
107
|
cosignBinary: null,
|
|
102
108
|
gitBinary: null,
|
|
103
109
|
entitlementDescriptorPath: null,
|
|
110
|
+
entitlementWorkflowSha: null,
|
|
104
111
|
customerId: null,
|
|
105
112
|
mode: null,
|
|
106
113
|
targetRegistry: null,
|
|
@@ -118,6 +125,7 @@ function parseArgs(args) {
|
|
|
118
125
|
else if (flag === '--cosign') {options.cosignBinary = value;}
|
|
119
126
|
else if (flag === '--git') {options.gitBinary = value;}
|
|
120
127
|
else if (flag === '--entitlement') {options.entitlementDescriptorPath = value;}
|
|
128
|
+
else if (flag === '--entitlement-workflow-sha') {options.entitlementWorkflowSha = value;}
|
|
121
129
|
else if (flag === '--customer') {options.customerId = value;}
|
|
122
130
|
else if (flag === '--mode') {options.mode = value;}
|
|
123
131
|
else if (flag === '--target-registry') {options.targetRegistry = value;}
|
|
@@ -137,9 +145,12 @@ function parseArgs(args) {
|
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
// An entitlement is verified whole or not at all: the descriptor names the
|
|
140
|
-
// signed statement,
|
|
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.
|
|
141
151
|
const entitlementFlags = [
|
|
142
152
|
['--entitlement', options.entitlementDescriptorPath],
|
|
153
|
+
['--entitlement-workflow-sha', options.entitlementWorkflowSha],
|
|
143
154
|
['--customer', options.customerId],
|
|
144
155
|
['--mode', options.mode],
|
|
145
156
|
];
|
|
@@ -150,7 +161,10 @@ function parseArgs(args) {
|
|
|
150
161
|
);
|
|
151
162
|
}
|
|
152
163
|
if (stated.length === 0 && options.targetRegistry !== null) {
|
|
153
|
-
throw new Error('--target-registry needs --entitlement, --customer and --mode');
|
|
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');
|
|
154
168
|
}
|
|
155
169
|
if (options.entitlementDescriptorPath !== null && isAbsolute(options.entitlementDescriptorPath)) {
|
|
156
170
|
throw new Error('--entitlement must be an evidence-root-relative path');
|
|
@@ -185,6 +199,7 @@ export const EDITION_EVIDENCE_VERIFICATION_SCHEMA = 'xema.edition-evidence-verif
|
|
|
185
199
|
* What `--json` prints once the evidence is verified: the channel it was verified
|
|
186
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
|
|
187
201
|
* (producers by subject, then completeness, then the entitlement if one was asked for).
|
|
202
|
+
* `entitlementDescriptor` is the Sigstore evidence descriptor `entitlement.json` holds.
|
|
188
203
|
*/
|
|
189
204
|
export function evidenceVerificationDocument({ distributionId, channel, evidenceSet, verified, entitlementDescriptor }) {
|
|
190
205
|
const producers = evidenceSet.producerEvidence
|
|
@@ -350,19 +365,32 @@ export async function verifyEvidence({ argv, env, bundlePath }) {
|
|
|
350
365
|
cosignBinary: options.cosignBinary,
|
|
351
366
|
schemas,
|
|
352
367
|
});
|
|
353
|
-
const
|
|
368
|
+
const entitlement =
|
|
354
369
|
options.entitlementDescriptorPath === null
|
|
355
370
|
? null
|
|
356
|
-
:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
371
|
+
: parseEntitlementDescriptorDocument(
|
|
372
|
+
readCanonicalJsonFromRoot(
|
|
373
|
+
evidenceRoot,
|
|
374
|
+
options.entitlementDescriptorPath,
|
|
375
|
+
'delivery entitlement descriptor',
|
|
376
|
+
),
|
|
377
|
+
schemas,
|
|
360
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;
|
|
361
389
|
const entitlementStatement =
|
|
362
390
|
entitlementDescriptor === null
|
|
363
391
|
? null
|
|
364
392
|
: await verifyImageDeliveryEntitlement({
|
|
365
|
-
evidenceRoot,
|
|
393
|
+
evidenceRoot: entitlementEvidenceRoot(evidenceRoot, options.entitlementDescriptorPath),
|
|
366
394
|
descriptor: entitlementDescriptor,
|
|
367
395
|
policy: anchors.policy,
|
|
368
396
|
policyRoot: dataRoot(),
|
|
@@ -373,9 +401,9 @@ export async function verifyEvidence({ argv, env, bundlePath }) {
|
|
|
373
401
|
mode: options.mode,
|
|
374
402
|
targetRegistry: options.targetRegistry,
|
|
375
403
|
now: new Date(),
|
|
376
|
-
// The entitlement predicate carries no producer run
|
|
377
|
-
//
|
|
378
|
-
expectedWorkflowSha:
|
|
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,
|
|
379
407
|
// A nonce spent by a producer or completeness statement of
|
|
380
408
|
// this release cannot be reused by its entitlement.
|
|
381
409
|
consumedNonces: result.verifiedNonces,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/xema-dev/xema-cli.git",
|
|
@@ -92,6 +92,6 @@
|
|
|
92
92
|
"typecheck": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"xemaRelease": {
|
|
95
|
-
"sourceDigest": "sha256:
|
|
95
|
+
"sourceDigest": "sha256:c33104cb26add433525e6447c931da38c0fc51a552b7f61f59b293734aeeb23c"
|
|
96
96
|
}
|
|
97
97
|
}
|