@credo-ts/openid4vc 0.6.0-pr-2195-20250226100854 → 0.6.0-pr-2195-20250321180923
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/build/openid4vc-holder/OpenId4VcHolderApi.d.ts +29 -29
- package/build/openid4vc-holder/OpenId4VcHolderApi.js +16 -16
- package/build/openid4vc-holder/OpenId4VcHolderApi.js.map +1 -1
- package/build/openid4vc-holder/OpenId4VcHolderModule.js +2 -2
- package/build/openid4vc-holder/OpenId4VcHolderModule.js.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderService.d.ts +2 -2
- package/build/openid4vc-holder/OpenId4VciHolderService.js +6 -6
- package/build/openid4vc-holder/OpenId4VciHolderService.js.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts +1 -1
- package/build/openid4vc-holder/{OpenId4vcSiopHolderService.d.ts → OpenId4vpHolderService.d.ts} +21 -22
- package/build/openid4vc-holder/OpenId4vpHolderService.js +317 -0
- package/build/openid4vc-holder/OpenId4vpHolderService.js.map +1 -0
- package/build/openid4vc-holder/OpenId4vpHolderServiceOptions.d.ts +81 -0
- package/build/openid4vc-holder/{OpenId4vcSiopHolderServiceOptions.js → OpenId4vpHolderServiceOptions.js} +1 -1
- package/build/openid4vc-holder/OpenId4vpHolderServiceOptions.js.map +1 -0
- package/build/openid4vc-holder/index.d.ts +2 -2
- package/build/openid4vc-holder/index.js +2 -2
- package/build/openid4vc-holder/index.js.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerEvents.d.ts +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerModule.d.ts +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerModuleConfig.d.ts +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerService.d.ts +2 -3
- package/build/openid4vc-issuer/OpenId4VcIssuerService.js +13 -16
- package/build/openid4vc-issuer/OpenId4VcIssuerService.js.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerServiceOptions.d.ts +8 -8
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.d.ts +11 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.js +3 -0
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.js.map +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRepository.d.ts +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.d.ts +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.js +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.js.map +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRepository.d.ts +1 -1
- package/build/openid4vc-issuer/router/accessTokenEndpoint.d.ts +2 -2
- package/build/openid4vc-issuer/router/authorizationChallengeEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/credentialEndpoint.d.ts +1 -1
- package/build/openid4vc-issuer/router/credentialEndpoint.js +1 -0
- package/build/openid4vc-issuer/router/credentialEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/credentialOfferEndpoint.d.ts +1 -1
- package/build/openid4vc-issuer/router/credentialOfferEndpoint.js +15 -0
- package/build/openid4vc-issuer/router/credentialOfferEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/jwksEndpoint.d.ts +1 -1
- package/build/openid4vc-issuer/router/nonceEndpoint.d.ts +1 -1
- package/build/openid4vc-issuer/util/txCode.d.ts +1 -1
- package/build/openid4vc-verifier/OpenId4VcVerifierApi.d.ts +13 -26
- package/build/openid4vc-verifier/OpenId4VcVerifierApi.js +16 -25
- package/build/openid4vc-verifier/OpenId4VcVerifierApi.js.map +1 -1
- package/build/openid4vc-verifier/OpenId4VcVerifierEvents.d.ts +1 -1
- package/build/openid4vc-verifier/OpenId4VcVerifierModule.d.ts +1 -1
- package/build/openid4vc-verifier/OpenId4VcVerifierModule.js +4 -4
- package/build/openid4vc-verifier/OpenId4VcVerifierModule.js.map +1 -1
- package/build/openid4vc-verifier/OpenId4VcVerifierModuleConfig.d.ts +30 -7
- package/build/openid4vc-verifier/OpenId4VcVerifierModuleConfig.js +16 -12
- package/build/openid4vc-verifier/OpenId4VcVerifierModuleConfig.js.map +1 -1
- package/build/openid4vc-verifier/{OpenId4VcSiopVerifierService.d.ts → OpenId4VpVerifierService.d.ts} +17 -33
- package/build/openid4vc-verifier/OpenId4VpVerifierService.js +765 -0
- package/build/openid4vc-verifier/OpenId4VpVerifierService.js.map +1 -0
- package/build/openid4vc-verifier/OpenId4VpVerifierServiceOptions.d.ts +146 -0
- package/build/openid4vc-verifier/{OpenId4VcSiopVerifierServiceOptions.js → OpenId4VpVerifierServiceOptions.js} +1 -1
- package/build/openid4vc-verifier/OpenId4VpVerifierServiceOptions.js.map +1 -0
- package/build/openid4vc-verifier/index.d.ts +2 -2
- package/build/openid4vc-verifier/index.js +2 -2
- package/build/openid4vc-verifier/index.js.map +1 -1
- package/build/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.d.ts +25 -9
- package/build/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.js +21 -2
- package/build/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.js.map +1 -1
- package/build/openid4vc-verifier/repository/OpenId4VcVerificationSessionRepository.d.ts +1 -1
- package/build/openid4vc-verifier/repository/OpenId4VcVerifierRecord.d.ts +3 -3
- package/build/openid4vc-verifier/repository/OpenId4VcVerifierRepository.d.ts +1 -1
- package/build/openid4vc-verifier/router/authorizationEndpoint.d.ts +2 -10
- package/build/openid4vc-verifier/router/authorizationEndpoint.js +94 -7
- package/build/openid4vc-verifier/router/authorizationEndpoint.js.map +1 -1
- package/build/openid4vc-verifier/router/authorizationRequestEndpoint.d.ts +2 -10
- package/build/openid4vc-verifier/router/authorizationRequestEndpoint.js +18 -6
- package/build/openid4vc-verifier/router/authorizationRequestEndpoint.js.map +1 -1
- package/build/shared/callbacks.d.ts +2 -2
- package/build/shared/callbacks.js +10 -7
- package/build/shared/callbacks.js.map +1 -1
- package/build/shared/models/index.d.ts +4 -4
- package/build/shared/models/index.js.map +1 -1
- package/build/shared/router/context.d.ts +2 -2
- package/build/shared/router/context.js +9 -5
- package/build/shared/router/context.js.map +1 -1
- package/build/shared/router/express.js +1 -2
- package/build/shared/router/express.js.map +1 -1
- package/build/shared/transactionData.d.ts +5 -0
- package/build/shared/transactionData.js +22 -0
- package/build/shared/transactionData.js.map +1 -0
- package/build/shared/utils.d.ts +1 -1
- package/build/shared/utils.js +3 -3
- package/build/shared/utils.js.map +1 -1
- package/package.json +7 -6
- package/build/openid4vc-holder/OpenId4vcSiopHolderService.js +0 -306
- package/build/openid4vc-holder/OpenId4vcSiopHolderService.js.map +0 -1
- package/build/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.d.ts +0 -54
- package/build/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.js.map +0 -1
- package/build/openid4vc-verifier/OpenId4VcSiopVerifierService.js +0 -806
- package/build/openid4vc-verifier/OpenId4VcSiopVerifierService.js.map +0 -1
- package/build/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.d.ts +0 -93
- package/build/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.js.map +0 -1
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OpenId4VpVerifierService = void 0;
|
|
16
|
+
const core_1 = require("@credo-ts/core");
|
|
17
|
+
const core_2 = require("@credo-ts/core");
|
|
18
|
+
const oauth2_1 = require("@openid4vc/oauth2");
|
|
19
|
+
const openid4vp_1 = require("@openid4vc/openid4vp");
|
|
20
|
+
const callbacks_1 = require("../shared/callbacks");
|
|
21
|
+
const router_1 = require("../shared/router");
|
|
22
|
+
const utils_1 = require("../shared/utils");
|
|
23
|
+
const transactionData_1 = require("../shared/transactionData");
|
|
24
|
+
const OpenId4VcVerificationSessionState_1 = require("./OpenId4VcVerificationSessionState");
|
|
25
|
+
const OpenId4VcVerifierEvents_1 = require("./OpenId4VcVerifierEvents");
|
|
26
|
+
const OpenId4VcVerifierModuleConfig_1 = require("./OpenId4VcVerifierModuleConfig");
|
|
27
|
+
const repository_1 = require("./repository");
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
let OpenId4VpVerifierService = class OpenId4VpVerifierService {
|
|
32
|
+
constructor(logger, w3cCredentialService, openId4VcVerifierRepository, config, openId4VcVerificationSessionRepository) {
|
|
33
|
+
this.logger = logger;
|
|
34
|
+
this.w3cCredentialService = w3cCredentialService;
|
|
35
|
+
this.openId4VcVerifierRepository = openId4VcVerifierRepository;
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.openId4VcVerificationSessionRepository = openId4VcVerificationSessionRepository;
|
|
38
|
+
}
|
|
39
|
+
getOpenid4vpVerifier(agentContext) {
|
|
40
|
+
const callbacks = (0, callbacks_1.getOid4vcCallbacks)(agentContext);
|
|
41
|
+
const openid4vpClient = new openid4vp_1.Openid4vpVerifier({ callbacks });
|
|
42
|
+
return openid4vpClient;
|
|
43
|
+
}
|
|
44
|
+
async createAuthorizationRequest(agentContext, options) {
|
|
45
|
+
const nonce = await agentContext.wallet.generateNonce();
|
|
46
|
+
const state = await agentContext.wallet.generateNonce();
|
|
47
|
+
const responseMode = options.responseMode ?? 'direct_post.jwt';
|
|
48
|
+
const isDcApiRequest = responseMode === 'dc_api' || responseMode === 'dc_api.jwt';
|
|
49
|
+
const version = options.version ?? 'v1.draft24';
|
|
50
|
+
if (version === 'v1.draft21' && isDcApiRequest) {
|
|
51
|
+
throw new core_2.CredoError(`OpenID4VP version '${version}' cannot be used with responseMode '${options.responseMode}'. Use version 'v1.draft24' instead.`);
|
|
52
|
+
}
|
|
53
|
+
if (version === 'v1.draft21' && options.transactionData) {
|
|
54
|
+
throw new core_2.CredoError(`OpenID4VP version '${version}' cannot be used with transactionData. Use version 'v1.draft24' instead.`);
|
|
55
|
+
}
|
|
56
|
+
if (version === 'v1.draft21' && options.dcql) {
|
|
57
|
+
throw new core_2.CredoError(`OpenID4VP version '${version}' cannot be used with dcql. Use version 'v1.draft24' instead.`);
|
|
58
|
+
}
|
|
59
|
+
// Check to prevent direct_post from being used with mDOC
|
|
60
|
+
const hasMdocRequest = options.presentationExchange?.definition.input_descriptors.some((i) => i.format?.mso_mdoc) ||
|
|
61
|
+
options.dcql?.query.credentials.some((c) => c.format === 'mso_mdoc');
|
|
62
|
+
if (responseMode === 'direct_post' && hasMdocRequest) {
|
|
63
|
+
throw new core_2.CredoError("Unable to create authorization request with response mode 'direct_post' containing mDOC credentials. ISO 18013-7 requires the usage of response mode 'direct_post.jwt', and needs parameters from the encrypted response header to verify the mDOC sigature.");
|
|
64
|
+
}
|
|
65
|
+
const authorizationRequestId = core_2.utils.uuid();
|
|
66
|
+
// We include the `session=` in the url so we can still easily
|
|
67
|
+
// find the session an encrypted response
|
|
68
|
+
const authorizationResponseUrl = `${(0, core_2.joinUriParts)(this.config.baseUrl, [options.verifier.verifierId, this.config.authorizationEndpoint])}?session=${authorizationRequestId}`;
|
|
69
|
+
const jwtIssuer = options.requestSigner.method === 'none'
|
|
70
|
+
? undefined
|
|
71
|
+
: options.requestSigner.method === 'x5c'
|
|
72
|
+
? await (0, utils_1.requestSignerToJwtIssuer)(agentContext, {
|
|
73
|
+
...options.requestSigner,
|
|
74
|
+
issuer: authorizationResponseUrl,
|
|
75
|
+
})
|
|
76
|
+
: await (0, utils_1.requestSignerToJwtIssuer)(agentContext, options.requestSigner);
|
|
77
|
+
let clientIdScheme;
|
|
78
|
+
let clientId;
|
|
79
|
+
if (!jwtIssuer) {
|
|
80
|
+
if (!isDcApiRequest) {
|
|
81
|
+
throw new Error("requestSigner method 'none' is only supported for response mode 'dc_api' and 'dc_api.jwt'");
|
|
82
|
+
}
|
|
83
|
+
clientIdScheme = 'web-origin';
|
|
84
|
+
clientId = undefined;
|
|
85
|
+
}
|
|
86
|
+
else if (jwtIssuer?.method === 'x5c') {
|
|
87
|
+
const leafCertificate = core_2.X509Service.getLeafCertificate(agentContext, { certificateChain: jwtIssuer.x5c });
|
|
88
|
+
if (leafCertificate.sanDnsNames.includes((0, core_2.getDomainFromUrl)(jwtIssuer.issuer))) {
|
|
89
|
+
clientIdScheme = 'x509_san_dns';
|
|
90
|
+
clientId = (0, core_2.getDomainFromUrl)(jwtIssuer.issuer);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new core_2.CredoError(`With jwtIssuer method 'x5c' the jwtIssuer's 'issuer' field must match a sanDnsName (FQDN) in the leaf x509 chain's leaf certificate.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (jwtIssuer?.method === 'did') {
|
|
97
|
+
clientId = jwtIssuer.didUrl.split('#')[0];
|
|
98
|
+
clientIdScheme = 'did';
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw new core_2.CredoError(`Unsupported jwt issuer method '${options.requestSigner.method}'. Only 'did' and 'x5c' are supported.`);
|
|
102
|
+
}
|
|
103
|
+
// We always use shortened URIs currently
|
|
104
|
+
const hostedAuthorizationRequestUri = !isDcApiRequest
|
|
105
|
+
? (0, core_2.joinUriParts)(this.config.baseUrl, [
|
|
106
|
+
options.verifier.verifierId,
|
|
107
|
+
this.config.authorizationRequestEndpoint,
|
|
108
|
+
authorizationRequestId,
|
|
109
|
+
])
|
|
110
|
+
: // No hosted request needed when using DC API
|
|
111
|
+
undefined;
|
|
112
|
+
const client_id =
|
|
113
|
+
// For did/https and draft 21 the client id has no special prefix
|
|
114
|
+
clientIdScheme === 'did' || clientIdScheme === 'https' || version === 'v1.draft21'
|
|
115
|
+
? clientId
|
|
116
|
+
: `${clientIdScheme}:${clientId}`;
|
|
117
|
+
// for did the client_id is same in draft 21 and 24 so we could support both at the same time
|
|
118
|
+
const legacyClientIdScheme = version === 'v1.draft21' && clientIdScheme !== 'web-origin' ? clientIdScheme : undefined;
|
|
119
|
+
const client_metadata = await this.getClientMetadata(agentContext, {
|
|
120
|
+
responseMode,
|
|
121
|
+
verifier: options.verifier,
|
|
122
|
+
authorizationResponseUrl,
|
|
123
|
+
});
|
|
124
|
+
const requestParamsBase = {
|
|
125
|
+
nonce,
|
|
126
|
+
presentation_definition: options.presentationExchange?.definition,
|
|
127
|
+
dcql_query: options.dcql?.query,
|
|
128
|
+
transaction_data: options.transactionData?.map((entry) => core_2.JsonEncoder.toBase64URL(entry)),
|
|
129
|
+
response_mode: responseMode,
|
|
130
|
+
response_type: 'vp_token',
|
|
131
|
+
client_metadata,
|
|
132
|
+
};
|
|
133
|
+
const openid4vpVerifier = this.getOpenid4vpVerifier(agentContext);
|
|
134
|
+
const authorizationRequest = await openid4vpVerifier.createOpenId4vpAuthorizationRequest({
|
|
135
|
+
jar: jwtIssuer
|
|
136
|
+
? {
|
|
137
|
+
jwtSigner: jwtIssuer,
|
|
138
|
+
requestUri: hostedAuthorizationRequestUri,
|
|
139
|
+
expiresInSeconds: this.config.authorizationRequestExpiresInSeconds,
|
|
140
|
+
}
|
|
141
|
+
: undefined,
|
|
142
|
+
authorizationRequestPayload: requestParamsBase.response_mode === 'dc_api.jwt' || requestParamsBase.response_mode === 'dc_api'
|
|
143
|
+
? {
|
|
144
|
+
...requestParamsBase,
|
|
145
|
+
// No client_id for unsigned requests
|
|
146
|
+
client_id: jwtIssuer ? client_id : undefined,
|
|
147
|
+
response_mode: requestParamsBase.response_mode,
|
|
148
|
+
expected_origins: options.expectedOrigins,
|
|
149
|
+
}
|
|
150
|
+
: {
|
|
151
|
+
...requestParamsBase,
|
|
152
|
+
response_mode: requestParamsBase.response_mode,
|
|
153
|
+
client_id: client_id,
|
|
154
|
+
state,
|
|
155
|
+
response_uri: authorizationResponseUrl,
|
|
156
|
+
client_id_scheme: legacyClientIdScheme,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
const verificationSession = new repository_1.OpenId4VcVerificationSessionRecord({
|
|
160
|
+
// Only store payload for unsiged requests
|
|
161
|
+
authorizationRequestPayload: authorizationRequest.jar
|
|
162
|
+
? undefined
|
|
163
|
+
: authorizationRequest.authorizationRequestPayload,
|
|
164
|
+
authorizationRequestJwt: authorizationRequest.jar?.authorizationRequestJwt,
|
|
165
|
+
authorizationRequestUri: hostedAuthorizationRequestUri,
|
|
166
|
+
authorizationRequestId,
|
|
167
|
+
state: OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.RequestCreated,
|
|
168
|
+
verifierId: options.verifier.verifierId,
|
|
169
|
+
expiresAt: (0, utils_1.addSecondsToDate)(new Date(), this.config.authorizationRequestExpiresInSeconds),
|
|
170
|
+
});
|
|
171
|
+
await this.openId4VcVerificationSessionRepository.save(agentContext, verificationSession);
|
|
172
|
+
this.emitStateChangedEvent(agentContext, verificationSession, null);
|
|
173
|
+
return {
|
|
174
|
+
authorizationRequest: authorizationRequest.authorizationRequest,
|
|
175
|
+
verificationSession,
|
|
176
|
+
authorizationRequestObject: authorizationRequest.authorizationRequestObject,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
getDcqlVerifiedResponse(agentContext, _dcqlQuery, presentations) {
|
|
180
|
+
const dcqlService = agentContext.dependencyManager.resolve(core_2.DcqlService);
|
|
181
|
+
const dcqlQuery = dcqlService.validateDcqlQuery(_dcqlQuery);
|
|
182
|
+
const dcqlPresentationEntries = Object.entries(presentations);
|
|
183
|
+
const dcqlPresentation = Object.fromEntries(dcqlPresentationEntries.map(([credentialId, presentation]) => {
|
|
184
|
+
const queryCredential = dcqlQuery.credentials.find((c) => c.id === credentialId);
|
|
185
|
+
if (!queryCredential) {
|
|
186
|
+
throw new core_2.CredoError(`vp_token contains presentation for credential query id '${credentialId}', but this credential is not present in the dcql query.`);
|
|
187
|
+
}
|
|
188
|
+
return [
|
|
189
|
+
credentialId,
|
|
190
|
+
this.decodePresentation(agentContext, {
|
|
191
|
+
presentation,
|
|
192
|
+
format: queryCredential.format === 'mso_mdoc'
|
|
193
|
+
? core_1.ClaimFormat.MsoMdoc
|
|
194
|
+
: queryCredential.format === 'dc+sd-jwt' || queryCredential.format === 'vc+sd-jwt'
|
|
195
|
+
? core_1.ClaimFormat.SdJwtVc
|
|
196
|
+
: core_1.ClaimFormat.JwtVc,
|
|
197
|
+
}),
|
|
198
|
+
];
|
|
199
|
+
}));
|
|
200
|
+
const dcqlPresentationResult = dcqlService.assertValidDcqlPresentation(dcqlPresentation, dcqlQuery);
|
|
201
|
+
return {
|
|
202
|
+
query: dcqlQuery,
|
|
203
|
+
presentations: dcqlPresentation,
|
|
204
|
+
presentationResult: dcqlPresentationResult,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async parseAuthorizationResponse(agentContext, options) {
|
|
208
|
+
const openid4vpVerifier = this.getOpenid4vpVerifier(agentContext);
|
|
209
|
+
const { authorizationResponse, verificationSession, origin } = options;
|
|
210
|
+
let parsedAuthorizationResponse = undefined;
|
|
211
|
+
try {
|
|
212
|
+
parsedAuthorizationResponse = await openid4vpVerifier.parseOpenid4vpAuthorizationResponse({
|
|
213
|
+
authorizationResponse,
|
|
214
|
+
origin,
|
|
215
|
+
authorizationRequestPayload: verificationSession.requestPayload,
|
|
216
|
+
callbacks: (0, callbacks_1.getOid4vcCallbacks)(agentContext),
|
|
217
|
+
});
|
|
218
|
+
// FIXME: use JarmMode enum when new release of oid4vp
|
|
219
|
+
if (parsedAuthorizationResponse.jarm && parsedAuthorizationResponse.jarm.type !== 'Encrypted') {
|
|
220
|
+
throw new oauth2_1.Oauth2ServerErrorResponseError({
|
|
221
|
+
error: oauth2_1.Oauth2ErrorCodes.InvalidRequest,
|
|
222
|
+
error_description: `Only encrypted JARM responses are supported, received '${parsedAuthorizationResponse.jarm.type}'.`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
...parsedAuthorizationResponse,
|
|
227
|
+
verificationSession,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (verificationSession?.state === OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.RequestUriRetrieved ||
|
|
232
|
+
verificationSession?.state === OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.RequestCreated) {
|
|
233
|
+
const parsed = openid4vp_1.zOpenid4vpAuthorizationResponse.safeParse(parsedAuthorizationResponse?.authorizationResponsePayload);
|
|
234
|
+
verificationSession.authorizationResponsePayload = parsed.success ? parsed.data : undefined;
|
|
235
|
+
verificationSession.errorMessage = error.message;
|
|
236
|
+
await this.updateState(agentContext, verificationSession, OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.Error);
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async verifyAuthorizationResponse(agentContext, options) {
|
|
242
|
+
const { verificationSession, authorizationResponse, origin } = options;
|
|
243
|
+
const authorizationRequest = options.verificationSession.requestPayload;
|
|
244
|
+
verificationSession.assertState([
|
|
245
|
+
OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.RequestUriRetrieved,
|
|
246
|
+
OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.RequestCreated,
|
|
247
|
+
]);
|
|
248
|
+
if (verificationSession.expiresAt && Date.now() > verificationSession.expiresAt.getTime()) {
|
|
249
|
+
throw new oauth2_1.Oauth2ServerErrorResponseError({
|
|
250
|
+
error: oauth2_1.Oauth2ErrorCodes.InvalidRequest,
|
|
251
|
+
error_description: 'session expired',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const result = await this.parseAuthorizationResponse(agentContext, {
|
|
255
|
+
verificationSession,
|
|
256
|
+
authorizationResponse,
|
|
257
|
+
origin,
|
|
258
|
+
});
|
|
259
|
+
let dcqlResponse = undefined;
|
|
260
|
+
let pexResponse = undefined;
|
|
261
|
+
let transactionData = undefined;
|
|
262
|
+
try {
|
|
263
|
+
const parsedClientId = (0, openid4vp_1.getOpenid4vpClientId)({
|
|
264
|
+
authorizationRequestPayload: authorizationRequest,
|
|
265
|
+
origin: options.origin,
|
|
266
|
+
});
|
|
267
|
+
// If client_id_scheme was used we need to use the legacy client id.
|
|
268
|
+
const clientId = parsedClientId.legacyClientId ?? parsedClientId.clientId;
|
|
269
|
+
const responseUri = (0, openid4vp_1.isOpenid4vpAuthorizationRequestDcApi)(authorizationRequest)
|
|
270
|
+
? undefined
|
|
271
|
+
: authorizationRequest.response_uri;
|
|
272
|
+
// NOTE: apu is needed for mDOC over OID4VP without DC API
|
|
273
|
+
const mdocGeneratedNonce = result.jarm?.jarmHeader.apu
|
|
274
|
+
? core_2.TypedArrayEncoder.toUtf8String(core_2.TypedArrayEncoder.fromBase64(result.jarm?.jarmHeader.apu))
|
|
275
|
+
: undefined;
|
|
276
|
+
if (result.type === 'dcql') {
|
|
277
|
+
const dcqlPresentationEntries = Object.entries(result.dcql.presentations);
|
|
278
|
+
if (!authorizationRequest.dcql_query) {
|
|
279
|
+
throw new core_2.CredoError('');
|
|
280
|
+
}
|
|
281
|
+
const dcql = agentContext.dependencyManager.resolve(core_2.DcqlService);
|
|
282
|
+
const dcqlQuery = dcql.validateDcqlQuery(authorizationRequest.dcql_query);
|
|
283
|
+
const presentationVerificationResults = await Promise.all(dcqlPresentationEntries.map(async ([credentialId, presentation]) => {
|
|
284
|
+
const queryCredential = dcqlQuery.credentials.find((c) => c.id === credentialId);
|
|
285
|
+
if (!queryCredential) {
|
|
286
|
+
throw new core_2.CredoError(`vp_token contains presentation for credential query id '${credentialId}', but this credential is not present in the dcql query.`);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...(await this.verifyPresentation(agentContext, {
|
|
290
|
+
format: queryCredential.format === 'mso_mdoc'
|
|
291
|
+
? core_1.ClaimFormat.MsoMdoc
|
|
292
|
+
: queryCredential.format === 'dc+sd-jwt' || queryCredential.format === 'vc+sd-jwt'
|
|
293
|
+
? core_1.ClaimFormat.SdJwtVc
|
|
294
|
+
: core_1.ClaimFormat.JwtVc,
|
|
295
|
+
nonce: authorizationRequest.nonce,
|
|
296
|
+
audience: clientId,
|
|
297
|
+
origin: options.origin,
|
|
298
|
+
responseUri,
|
|
299
|
+
mdocGeneratedNonce,
|
|
300
|
+
verificationSessionId: result.verificationSession.id,
|
|
301
|
+
presentation,
|
|
302
|
+
})),
|
|
303
|
+
credentialId,
|
|
304
|
+
};
|
|
305
|
+
}));
|
|
306
|
+
const presentations = presentationVerificationResults.reduce((all, p) => {
|
|
307
|
+
if (p.verified)
|
|
308
|
+
all[p.credentialId] = p.presentation;
|
|
309
|
+
return all;
|
|
310
|
+
}, {});
|
|
311
|
+
const presentationResult = dcql.assertValidDcqlPresentation(presentations, dcqlQuery);
|
|
312
|
+
const errorMessages = presentationVerificationResults
|
|
313
|
+
.map((result, index) => (!result.verified ? `\t- [${index}]: ${result.reason}` : undefined))
|
|
314
|
+
.filter((i) => i !== undefined);
|
|
315
|
+
if (errorMessages.length > 0) {
|
|
316
|
+
throw new core_2.CredoError(`One or more presentations failed verification. \n\t${errorMessages.join('\n')}`);
|
|
317
|
+
}
|
|
318
|
+
dcqlResponse = {
|
|
319
|
+
presentations,
|
|
320
|
+
presentationResult,
|
|
321
|
+
query: dcqlQuery,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (result.type === 'pex') {
|
|
325
|
+
const pex = agentContext.dependencyManager.resolve(core_2.DifPresentationExchangeService);
|
|
326
|
+
const encodedPresentations = result.pex.presentations;
|
|
327
|
+
const submission = result.pex.presentationSubmission;
|
|
328
|
+
const definition = result.pex.presentationDefinition;
|
|
329
|
+
pex.validatePresentationDefinition(definition);
|
|
330
|
+
pex.validatePresentationSubmission(submission);
|
|
331
|
+
const presentationsArray = Array.isArray(encodedPresentations) ? encodedPresentations : [encodedPresentations];
|
|
332
|
+
const presentationVerificationResults = await Promise.all(presentationsArray.map((presentation) => {
|
|
333
|
+
return this.verifyPresentation(agentContext, {
|
|
334
|
+
nonce: authorizationRequest.nonce,
|
|
335
|
+
audience: clientId,
|
|
336
|
+
responseUri,
|
|
337
|
+
mdocGeneratedNonce,
|
|
338
|
+
verificationSessionId: result.verificationSession.id,
|
|
339
|
+
presentation,
|
|
340
|
+
format: this.claimFormatFromEncodedPresentation(presentation),
|
|
341
|
+
origin: options.origin,
|
|
342
|
+
});
|
|
343
|
+
}));
|
|
344
|
+
const errorMessages = presentationVerificationResults
|
|
345
|
+
.map((result, index) => (!result.verified ? `\t- [${index}]: ${result.reason}` : undefined))
|
|
346
|
+
.filter((i) => i !== undefined);
|
|
347
|
+
if (errorMessages.length > 0) {
|
|
348
|
+
throw new core_2.CredoError(`One or more presentations failed verification. \n\t${errorMessages.join('\n')}`);
|
|
349
|
+
}
|
|
350
|
+
const verifiablePresentations = presentationVerificationResults
|
|
351
|
+
.map((p) => (p.verified ? p.presentation : undefined))
|
|
352
|
+
.filter((p) => p !== undefined);
|
|
353
|
+
pex.validatePresentation(definition,
|
|
354
|
+
// vp_token MUST not be an array if only one entry
|
|
355
|
+
verifiablePresentations.length === 1 ? verifiablePresentations[0] : verifiablePresentations, submission);
|
|
356
|
+
const descriptors = (0, core_2.extractPresentationsWithDescriptorsFromSubmission)(
|
|
357
|
+
// vp_token MUST not be an array if only one entry
|
|
358
|
+
verifiablePresentations.length === 1 ? verifiablePresentations[0] : verifiablePresentations, submission, definition);
|
|
359
|
+
pexResponse = {
|
|
360
|
+
definition,
|
|
361
|
+
descriptors,
|
|
362
|
+
presentations: verifiablePresentations,
|
|
363
|
+
submission,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
transactionData = await this.getVerifiedTransactionData(agentContext, {
|
|
367
|
+
authorizationRequest,
|
|
368
|
+
dcql: dcqlResponse,
|
|
369
|
+
presentationExchange: pexResponse,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
result.verificationSession.errorMessage = error.message;
|
|
374
|
+
await this.updateState(agentContext, result.verificationSession, OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.Error);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
result.verificationSession.authorizationResponsePayload = result.authorizationResponsePayload;
|
|
378
|
+
await this.updateState(agentContext, result.verificationSession, OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.ResponseVerified);
|
|
379
|
+
return {
|
|
380
|
+
presentationExchange: pexResponse,
|
|
381
|
+
dcql: dcqlResponse,
|
|
382
|
+
transactionData,
|
|
383
|
+
verificationSession: result.verificationSession,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get the format based on an encoded presentation. This is mostly leveraged for
|
|
388
|
+
* PEX where it's not known based on the request which format to expect
|
|
389
|
+
*/
|
|
390
|
+
claimFormatFromEncodedPresentation(presentation) {
|
|
391
|
+
if (typeof presentation === 'object')
|
|
392
|
+
return core_1.ClaimFormat.LdpVc;
|
|
393
|
+
if (presentation.includes('~'))
|
|
394
|
+
return core_1.ClaimFormat.SdJwtVc;
|
|
395
|
+
if (core_2.Jwt.format.test(presentation))
|
|
396
|
+
return core_1.ClaimFormat.JwtVc;
|
|
397
|
+
// Fallback, we tried all other formats
|
|
398
|
+
return core_1.ClaimFormat.MsoMdoc;
|
|
399
|
+
}
|
|
400
|
+
async getVerifiedAuthorizationResponse(agentContext, verificationSession) {
|
|
401
|
+
verificationSession.assertState(OpenId4VcVerificationSessionState_1.OpenId4VcVerificationSessionState.ResponseVerified);
|
|
402
|
+
if (!verificationSession.authorizationResponsePayload) {
|
|
403
|
+
throw new core_2.CredoError('No authorization response payload found in the verification session.');
|
|
404
|
+
}
|
|
405
|
+
const authorizationRequestPayload = verificationSession.requestPayload;
|
|
406
|
+
const openid4vpAuthorizationResponsePayload = verificationSession.authorizationResponsePayload;
|
|
407
|
+
const openid4vpVerifier = this.getOpenid4vpVerifier(agentContext);
|
|
408
|
+
const result = openid4vpVerifier.validateOpenid4vpAuthorizationResponsePayload({
|
|
409
|
+
authorizationRequestPayload: verificationSession.requestPayload,
|
|
410
|
+
authorizationResponsePayload: openid4vpAuthorizationResponsePayload,
|
|
411
|
+
});
|
|
412
|
+
let presentationExchange = undefined;
|
|
413
|
+
const dcql = result.type === 'dcql'
|
|
414
|
+
? this.getDcqlVerifiedResponse(agentContext, authorizationRequestPayload.dcql_query, result.dcql.presentations)
|
|
415
|
+
: undefined;
|
|
416
|
+
if (result.type === 'pex') {
|
|
417
|
+
const presentationDefinition = authorizationRequestPayload.presentation_definition;
|
|
418
|
+
const submission = openid4vpAuthorizationResponsePayload.presentation_submission;
|
|
419
|
+
if (!submission) {
|
|
420
|
+
throw new core_2.CredoError('Unable to extract submission from the response.');
|
|
421
|
+
}
|
|
422
|
+
const verifiablePresentations = result.pex.presentations.map((presentation) => this.decodePresentation(agentContext, {
|
|
423
|
+
presentation,
|
|
424
|
+
format: this.claimFormatFromEncodedPresentation(presentation),
|
|
425
|
+
}));
|
|
426
|
+
presentationExchange = {
|
|
427
|
+
definition: presentationDefinition,
|
|
428
|
+
submission,
|
|
429
|
+
presentations: verifiablePresentations,
|
|
430
|
+
descriptors: (0, core_2.extractPresentationsWithDescriptorsFromSubmission)(
|
|
431
|
+
// vp_token MUST not be an array if only one entry
|
|
432
|
+
verifiablePresentations.length === 1 ? verifiablePresentations[0] : verifiablePresentations, submission, presentationDefinition),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
if (!presentationExchange && !dcql) {
|
|
436
|
+
throw new core_2.CredoError('No presentationExchange or dcql found in the response.');
|
|
437
|
+
}
|
|
438
|
+
const transactionData = await this.getVerifiedTransactionData(agentContext, {
|
|
439
|
+
authorizationRequest: authorizationRequestPayload,
|
|
440
|
+
dcql,
|
|
441
|
+
presentationExchange,
|
|
442
|
+
});
|
|
443
|
+
return {
|
|
444
|
+
presentationExchange,
|
|
445
|
+
dcql,
|
|
446
|
+
transactionData,
|
|
447
|
+
verificationSession,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async getVerifiedTransactionData(agentContext, { authorizationRequest, presentationExchange, dcql, }) {
|
|
451
|
+
if (!authorizationRequest.transaction_data)
|
|
452
|
+
return undefined;
|
|
453
|
+
const openid4vpVerifier = this.getOpenid4vpVerifier(agentContext);
|
|
454
|
+
const transactionDataHashesCredentials = {};
|
|
455
|
+
// Extract presentations with credentialId
|
|
456
|
+
const idToCredential = dcql
|
|
457
|
+
? Object.entries(dcql.presentations)
|
|
458
|
+
: (presentationExchange?.descriptors.map((descriptor) => [descriptor.descriptor.id, descriptor.presentation]) ?? []);
|
|
459
|
+
for (const [credentialId, presentation] of idToCredential) {
|
|
460
|
+
// Only SD-JWT VC supported for now
|
|
461
|
+
if (presentation.claimFormat === core_1.ClaimFormat.SdJwtVc) {
|
|
462
|
+
transactionDataHashesCredentials[credentialId] = (0, transactionData_1.getSdJwtVcTransactionDataHashes)(presentation);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Verify the transaction data
|
|
466
|
+
const transactionData = await openid4vpVerifier.verifyTransactionData({
|
|
467
|
+
credentials: transactionDataHashesCredentials,
|
|
468
|
+
transactionData: authorizationRequest.transaction_data,
|
|
469
|
+
});
|
|
470
|
+
return transactionData.map(({ hash, hashAlg, credentialHashIndex, credentialId, transactionDataEntry }) => ({
|
|
471
|
+
credentialHashIndex,
|
|
472
|
+
credentialId,
|
|
473
|
+
encoded: transactionDataEntry.encoded,
|
|
474
|
+
decoded: transactionDataEntry.transactionData,
|
|
475
|
+
transactionDataIndex: transactionDataEntry.transactionDataIndex,
|
|
476
|
+
hash,
|
|
477
|
+
// We only support the values supported by Credo hasher, so it can't be any other value than those.
|
|
478
|
+
hashAlg: hashAlg,
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
async getAllVerifiers(agentContext) {
|
|
482
|
+
return this.openId4VcVerifierRepository.getAll(agentContext);
|
|
483
|
+
}
|
|
484
|
+
async getVerifierByVerifierId(agentContext, verifierId) {
|
|
485
|
+
return this.openId4VcVerifierRepository.getByVerifierId(agentContext, verifierId);
|
|
486
|
+
}
|
|
487
|
+
async updateVerifier(agentContext, verifier) {
|
|
488
|
+
return this.openId4VcVerifierRepository.update(agentContext, verifier);
|
|
489
|
+
}
|
|
490
|
+
async createVerifier(agentContext, options) {
|
|
491
|
+
const openId4VcVerifier = new repository_1.OpenId4VcVerifierRecord({
|
|
492
|
+
verifierId: options?.verifierId ?? core_2.utils.uuid(),
|
|
493
|
+
clientMetadata: options?.clientMetadata,
|
|
494
|
+
});
|
|
495
|
+
await this.openId4VcVerifierRepository.save(agentContext, openId4VcVerifier);
|
|
496
|
+
await (0, router_1.storeActorIdForContextCorrelationId)(agentContext, openId4VcVerifier.verifierId);
|
|
497
|
+
return openId4VcVerifier;
|
|
498
|
+
}
|
|
499
|
+
async findVerificationSessionsByQuery(agentContext, query, queryOptions) {
|
|
500
|
+
return this.openId4VcVerificationSessionRepository.findByQuery(agentContext, query, queryOptions);
|
|
501
|
+
}
|
|
502
|
+
async getVerificationSessionById(agentContext, verificationSessionId) {
|
|
503
|
+
return this.openId4VcVerificationSessionRepository.getById(agentContext, verificationSessionId);
|
|
504
|
+
}
|
|
505
|
+
async getClientMetadata(agentContext, options) {
|
|
506
|
+
const { responseMode, verifier } = options;
|
|
507
|
+
const signatureSuiteRegistry = agentContext.dependencyManager.resolve(core_2.SignatureSuiteRegistry);
|
|
508
|
+
const supportedAlgs = (0, utils_1.getSupportedJwaSignatureAlgorithms)(agentContext);
|
|
509
|
+
const supportedMdocAlgs = supportedAlgs.filter(core_2.isMdocSupportedSignatureAlgorithm);
|
|
510
|
+
const supportedProofTypes = signatureSuiteRegistry.supportedProofTypes;
|
|
511
|
+
let jarmEncryptionJwk;
|
|
512
|
+
if ((0, openid4vp_1.isJarmResponseMode)(responseMode)) {
|
|
513
|
+
const key = await agentContext.wallet.createKey({ keyType: core_2.KeyType.P256 });
|
|
514
|
+
jarmEncryptionJwk = { ...(0, core_2.getJwkFromKey)(key).toJson(), kid: key.fingerprint, use: 'enc' };
|
|
515
|
+
}
|
|
516
|
+
const jarmClientMetadata = jarmEncryptionJwk
|
|
517
|
+
? {
|
|
518
|
+
jwks: { keys: [jarmEncryptionJwk] },
|
|
519
|
+
authorization_encrypted_response_alg: 'ECDH-ES',
|
|
520
|
+
// FIXME: we need to allow setting this, but also to fetch it based on the `request_uri` and
|
|
521
|
+
// `request_uri_method`
|
|
522
|
+
authorization_encrypted_response_enc: 'A128GCM',
|
|
523
|
+
}
|
|
524
|
+
: undefined;
|
|
525
|
+
return {
|
|
526
|
+
...jarmClientMetadata,
|
|
527
|
+
...verifier.clientMetadata,
|
|
528
|
+
response_types_supported: ['vp_token'],
|
|
529
|
+
vp_formats: {
|
|
530
|
+
mso_mdoc: {
|
|
531
|
+
alg: supportedMdocAlgs,
|
|
532
|
+
},
|
|
533
|
+
jwt_vc: {
|
|
534
|
+
alg: supportedAlgs,
|
|
535
|
+
},
|
|
536
|
+
jwt_vc_json: {
|
|
537
|
+
alg: supportedAlgs,
|
|
538
|
+
},
|
|
539
|
+
jwt_vp_json: {
|
|
540
|
+
alg: supportedAlgs,
|
|
541
|
+
},
|
|
542
|
+
jwt_vp: {
|
|
543
|
+
alg: supportedAlgs,
|
|
544
|
+
},
|
|
545
|
+
ldp_vc: {
|
|
546
|
+
proof_type: supportedProofTypes,
|
|
547
|
+
},
|
|
548
|
+
ldp_vp: {
|
|
549
|
+
proof_type: supportedProofTypes,
|
|
550
|
+
},
|
|
551
|
+
'vc+sd-jwt': {
|
|
552
|
+
'kb-jwt_alg_values': supportedAlgs,
|
|
553
|
+
'sd-jwt_alg_values': supportedAlgs,
|
|
554
|
+
},
|
|
555
|
+
'dc+sd-jwt': {
|
|
556
|
+
'kb-jwt_alg_values': supportedAlgs,
|
|
557
|
+
'sd-jwt_alg_values': supportedAlgs,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
decodePresentation(agentContext, options) {
|
|
563
|
+
const { presentation, format } = options;
|
|
564
|
+
if (format === core_1.ClaimFormat.SdJwtVc) {
|
|
565
|
+
if (typeof presentation !== 'string') {
|
|
566
|
+
throw new core_2.CredoError(`Expected vp_token entry for format ${format} to be of type string`);
|
|
567
|
+
}
|
|
568
|
+
const sdJwtVcApi = agentContext.dependencyManager.resolve(core_2.SdJwtVcApi);
|
|
569
|
+
const sdJwtVc = sdJwtVcApi.fromCompact(presentation);
|
|
570
|
+
return sdJwtVc;
|
|
571
|
+
}
|
|
572
|
+
if (format === core_1.ClaimFormat.MsoMdoc) {
|
|
573
|
+
if (typeof presentation !== 'string') {
|
|
574
|
+
throw new core_2.CredoError(`Expected vp_token entry for format ${format} to be of type string`);
|
|
575
|
+
}
|
|
576
|
+
const mdocDeviceResponse = core_2.MdocDeviceResponse.fromBase64Url(presentation);
|
|
577
|
+
return mdocDeviceResponse;
|
|
578
|
+
}
|
|
579
|
+
if (core_1.ClaimFormat.JwtVc) {
|
|
580
|
+
if (typeof presentation !== 'string') {
|
|
581
|
+
throw new core_2.CredoError(`Expected vp_token entry for format ${format} to be of type string`);
|
|
582
|
+
}
|
|
583
|
+
return core_2.W3cJwtVerifiablePresentation.fromSerializedJwt(presentation);
|
|
584
|
+
}
|
|
585
|
+
return core_2.JsonTransformer.fromJSON(presentation, core_2.W3cJsonLdVerifiablePresentation);
|
|
586
|
+
}
|
|
587
|
+
async verifyPresentation(agentContext, options) {
|
|
588
|
+
const x509Config = agentContext.dependencyManager.resolve(core_2.X509ModuleConfig);
|
|
589
|
+
const sdJwtVcApi = agentContext.dependencyManager.resolve(core_2.SdJwtVcApi);
|
|
590
|
+
const { presentation, format } = options;
|
|
591
|
+
try {
|
|
592
|
+
this.logger.trace('Presentation response', core_2.JsonTransformer.toJSON(presentation));
|
|
593
|
+
let isValid;
|
|
594
|
+
let reason = undefined;
|
|
595
|
+
let verifiablePresentation;
|
|
596
|
+
if (format === core_1.ClaimFormat.SdJwtVc) {
|
|
597
|
+
if (typeof presentation !== 'string') {
|
|
598
|
+
throw new core_2.CredoError(`Expected vp_token entry for format ${format} to be of type string`);
|
|
599
|
+
}
|
|
600
|
+
const sdJwtVc = sdJwtVcApi.fromCompact(presentation);
|
|
601
|
+
const jwt = core_2.Jwt.fromSerializedJwt(presentation.split('~')[0]);
|
|
602
|
+
const certificateChain = (0, core_2.extractX509CertificatesFromJwt)(jwt);
|
|
603
|
+
let trustedCertificates = undefined;
|
|
604
|
+
if (certificateChain && x509Config.getTrustedCertificatesForVerification) {
|
|
605
|
+
trustedCertificates = await x509Config.getTrustedCertificatesForVerification(agentContext, {
|
|
606
|
+
certificateChain,
|
|
607
|
+
verification: {
|
|
608
|
+
type: 'credential',
|
|
609
|
+
credential: sdJwtVc,
|
|
610
|
+
openId4VcVerificationSessionId: options.verificationSessionId,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (!trustedCertificates) {
|
|
615
|
+
// We also take from the config here to avoid the callback being called again
|
|
616
|
+
trustedCertificates = x509Config.trustedCertificates ?? [];
|
|
617
|
+
}
|
|
618
|
+
const verificationResult = await sdJwtVcApi.verify({
|
|
619
|
+
compactSdJwtVc: presentation,
|
|
620
|
+
keyBinding: {
|
|
621
|
+
audience: options.audience,
|
|
622
|
+
nonce: options.nonce,
|
|
623
|
+
},
|
|
624
|
+
trustedCertificates,
|
|
625
|
+
});
|
|
626
|
+
isValid = verificationResult.verification.isValid;
|
|
627
|
+
reason = verificationResult.isValid ? undefined : verificationResult.error.message;
|
|
628
|
+
verifiablePresentation = sdJwtVc;
|
|
629
|
+
}
|
|
630
|
+
else if (format === core_1.ClaimFormat.MsoMdoc) {
|
|
631
|
+
if (typeof presentation !== 'string') {
|
|
632
|
+
throw new core_2.CredoError('Expected vp_token entry for format mso_mdoc to be of type string');
|
|
633
|
+
}
|
|
634
|
+
const mdocDeviceResponse = core_2.MdocDeviceResponse.fromBase64Url(presentation);
|
|
635
|
+
if (mdocDeviceResponse.documents.length !== 1) {
|
|
636
|
+
throw new core_2.CredoError('Only a single mdoc is supported per device response for OpenID4VP verification');
|
|
637
|
+
}
|
|
638
|
+
const document = mdocDeviceResponse.documents[0];
|
|
639
|
+
const certificateChain = document.issuerSignedCertificateChain.map((cert) => core_2.X509Certificate.fromRawCertificate(cert));
|
|
640
|
+
const trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, {
|
|
641
|
+
certificateChain,
|
|
642
|
+
verification: {
|
|
643
|
+
type: 'credential',
|
|
644
|
+
credential: document,
|
|
645
|
+
openId4VcVerificationSessionId: options.verificationSessionId,
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
let sessionTranscriptOptions;
|
|
649
|
+
if (options.origin) {
|
|
650
|
+
sessionTranscriptOptions = {
|
|
651
|
+
type: 'openId4VpDcApi',
|
|
652
|
+
clientId: options.audience,
|
|
653
|
+
verifierGeneratedNonce: options.nonce,
|
|
654
|
+
origin: options.origin,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
if (!options.mdocGeneratedNonce || !options.responseUri) {
|
|
659
|
+
throw new core_2.CredoError('mdocGeneratedNonce and responseUri are required for mdoc openid4vp session transcript calculation');
|
|
660
|
+
}
|
|
661
|
+
sessionTranscriptOptions = {
|
|
662
|
+
type: 'openId4Vp',
|
|
663
|
+
clientId: options.audience,
|
|
664
|
+
mdocGeneratedNonce: options.mdocGeneratedNonce,
|
|
665
|
+
responseUri: options.responseUri,
|
|
666
|
+
verifierGeneratedNonce: options.nonce,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
await mdocDeviceResponse.verify(agentContext, {
|
|
670
|
+
sessionTranscriptOptions,
|
|
671
|
+
trustedCertificates,
|
|
672
|
+
});
|
|
673
|
+
// TODO: extract transaction data hashes once https://github.com/openid/OpenID4VP/pull/330 is resolved
|
|
674
|
+
isValid = true;
|
|
675
|
+
verifiablePresentation = mdocDeviceResponse;
|
|
676
|
+
}
|
|
677
|
+
else if (format === core_1.ClaimFormat.JwtVc) {
|
|
678
|
+
if (typeof presentation !== 'string') {
|
|
679
|
+
throw new core_2.CredoError(`Expected vp_token entry for format ${format} to be of type string`);
|
|
680
|
+
}
|
|
681
|
+
verifiablePresentation = core_2.W3cJwtVerifiablePresentation.fromSerializedJwt(presentation);
|
|
682
|
+
const certificateChain = (0, core_2.extractX509CertificatesFromJwt)(verifiablePresentation.jwt);
|
|
683
|
+
let trustedCertificates = undefined;
|
|
684
|
+
if (certificateChain && x509Config.getTrustedCertificatesForVerification) {
|
|
685
|
+
trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, {
|
|
686
|
+
certificateChain,
|
|
687
|
+
verification: {
|
|
688
|
+
type: 'credential',
|
|
689
|
+
credential: verifiablePresentation,
|
|
690
|
+
openId4VcVerificationSessionId: options.verificationSessionId,
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
if (!trustedCertificates) {
|
|
695
|
+
trustedCertificates = x509Config.trustedCertificates ?? [];
|
|
696
|
+
}
|
|
697
|
+
const verificationResult = await this.w3cCredentialService.verifyPresentation(agentContext, {
|
|
698
|
+
presentation,
|
|
699
|
+
challenge: options.nonce,
|
|
700
|
+
domain: options.audience,
|
|
701
|
+
trustedCertificates,
|
|
702
|
+
});
|
|
703
|
+
isValid = verificationResult.isValid;
|
|
704
|
+
reason = verificationResult.error?.message;
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
verifiablePresentation = core_2.JsonTransformer.fromJSON(presentation, core_2.W3cJsonLdVerifiablePresentation);
|
|
708
|
+
const verificationResult = await this.w3cCredentialService.verifyPresentation(agentContext, {
|
|
709
|
+
presentation: verifiablePresentation,
|
|
710
|
+
challenge: options.nonce,
|
|
711
|
+
domain: options.audience,
|
|
712
|
+
});
|
|
713
|
+
isValid = verificationResult.isValid;
|
|
714
|
+
reason = verificationResult.error?.message;
|
|
715
|
+
}
|
|
716
|
+
if (!isValid) {
|
|
717
|
+
throw new Error(reason);
|
|
718
|
+
}
|
|
719
|
+
return {
|
|
720
|
+
verified: true,
|
|
721
|
+
presentation: verifiablePresentation,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
agentContext.config.logger.warn('Error occurred during verification of presentation', {
|
|
726
|
+
error,
|
|
727
|
+
});
|
|
728
|
+
return {
|
|
729
|
+
verified: false,
|
|
730
|
+
reason: error.message,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Update the record to a new state and emit an state changed event. Also updates the record
|
|
736
|
+
* in storage.
|
|
737
|
+
*/
|
|
738
|
+
async updateState(agentContext, verificationSession, newState) {
|
|
739
|
+
agentContext.config.logger.debug(`Updating openid4vc verification session record ${verificationSession.id} to state ${newState} (previous=${verificationSession.state})`);
|
|
740
|
+
const previousState = verificationSession.state;
|
|
741
|
+
verificationSession.state = newState;
|
|
742
|
+
await this.openId4VcVerificationSessionRepository.update(agentContext, verificationSession);
|
|
743
|
+
this.emitStateChangedEvent(agentContext, verificationSession, previousState);
|
|
744
|
+
}
|
|
745
|
+
emitStateChangedEvent(agentContext, verificationSession, previousState) {
|
|
746
|
+
const eventEmitter = agentContext.dependencyManager.resolve(core_2.EventEmitter);
|
|
747
|
+
eventEmitter.emit(agentContext, {
|
|
748
|
+
type: OpenId4VcVerifierEvents_1.OpenId4VcVerifierEvents.VerificationSessionStateChanged,
|
|
749
|
+
payload: {
|
|
750
|
+
verificationSession: verificationSession.clone(),
|
|
751
|
+
previousState,
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
exports.OpenId4VpVerifierService = OpenId4VpVerifierService;
|
|
757
|
+
exports.OpenId4VpVerifierService = OpenId4VpVerifierService = __decorate([
|
|
758
|
+
(0, core_2.injectable)(),
|
|
759
|
+
__param(0, (0, core_2.inject)(core_2.InjectionSymbols.Logger)),
|
|
760
|
+
__metadata("design:paramtypes", [Object, core_2.W3cCredentialService,
|
|
761
|
+
repository_1.OpenId4VcVerifierRepository,
|
|
762
|
+
OpenId4VcVerifierModuleConfig_1.OpenId4VcVerifierModuleConfig,
|
|
763
|
+
repository_1.OpenId4VcVerificationSessionRepository])
|
|
764
|
+
], OpenId4VpVerifierService);
|
|
765
|
+
//# sourceMappingURL=OpenId4VpVerifierService.js.map
|