@credo-ts/openid4vc 0.6.0-pr-2195-20250322195244 → 0.6.0-pr-2324-20250625125220
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 +10 -6
- package/build/openid4vc-holder/OpenId4VcHolderApi.js +2 -4
- package/build/openid4vc-holder/OpenId4VcHolderApi.js.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderService.d.ts +14 -19
- package/build/openid4vc-holder/OpenId4VciHolderService.js +425 -203
- package/build/openid4vc-holder/OpenId4VciHolderService.js.map +1 -1
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.d.ts +117 -37
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.js +1 -0
- package/build/openid4vc-holder/OpenId4VciHolderServiceOptions.js.map +1 -1
- package/build/openid4vc-holder/OpenId4vpHolderService.js +24 -15
- package/build/openid4vc-holder/OpenId4vpHolderService.js.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerModuleConfig.d.ts +21 -0
- package/build/openid4vc-issuer/OpenId4VcIssuerModuleConfig.js +11 -0
- package/build/openid4vc-issuer/OpenId4VcIssuerModuleConfig.js.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerService.d.ts +7 -3
- package/build/openid4vc-issuer/OpenId4VcIssuerService.js +387 -167
- package/build/openid4vc-issuer/OpenId4VcIssuerService.js.map +1 -1
- package/build/openid4vc-issuer/OpenId4VcIssuerServiceOptions.d.ts +67 -27
- package/build/openid4vc-issuer/index.d.ts +1 -1
- package/build/openid4vc-issuer/index.js +2 -1
- package/build/openid4vc-issuer/index.js.map +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.d.ts +29 -5
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.js +2 -0
- package/build/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.js.map +1 -1
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.d.ts +12 -7
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.js +15 -3
- package/build/openid4vc-issuer/repository/OpenId4VcIssuerRecord.js.map +1 -1
- package/build/openid4vc-issuer/router/accessTokenEndpoint.js +41 -13
- package/build/openid4vc-issuer/router/accessTokenEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/authorizationChallengeEndpoint.js +102 -33
- package/build/openid4vc-issuer/router/authorizationChallengeEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/credentialEndpoint.js +42 -10
- package/build/openid4vc-issuer/router/credentialEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/router/jwksEndpoint.js +2 -2
- package/build/openid4vc-issuer/router/jwksEndpoint.js.map +1 -1
- package/build/openid4vc-issuer/util/txCode.d.ts +1 -1
- package/build/openid4vc-issuer/util/txCode.js +3 -1
- package/build/openid4vc-issuer/util/txCode.js.map +1 -1
- package/build/openid4vc-verifier/OpenId4VpVerifierService.d.ts +1 -1
- package/build/openid4vc-verifier/OpenId4VpVerifierService.js +70 -65
- package/build/openid4vc-verifier/OpenId4VpVerifierService.js.map +1 -1
- package/build/openid4vc-verifier/OpenId4VpVerifierServiceOptions.d.ts +7 -1
- package/build/shared/callbacks.d.ts +6 -4
- package/build/shared/callbacks.js +212 -69
- package/build/shared/callbacks.js.map +1 -1
- package/build/shared/models/CredentialHolderBinding.d.ts +65 -11
- package/build/shared/models/OpenId4VcJwtIssuer.d.ts +10 -5
- package/build/shared/models/OpenId4VciCredentialFormatProfile.d.ts +1 -0
- package/build/shared/models/OpenId4VciCredentialFormatProfile.js +1 -0
- package/build/shared/models/OpenId4VciCredentialFormatProfile.js.map +1 -1
- package/build/shared/router/tenants.js +2 -2
- package/build/shared/router/tenants.js.map +1 -1
- package/build/shared/utils.d.ts +4 -9
- package/build/shared/utils.js +27 -44
- package/build/shared/utils.js.map +1 -1
- package/package.json +14 -14
|
@@ -14,8 +14,10 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.OpenId4VciHolderService = void 0;
|
|
16
16
|
const core_1 = require("@credo-ts/core");
|
|
17
|
+
const core_2 = require("@credo-ts/core");
|
|
17
18
|
const oauth2_1 = require("@openid4vc/oauth2");
|
|
18
19
|
const openid4vci_1 = require("@openid4vc/openid4vci");
|
|
20
|
+
const openid4vci_2 = require("@openid4vc/openid4vci");
|
|
19
21
|
const shared_1 = require("../shared");
|
|
20
22
|
const callbacks_1 = require("../shared/callbacks");
|
|
21
23
|
const issuerMetadataUtils_1 = require("../shared/issuerMetadataUtils");
|
|
@@ -49,28 +51,63 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
49
51
|
async resolveAuthorizationRequest(agentContext, resolvedCredentialOffer, authCodeFlowOptions) {
|
|
50
52
|
const { clientId, redirectUri } = authCodeFlowOptions;
|
|
51
53
|
const { metadata, credentialOfferPayload, offeredCredentialConfigurations } = resolvedCredentialOffer;
|
|
52
|
-
const
|
|
54
|
+
const oauth2Client = this.getOauth2Client(agentContext);
|
|
55
|
+
const client = this.getClient(agentContext, {
|
|
56
|
+
clientId: authCodeFlowOptions.clientId,
|
|
57
|
+
clientAttestation: authCodeFlowOptions.walletAttestationJwt,
|
|
58
|
+
});
|
|
53
59
|
// If scope is not provided, we request scope for all offered credentials
|
|
54
60
|
const scope = authCodeFlowOptions.scope ?? (0, issuerMetadataUtils_1.getScopesFromCredentialConfigurationsSupported)(offeredCredentialConfigurations);
|
|
61
|
+
if (!credentialOfferPayload.grants?.[oauth2_1.authorizationCodeGrantIdentifier]) {
|
|
62
|
+
throw new core_2.CredoError(`Provided credential offer does not include the 'authorization_code' grant.`);
|
|
63
|
+
}
|
|
64
|
+
const authorizationCodeGrant = credentialOfferPayload.grants[oauth2_1.authorizationCodeGrantIdentifier];
|
|
65
|
+
const authorizationServer = (0, openid4vci_1.determineAuthorizationServerForCredentialOffer)({
|
|
66
|
+
issuerMetadata: metadata,
|
|
67
|
+
grantAuthorizationServer: authorizationCodeGrant.authorization_server,
|
|
68
|
+
});
|
|
69
|
+
const authorizationServerMetadata = (0, oauth2_1.getAuthorizationServerMetadataFromList)(metadata.authorizationServers, authorizationServer);
|
|
70
|
+
// TODO: should we allow key reuse between dpop and wallet attestation?
|
|
71
|
+
const isDpopSupported = oauth2Client.isDpopSupported({ authorizationServerMetadata });
|
|
72
|
+
const dpop = isDpopSupported.supported
|
|
73
|
+
? await this.getDpopOptions(agentContext, {
|
|
74
|
+
dpopSigningAlgValuesSupported: isDpopSupported.dpopSigningAlgValuesSupported,
|
|
75
|
+
})
|
|
76
|
+
: undefined;
|
|
55
77
|
const authorizationResult = await client.initiateAuthorization({
|
|
56
78
|
clientId,
|
|
57
79
|
issuerMetadata: metadata,
|
|
58
80
|
credentialOffer: credentialOfferPayload,
|
|
59
81
|
scope: scope.join(' '),
|
|
60
82
|
redirectUri,
|
|
83
|
+
dpop,
|
|
61
84
|
});
|
|
62
|
-
if (authorizationResult.authorizationFlow ===
|
|
85
|
+
if (authorizationResult.authorizationFlow === openid4vci_2.AuthorizationFlow.PresentationDuringIssuance) {
|
|
63
86
|
return {
|
|
64
|
-
authorizationFlow:
|
|
87
|
+
authorizationFlow: openid4vci_2.AuthorizationFlow.PresentationDuringIssuance,
|
|
65
88
|
openid4vpRequestUrl: authorizationResult.openid4vpRequestUrl,
|
|
66
89
|
authSession: authorizationResult.authSession,
|
|
90
|
+
// FIXME: return dpop result from this endpoint (dpop nonce)
|
|
91
|
+
dpop: dpop
|
|
92
|
+
? {
|
|
93
|
+
alg: dpop.signer.alg,
|
|
94
|
+
jwk: core_2.Kms.PublicJwk.fromUnknown(dpop.signer.publicJwk),
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
67
97
|
};
|
|
68
98
|
}
|
|
69
99
|
// Normal Oauth2Redirect flow
|
|
70
100
|
return {
|
|
71
|
-
authorizationFlow:
|
|
101
|
+
authorizationFlow: openid4vci_2.AuthorizationFlow.Oauth2Redirect,
|
|
72
102
|
codeVerifier: authorizationResult.pkce?.codeVerifier,
|
|
73
103
|
authorizationRequestUrl: authorizationResult.authorizationRequestUrl,
|
|
104
|
+
// FIXME: return dpop result from this endpoint (dpop nonce)
|
|
105
|
+
dpop: dpop
|
|
106
|
+
? {
|
|
107
|
+
alg: dpop.signer.alg,
|
|
108
|
+
jwk: core_2.Kms.PublicJwk.fromUnknown(dpop.signer.publicJwk),
|
|
109
|
+
}
|
|
110
|
+
: undefined,
|
|
74
111
|
};
|
|
75
112
|
}
|
|
76
113
|
async sendNotification(agentContext, options) {
|
|
@@ -91,10 +128,11 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
91
128
|
});
|
|
92
129
|
}
|
|
93
130
|
async getDpopOptions(agentContext, { jwk, dpopSigningAlgValuesSupported, nonce, }) {
|
|
131
|
+
const kms = agentContext.resolve(core_2.Kms.KeyManagementApi);
|
|
94
132
|
if (jwk) {
|
|
95
133
|
const alg = dpopSigningAlgValuesSupported.find((alg) => jwk.supportedSignatureAlgorithms.includes(alg));
|
|
96
134
|
if (!alg) {
|
|
97
|
-
throw new
|
|
135
|
+
throw new core_2.CredoError(`No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join(', ')}' matching jwk ${jwk.jwkTypehumanDescription}`);
|
|
98
136
|
}
|
|
99
137
|
return {
|
|
100
138
|
signer: {
|
|
@@ -105,59 +143,83 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
105
143
|
nonce,
|
|
106
144
|
};
|
|
107
145
|
}
|
|
108
|
-
const alg = dpopSigningAlgValuesSupported.find((alg) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
const alg = dpopSigningAlgValuesSupported.find((alg) => {
|
|
147
|
+
try {
|
|
148
|
+
core_2.Kms.PublicJwk.supportedPublicJwkClassForSignatureAlgorithm(alg);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (!alg) {
|
|
156
|
+
throw new core_2.CredoError(`No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join(', ')}'`);
|
|
112
157
|
}
|
|
113
|
-
const key = await
|
|
158
|
+
const key = await kms.createKeyForSignatureAlgorithm({ algorithm: alg });
|
|
114
159
|
return {
|
|
115
160
|
signer: {
|
|
116
161
|
method: 'jwk',
|
|
117
162
|
alg,
|
|
118
|
-
publicJwk:
|
|
163
|
+
publicJwk: key.publicJwk,
|
|
119
164
|
},
|
|
120
165
|
nonce,
|
|
121
166
|
};
|
|
122
167
|
}
|
|
123
168
|
async retrieveAuthorizationCodeUsingPresentation(agentContext, options) {
|
|
124
|
-
const client = this.getClient(agentContext
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const { authorizationChallengeResponse } = await client.retrieveAuthorizationCodeUsingPresentation({
|
|
169
|
+
const client = this.getClient(agentContext, {
|
|
170
|
+
clientAttestation: options.walletAttestationJwt,
|
|
171
|
+
});
|
|
172
|
+
const dpop = options.dpop
|
|
173
|
+
? await this.getDpopOptions(agentContext, {
|
|
174
|
+
...options.dpop,
|
|
175
|
+
dpopSigningAlgValuesSupported: [options.dpop.alg],
|
|
176
|
+
})
|
|
177
|
+
: undefined;
|
|
178
|
+
const { authorizationChallengeResponse, dpop: dpopResult } = await client.retrieveAuthorizationCodeUsingPresentation({
|
|
134
179
|
authSession: options.authSession,
|
|
135
180
|
presentationDuringIssuanceSession: options.presentationDuringIssuanceSession,
|
|
136
181
|
credentialOffer: options.resolvedCredentialOffer.credentialOfferPayload,
|
|
137
182
|
issuerMetadata: options.resolvedCredentialOffer.metadata,
|
|
138
|
-
|
|
183
|
+
dpop,
|
|
139
184
|
});
|
|
140
185
|
return {
|
|
141
186
|
authorizationCode: authorizationChallengeResponse.authorization_code,
|
|
187
|
+
dpop: dpop
|
|
188
|
+
? {
|
|
189
|
+
...dpopResult,
|
|
190
|
+
alg: dpop.signer.alg,
|
|
191
|
+
jwk: core_2.Kms.PublicJwk.fromUnknown(dpop.signer.publicJwk),
|
|
192
|
+
}
|
|
193
|
+
: undefined,
|
|
142
194
|
};
|
|
143
195
|
}
|
|
144
196
|
async requestAccessToken(agentContext, options) {
|
|
145
197
|
const { metadata, credentialOfferPayload } = options.resolvedCredentialOffer;
|
|
146
|
-
const client = this.getClient(agentContext
|
|
198
|
+
const client = this.getClient(agentContext, {
|
|
199
|
+
clientAttestation: options.walletAttestationJwt,
|
|
200
|
+
clientId: 'clientId' in options ? options.clientId : undefined,
|
|
201
|
+
});
|
|
147
202
|
const oauth2Client = this.getOauth2Client(agentContext);
|
|
148
203
|
const authorizationServer = options.code
|
|
149
204
|
? credentialOfferPayload.grants?.authorization_code?.authorization_server
|
|
150
205
|
: credentialOfferPayload.grants?.[oauth2_1.preAuthorizedCodeGrantIdentifier]?.authorization_server;
|
|
151
206
|
const authorizationServerMetadata = (0, oauth2_1.getAuthorizationServerMetadataFromList)(metadata.authorizationServers, authorizationServer ?? metadata.authorizationServers[0].issuer);
|
|
152
|
-
// TODO: should allow dpop input parameter for if it was already bound earlier
|
|
153
207
|
const isDpopSupported = oauth2Client.isDpopSupported({
|
|
154
208
|
authorizationServerMetadata,
|
|
155
209
|
});
|
|
156
|
-
const dpop =
|
|
210
|
+
const dpop = options.dpop
|
|
157
211
|
? await this.getDpopOptions(agentContext, {
|
|
158
|
-
|
|
212
|
+
...options.dpop,
|
|
213
|
+
dpopSigningAlgValuesSupported: [options.dpop.alg],
|
|
159
214
|
})
|
|
160
|
-
:
|
|
215
|
+
: // We should be careful about this case. It could just be the user didn't correctly
|
|
216
|
+
// provide the DPoP from the auth response. In whic case different DPoP will be used
|
|
217
|
+
// However it might be that they only use DPoP for the token request (esp in pre-auth case)
|
|
218
|
+
isDpopSupported.supported
|
|
219
|
+
? await this.getDpopOptions(agentContext, {
|
|
220
|
+
dpopSigningAlgValuesSupported: isDpopSupported.dpopSigningAlgValuesSupported,
|
|
221
|
+
})
|
|
222
|
+
: undefined;
|
|
161
223
|
const result = options.code
|
|
162
224
|
? await client.retrieveAuthorizationCodeAccessTokenFromOffer({
|
|
163
225
|
issuerMetadata: metadata,
|
|
@@ -166,11 +228,6 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
166
228
|
dpop,
|
|
167
229
|
pkceCodeVerifier: options.codeVerifier,
|
|
168
230
|
redirectUri: options.redirectUri,
|
|
169
|
-
additionalRequestPayload: {
|
|
170
|
-
// TODO: handle it as part of client auth once we support
|
|
171
|
-
// assertion based client authentication
|
|
172
|
-
client_id: options.clientId,
|
|
173
|
-
},
|
|
174
231
|
})
|
|
175
232
|
: await client.retrievePreAuthorizedCodeAccessTokenFromOffer({
|
|
176
233
|
credentialOffer: credentialOfferPayload,
|
|
@@ -184,7 +241,7 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
184
241
|
? {
|
|
185
242
|
...result.dpop,
|
|
186
243
|
alg: dpop.signer.alg,
|
|
187
|
-
jwk:
|
|
244
|
+
jwk: core_2.Kms.PublicJwk.fromUnknown(dpop.signer.publicJwk),
|
|
188
245
|
}
|
|
189
246
|
: undefined,
|
|
190
247
|
};
|
|
@@ -192,22 +249,10 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
192
249
|
async acceptCredentialOffer(agentContext, options) {
|
|
193
250
|
const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options;
|
|
194
251
|
const { metadata, offeredCredentialConfigurations } = resolvedCredentialOffer;
|
|
195
|
-
const { credentialConfigurationIds, credentialBindingResolver, verifyCredentialStatus,
|
|
252
|
+
const { credentialConfigurationIds, credentialBindingResolver, verifyCredentialStatus, allowedProofOfPossessionSignatureAlgorithms, } = acceptCredentialOfferOptions;
|
|
196
253
|
const client = this.getClient(agentContext);
|
|
197
254
|
if (credentialConfigurationIds?.length === 0) {
|
|
198
|
-
throw new
|
|
199
|
-
}
|
|
200
|
-
const supportedJwaSignatureAlgorithms = (0, utils_1.getSupportedJwaSignatureAlgorithms)(agentContext);
|
|
201
|
-
const allowedProofOfPossessionSigAlgs = acceptCredentialOfferOptions.allowedProofOfPossessionSignatureAlgorithms;
|
|
202
|
-
const possibleProofOfPossessionSigAlgs = allowedProofOfPossessionSigAlgs
|
|
203
|
-
? allowedProofOfPossessionSigAlgs.filter((algorithm) => supportedJwaSignatureAlgorithms.includes(algorithm))
|
|
204
|
-
: supportedJwaSignatureAlgorithms;
|
|
205
|
-
if (possibleProofOfPossessionSigAlgs.length === 0) {
|
|
206
|
-
throw new core_1.CredoError([
|
|
207
|
-
'No possible proof of possession signature algorithm found.',
|
|
208
|
-
`Signature algorithms supported by the Agent '${supportedJwaSignatureAlgorithms.join(', ')}'`,
|
|
209
|
-
`Allowed Signature algorithms '${allowedProofOfPossessionSigAlgs?.join(', ')}'`,
|
|
210
|
-
].join('\n'));
|
|
255
|
+
throw new core_2.CredoError(`'credentialConfigurationIds' may not be empty`);
|
|
211
256
|
}
|
|
212
257
|
const receivedCredentials = [];
|
|
213
258
|
let cNonce = options.cNonce;
|
|
@@ -215,7 +260,7 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
215
260
|
const credentialConfigurationsToRequest = credentialConfigurationIds?.map((id) => {
|
|
216
261
|
if (!offeredCredentialConfigurations[id]) {
|
|
217
262
|
const offeredCredentialIds = Object.keys(offeredCredentialConfigurations).join(', ');
|
|
218
|
-
throw new
|
|
263
|
+
throw new core_2.CredoError(`Credential to request '${id}' is not present in offered credentials. Offered credentials are ${offeredCredentialIds}`);
|
|
219
264
|
}
|
|
220
265
|
return [id, offeredCredentialConfigurations[id]];
|
|
221
266
|
}) ?? Object.entries(offeredCredentialConfigurations);
|
|
@@ -242,46 +287,41 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
242
287
|
: undefined,
|
|
243
288
|
})
|
|
244
289
|
.catch((e) => {
|
|
245
|
-
if (e instanceof
|
|
290
|
+
if (e instanceof openid4vci_2.Openid4vciRetrieveCredentialsError && e.response.credentialErrorResponseResult?.success) {
|
|
246
291
|
cNonce = e.response.credentialErrorResponseResult.data.c_nonce;
|
|
247
292
|
}
|
|
248
293
|
});
|
|
249
294
|
}
|
|
250
295
|
}
|
|
251
296
|
if (!cNonce) {
|
|
252
|
-
throw new
|
|
253
|
-
}
|
|
254
|
-
// If true: use max from issuer or otherwise 1
|
|
255
|
-
// If number not 0: use the number
|
|
256
|
-
// Else: use 1
|
|
257
|
-
const batchSize = requestBatch === true ? (metadata.credentialIssuer.batch_credential_issuance?.batch_size ?? 1) : requestBatch || 1;
|
|
258
|
-
if (typeof requestBatch === 'number' && requestBatch > 1 && !metadata.credentialIssuer.batch_credential_issuance) {
|
|
259
|
-
throw new core_1.CredoError(`Credential issuer '${metadata.credentialIssuer.credential_issuer}' does not support batch credential issuance using the 'proofs' request property. Onlt 'proof' supported.`);
|
|
297
|
+
throw new core_2.CredoError('No cNonce provided and unable to acquire cNonce from the credential issuer');
|
|
260
298
|
}
|
|
261
299
|
for (const [offeredCredentialId, offeredCredentialConfiguration] of credentialConfigurationsToRequest) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
300
|
+
const proofs = await this.getCredentialRequestOptions(agentContext, {
|
|
301
|
+
allowedProofOfPossesionAlgorithms: allowedProofOfPossessionSignatureAlgorithms ?? (0, utils_1.getSupportedJwaSignatureAlgorithms)(agentContext),
|
|
302
|
+
metadata,
|
|
303
|
+
offeredCredential: {
|
|
304
|
+
id: offeredCredentialId,
|
|
305
|
+
configuration: offeredCredentialConfiguration,
|
|
306
|
+
},
|
|
307
|
+
clientId: options.clientId,
|
|
308
|
+
// We already checked whether nonce exists above
|
|
309
|
+
cNonce: cNonce,
|
|
310
|
+
credentialBindingResolver,
|
|
311
|
+
});
|
|
312
|
+
this.logger.debug('Generated credential request proof of possesion', { proofs });
|
|
313
|
+
const proof =
|
|
314
|
+
// Draft 11 ALWAYS uses proof
|
|
315
|
+
(metadata.originalDraftVersion === openid4vci_2.Openid4vciDraftVersion.Draft11 ||
|
|
316
|
+
// Draft 14 allows both proof and proofs. Try to use proof when it makes to improve interoperability
|
|
317
|
+
(metadata.originalDraftVersion === openid4vci_2.Openid4vciDraftVersion.Draft14 &&
|
|
318
|
+
metadata.credentialIssuer.batch_credential_issuance === undefined)) &&
|
|
319
|
+
proofs.jwt?.length === 1
|
|
320
|
+
? {
|
|
321
|
+
proof_type: 'jwt',
|
|
322
|
+
jwt: proofs.jwt[0],
|
|
323
|
+
}
|
|
324
|
+
: undefined;
|
|
285
325
|
const { credentialResponse, dpop } = await client.retrieveCredentials({
|
|
286
326
|
issuerMetadata: metadata,
|
|
287
327
|
accessToken: options.accessToken,
|
|
@@ -293,13 +333,9 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
293
333
|
dpopSigningAlgValuesSupported: [options.dpop.alg],
|
|
294
334
|
})
|
|
295
335
|
: undefined,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
proof_type: 'jwt',
|
|
300
|
-
jwt: jwts[0],
|
|
301
|
-
}
|
|
302
|
-
: undefined,
|
|
336
|
+
// Only include proofs if we don't add proof
|
|
337
|
+
proofs: !proof ? proofs : undefined,
|
|
338
|
+
proof,
|
|
303
339
|
});
|
|
304
340
|
// Set new nonce values
|
|
305
341
|
cNonce = credentialResponse.c_nonce;
|
|
@@ -310,9 +346,10 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
310
346
|
credentialIssuerMetadata: metadata.credentialIssuer,
|
|
311
347
|
format: offeredCredentialConfiguration.format,
|
|
312
348
|
credentialConfigurationId: offeredCredentialId,
|
|
349
|
+
credentialConfiguration: offeredCredentialConfiguration,
|
|
313
350
|
});
|
|
314
|
-
this.logger.debug('received credential', credential.credentials.map((c) => c instanceof
|
|
315
|
-
receivedCredentials.push(
|
|
351
|
+
this.logger.debug('received credential', credential.credentials.map((c) => c instanceof core_2.Mdoc ? { issuerSignedNamespaces: c.issuerSignedNamespaces, base64Url: c.base64Url } : c));
|
|
352
|
+
receivedCredentials.push(credential);
|
|
316
353
|
}
|
|
317
354
|
return {
|
|
318
355
|
credentials: receivedCredentials,
|
|
@@ -332,82 +369,178 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
332
369
|
* of possession.
|
|
333
370
|
*/
|
|
334
371
|
async getCredentialRequestOptions(agentContext, options) {
|
|
335
|
-
const
|
|
372
|
+
const dids = agentContext.resolve(core_1.DidsApi);
|
|
373
|
+
const { allowedProofOfPossesionAlgorithms, offeredCredential } = options;
|
|
374
|
+
const { configuration, id: configurationId } = offeredCredential;
|
|
375
|
+
const supportedJwaSignatureAlgorithms = (0, utils_1.getSupportedJwaSignatureAlgorithms)(agentContext);
|
|
376
|
+
const possibleProofOfPossessionSignatureAlgorithms = allowedProofOfPossesionAlgorithms
|
|
377
|
+
? allowedProofOfPossesionAlgorithms.filter((algorithm) => supportedJwaSignatureAlgorithms.includes(algorithm))
|
|
378
|
+
: supportedJwaSignatureAlgorithms;
|
|
379
|
+
if (possibleProofOfPossessionSignatureAlgorithms.length === 0) {
|
|
380
|
+
throw new core_2.CredoError([
|
|
381
|
+
'No possible proof of possession signature algorithm found.',
|
|
382
|
+
`Signature algorithms supported by the Agent '${supportedJwaSignatureAlgorithms.join(', ')}'`,
|
|
383
|
+
`Allowed Signature algorithms '${allowedProofOfPossesionAlgorithms?.join(', ')}'`,
|
|
384
|
+
].join('\n'));
|
|
385
|
+
}
|
|
386
|
+
const { proofTypes, supportedDidMethods, supportsAllDidMethods, supportsJwk } = this.getProofOfPossessionRequirements(agentContext, {
|
|
336
387
|
credentialToRequest: options.offeredCredential,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const JwkClasses = signatureAlgorithms.map((signatureAlgorithm) => {
|
|
340
|
-
const JwkClass = (0, core_1.getJwkClassFromJwaSignatureAlgorithm)(signatureAlgorithm);
|
|
341
|
-
if (!JwkClass) {
|
|
342
|
-
throw new core_1.CredoError(`Could not determine JWK key type of the JWA signature algorithm '${signatureAlgorithm}'`);
|
|
343
|
-
}
|
|
344
|
-
return JwkClass;
|
|
388
|
+
metadata: options.metadata,
|
|
389
|
+
possibleProofOfPossessionSignatureAlgorithms,
|
|
345
390
|
});
|
|
346
|
-
const
|
|
347
|
-
const supportedVerificationMethods = keyTypes.flatMap((keyType) => (0, core_1.getSupportedVerificationMethodTypesFromKeyType)(keyType));
|
|
348
|
-
const format = options.offeredCredential.configuration.format;
|
|
391
|
+
const format = configuration.format;
|
|
349
392
|
const supportsAnyMethod = supportedDidMethods !== undefined || supportsAllDidMethods || supportsJwk;
|
|
393
|
+
const issuerMaxBatchSize = options.metadata.credentialIssuer.batch_credential_issuance?.batch_size ?? 1;
|
|
350
394
|
// Now we need to determine how the credential will be bound to us
|
|
351
395
|
const credentialBinding = await options.credentialBindingResolver({
|
|
352
396
|
agentContext,
|
|
353
397
|
credentialFormat: format,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
398
|
+
credentialConfigurationId: configurationId,
|
|
399
|
+
credentialConfiguration: configuration,
|
|
400
|
+
metadata: options.metadata,
|
|
401
|
+
issuerMaxBatchSize,
|
|
402
|
+
proofTypes,
|
|
358
403
|
supportsAllDidMethods,
|
|
359
404
|
supportedDidMethods,
|
|
360
405
|
supportsJwk,
|
|
361
406
|
});
|
|
362
|
-
|
|
407
|
+
const client = this.getClient(agentContext);
|
|
363
408
|
// Make sure the issuer of proof of possession is valid according to openid issuer metadata
|
|
364
409
|
if (credentialBinding.method === 'did') {
|
|
365
|
-
|
|
410
|
+
if (!proofTypes.jwt) {
|
|
411
|
+
throw new core_2.CredoError(`JWT proof type is not supported for configuration '${configurationId}', which is required for did based credential binding.`);
|
|
412
|
+
}
|
|
413
|
+
if (proofTypes.jwt.keyAttestationsRequired) {
|
|
414
|
+
throw new core_2.CredoError(`Credential binding returned list of DID urls, but credential configuration '${configurationId}' requires key attestations. Key attestations and DIDs are not compatible.`);
|
|
415
|
+
}
|
|
416
|
+
if (credentialBinding.didUrls.length > issuerMaxBatchSize) {
|
|
417
|
+
throw new core_2.CredoError(`Issuer supports issuing a batch of maximum ${issuerMaxBatchSize} credential(s). Binding resolver returned ${credentialBinding.didUrls.length} DID urls. Make sure the returned value does not exceed the max batch issuance.`);
|
|
418
|
+
}
|
|
419
|
+
if (credentialBinding.didUrls.length === 0) {
|
|
420
|
+
throw new core_2.CredoError('Credential binding with method did returned empty didUrls list');
|
|
421
|
+
}
|
|
422
|
+
const firstDid = (0, core_2.parseDid)(credentialBinding.didUrls[0]);
|
|
423
|
+
if (!credentialBinding.didUrls.every((didUrl) => (0, core_2.parseDid)(didUrl).method === firstDid.method)) {
|
|
424
|
+
throw new core_2.CredoError('Expected all did urls for binding method did to use the same did method');
|
|
425
|
+
}
|
|
366
426
|
if (!supportsAllDidMethods &&
|
|
367
427
|
// If supportedDidMethods is undefined, it means the issuer didn't include the binding methods in the metadata
|
|
368
428
|
// The user can still select a verification method, but we can't validate it
|
|
369
429
|
supportedDidMethods !== undefined &&
|
|
370
|
-
!supportedDidMethods.find((supportedDidMethod) =>
|
|
371
|
-
|
|
430
|
+
!supportedDidMethods.find((supportedDidMethod) => firstDid.did.startsWith(supportedDidMethod) && supportsAnyMethod)) {
|
|
431
|
+
// Test binding method
|
|
372
432
|
const supportedDidMethodsString = supportedDidMethods.join(', ');
|
|
373
|
-
throw new
|
|
433
|
+
throw new core_2.CredoError(`Resolved credential binding for proof of possession uses did method '${firstDid.method}', but issuer only supports '${supportedDidMethodsString}'`);
|
|
374
434
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
if (!
|
|
378
|
-
throw new
|
|
435
|
+
const { publicJwk: firstKey } = await dids.resolveVerificationMethodFromCreatedDidRecord(firstDid.didUrl);
|
|
436
|
+
const algorithm = proofTypes.jwt.supportedSignatureAlgorithms.find((algorithm) => firstKey.supportedSignatureAlgorithms.includes(algorithm));
|
|
437
|
+
if (!algorithm) {
|
|
438
|
+
throw new core_2.CredoError(`Credential binding returned did url that points to key '${firstKey.jwkTypehumanDescription}' that supports signature algorithms ${firstKey.supportedSignatureAlgorithms.join(', ')}, but one of '${proofTypes.jwt.supportedSignatureAlgorithms.join(', ')}' was expected`);
|
|
379
439
|
}
|
|
440
|
+
// This will/should leverage the caching, so it's ok to resolve the did here
|
|
441
|
+
const keys = await Promise.all(credentialBinding.didUrls.map(async (didUrl, index) => index === 0
|
|
442
|
+
? // We already fetched the first did
|
|
443
|
+
{ jwk: firstKey, didUrl: firstDid.didUrl }
|
|
444
|
+
: { jwk: (await dids.resolveVerificationMethodFromCreatedDidRecord(didUrl)).publicJwk, didUrl }));
|
|
445
|
+
if (!keys.every((key) => core_2.Kms.assymetricJwkKeyTypeMatches(key.jwk.toJson(), firstKey.toJson()))) {
|
|
446
|
+
throw new core_2.CredoError('Expected all did urls to point to the same key type');
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
jwt: await Promise.all(keys.map((key) => client
|
|
450
|
+
.createCredentialRequestJwtProof({
|
|
451
|
+
credentialConfigurationId: configurationId,
|
|
452
|
+
issuerMetadata: options.metadata,
|
|
453
|
+
signer: {
|
|
454
|
+
method: 'did',
|
|
455
|
+
didUrl: key.didUrl,
|
|
456
|
+
alg: algorithm,
|
|
457
|
+
kid: key.jwk.keyId,
|
|
458
|
+
},
|
|
459
|
+
nonce: options.cNonce,
|
|
460
|
+
clientId: options.clientId,
|
|
461
|
+
})
|
|
462
|
+
.then(({ jwt }) => jwt))),
|
|
463
|
+
};
|
|
380
464
|
}
|
|
381
|
-
|
|
465
|
+
if (credentialBinding.method === 'jwk') {
|
|
382
466
|
if (!supportsJwk && supportsAnyMethod) {
|
|
383
|
-
throw new
|
|
467
|
+
throw new core_2.CredoError(`Resolved credential binding for proof of possession uses jwk, but openid issuer does not support 'jwk' or 'cose_key' cryptographic binding method`);
|
|
384
468
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
throw new core_1.CredoError(`Credential binding returned jwk with key with type '${credentialBinding.jwk.key.keyType}', but one of '${keyTypes.join(', ')}' was expected`);
|
|
469
|
+
if (!proofTypes.jwt) {
|
|
470
|
+
throw new core_2.CredoError(`JWT proof type is not supported for configuration '${configurationId}', which is required for jwk based credential binding.`);
|
|
388
471
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// @ts-expect-error currently if/else if exhaustive, but once we add new option it will give ts error
|
|
392
|
-
throw new core_1.CredoError(`Unsupported credential binding method ${credentialBinding.method}`);
|
|
393
|
-
}
|
|
394
|
-
const alg = jwk.supportedSignatureAlgorithms.find((alg) => signatureAlgorithms.includes(alg));
|
|
395
|
-
if (!alg) {
|
|
396
|
-
// Should not happen, to make ts happy
|
|
397
|
-
throw new core_1.CredoError(`Unable to determine alg for key type ${jwk.keyType}`);
|
|
398
|
-
}
|
|
399
|
-
const jwtSigner = credentialBinding.method === 'did'
|
|
400
|
-
? {
|
|
401
|
-
method: credentialBinding.method,
|
|
402
|
-
didUrl: credentialBinding.didUrl,
|
|
403
|
-
alg,
|
|
472
|
+
if (proofTypes.jwt.keyAttestationsRequired) {
|
|
473
|
+
throw new core_2.CredoError(`Credential binding returned list of JWK keys, but credential configuration '${configurationId}' requires key attestations. Return a key attestation with binding method 'attestation'.`);
|
|
404
474
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
475
|
+
if (credentialBinding.keys.length > issuerMaxBatchSize) {
|
|
476
|
+
throw new core_2.CredoError(`Issuer supports issuing a batch of maximum ${issuerMaxBatchSize} credential(s). Binding resolver returned ${credentialBinding.keys.length} keys. Make sure the returned value does not exceed the max batch issuance.`);
|
|
477
|
+
}
|
|
478
|
+
if (credentialBinding.keys.length === 0) {
|
|
479
|
+
throw new core_2.CredoError('Credential binding with method jwk returned empty keys list');
|
|
480
|
+
}
|
|
481
|
+
const firstJwk = credentialBinding.keys[0];
|
|
482
|
+
if (!credentialBinding.keys.every((key) => core_2.Kms.assymetricJwkKeyTypeMatches(key.toJson(), firstJwk.toJson()))) {
|
|
483
|
+
throw new core_2.CredoError('Expected all keys for binding method jwk to use the same key type');
|
|
484
|
+
}
|
|
485
|
+
const algorithm = proofTypes.jwt.supportedSignatureAlgorithms.find((algorithm) => firstJwk.supportedSignatureAlgorithms.includes(algorithm));
|
|
486
|
+
if (!algorithm) {
|
|
487
|
+
throw new core_2.CredoError(`Credential binding returned jwk that points to key '${firstJwk.jwkTypehumanDescription}' that supports signature algorithms ${firstJwk.supportedSignatureAlgorithms.join(', ')}, but one of '${proofTypes.jwt.supportedSignatureAlgorithms.join(', ')}' was expected`);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
jwt: await Promise.all(credentialBinding.keys.map((jwk) => client
|
|
491
|
+
.createCredentialRequestJwtProof({
|
|
492
|
+
credentialConfigurationId: configurationId,
|
|
493
|
+
issuerMetadata: options.metadata,
|
|
494
|
+
signer: {
|
|
495
|
+
method: 'jwk',
|
|
496
|
+
publicJwk: jwk.toJson(),
|
|
497
|
+
alg: algorithm,
|
|
498
|
+
},
|
|
499
|
+
nonce: options.cNonce,
|
|
500
|
+
clientId: options.clientId,
|
|
501
|
+
})
|
|
502
|
+
.then(({ jwt }) => jwt))),
|
|
409
503
|
};
|
|
410
|
-
|
|
504
|
+
}
|
|
505
|
+
if (credentialBinding.method === 'attestation') {
|
|
506
|
+
const { payload } = (0, openid4vci_1.parseKeyAttestationJwt)({ keyAttestationJwt: credentialBinding.keyAttestationJwt });
|
|
507
|
+
// TODO: check client_id matches in payload
|
|
508
|
+
if (payload.attested_keys.length > issuerMaxBatchSize) {
|
|
509
|
+
throw new core_2.CredoError(`Issuer supports issuing a batch of maximum ${issuerMaxBatchSize} credential(s). Binding resolver returned key attestation with ${payload.attested_keys.length} attested keys. Make sure the returned value does not exceed the max batch issuance.`);
|
|
510
|
+
}
|
|
511
|
+
// TODO: check nonce matches cNonce
|
|
512
|
+
if (proofTypes.attestation && payload.nonce) {
|
|
513
|
+
// If attestation is supported and the attestation contains a nonce, we can use the attestation directly
|
|
514
|
+
return {
|
|
515
|
+
attestation: [credentialBinding.keyAttestationJwt],
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (proofTypes.jwt) {
|
|
519
|
+
const jwk = core_2.Kms.PublicJwk.fromUnknown(payload.attested_keys[0]);
|
|
520
|
+
return {
|
|
521
|
+
jwt: [
|
|
522
|
+
await client
|
|
523
|
+
.createCredentialRequestJwtProof({
|
|
524
|
+
credentialConfigurationId: configurationId,
|
|
525
|
+
issuerMetadata: options.metadata,
|
|
526
|
+
signer: {
|
|
527
|
+
method: 'jwk',
|
|
528
|
+
publicJwk: payload.attested_keys[0],
|
|
529
|
+
// TODO: we should probably use the 'alg' from the jwk
|
|
530
|
+
alg: jwk.supportedSignatureAlgorithms[0],
|
|
531
|
+
},
|
|
532
|
+
keyAttestationJwt: credentialBinding.keyAttestationJwt,
|
|
533
|
+
nonce: options.cNonce,
|
|
534
|
+
clientId: options.clientId,
|
|
535
|
+
})
|
|
536
|
+
.then(({ jwt }) => jwt),
|
|
537
|
+
],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
throw new core_2.CredoError(`Unable to create credential request proofs. Configuration supports 'attestation' proof type, but attestation did not contain a 'nonce' value`);
|
|
541
|
+
}
|
|
542
|
+
// @ts-expect-error currently if/else if exhaustive, but once we add new option it will give ts error
|
|
543
|
+
throw new core_2.CredoError(`Unsupported credential binding method ${credentialBinding.method}`);
|
|
411
544
|
}
|
|
412
545
|
/**
|
|
413
546
|
* Get the requirements for creating the proof of possession. Based on the allowed
|
|
@@ -416,53 +549,87 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
416
549
|
* algorithm to use, based on the order of preference.
|
|
417
550
|
*/
|
|
418
551
|
getProofOfPossessionRequirements(agentContext, options) {
|
|
419
|
-
const { credentialToRequest } = options;
|
|
420
|
-
|
|
421
|
-
|
|
552
|
+
const { credentialToRequest, possibleProofOfPossessionSignatureAlgorithms, metadata } = options;
|
|
553
|
+
const { configuration, id: configurationId } = credentialToRequest;
|
|
554
|
+
if (!OpenId4VciHolderServiceOptions_1.openId4VciSupportedCredentialFormats.includes(configuration.format)) {
|
|
555
|
+
throw new core_2.CredoError([
|
|
422
556
|
`Requested credential with format '${credentialToRequest.configuration.format}',`,
|
|
423
557
|
`for the credential with id '${credentialToRequest.id},`,
|
|
424
558
|
`but the wallet only supports the following formats '${OpenId4VciHolderServiceOptions_1.openId4VciSupportedCredentialFormats.join(', ')}'`,
|
|
425
559
|
].join('\n'));
|
|
426
560
|
}
|
|
427
561
|
// For each of the supported algs, find the key types, then find the proof types
|
|
428
|
-
const signatureSuiteRegistry = agentContext.dependencyManager.resolve(
|
|
429
|
-
let
|
|
430
|
-
if (
|
|
431
|
-
|
|
432
|
-
|
|
562
|
+
const signatureSuiteRegistry = agentContext.dependencyManager.resolve(core_2.SignatureSuiteRegistry);
|
|
563
|
+
let proofTypesSupported = configuration.proof_types_supported;
|
|
564
|
+
if (!proofTypesSupported) {
|
|
565
|
+
// For draft above 11 we do not allow no proof_type (we do not support no key binding for now)
|
|
566
|
+
if (metadata.originalDraftVersion !== openid4vci_2.Openid4vciDraftVersion.Draft11) {
|
|
567
|
+
throw new core_2.CredoError(`Credential configuration '${configurationId}' does not specifcy proof_types_supported. Credentials not bound to keys are not supported at the moment`);
|
|
433
568
|
}
|
|
569
|
+
// For draft 11 we fall back to jwt proof type
|
|
570
|
+
proofTypesSupported = {
|
|
571
|
+
jwt: {
|
|
572
|
+
proof_signing_alg_values_supported: possibleProofOfPossessionSignatureAlgorithms,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
434
575
|
}
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
576
|
+
const proofTypes = {
|
|
577
|
+
jwt: undefined,
|
|
578
|
+
attestation: undefined,
|
|
579
|
+
};
|
|
580
|
+
for (const [proofType, proofTypeConfig] of Object.entries(proofTypesSupported)) {
|
|
581
|
+
if (proofType !== 'jwt' && proofType !== 'attestation')
|
|
582
|
+
continue;
|
|
583
|
+
let signatureAlgorithms = [];
|
|
584
|
+
const proofSigningAlgsSupported = proofTypeConfig?.proof_signing_alg_values_supported;
|
|
585
|
+
if (proofSigningAlgsSupported === undefined) {
|
|
586
|
+
// If undefined, it means the issuer didn't include the cryptographic suites in the metadata
|
|
587
|
+
// We just guess that the first one is supported
|
|
588
|
+
signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms;
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
switch (credentialToRequest.configuration.format) {
|
|
592
|
+
case shared_1.OpenId4VciCredentialFormatProfile.JwtVcJson:
|
|
593
|
+
case shared_1.OpenId4VciCredentialFormatProfile.JwtVcJsonLd:
|
|
594
|
+
case shared_1.OpenId4VciCredentialFormatProfile.SdJwtVc:
|
|
595
|
+
case shared_1.OpenId4VciCredentialFormatProfile.SdJwtDc:
|
|
596
|
+
case shared_1.OpenId4VciCredentialFormatProfile.MsoMdoc:
|
|
597
|
+
signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms.filter((signatureAlgorithm) => proofSigningAlgsSupported.includes(signatureAlgorithm));
|
|
598
|
+
break;
|
|
599
|
+
// FIXME: this is wrong, as the proof type is separate from the credential signing alg
|
|
600
|
+
// But there might be some draft 11 logic that depends on this, can be removed soon
|
|
601
|
+
case shared_1.OpenId4VciCredentialFormatProfile.LdpVc:
|
|
602
|
+
signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms.filter((signatureAlgorithm) => {
|
|
603
|
+
try {
|
|
604
|
+
const jwkClass = core_2.Kms.PublicJwk.supportedPublicJwkClassForSignatureAlgorithm(signatureAlgorithm);
|
|
605
|
+
const matchingSuites = signatureSuiteRegistry.getAllByPublicJwkType(jwkClass);
|
|
606
|
+
if (matchingSuites.length === 0)
|
|
607
|
+
return false;
|
|
608
|
+
return proofSigningAlgsSupported.includes(matchingSuites[0].proofType);
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
break;
|
|
615
|
+
default:
|
|
616
|
+
throw new core_2.CredoError('Unsupported credential format.');
|
|
617
|
+
}
|
|
462
618
|
}
|
|
619
|
+
proofTypes[proofType] = {
|
|
620
|
+
supportedSignatureAlgorithms: signatureAlgorithms,
|
|
621
|
+
keyAttestationsRequired: proofTypeConfig.key_attestations_required
|
|
622
|
+
? {
|
|
623
|
+
keyStorage: proofTypeConfig.key_attestations_required.key_storage,
|
|
624
|
+
userAuthentication: proofTypeConfig.key_attestations_required.user_authentication,
|
|
625
|
+
}
|
|
626
|
+
: undefined,
|
|
627
|
+
};
|
|
463
628
|
}
|
|
464
|
-
|
|
465
|
-
|
|
629
|
+
const { jwt, attestation } = proofTypes;
|
|
630
|
+
if (!jwt && !attestation) {
|
|
631
|
+
const supported = Object.keys(proofTypesSupported).join(', ');
|
|
632
|
+
throw new core_2.CredoError(`Unsupported proof type(s) ${supported}. Supported proof type(s) are: jwt, attestation`);
|
|
466
633
|
}
|
|
467
634
|
const issuerSupportedBindingMethods = credentialToRequest.configuration.cryptographic_binding_methods_supported;
|
|
468
635
|
const supportsAllDidMethods = issuerSupportedBindingMethods?.includes('did') ?? false;
|
|
@@ -471,26 +638,26 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
471
638
|
const supportsCoseKey = issuerSupportedBindingMethods?.includes('cose_key') ?? false;
|
|
472
639
|
const supportsJwk = issuerSupportedBindingMethods?.includes('jwk') || supportsCoseKey;
|
|
473
640
|
return {
|
|
474
|
-
|
|
641
|
+
proofTypes,
|
|
475
642
|
supportedDidMethods,
|
|
476
643
|
supportsAllDidMethods,
|
|
477
644
|
supportsJwk,
|
|
478
645
|
};
|
|
479
646
|
}
|
|
480
647
|
async handleCredentialResponse(agentContext, credentialResponse, options) {
|
|
481
|
-
const { verifyCredentialStatus, credentialConfigurationId } = options;
|
|
648
|
+
const { verifyCredentialStatus, credentialConfigurationId, credentialConfiguration } = options;
|
|
482
649
|
this.logger.debug('Credential response', credentialResponse);
|
|
483
650
|
const credentials = credentialResponse.credentials ?? (credentialResponse.credential ? [credentialResponse.credential] : undefined);
|
|
484
651
|
if (!credentials) {
|
|
485
|
-
throw new
|
|
652
|
+
throw new core_2.CredoError(`Credential response returned neither 'credentials' nor 'credential' parameter.`);
|
|
486
653
|
}
|
|
487
654
|
const notificationId = credentialResponse.notification_id;
|
|
488
655
|
const format = options.format;
|
|
489
|
-
if (format === shared_1.OpenId4VciCredentialFormatProfile.SdJwtVc) {
|
|
656
|
+
if (format === shared_1.OpenId4VciCredentialFormatProfile.SdJwtVc || format === shared_1.OpenId4VciCredentialFormatProfile.SdJwtDc) {
|
|
490
657
|
if (!credentials.every((c) => typeof c === 'string')) {
|
|
491
|
-
throw new
|
|
658
|
+
throw new core_2.CredoError(`Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify(credentials)}`);
|
|
492
659
|
}
|
|
493
|
-
const sdJwtVcApi = agentContext.dependencyManager.resolve(
|
|
660
|
+
const sdJwtVcApi = agentContext.dependencyManager.resolve(core_2.SdJwtVcApi);
|
|
494
661
|
const verificationResults = await Promise.all(credentials.map((compactSdJwtVc, index) => sdJwtVcApi.verify({
|
|
495
662
|
compactSdJwtVc,
|
|
496
663
|
// Only load and verify it for the first instance
|
|
@@ -498,21 +665,22 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
498
665
|
})));
|
|
499
666
|
if (!verificationResults.every((result) => result.isValid)) {
|
|
500
667
|
agentContext.config.logger.error('Failed to validate credential(s)', { verificationResults });
|
|
501
|
-
throw new
|
|
668
|
+
throw new core_2.CredoError(`Failed to validate sd-jwt-vc credentials. Results = ${JSON.stringify(verificationResults)}`);
|
|
502
669
|
}
|
|
503
670
|
return {
|
|
504
671
|
credentials: verificationResults.map((result) => result.sdJwtVc),
|
|
505
672
|
notificationId,
|
|
506
673
|
credentialConfigurationId,
|
|
674
|
+
credentialConfiguration,
|
|
507
675
|
};
|
|
508
676
|
}
|
|
509
677
|
if (options.format === shared_1.OpenId4VciCredentialFormatProfile.JwtVcJson ||
|
|
510
678
|
options.format === shared_1.OpenId4VciCredentialFormatProfile.JwtVcJsonLd) {
|
|
511
679
|
if (!credentials.every((c) => typeof c === 'string')) {
|
|
512
|
-
throw new
|
|
680
|
+
throw new core_2.CredoError(`Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify(credentials)}`);
|
|
513
681
|
}
|
|
514
682
|
const result = await Promise.all(credentials.map(async (c) => {
|
|
515
|
-
const credential =
|
|
683
|
+
const credential = core_2.W3cJwtVerifiableCredential.fromSerializedJwt(c);
|
|
516
684
|
const result = await this.w3cCredentialService.verifyCredential(agentContext, {
|
|
517
685
|
credential,
|
|
518
686
|
verifyCredentialStatus,
|
|
@@ -521,19 +689,24 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
521
689
|
}));
|
|
522
690
|
if (!result.every((c) => c.result.isValid)) {
|
|
523
691
|
agentContext.config.logger.error('Failed to validate credentials', { result });
|
|
524
|
-
throw new
|
|
692
|
+
throw new core_2.CredoError(`Failed to validate credential, error = ${result
|
|
525
693
|
.map((e) => e.result.error?.message)
|
|
526
694
|
.filter(Boolean)
|
|
527
695
|
.join(', ')}`);
|
|
528
696
|
}
|
|
529
|
-
return {
|
|
697
|
+
return {
|
|
698
|
+
credentials: result.map((r) => r.credential),
|
|
699
|
+
notificationId,
|
|
700
|
+
credentialConfigurationId,
|
|
701
|
+
credentialConfiguration,
|
|
702
|
+
};
|
|
530
703
|
}
|
|
531
704
|
if (format === shared_1.OpenId4VciCredentialFormatProfile.LdpVc) {
|
|
532
705
|
if (!credentials.every((c) => typeof c === 'object')) {
|
|
533
|
-
throw new
|
|
706
|
+
throw new core_2.CredoError(`Received credential(s) of format ${format}, but not all credential(s) are an object. ${JSON.stringify(credentials)}`);
|
|
534
707
|
}
|
|
535
708
|
const result = await Promise.all(credentials.map(async (c) => {
|
|
536
|
-
const credential =
|
|
709
|
+
const credential = core_2.W3cJsonLdVerifiableCredential.fromJson(c);
|
|
537
710
|
const result = await this.w3cCredentialService.verifyCredential(agentContext, {
|
|
538
711
|
credential,
|
|
539
712
|
verifyCredentialStatus,
|
|
@@ -542,20 +715,25 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
542
715
|
}));
|
|
543
716
|
if (!result.every((c) => c.result.isValid)) {
|
|
544
717
|
agentContext.config.logger.error('Failed to validate credentials', { result });
|
|
545
|
-
throw new
|
|
718
|
+
throw new core_2.CredoError(`Failed to validate credential, error = ${result
|
|
546
719
|
.map((e) => e.result.error?.message)
|
|
547
720
|
.filter(Boolean)
|
|
548
721
|
.join(', ')}`);
|
|
549
722
|
}
|
|
550
|
-
return {
|
|
723
|
+
return {
|
|
724
|
+
credentials: result.map((r) => r.credential),
|
|
725
|
+
notificationId,
|
|
726
|
+
credentialConfigurationId,
|
|
727
|
+
credentialConfiguration,
|
|
728
|
+
};
|
|
551
729
|
}
|
|
552
730
|
if (format === shared_1.OpenId4VciCredentialFormatProfile.MsoMdoc) {
|
|
553
731
|
if (!credentials.every((c) => typeof c === 'string')) {
|
|
554
|
-
throw new
|
|
732
|
+
throw new core_2.CredoError(`Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify(credentials)}`);
|
|
555
733
|
}
|
|
556
|
-
const mdocApi = agentContext.dependencyManager.resolve(
|
|
734
|
+
const mdocApi = agentContext.dependencyManager.resolve(core_2.MdocApi);
|
|
557
735
|
const result = await Promise.all(credentials.map(async (credential) => {
|
|
558
|
-
const mdoc =
|
|
736
|
+
const mdoc = core_2.Mdoc.fromBase64Url(credential);
|
|
559
737
|
const result = await mdocApi.verify(mdoc, {});
|
|
560
738
|
return {
|
|
561
739
|
result,
|
|
@@ -564,18 +742,62 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
564
742
|
}));
|
|
565
743
|
if (!result.every((r) => r.result.isValid)) {
|
|
566
744
|
agentContext.config.logger.error('Failed to validate credentials', { result });
|
|
567
|
-
throw new
|
|
745
|
+
throw new core_2.CredoError(`Failed to validate mdoc credential(s). \n - ${result
|
|
568
746
|
.map((r, i) => (r.result.isValid ? undefined : `(${i}) ${r.result.error}`))
|
|
569
747
|
.filter(Boolean)
|
|
570
748
|
.join('\n - ')}`);
|
|
571
749
|
}
|
|
572
|
-
return {
|
|
750
|
+
return {
|
|
751
|
+
credentials: result.map((c) => c.mdoc),
|
|
752
|
+
notificationId,
|
|
753
|
+
credentialConfigurationId,
|
|
754
|
+
credentialConfiguration,
|
|
755
|
+
};
|
|
573
756
|
}
|
|
574
|
-
throw new
|
|
757
|
+
throw new core_2.CredoError(`Unsupported credential format ${options.format}`);
|
|
575
758
|
}
|
|
576
|
-
getClient(agentContext) {
|
|
577
|
-
|
|
578
|
-
|
|
759
|
+
getClient(agentContext, { clientAttestation, clientId } = {}) {
|
|
760
|
+
const callbacks = (0, callbacks_1.getOid4vcCallbacks)(agentContext);
|
|
761
|
+
return new openid4vci_2.Openid4vciClient({
|
|
762
|
+
callbacks: {
|
|
763
|
+
...callbacks,
|
|
764
|
+
clientAuthentication: (options) => {
|
|
765
|
+
const { authorizationServerMetadata, url, body } = options;
|
|
766
|
+
const oauth2Client = this.getOauth2Client(agentContext);
|
|
767
|
+
const clientAttestationSupported = oauth2Client.isClientAttestationSupported({
|
|
768
|
+
authorizationServerMetadata,
|
|
769
|
+
});
|
|
770
|
+
// Client attestations
|
|
771
|
+
if (clientAttestation && clientAttestationSupported) {
|
|
772
|
+
return (0, oauth2_1.clientAuthenticationClientAttestationJwt)({
|
|
773
|
+
clientAttestationJwt: clientAttestation,
|
|
774
|
+
callbacks,
|
|
775
|
+
})(options);
|
|
776
|
+
}
|
|
777
|
+
// Pre auth flow
|
|
778
|
+
if (url === authorizationServerMetadata.token_endpoint &&
|
|
779
|
+
authorizationServerMetadata['pre-authorized_grant_anonymous_access_supported'] &&
|
|
780
|
+
body.grant_type === oauth2_1.preAuthorizedCodeGrantIdentifier) {
|
|
781
|
+
return (0, oauth2_1.clientAuthenticationAnonymous)()(options);
|
|
782
|
+
}
|
|
783
|
+
// Just a client id (no auth)
|
|
784
|
+
if (clientId) {
|
|
785
|
+
return (0, oauth2_1.clientAuthenticationNone)({ clientId })(options);
|
|
786
|
+
}
|
|
787
|
+
// NOTE: we fall back to anonymous authentication for pre-auth for now, as there's quite some
|
|
788
|
+
// issuers that do not have pre-authorized_grant_anonymous_access_supported defined
|
|
789
|
+
if (url === authorizationServerMetadata.token_endpoint &&
|
|
790
|
+
body.grant_type === oauth2_1.preAuthorizedCodeGrantIdentifier) {
|
|
791
|
+
return (0, oauth2_1.clientAuthenticationAnonymous)()(options);
|
|
792
|
+
}
|
|
793
|
+
// TODO: We should still look at auth_methods_supported
|
|
794
|
+
// If there is an auth session for the auth challenge endpoint, we don't have to include the client_id
|
|
795
|
+
if (url === authorizationServerMetadata.authorization_challenge_endpoint && body.auth_session) {
|
|
796
|
+
return (0, oauth2_1.clientAuthenticationAnonymous)()(options);
|
|
797
|
+
}
|
|
798
|
+
throw new core_2.CredoError('Unable to perform client authentication.');
|
|
799
|
+
},
|
|
800
|
+
},
|
|
579
801
|
});
|
|
580
802
|
}
|
|
581
803
|
getOauth2Client(agentContext) {
|
|
@@ -586,8 +808,8 @@ let OpenId4VciHolderService = class OpenId4VciHolderService {
|
|
|
586
808
|
};
|
|
587
809
|
exports.OpenId4VciHolderService = OpenId4VciHolderService;
|
|
588
810
|
exports.OpenId4VciHolderService = OpenId4VciHolderService = __decorate([
|
|
589
|
-
(0,
|
|
590
|
-
__param(0, (0,
|
|
591
|
-
__metadata("design:paramtypes", [Object,
|
|
811
|
+
(0, core_2.injectable)(),
|
|
812
|
+
__param(0, (0, core_2.inject)(core_2.InjectionSymbols.Logger)),
|
|
813
|
+
__metadata("design:paramtypes", [Object, core_2.W3cCredentialService])
|
|
592
814
|
], OpenId4VciHolderService);
|
|
593
815
|
//# sourceMappingURL=OpenId4VciHolderService.js.map
|