@atrib/verify 0.7.10 → 0.8.1

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.
@@ -0,0 +1,94 @@
1
+ import type { EvidenceVerificationBlock } from './authorization-evidence.js';
2
+ export type X401EvidenceProtocol = 'x401';
3
+ export type X401VerificationPolicy = 'require' | 'best-effort' | 'off';
4
+ export type X401HeaderValue = string | string[] | undefined;
5
+ export type X401HeaderSource = Record<string, X401HeaderValue>;
6
+ export type X401ResponseKind = 'result_artifact' | 'token' | 'unknown';
7
+ export type X401ProofGateStatus = 'passed' | 'failed' | 'unresolved' | 'not_checked';
8
+ export interface X401HeaderSet {
9
+ proofRequest?: string;
10
+ proofResponse?: string;
11
+ proofResult?: string;
12
+ legacyProofRequired?: string;
13
+ legacyProofPresentation?: string;
14
+ legacyProofResponse?: string;
15
+ }
16
+ export interface X401AuthorizationEvidenceInput {
17
+ protocol?: X401EvidenceProtocol;
18
+ headers?: X401HeaderSource;
19
+ headerSet?: X401HeaderSet;
20
+ proofRequest?: unknown;
21
+ proofResponse?: unknown;
22
+ proofResult?: unknown;
23
+ allowLegacyHeaders?: boolean;
24
+ allowLegacyFields?: boolean;
25
+ verificationPolicy?: X401VerificationPolicy;
26
+ resultVerified?: boolean;
27
+ tokenVerified?: boolean;
28
+ expectedVersion?: string;
29
+ expectedRequestId?: string;
30
+ expectedAgentId?: string;
31
+ expectedCredentialProtocol?: string;
32
+ requiredSatisfiedRequirements?: string[];
33
+ expectedNonce?: string;
34
+ expectedAgentOrigin?: string;
35
+ agentOrigin?: string;
36
+ agentOriginVerified?: boolean;
37
+ issuerTrustVerified?: boolean;
38
+ issuerTrustRootType?: string;
39
+ issuerTrustRootRef?: string;
40
+ proofPaymentBindingVerified?: boolean;
41
+ proofPaymentBindingRef?: string;
42
+ requireProofRequest?: boolean;
43
+ requireProofResponse?: boolean;
44
+ }
45
+ export interface X401AuthorizationEvidenceDetails {
46
+ version: string | null;
47
+ request_id: string | null;
48
+ header_names: string[];
49
+ legacy_headers_used: string[];
50
+ legacy_fields_used: string[];
51
+ proof_request_hash: string | null;
52
+ proof_response_hash: string | null;
53
+ proof_result_hash: string | null;
54
+ credential_protocol: string | null;
55
+ nonce: string | null;
56
+ agent_id: string | null;
57
+ response_kind: X401ResponseKind | null;
58
+ result_verified: boolean | null;
59
+ token_verified: boolean | null;
60
+ proof_gate: {
61
+ kind: X401ResponseKind | null;
62
+ status: X401ProofGateStatus;
63
+ };
64
+ satisfied_requirements: string[];
65
+ payment_separation: {
66
+ present: boolean;
67
+ required: boolean | null;
68
+ scheme_hint: string | null;
69
+ };
70
+ agent_origin: {
71
+ expected_hash: string | null;
72
+ actual_hash: string | null;
73
+ verified: boolean | null;
74
+ };
75
+ issuer_trust: {
76
+ verified: boolean | null;
77
+ root_type: string | null;
78
+ root_ref_hash: string | null;
79
+ };
80
+ proof_payment_binding: {
81
+ verified: boolean | null;
82
+ reference_hash: string | null;
83
+ };
84
+ verifier_client_id: string | null;
85
+ credential_result_uri_present: boolean;
86
+ credential_result_uri_hash: string | null;
87
+ }
88
+ export interface X401AuthorizationEvidenceVerification extends EvidenceVerificationBlock<X401AuthorizationEvidenceDetails> {
89
+ protocol: X401EvidenceProtocol;
90
+ }
91
+ export declare function encodeX401HeaderObject(value: unknown): string;
92
+ export declare function decodeX401HeaderObject(value: string): unknown;
93
+ export declare function verifyX401AuthorizationEvidence(input: X401AuthorizationEvidenceInput): X401AuthorizationEvidenceVerification;
94
+ //# sourceMappingURL=x401-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x401-evidence.d.ts","sourceRoot":"","sources":["../src/x401-evidence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,560 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { base64urlDecode, base64urlEncode, hexEncode, sha256 } from '@atrib/mcp';
3
+ import canonicalize from 'canonicalize';
4
+ import { decodeJwt } from 'jose';
5
+ const textEncoder = new TextEncoder();
6
+ const textDecoder = new TextDecoder();
7
+ export function encodeX401HeaderObject(value) {
8
+ return base64urlEncode(textEncoder.encode(JSON.stringify(value)));
9
+ }
10
+ export function decodeX401HeaderObject(value) {
11
+ return JSON.parse(textDecoder.decode(base64urlDecode(value)));
12
+ }
13
+ function check(type, status, expected, actual, reason) {
14
+ const result = { type, status };
15
+ if (expected !== undefined)
16
+ result.expected = expected;
17
+ if (actual !== undefined)
18
+ result.actual = actual;
19
+ if (reason !== undefined)
20
+ result.reason = reason;
21
+ return result;
22
+ }
23
+ function asRecord(value) {
24
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
25
+ return null;
26
+ return value;
27
+ }
28
+ function canonicalObjectHash(value) {
29
+ if (value === null || value === undefined)
30
+ return null;
31
+ const canonical = canonicalize(value);
32
+ if (canonical === undefined)
33
+ return null;
34
+ return `sha256:${hexEncode(sha256(textEncoder.encode(canonical)))}`;
35
+ }
36
+ function labeledStringHash(label, value) {
37
+ if (value === undefined)
38
+ return null;
39
+ return canonicalObjectHash({ [label]: value });
40
+ }
41
+ function stringMember(record, key) {
42
+ const value = record?.[key];
43
+ return typeof value === 'string' ? value : null;
44
+ }
45
+ function objectMember(record, key) {
46
+ return asRecord(record?.[key]);
47
+ }
48
+ function stringArrayMember(record, key) {
49
+ const value = record?.[key];
50
+ if (!Array.isArray(value))
51
+ return [];
52
+ return value.filter((entry) => typeof entry === 'string');
53
+ }
54
+ function firstString(value) {
55
+ if (typeof value === 'string')
56
+ return value;
57
+ if (Array.isArray(value) && value.length === 1 && typeof value[0] === 'string')
58
+ return value[0];
59
+ return null;
60
+ }
61
+ function readHeader(headers, names) {
62
+ if (!headers)
63
+ return { value: null, actualName: null, constraint: null };
64
+ const normalized = names.map((name) => name.toLowerCase());
65
+ for (const [name, value] of Object.entries(headers)) {
66
+ if (!normalized.includes(name.toLowerCase()))
67
+ continue;
68
+ const exactValue = firstString(value);
69
+ const invalidList = Array.isArray(value) && (value.length !== 1 || typeof value[0] !== 'string');
70
+ const commaList = typeof exactValue === 'string' && exactValue.includes(',');
71
+ if (invalidList || commaList || exactValue === null) {
72
+ return {
73
+ value: null,
74
+ actualName: name,
75
+ constraint: check(`x401.header.${name.toLowerCase()}.single`, 'failed', 'one base64url JSON object', value, 'x401 proof headers must not contain multiple values or comma lists'),
76
+ };
77
+ }
78
+ return {
79
+ value: exactValue,
80
+ actualName: name,
81
+ constraint: check(`x401.header.${name.toLowerCase()}.single`, 'passed'),
82
+ };
83
+ }
84
+ return { value: null, actualName: null, constraint: null };
85
+ }
86
+ function pushHeaderValue(set, key, value) {
87
+ if (value !== undefined)
88
+ set[key] = value;
89
+ }
90
+ function resolveHeaders(input) {
91
+ const set = { ...(input.headerSet ?? {}) };
92
+ const headerNames = [];
93
+ const legacyHeadersUsed = [];
94
+ const constraints = [];
95
+ const errors = [];
96
+ const request = readHeader(input.headers, ['proof-request']);
97
+ const response = readHeader(input.headers, ['proof-response']);
98
+ const result = readHeader(input.headers, ['proof-result']);
99
+ const legacyRequired = readHeader(input.headers, ['proof-required']);
100
+ const legacyPresentation = readHeader(input.headers, ['proof-presentation']);
101
+ for (const item of [request, response, result, legacyRequired, legacyPresentation]) {
102
+ if (item.constraint)
103
+ constraints.push(item.constraint);
104
+ if (item.actualName)
105
+ headerNames.push(item.actualName);
106
+ }
107
+ pushHeaderValue(set, 'proofRequest', request.value ?? undefined);
108
+ pushHeaderValue(set, 'proofResponse', response.value ?? undefined);
109
+ pushHeaderValue(set, 'proofResult', result.value ?? undefined);
110
+ pushHeaderValue(set, 'legacyProofRequired', legacyRequired.value ?? undefined);
111
+ pushHeaderValue(set, 'legacyProofPresentation', legacyPresentation.value ?? undefined);
112
+ if (input.headerSet) {
113
+ for (const key of Object.keys(input.headerSet))
114
+ headerNames.push(`headerSet.${key}`);
115
+ }
116
+ if (input.proofRequest !== undefined)
117
+ headerNames.push('direct.proofRequest');
118
+ if (input.proofResponse !== undefined)
119
+ headerNames.push('direct.proofResponse');
120
+ if (input.proofResult !== undefined)
121
+ headerNames.push('direct.proofResult');
122
+ if (set.legacyProofRequired)
123
+ legacyHeadersUsed.push('PROOF-REQUIRED');
124
+ if (set.legacyProofPresentation)
125
+ legacyHeadersUsed.push('PROOF-PRESENTATION');
126
+ if (set.legacyProofResponse)
127
+ legacyHeadersUsed.push('PROOF-RESPONSE');
128
+ const allowLegacy = input.allowLegacyHeaders ?? true;
129
+ if (!allowLegacy && legacyHeadersUsed.length > 0) {
130
+ errors.push('x401_evidence legacy proof headers are not allowed');
131
+ }
132
+ return {
133
+ set,
134
+ headerNames: Array.from(new Set(headerNames)),
135
+ legacyHeadersUsed,
136
+ constraints,
137
+ errors,
138
+ };
139
+ }
140
+ function decodeObjectValue(value, name, errors) {
141
+ if (value === undefined)
142
+ return null;
143
+ if (typeof value === 'string') {
144
+ try {
145
+ const decoded = decodeX401HeaderObject(value);
146
+ const record = asRecord(decoded);
147
+ if (!record) {
148
+ errors.push(`x401_evidence ${name} decoded value is not an object`);
149
+ return null;
150
+ }
151
+ return record;
152
+ }
153
+ catch (err) {
154
+ errors.push(`x401_evidence ${name} decode error: ${err.message}`);
155
+ return null;
156
+ }
157
+ }
158
+ const record = asRecord(value);
159
+ if (!record)
160
+ errors.push(`x401_evidence ${name} value is not an object`);
161
+ return record;
162
+ }
163
+ function decodeInput(input, headers) {
164
+ const errors = [];
165
+ const proofRequest = decodeObjectValue(input.proofRequest, 'proof request', errors) ??
166
+ decodeObjectValue(headers.proofRequest, 'PROOF-REQUEST', errors) ??
167
+ decodeObjectValue(headers.legacyProofRequired, 'PROOF-REQUIRED', errors);
168
+ const proofResponse = decodeObjectValue(input.proofResponse, 'proof response', errors) ??
169
+ decodeObjectValue(headers.proofResponse, 'PROOF-RESPONSE', errors) ??
170
+ decodeObjectValue(headers.legacyProofPresentation, 'PROOF-PRESENTATION', errors);
171
+ const proofResult = decodeObjectValue(input.proofResult, 'proof result', errors) ??
172
+ decodeObjectValue(headers.proofResult, 'PROOF-RESULT', errors) ??
173
+ decodeObjectValue(headers.legacyProofResponse, 'legacy PROOF-RESPONSE', errors);
174
+ if (errors.length > 0)
175
+ throw new Error(errors.join('; '));
176
+ return { proofRequest, proofResponse, proofResult };
177
+ }
178
+ function decodeRequestJwt(jwt) {
179
+ try {
180
+ return decodeJwt(jwt);
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ function summarizeCredentialRequest(payload, allowLegacyFields) {
187
+ if (!payload) {
188
+ return {
189
+ valid: false,
190
+ legacyFieldsUsed: [],
191
+ credentialProtocol: null,
192
+ nonce: null,
193
+ verifierClientId: null,
194
+ };
195
+ }
196
+ const legacyFieldsUsed = [];
197
+ let credentialRequirements = objectMember(payload, 'credential_requirements');
198
+ if (!credentialRequirements) {
199
+ credentialRequirements = objectMember(payload, 'presentation_requirements');
200
+ if (credentialRequirements)
201
+ legacyFieldsUsed.push('presentation_requirements');
202
+ }
203
+ if (!credentialRequirements || (!allowLegacyFields && legacyFieldsUsed.length > 0)) {
204
+ return {
205
+ valid: false,
206
+ legacyFieldsUsed,
207
+ credentialProtocol: null,
208
+ nonce: null,
209
+ verifierClientId: null,
210
+ };
211
+ }
212
+ const digital = objectMember(credentialRequirements, 'digital');
213
+ const requests = Array.isArray(digital?.['requests']) ? digital['requests'] : [];
214
+ const firstRequest = requests.map(asRecord).find((entry) => entry !== null) ?? null;
215
+ const credentialProtocol = stringMember(firstRequest, 'protocol');
216
+ const data = objectMember(firstRequest, 'data');
217
+ const requestJwt = stringMember(data, 'request');
218
+ const decodedJwt = requestJwt ? decodeRequestJwt(requestJwt) : null;
219
+ return {
220
+ valid: digital !== null && requests.length > 0,
221
+ legacyFieldsUsed,
222
+ credentialProtocol,
223
+ nonce: stringMember(decodedJwt, 'nonce') ?? stringMember(data, 'nonce'),
224
+ verifierClientId: stringMember(decodedJwt, 'client_id') ??
225
+ stringMember(decodedJwt, 'iss') ??
226
+ stringMember(data, 'client_id'),
227
+ };
228
+ }
229
+ function summarizeProofResponse(response) {
230
+ if (!response) {
231
+ return {
232
+ kind: 'unknown',
233
+ requestId: null,
234
+ agentId: null,
235
+ credentialProtocol: null,
236
+ credentialResultUri: null,
237
+ };
238
+ }
239
+ const credentialResult = objectMember(response, 'credential_result');
240
+ const credentialResultUri = stringMember(response, 'credential_result_uri');
241
+ const accessToken = stringMember(response, 'access_token');
242
+ const tokenType = stringMember(response, 'token_type');
243
+ const requestId = stringMember(response, 'request_id');
244
+ const agentId = stringMember(response, 'agent_id');
245
+ if (credentialResult || credentialResultUri) {
246
+ return {
247
+ kind: 'result_artifact',
248
+ requestId,
249
+ agentId,
250
+ credentialProtocol: stringMember(credentialResult, 'protocol'),
251
+ credentialResultUri,
252
+ };
253
+ }
254
+ if (accessToken || tokenType) {
255
+ return {
256
+ kind: 'token',
257
+ requestId,
258
+ agentId,
259
+ credentialProtocol: null,
260
+ credentialResultUri: null,
261
+ };
262
+ }
263
+ return {
264
+ kind: 'unknown',
265
+ requestId,
266
+ agentId,
267
+ credentialProtocol: null,
268
+ credentialResultUri: null,
269
+ };
270
+ }
271
+ function proofGateStatus(responseKind, resultVerified, tokenVerified, proofResult) {
272
+ if (typeof proofResult?.['error'] === 'string')
273
+ return 'failed';
274
+ if (responseKind === 'result_artifact') {
275
+ if (resultVerified === true)
276
+ return 'passed';
277
+ if (resultVerified === false)
278
+ return 'failed';
279
+ return 'unresolved';
280
+ }
281
+ if (responseKind === 'token') {
282
+ if (tokenVerified === true)
283
+ return 'passed';
284
+ if (tokenVerified === false)
285
+ return 'failed';
286
+ return 'unresolved';
287
+ }
288
+ return 'not_checked';
289
+ }
290
+ function pushVersionChecks(constraints, payload, expectedVersion) {
291
+ const version = stringMember(payload, 'version');
292
+ if (expectedVersion) {
293
+ constraints.push(check('x401.payload.version', version === expectedVersion ? 'passed' : 'failed', expectedVersion, version));
294
+ }
295
+ else {
296
+ constraints.push(check('x401.payload.version', typeof version === 'string' ? 'passed' : 'failed', 'string', version));
297
+ }
298
+ return version;
299
+ }
300
+ function pushRequestIdChecks(constraints, expectedRequestId, payloadRequestId, responseRequestId) {
301
+ if (expectedRequestId) {
302
+ const values = [payloadRequestId, responseRequestId].filter((entry) => typeof entry === 'string');
303
+ const passed = values.length > 0 && values.every((entry) => entry === expectedRequestId);
304
+ constraints.push(check('x401.request_id', passed ? 'passed' : 'failed', expectedRequestId, values, passed ? undefined : 'request id did not match expected value'));
305
+ }
306
+ if (payloadRequestId && responseRequestId) {
307
+ constraints.push(check('x401.request_id.binding', payloadRequestId === responseRequestId ? 'passed' : 'failed', payloadRequestId, responseRequestId));
308
+ }
309
+ return responseRequestId ?? payloadRequestId;
310
+ }
311
+ function pushSatisfiedRequirementChecks(constraints, satisfiedRequirements, requiredSatisfiedRequirements) {
312
+ if (!requiredSatisfiedRequirements || requiredSatisfiedRequirements.length === 0) {
313
+ constraints.push(check('x401.satisfied_requirements', 'not_checked'));
314
+ return null;
315
+ }
316
+ const missing = requiredSatisfiedRequirements.filter((requirement) => !satisfiedRequirements.includes(requirement));
317
+ constraints.push(check('x401.satisfied_requirements', missing.length === 0 ? 'passed' : 'failed', requiredSatisfiedRequirements, satisfiedRequirements, missing.length === 0 ? undefined : `missing requirements: ${missing.join(', ')}`));
318
+ return missing.length === 0;
319
+ }
320
+ function pushVerificationPolicyCheck(constraints, errors, warnings, type, verified, policy) {
321
+ const constraintType = `x401.${type}_verified`;
322
+ if (policy === 'off') {
323
+ constraints.push(check(constraintType, 'not_checked'));
324
+ return null;
325
+ }
326
+ if (verified === true) {
327
+ constraints.push(check(constraintType, 'passed', true, true));
328
+ return true;
329
+ }
330
+ if (verified === false) {
331
+ constraints.push(check(constraintType, 'failed', true, false));
332
+ errors.push(`x401_evidence ${type} verification failed`);
333
+ return false;
334
+ }
335
+ constraints.push(check(constraintType, 'unresolved', true, null, 'caller must supply verified credential or token outcome'));
336
+ if (policy === 'require') {
337
+ errors.push(`x401_evidence ${type} verification unresolved`);
338
+ }
339
+ else {
340
+ warnings.push(`x401_evidence ${type} verification unresolved`);
341
+ }
342
+ return null;
343
+ }
344
+ function pushOptionalVerifiedFactCheck(constraints, errors, warnings, type, verified, active, failureMessage, unresolvedMessage) {
345
+ if (verified === true) {
346
+ constraints.push(check(type, 'passed', true, true));
347
+ return true;
348
+ }
349
+ if (verified === false) {
350
+ constraints.push(check(type, 'failed', true, false));
351
+ errors.push(failureMessage);
352
+ return false;
353
+ }
354
+ if (active) {
355
+ constraints.push(check(type, 'unresolved', true, null, unresolvedMessage));
356
+ warnings.push(unresolvedMessage);
357
+ return null;
358
+ }
359
+ constraints.push(check(type, 'not_checked'));
360
+ return null;
361
+ }
362
+ function pushProofResultChecks(constraints, errors, proofResult) {
363
+ if (!proofResult) {
364
+ constraints.push(check('x401.proof_result', 'not_checked'));
365
+ return;
366
+ }
367
+ const error = stringMember(proofResult, 'error');
368
+ if (error) {
369
+ constraints.push(check('x401.proof_result.error', 'failed', 'absent', error, 'verifier returned error'));
370
+ errors.push(`x401_evidence proof result error: ${error}`);
371
+ return;
372
+ }
373
+ constraints.push(check('x401.proof_result', 'passed'));
374
+ }
375
+ export function verifyX401AuthorizationEvidence(input) {
376
+ const errors = [];
377
+ const warnings = [];
378
+ const constraints = [];
379
+ const headerResolution = resolveHeaders(input);
380
+ constraints.push(...headerResolution.constraints);
381
+ errors.push(...headerResolution.errors);
382
+ const allowLegacyFields = input.allowLegacyFields ?? true;
383
+ let decoded = {
384
+ proofRequest: null,
385
+ proofResponse: null,
386
+ proofResult: null,
387
+ };
388
+ try {
389
+ decoded = decodeInput(input, headerResolution.set);
390
+ }
391
+ catch (err) {
392
+ errors.push(err.message);
393
+ }
394
+ if (headerResolution.legacyHeadersUsed.length > 0) {
395
+ warnings.push(`x401_evidence legacy header names used: ${headerResolution.legacyHeadersUsed.join(', ')}`);
396
+ }
397
+ const requireProofRequest = input.requireProofRequest ?? true;
398
+ constraints.push(check('x401.proof_request', decoded.proofRequest ? 'passed' : requireProofRequest ? 'failed' : 'not_checked', requireProofRequest ? 'present' : undefined, decoded.proofRequest ? 'present' : null));
399
+ if (requireProofRequest && !decoded.proofRequest) {
400
+ errors.push('x401_evidence missing proof request');
401
+ }
402
+ const credentialSummary = summarizeCredentialRequest(decoded.proofRequest, allowLegacyFields);
403
+ if (credentialSummary.legacyFieldsUsed.length > 0) {
404
+ warnings.push(`x401_evidence legacy payload fields used: ${credentialSummary.legacyFieldsUsed.join(', ')}`);
405
+ }
406
+ if (!allowLegacyFields && credentialSummary.legacyFieldsUsed.length > 0) {
407
+ errors.push('x401_evidence legacy payload fields are not allowed');
408
+ }
409
+ constraints.push(check('x401.payload.scheme', stringMember(decoded.proofRequest, 'scheme') === 'x401' ? 'passed' : 'failed', 'x401', stringMember(decoded.proofRequest, 'scheme')));
410
+ const version = pushVersionChecks(constraints, decoded.proofRequest, input.expectedVersion);
411
+ constraints.push(check('x401.payload.credential_requirements.digital', credentialSummary.valid ? 'passed' : 'failed', 'digital.requests[]', credentialSummary.credentialProtocol));
412
+ const oauth = objectMember(decoded.proofRequest, 'oauth');
413
+ constraints.push(check('x401.payload.oauth.token_endpoint', typeof oauth?.['token_endpoint'] === 'string' ? 'passed' : 'failed', 'string', oauth?.['token_endpoint'] ?? null));
414
+ if (input.expectedNonce) {
415
+ constraints.push(check('x401.openid4vp.nonce', credentialSummary.nonce === input.expectedNonce ? 'passed' : 'failed', input.expectedNonce, credentialSummary.nonce));
416
+ }
417
+ else {
418
+ constraints.push(check('x401.openid4vp.nonce', credentialSummary.nonce ? 'passed' : 'unresolved', 'present', credentialSummary.nonce, credentialSummary.nonce
419
+ ? undefined
420
+ : 'nonce is inside the credential protocol request and was not decoded'));
421
+ }
422
+ const responseSummary = summarizeProofResponse(decoded.proofResponse);
423
+ const requireProofResponse = input.requireProofResponse ?? false;
424
+ constraints.push(check('x401.proof_response', decoded.proofResponse ? 'passed' : requireProofResponse ? 'failed' : 'not_checked', requireProofResponse ? 'present' : undefined, decoded.proofResponse ? responseSummary.kind : null));
425
+ if (requireProofResponse && !decoded.proofResponse) {
426
+ errors.push('x401_evidence missing proof response');
427
+ }
428
+ if (decoded.proofResponse) {
429
+ const hasCredentialResult = objectMember(decoded.proofResponse, 'credential_result') !== null;
430
+ const hasCredentialResultUri = typeof decoded.proofResponse['credential_result_uri'] === 'string';
431
+ if (responseSummary.kind === 'result_artifact') {
432
+ constraints.push(check('x401.result_artifact.exactly_one_result_source', hasCredentialResult !== hasCredentialResultUri ? 'passed' : 'failed', 'credential_result xor credential_result_uri', {
433
+ credential_result: hasCredentialResult,
434
+ credential_result_uri: hasCredentialResultUri,
435
+ }));
436
+ }
437
+ else if (responseSummary.kind === 'token') {
438
+ constraints.push(check('x401.token.scheme', stringMember(decoded.proofResponse, 'scheme') === 'x401' ? 'passed' : 'failed', 'x401', stringMember(decoded.proofResponse, 'scheme')));
439
+ constraints.push(check('x401.token.type', stringMember(decoded.proofResponse, 'token_type') === 'Bearer' ? 'passed' : 'failed', 'Bearer', stringMember(decoded.proofResponse, 'token_type')));
440
+ }
441
+ else {
442
+ constraints.push(check('x401.proof_response.kind', 'failed', 'result_artifact or token', Object.keys(decoded.proofResponse).sort()));
443
+ }
444
+ }
445
+ const payloadRequestId = stringMember(decoded.proofRequest, 'request_id');
446
+ const requestId = pushRequestIdChecks(constraints, input.expectedRequestId, payloadRequestId, responseSummary.requestId);
447
+ const credentialProtocol = responseSummary.credentialProtocol ?? credentialSummary.credentialProtocol;
448
+ if (input.expectedCredentialProtocol) {
449
+ constraints.push(check('x401.credential_result.protocol', credentialProtocol === input.expectedCredentialProtocol ? 'passed' : 'failed', input.expectedCredentialProtocol, credentialProtocol));
450
+ }
451
+ const satisfiedRequirements = stringArrayMember(decoded.proofRequest, 'satisfied_requirements');
452
+ const attenuationOk = pushSatisfiedRequirementChecks(constraints, satisfiedRequirements, input.requiredSatisfiedRequirements);
453
+ let delegationOk = null;
454
+ if (input.expectedAgentId) {
455
+ delegationOk = responseSummary.agentId === input.expectedAgentId;
456
+ constraints.push(check('x401.agent_id', delegationOk ? 'passed' : 'failed', input.expectedAgentId, responseSummary.agentId));
457
+ }
458
+ const expectedAgentOriginHash = labeledStringHash('expected_agent_origin', input.expectedAgentOrigin);
459
+ const agentOriginHash = labeledStringHash('agent_origin', input.agentOrigin);
460
+ const hasAgentOriginFacts = input.expectedAgentOrigin !== undefined ||
461
+ input.agentOrigin !== undefined ||
462
+ input.agentOriginVerified !== undefined;
463
+ if (input.expectedAgentOrigin !== undefined) {
464
+ constraints.push(check('x401.agent_origin.value', input.agentOrigin === input.expectedAgentOrigin ? 'passed' : 'failed', expectedAgentOriginHash, agentOriginHash, input.agentOrigin === input.expectedAgentOrigin
465
+ ? undefined
466
+ : 'agent origin did not match expected value'));
467
+ }
468
+ else if (input.agentOrigin !== undefined) {
469
+ constraints.push(check('x401.agent_origin.value', 'unresolved', 'expected origin hash', agentOriginHash, 'caller supplied an agent origin without an expected origin'));
470
+ warnings.push('x401_evidence agent origin supplied without expected origin');
471
+ }
472
+ else {
473
+ constraints.push(check('x401.agent_origin.value', 'not_checked'));
474
+ }
475
+ const agentOriginVerified = pushOptionalVerifiedFactCheck(constraints, errors, warnings, 'x401.agent_origin_verified', input.agentOriginVerified, hasAgentOriginFacts, 'x401_evidence agent origin verification failed', 'x401_evidence agent origin verification unresolved');
476
+ const hasIssuerTrustFacts = input.issuerTrustVerified !== undefined ||
477
+ input.issuerTrustRootType !== undefined ||
478
+ input.issuerTrustRootRef !== undefined;
479
+ const issuerTrustVerified = pushOptionalVerifiedFactCheck(constraints, errors, warnings, 'x401.issuer_trust_verified', input.issuerTrustVerified, hasIssuerTrustFacts, 'x401_evidence issuer trust verification failed', 'x401_evidence issuer trust verification unresolved');
480
+ const hasProofPaymentBindingFacts = input.proofPaymentBindingVerified !== undefined || input.proofPaymentBindingRef !== undefined;
481
+ const proofPaymentBindingVerified = pushOptionalVerifiedFactCheck(constraints, errors, warnings, 'x401.proof_payment_binding_verified', input.proofPaymentBindingVerified, hasProofPaymentBindingFacts, 'x401_evidence proof payment binding verification failed', 'x401_evidence proof payment binding verification unresolved');
482
+ const policy = input.verificationPolicy ?? 'require';
483
+ if (responseSummary.kind === 'result_artifact') {
484
+ pushVerificationPolicyCheck(constraints, errors, warnings, 'result', input.resultVerified, policy);
485
+ }
486
+ else if (responseSummary.kind === 'token') {
487
+ pushVerificationPolicyCheck(constraints, errors, warnings, 'token', input.tokenVerified, policy);
488
+ }
489
+ const paymentHint = decoded.proofRequest?.['payment'] ?? null;
490
+ const paymentRecord = asRecord(paymentHint);
491
+ const paymentRequired = typeof paymentRecord?.['required'] === 'boolean' ? paymentRecord['required'] : null;
492
+ const paymentSchemeHint = typeof paymentRecord?.['scheme_hint'] === 'string' ? paymentRecord['scheme_hint'] : null;
493
+ if (paymentHint !== null) {
494
+ constraints.push(check('x401.payment_separation', 'passed', 'informational only', paymentHint, 'x401 payment hints do not satisfy payment protocols'));
495
+ }
496
+ pushProofResultChecks(constraints, errors, decoded.proofResult);
497
+ for (const constraint of constraints) {
498
+ if (constraint.status === 'failed') {
499
+ errors.push(`x401_evidence constraint failed: ${constraint.type}`);
500
+ }
501
+ }
502
+ return {
503
+ protocol: 'x401',
504
+ valid: errors.length === 0,
505
+ issuer: credentialSummary.verifierClientId,
506
+ subject: responseSummary.agentId,
507
+ scope: satisfiedRequirements,
508
+ attenuation_ok: attenuationOk,
509
+ delegation_ok: delegationOk,
510
+ constraints,
511
+ errors: Array.from(new Set(errors)),
512
+ warnings,
513
+ details: {
514
+ version,
515
+ request_id: requestId,
516
+ header_names: headerResolution.headerNames,
517
+ legacy_headers_used: headerResolution.legacyHeadersUsed,
518
+ legacy_fields_used: credentialSummary.legacyFieldsUsed,
519
+ proof_request_hash: canonicalObjectHash(decoded.proofRequest),
520
+ proof_response_hash: canonicalObjectHash(decoded.proofResponse),
521
+ proof_result_hash: canonicalObjectHash(decoded.proofResult),
522
+ credential_protocol: credentialProtocol,
523
+ nonce: credentialSummary.nonce,
524
+ agent_id: responseSummary.agentId,
525
+ response_kind: decoded.proofResponse ? responseSummary.kind : null,
526
+ result_verified: responseSummary.kind === 'result_artifact' ? input.resultVerified === true : null,
527
+ token_verified: responseSummary.kind === 'token' ? input.tokenVerified === true : null,
528
+ proof_gate: {
529
+ kind: decoded.proofResponse ? responseSummary.kind : null,
530
+ status: proofGateStatus(responseSummary.kind, input.resultVerified, input.tokenVerified, decoded.proofResult),
531
+ },
532
+ satisfied_requirements: satisfiedRequirements,
533
+ payment_separation: {
534
+ present: paymentHint !== null,
535
+ required: paymentRequired,
536
+ scheme_hint: paymentSchemeHint,
537
+ },
538
+ agent_origin: {
539
+ expected_hash: expectedAgentOriginHash,
540
+ actual_hash: agentOriginHash,
541
+ verified: agentOriginVerified,
542
+ },
543
+ issuer_trust: {
544
+ verified: issuerTrustVerified,
545
+ root_type: input.issuerTrustRootType ?? null,
546
+ root_ref_hash: labeledStringHash('issuer_trust_root_ref', input.issuerTrustRootRef),
547
+ },
548
+ proof_payment_binding: {
549
+ verified: proofPaymentBindingVerified,
550
+ reference_hash: labeledStringHash('proof_payment_binding_ref', input.proofPaymentBindingRef),
551
+ },
552
+ verifier_client_id: credentialSummary.verifierClientId,
553
+ credential_result_uri_present: responseSummary.credentialResultUri !== null,
554
+ credential_result_uri_hash: responseSummary.credentialResultUri
555
+ ? canonicalObjectHash({ credential_result_uri: responseSummary.credentialResultUri })
556
+ : null,
557
+ },
558
+ };
559
+ }
560
+ //# sourceMappingURL=x401-evidence.js.map