@pagopa/io-react-native-wallet 0.28.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/lib/commonjs/credential/issuance/03-start-user-authorization.js +5 -0
- package/lib/commonjs/credential/issuance/03-start-user-authorization.js.map +1 -1
- package/lib/commonjs/credential/issuance/04-complete-user-authorization.js +12 -0
- package/lib/commonjs/credential/issuance/04-complete-user-authorization.js.map +1 -1
- package/lib/commonjs/credential/issuance/05-authorize-access.js +5 -0
- package/lib/commonjs/credential/issuance/05-authorize-access.js.map +1 -1
- package/lib/commonjs/credential/issuance/06-obtain-credential.js +13 -2
- package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +10 -0
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/credential/presentation/01-start-flow.js +9 -8
- package/lib/commonjs/credential/presentation/01-start-flow.js.map +1 -1
- package/lib/commonjs/credential/presentation/03-get-request-object.js.map +1 -1
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +36 -5
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +16 -4
- package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
- package/lib/commonjs/credential/presentation/README.md +17 -9
- package/lib/commonjs/credential/presentation/errors.js +11 -13
- package/lib/commonjs/credential/presentation/errors.js.map +1 -1
- package/lib/commonjs/credential/status/02-status-attestation.js +2 -0
- package/lib/commonjs/credential/status/02-status-attestation.js.map +1 -1
- package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js +3 -0
- package/lib/commonjs/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
- package/lib/commonjs/credential/trustmark/get-credential-trustmark.js +5 -0
- package/lib/commonjs/credential/trustmark/get-credential-trustmark.js.map +1 -1
- package/lib/commonjs/index.js +3 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/decoder.js +2 -0
- package/lib/commonjs/utils/decoder.js.map +1 -1
- package/lib/commonjs/utils/logging.js +68 -0
- package/lib/commonjs/utils/logging.js.map +1 -0
- package/lib/commonjs/utils/misc.js +2 -0
- package/lib/commonjs/utils/misc.js.map +1 -1
- package/lib/commonjs/utils/par.js +2 -0
- package/lib/commonjs/utils/par.js.map +1 -1
- package/lib/commonjs/wallet-instance/index.js +4 -0
- package/lib/commonjs/wallet-instance/index.js.map +1 -1
- package/lib/commonjs/wallet-instance-attestation/issuing.js +5 -0
- package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/module/credential/issuance/03-start-user-authorization.js +5 -0
- package/lib/module/credential/issuance/03-start-user-authorization.js.map +1 -1
- package/lib/module/credential/issuance/04-complete-user-authorization.js +12 -0
- package/lib/module/credential/issuance/04-complete-user-authorization.js.map +1 -1
- package/lib/module/credential/issuance/05-authorize-access.js +5 -0
- package/lib/module/credential/issuance/05-authorize-access.js.map +1 -1
- package/lib/module/credential/issuance/06-obtain-credential.js +13 -2
- package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +10 -0
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/credential/presentation/01-start-flow.js +9 -8
- package/lib/module/credential/presentation/01-start-flow.js.map +1 -1
- package/lib/module/credential/presentation/03-get-request-object.js.map +1 -1
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js +36 -5
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
- package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +17 -5
- package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
- package/lib/module/credential/presentation/README.md +17 -9
- package/lib/module/credential/presentation/errors.js +8 -10
- package/lib/module/credential/presentation/errors.js.map +1 -1
- package/lib/module/credential/status/02-status-attestation.js +2 -0
- package/lib/module/credential/status/02-status-attestation.js.map +1 -1
- package/lib/module/credential/status/03-verify-and-parse-status-attestation.js +3 -0
- package/lib/module/credential/status/03-verify-and-parse-status-attestation.js.map +1 -1
- package/lib/module/credential/trustmark/get-credential-trustmark.js +5 -0
- package/lib/module/credential/trustmark/get-credential-trustmark.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/decoder.js +2 -0
- package/lib/module/utils/decoder.js.map +1 -1
- package/lib/module/utils/logging.js +62 -0
- package/lib/module/utils/logging.js.map +1 -0
- package/lib/module/utils/misc.js +2 -0
- package/lib/module/utils/misc.js.map +1 -1
- package/lib/module/utils/par.js +2 -0
- package/lib/module/utils/par.js.map +1 -1
- package/lib/module/wallet-instance/index.js +4 -0
- package/lib/module/wallet-instance/index.js.map +1 -1
- package/lib/module/wallet-instance-attestation/issuing.js +5 -0
- package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/05-authorize-access.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +1 -1
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/01-start-flow.d.ts +17 -14
- package/lib/typescript/credential/presentation/01-start-flow.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/03-get-request-object.d.ts +1 -3
- package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/errors.d.ts +11 -5
- package/lib/typescript/credential/presentation/errors.d.ts.map +1 -1
- package/lib/typescript/credential/status/02-status-attestation.d.ts.map +1 -1
- package/lib/typescript/credential/status/03-verify-and-parse-status-attestation.d.ts.map +1 -1
- package/lib/typescript/credential/trustmark/get-credential-trustmark.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/decoder.d.ts.map +1 -1
- package/lib/typescript/utils/logging.d.ts +35 -0
- package/lib/typescript/utils/logging.d.ts.map +1 -0
- package/lib/typescript/utils/misc.d.ts.map +1 -1
- package/lib/typescript/utils/par.d.ts.map +1 -1
- package/lib/typescript/wallet-instance/index.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential/issuance/03-start-user-authorization.ts +18 -0
- package/src/credential/issuance/04-complete-user-authorization.ts +51 -0
- package/src/credential/issuance/05-authorize-access.ts +16 -0
- package/src/credential/issuance/06-obtain-credential.ts +31 -2
- package/src/credential/issuance/07-verify-and-parse-credential.ts +27 -1
- package/src/credential/presentation/01-start-flow.ts +12 -11
- package/src/credential/presentation/03-get-request-object.ts +2 -3
- package/src/credential/presentation/07-evaluate-dcql-query.ts +36 -1
- package/src/credential/presentation/07-evaluate-input-descriptor.ts +25 -13
- package/src/credential/presentation/README.md +17 -9
- package/src/credential/presentation/errors.ts +15 -8
- package/src/credential/status/02-status-attestation.ts +3 -0
- package/src/credential/status/03-verify-and-parse-status-attestation.ts +10 -0
- package/src/credential/trustmark/get-credential-trustmark.ts +19 -0
- package/src/index.ts +2 -0
- package/src/utils/decoder.ts +5 -0
- package/src/utils/logging.ts +68 -0
- package/src/utils/misc.ts +5 -0
- package/src/utils/par.ts +6 -0
- package/src/wallet-instance/index.ts +17 -1
- package/src/wallet-instance-attestation/issuing.ts +19 -0
@@ -19,6 +19,7 @@ import { v4 as uuidv4 } from "uuid";
|
|
19
19
|
import { ResponseUriResultShape } from "./types";
|
20
20
|
import { getJwtFromFormPost } from "../../utils/decoder";
|
21
21
|
import { AuthorizationError, AuthorizationIdpError } from "./errors";
|
22
|
+
import { LogLevel, Logger } from "../../utils/logging";
|
22
23
|
|
23
24
|
/**
|
24
25
|
* The interface of the phase to complete User authorization via strong identification when the response mode is "query" and the request credential is a PersonIdentificationData.
|
@@ -95,6 +96,10 @@ export const buildAuthorizationUrl: BuildAuthorizationUrl = async (
|
|
95
96
|
*/
|
96
97
|
export const completeUserAuthorizationWithQueryMode: CompleteUserAuthorizationWithQueryMode =
|
97
98
|
async (authRedirectUrl) => {
|
99
|
+
Logger.log(
|
100
|
+
LogLevel.DEBUG,
|
101
|
+
`The requeste credential is a PersonIdentificationData, completing the user authorization with query mode`
|
102
|
+
);
|
98
103
|
const query = parseUrl(authRedirectUrl).query;
|
99
104
|
|
100
105
|
return parseAuthorizationResponse(query);
|
@@ -114,6 +119,10 @@ export const completeUserAuthorizationWithQueryMode: CompleteUserAuthorizationWi
|
|
114
119
|
*/
|
115
120
|
export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePresented =
|
116
121
|
async (issuerRequestUri, clientId, issuerConf, appFetch = fetch) => {
|
122
|
+
Logger.log(
|
123
|
+
LogLevel.DEBUG,
|
124
|
+
`The requeste credential is not a PersonIdentificationData, requesting the credential to be presented`
|
125
|
+
);
|
117
126
|
const authzRequestEndpoint =
|
118
127
|
issuerConf.oauth_authorization_server.authorization_endpoint;
|
119
128
|
const params = new URLSearchParams({
|
@@ -121,6 +130,11 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
|
|
121
130
|
request_uri: issuerRequestUri,
|
122
131
|
});
|
123
132
|
|
133
|
+
Logger.log(
|
134
|
+
LogLevel.DEBUG,
|
135
|
+
`Requesting the request object to ${authzRequestEndpoint}?${params.toString()}`
|
136
|
+
);
|
137
|
+
|
124
138
|
const requestObject = await appFetch(
|
125
139
|
`${authzRequestEndpoint}?${params.toString()}`,
|
126
140
|
{ method: "GET" }
|
@@ -131,6 +145,10 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
|
|
131
145
|
.then((reqObj) => RequestObject.safeParse(reqObj.payload));
|
132
146
|
|
133
147
|
if (!requestObject.success) {
|
148
|
+
Logger.log(
|
149
|
+
LogLevel.ERROR,
|
150
|
+
`Error while validating the response object: ${requestObject.error.message}`
|
151
|
+
);
|
134
152
|
throw new ValidationFailed({
|
135
153
|
message: "Request Object validation failed",
|
136
154
|
reason: requestObject.error.message,
|
@@ -157,6 +175,11 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
|
|
157
175
|
*/
|
158
176
|
export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthorizationWithFormPostJwtMode =
|
159
177
|
async (requestObject, ctx) => {
|
178
|
+
Logger.log(
|
179
|
+
LogLevel.DEBUG,
|
180
|
+
`The requeste credential is not a PersonIdentificationData, completing the user authorization with form_post.jwt mode`
|
181
|
+
);
|
182
|
+
|
160
183
|
const {
|
161
184
|
wiaCryptoContext,
|
162
185
|
pidCryptoContext,
|
@@ -195,6 +218,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
|
|
195
218
|
.setAudience(requestObject.response_uri)
|
196
219
|
.sign();
|
197
220
|
|
221
|
+
Logger.log(
|
222
|
+
LogLevel.DEBUG,
|
223
|
+
`Wallet instance attestation JWT token: ${wiaWpToken}`
|
224
|
+
);
|
225
|
+
|
198
226
|
/* The path parameter refers to the vp_token variable of the authzResponsePayload and must point to the plain credential which
|
199
227
|
* is cointaned in the `vp` property of the signed jwt token payload
|
200
228
|
*/
|
@@ -215,6 +243,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
|
|
215
243
|
],
|
216
244
|
};
|
217
245
|
|
246
|
+
Logger.log(
|
247
|
+
LogLevel.DEBUG,
|
248
|
+
`Presentation submission: ${JSON.stringify(presentationSubmission)}`
|
249
|
+
);
|
250
|
+
|
218
251
|
const authzResponsePayload = encodeBase64(
|
219
252
|
JSON.stringify({
|
220
253
|
state: requestObject.state,
|
@@ -223,6 +256,11 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
|
|
223
256
|
})
|
224
257
|
);
|
225
258
|
|
259
|
+
Logger.log(
|
260
|
+
LogLevel.DEBUG,
|
261
|
+
`Authz response payload: ${authzResponsePayload}`
|
262
|
+
);
|
263
|
+
|
226
264
|
// Note: according to the spec, the response should be encrypted with the public key of the RP however this is not implemented yet
|
227
265
|
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-response
|
228
266
|
// const rsaPublicJwk = chooseRSAPublicKeyToEncrypt(rpConf);
|
@@ -235,6 +273,7 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
|
|
235
273
|
const body = new URLSearchParams({
|
236
274
|
response: authzResponsePayload,
|
237
275
|
}).toString();
|
276
|
+
|
238
277
|
const resUriRes = await appFetch(requestObject.response_uri, {
|
239
278
|
method: "POST",
|
240
279
|
headers: {
|
@@ -247,6 +286,10 @@ export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthoriza
|
|
247
286
|
|
248
287
|
const responseUri = ResponseUriResultShape.safeParse(resUriRes);
|
249
288
|
if (!responseUri.success) {
|
289
|
+
Logger.log(
|
290
|
+
LogLevel.ERROR,
|
291
|
+
`Error while validating the response uri: ${responseUri.error.message}`
|
292
|
+
);
|
250
293
|
throw new ValidationFailed({
|
251
294
|
message: "Response Uri validation failed",
|
252
295
|
reason: responseUri.error.message,
|
@@ -274,8 +317,16 @@ export const parseAuthorizationResponse = (
|
|
274
317
|
if (!authResParsed.success) {
|
275
318
|
const authErr = AuthorizationErrorShape.safeParse(authRes);
|
276
319
|
if (!authErr.success) {
|
320
|
+
Logger.log(
|
321
|
+
LogLevel.ERROR,
|
322
|
+
`Error while parsing the authorization response: ${authResParsed.error.message}`
|
323
|
+
);
|
277
324
|
throw new AuthorizationError(authResParsed.error.message); // an error occured while parsing the result and the error
|
278
325
|
}
|
326
|
+
Logger.log(
|
327
|
+
LogLevel.ERROR,
|
328
|
+
`Error while authorizating with the idp: ${JSON.stringify(authErr)}`
|
329
|
+
);
|
279
330
|
throw new AuthorizationIdpError(
|
280
331
|
authErr.data.error,
|
281
332
|
authErr.data.error_description
|
@@ -10,6 +10,7 @@ import { ASSERTION_TYPE } from "./const";
|
|
10
10
|
import { TokenResponse } from "./types";
|
11
11
|
import { IssuerResponseError, ValidationFailed } from "../../utils/errors";
|
12
12
|
import type { CompleteUserAuthorizationWithQueryMode } from "./04-complete-user-authorization";
|
13
|
+
import { LogLevel, Logger } from "../../utils/logging";
|
13
14
|
|
14
15
|
export type AuthorizeAccess = (
|
15
16
|
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
@@ -76,6 +77,8 @@ export const authorizeAccess: AuthorizeAccess = async (
|
|
76
77
|
dPopCryptoContext
|
77
78
|
);
|
78
79
|
|
80
|
+
Logger.log(LogLevel.DEBUG, `Token request DPoP: ${tokenRequestSignedDPop}`);
|
81
|
+
|
79
82
|
const signedWiaPoP = await createPopToken(
|
80
83
|
{
|
81
84
|
jti: `${uuidv4()}`,
|
@@ -85,6 +88,8 @@ export const authorizeAccess: AuthorizeAccess = async (
|
|
85
88
|
wiaCryptoContext
|
86
89
|
);
|
87
90
|
|
91
|
+
Logger.log(LogLevel.DEBUG, `WIA DPoP token: ${signedWiaPoP}`);
|
92
|
+
|
88
93
|
const requestBody = {
|
89
94
|
grant_type: "authorization_code",
|
90
95
|
client_id: clientId,
|
@@ -96,6 +101,12 @@ export const authorizeAccess: AuthorizeAccess = async (
|
|
96
101
|
};
|
97
102
|
|
98
103
|
const authorizationRequestFormBody = new URLSearchParams(requestBody);
|
104
|
+
|
105
|
+
Logger.log(
|
106
|
+
LogLevel.DEBUG,
|
107
|
+
`Auth form request body: ${authorizationRequestFormBody}`
|
108
|
+
);
|
109
|
+
|
99
110
|
const tokenRes = await appFetch(tokenUrl, {
|
100
111
|
method: "POST",
|
101
112
|
headers: {
|
@@ -109,6 +120,11 @@ export const authorizeAccess: AuthorizeAccess = async (
|
|
109
120
|
.then((body) => TokenResponse.safeParse(body));
|
110
121
|
|
111
122
|
if (!tokenRes.success) {
|
123
|
+
Logger.log(
|
124
|
+
LogLevel.ERROR,
|
125
|
+
`Token Response validation failed: ${tokenRes.error.message}`
|
126
|
+
);
|
127
|
+
|
112
128
|
throw new ValidationFailed({
|
113
129
|
message: "Token Response validation failed",
|
114
130
|
reason: tokenRes.error.message,
|
@@ -17,6 +17,7 @@ import {
|
|
17
17
|
import { CredentialResponse } from "./types";
|
18
18
|
import { createDPopToken } from "../../utils/dpop";
|
19
19
|
import { v4 as uuidv4 } from "uuid";
|
20
|
+
import { LogLevel, Logger } from "../../utils/logging";
|
20
21
|
|
21
22
|
export type ObtainCredential = (
|
22
23
|
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
@@ -27,7 +28,8 @@ export type ObtainCredential = (
|
|
27
28
|
dPopCryptoContext: CryptoContext;
|
28
29
|
credentialCryptoContext: CryptoContext;
|
29
30
|
appFetch?: GlobalFetch["fetch"];
|
30
|
-
}
|
31
|
+
},
|
32
|
+
operationType?: "reissuing"
|
31
33
|
) => Promise<CredentialResponse>;
|
32
34
|
|
33
35
|
export const createNonceProof = async (
|
@@ -73,7 +75,8 @@ export const obtainCredential: ObtainCredential = async (
|
|
73
75
|
accessToken,
|
74
76
|
clientId,
|
75
77
|
credentialDefinition,
|
76
|
-
context
|
78
|
+
context,
|
79
|
+
operationType
|
77
80
|
) => {
|
78
81
|
const {
|
79
82
|
credentialCryptoContext,
|
@@ -95,6 +98,8 @@ export const obtainCredential: ObtainCredential = async (
|
|
95
98
|
credentialCryptoContext
|
96
99
|
);
|
97
100
|
|
101
|
+
Logger.log(LogLevel.DEBUG, `Signed nonce proof: ${signedNonceProof}`);
|
102
|
+
|
98
103
|
// Validation of accessTokenResponse.authorization_details if contain credentialDefinition
|
99
104
|
const containsCredentialDefinition = accessToken.authorization_details.some(
|
100
105
|
(c) =>
|
@@ -105,6 +110,10 @@ export const obtainCredential: ObtainCredential = async (
|
|
105
110
|
);
|
106
111
|
|
107
112
|
if (!containsCredentialDefinition) {
|
113
|
+
Logger.log(
|
114
|
+
LogLevel.ERROR,
|
115
|
+
`Credential definition not found in the access token response ${accessToken.authorization_details}`
|
116
|
+
);
|
108
117
|
throw new ValidationFailed({
|
109
118
|
message:
|
110
119
|
"The access token response does not contain the requested credential",
|
@@ -123,6 +132,11 @@ export const obtainCredential: ObtainCredential = async (
|
|
123
132
|
},
|
124
133
|
};
|
125
134
|
|
135
|
+
Logger.log(
|
136
|
+
LogLevel.DEBUG,
|
137
|
+
`Credential request body: ${JSON.stringify(credentialRequestFormBody)}`
|
138
|
+
);
|
139
|
+
|
126
140
|
const tokenRequestSignedDPop = await createDPopToken(
|
127
141
|
{
|
128
142
|
htm: "POST",
|
@@ -132,12 +146,16 @@ export const obtainCredential: ObtainCredential = async (
|
|
132
146
|
},
|
133
147
|
dPopCryptoContext
|
134
148
|
);
|
149
|
+
|
150
|
+
Logger.log(LogLevel.DEBUG, `Token request DPoP: ${tokenRequestSignedDPop}`);
|
151
|
+
|
135
152
|
const credentialRes = await appFetch(credentialUrl, {
|
136
153
|
method: "POST",
|
137
154
|
headers: {
|
138
155
|
"Content-Type": "application/json",
|
139
156
|
DPoP: tokenRequestSignedDPop,
|
140
157
|
Authorization: `${accessToken.token_type} ${accessToken.access_token}`,
|
158
|
+
...(operationType === "reissuing" && { operationType }),
|
141
159
|
},
|
142
160
|
body: JSON.stringify(credentialRequestFormBody),
|
143
161
|
})
|
@@ -147,12 +165,21 @@ export const obtainCredential: ObtainCredential = async (
|
|
147
165
|
.catch(handleObtainCredentialError);
|
148
166
|
|
149
167
|
if (!credentialRes.success) {
|
168
|
+
Logger.log(
|
169
|
+
LogLevel.ERROR,
|
170
|
+
`Credential Response validation failed: ${credentialRes.error.message}`
|
171
|
+
);
|
150
172
|
throw new ValidationFailed({
|
151
173
|
message: "Credential Response validation failed",
|
152
174
|
reason: credentialRes.error.message,
|
153
175
|
});
|
154
176
|
}
|
155
177
|
|
178
|
+
Logger.log(
|
179
|
+
LogLevel.DEBUG,
|
180
|
+
`Credential Response: ${JSON.stringify(credentialRes.data)}`
|
181
|
+
);
|
182
|
+
|
156
183
|
return credentialRes.data;
|
157
184
|
};
|
158
185
|
|
@@ -163,6 +190,8 @@ export const obtainCredential: ObtainCredential = async (
|
|
163
190
|
* @throws {IssuerResponseError} with a specific code for more context
|
164
191
|
*/
|
165
192
|
const handleObtainCredentialError = (e: unknown) => {
|
193
|
+
Logger.log(LogLevel.ERROR, `Error occurred while obtaining credential: ${e}`);
|
194
|
+
|
166
195
|
if (!(e instanceof UnexpectedStatusCodeError)) {
|
167
196
|
throw e;
|
168
197
|
}
|
@@ -7,6 +7,7 @@ import { verify as verifySdJwt } from "../../sd-jwt";
|
|
7
7
|
import { getValueFromDisclosures } from "../../sd-jwt/converters";
|
8
8
|
import type { JWK } from "../../utils/jwk";
|
9
9
|
import type { ObtainCredential } from "./06-obtain-credential";
|
10
|
+
import { LogLevel, Logger } from "../../utils/logging";
|
10
11
|
|
11
12
|
export type VerifyAndParseCredential = (
|
12
13
|
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
@@ -62,10 +63,18 @@ const parseCredentialSdJwt = (
|
|
62
63
|
const credentialSubject = credentials_supported[sdJwt.payload.vct];
|
63
64
|
|
64
65
|
if (!credentialSubject) {
|
66
|
+
Logger.log(
|
67
|
+
LogLevel.ERROR,
|
68
|
+
`Credential type not supported by the issuer: ${sdJwt.payload.vct}`
|
69
|
+
);
|
65
70
|
throw new IoWalletError("Credential type not supported by the issuer");
|
66
71
|
}
|
67
72
|
|
68
73
|
if (credentialSubject.format !== sdJwt.header.typ) {
|
74
|
+
Logger.log(
|
75
|
+
LogLevel.ERROR,
|
76
|
+
`Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}'`
|
77
|
+
);
|
69
78
|
throw new IoWalletError(
|
70
79
|
`Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}', `
|
71
80
|
);
|
@@ -73,6 +82,7 @@ const parseCredentialSdJwt = (
|
|
73
82
|
|
74
83
|
// transfrom a record { key: value } in an iterable of pairs [key, value]
|
75
84
|
if (!credentialSubject.claims) {
|
85
|
+
Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
|
76
86
|
throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
|
77
87
|
}
|
78
88
|
const attrDefinitions = Object.entries(credentialSubject.claims);
|
@@ -85,6 +95,10 @@ const parseCredentialSdJwt = (
|
|
85
95
|
const missing = attrsNotInDisclosures.map((_) => _[0 /* key */]).join(", ");
|
86
96
|
const received = disclosures.map((_) => _[1 /* name */]).join(", ");
|
87
97
|
if (!ignoreMissingAttributes) {
|
98
|
+
Logger.log(
|
99
|
+
LogLevel.ERROR,
|
100
|
+
`Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
|
101
|
+
);
|
88
102
|
throw new IoWalletError(
|
89
103
|
`Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
|
90
104
|
);
|
@@ -172,6 +186,10 @@ async function verifyCredentialSdJwt(
|
|
172
186
|
const { cnf } = decodedCredential.sdJwt.payload;
|
173
187
|
|
174
188
|
if (!cnf.jwk.kid || cnf.jwk.kid !== holderBindingKey.kid) {
|
189
|
+
Logger.log(
|
190
|
+
LogLevel.ERROR,
|
191
|
+
`Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
|
192
|
+
);
|
175
193
|
throw new IoWalletError(
|
176
194
|
`Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
|
177
195
|
);
|
@@ -204,15 +222,21 @@ const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
|
|
204
222
|
credentialCryptoContext
|
205
223
|
);
|
206
224
|
|
225
|
+
Logger.log(LogLevel.DEBUG, `Decoded credential: ${JSON.stringify(decoded)}`);
|
226
|
+
|
207
227
|
const parsedCredential = parseCredentialSdJwt(
|
208
228
|
issuerConf.openid_credential_issuer.credential_configurations_supported,
|
209
229
|
decoded,
|
210
230
|
ignoreMissingAttributes,
|
211
231
|
includeUndefinedAttributes
|
212
232
|
);
|
213
|
-
|
214
233
|
const maybeIssuedAt = getValueFromDisclosures(decoded.disclosures, "iat");
|
215
234
|
|
235
|
+
Logger.log(
|
236
|
+
LogLevel.DEBUG,
|
237
|
+
`Parsed credential: ${JSON.stringify(parsedCredential)}\nIssued at: ${maybeIssuedAt}`
|
238
|
+
);
|
239
|
+
|
216
240
|
return {
|
217
241
|
parsedCredential,
|
218
242
|
expiration: new Date(decoded.sdJwt.payload.exp * 1000),
|
@@ -243,6 +267,7 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
|
|
243
267
|
context
|
244
268
|
) => {
|
245
269
|
if (format === "vc+sd-jwt") {
|
270
|
+
Logger.log(LogLevel.DEBUG, "Parsing credential in vc+sd-jwt format");
|
246
271
|
return verifyAndParseCredentialSdJwt(
|
247
272
|
issuerConf,
|
248
273
|
credential,
|
@@ -251,5 +276,6 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
|
|
251
276
|
);
|
252
277
|
}
|
253
278
|
|
279
|
+
Logger.log(LogLevel.ERROR, `Unsupported credential format: ${format}`);
|
254
280
|
throw new IoWalletError(`Unsupported credential format: ${format}`);
|
255
281
|
};
|
@@ -2,9 +2,9 @@ import * as z from "zod";
|
|
2
2
|
import { InvalidQRCodeError } from "./errors";
|
3
3
|
|
4
4
|
const PresentationParams = z.object({
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
client_id: z.string().nonempty(),
|
6
|
+
request_uri: z.string().url(),
|
7
|
+
request_uri_method: z.enum(["get", "post"]),
|
8
8
|
state: z.string().optional(),
|
9
9
|
});
|
10
10
|
export type PresentationParams = z.infer<typeof PresentationParams>;
|
@@ -13,24 +13,25 @@ export type PresentationParams = z.infer<typeof PresentationParams>;
|
|
13
13
|
* The beginning of the presentation flow.
|
14
14
|
* To be implemented accordind to the user touchpoint
|
15
15
|
*
|
16
|
-
* @param params Presentation parameters, depending on the starting
|
16
|
+
* @param params Presentation parameters, depending on the starting touchpoint
|
17
17
|
* @returns The url for the Relying Party to connect with
|
18
18
|
*/
|
19
|
-
export type StartFlow = (
|
20
|
-
|
21
|
-
) => PresentationParams;
|
19
|
+
export type StartFlow = (params: {
|
20
|
+
[K in keyof PresentationParams]?: PresentationParams[K] | null;
|
21
|
+
}) => PresentationParams;
|
22
22
|
|
23
23
|
/**
|
24
|
-
* Start a presentation flow by
|
24
|
+
* Start a presentation flow by validating the required parameters.
|
25
|
+
* Parameters are extracted from a url encoded in a QR code or in a deep link.
|
25
26
|
*
|
26
|
-
* @param params The
|
27
|
+
* @param params The parameters to be validated
|
27
28
|
* @returns The url for the Relying Party to connect with
|
28
|
-
* @throws If the provided
|
29
|
+
* @throws If the provided parameters are not valid
|
29
30
|
*/
|
30
31
|
export const startFlowFromQR: StartFlow = (params) => {
|
31
32
|
const result = PresentationParams.safeParse({
|
32
33
|
...params,
|
33
|
-
|
34
|
+
request_uri_method: params.request_uri_method ?? "get",
|
34
35
|
});
|
35
36
|
|
36
37
|
if (result.success) {
|
@@ -1,9 +1,8 @@
|
|
1
|
-
import { hasStatusOrThrow
|
2
|
-
import type { StartFlow } from "./01-start-flow";
|
1
|
+
import { hasStatusOrThrow } from "../../utils/misc";
|
3
2
|
import { RequestObjectWalletCapabilities } from "./types";
|
4
3
|
|
5
4
|
export type GetRequestObject = (
|
6
|
-
requestUri:
|
5
|
+
requestUri: string,
|
7
6
|
context?: {
|
8
7
|
appFetch?: GlobalFetch["fetch"];
|
9
8
|
walletCapabilities?: RequestObjectWalletCapabilities;
|
@@ -10,6 +10,7 @@ import type { Disclosure } from "../../sd-jwt/types";
|
|
10
10
|
import { ValidationFailed } from "../../utils/errors";
|
11
11
|
import { createCryptoContextFor } from "../../utils/crypto";
|
12
12
|
import type { RemotePresentation } from "./types";
|
13
|
+
import { CredentialsNotFoundError, type NotFoundDetail } from "./errors";
|
13
14
|
|
14
15
|
/**
|
15
16
|
* The purpose for the credential request by the RP.
|
@@ -47,6 +48,11 @@ type DcqlMatchSuccess = Extract<
|
|
47
48
|
{ success: true }
|
48
49
|
>;
|
49
50
|
|
51
|
+
type DcqlMatchFailure = Extract<
|
52
|
+
DcqlQueryResult.CredentialMatch,
|
53
|
+
{ success: false }
|
54
|
+
>;
|
55
|
+
|
50
56
|
/**
|
51
57
|
* Convert a credential in JWT format to an object with claims
|
52
58
|
* for correct parsing by the `dcql` library.
|
@@ -81,6 +87,32 @@ const getDcqlQueryMatches = (result: DcqlQueryResult) =>
|
|
81
87
|
([, match]) => match.success === true
|
82
88
|
) as [string, DcqlMatchSuccess][];
|
83
89
|
|
90
|
+
/**
|
91
|
+
* Extract only failed matches from the DCQL query result.
|
92
|
+
*/
|
93
|
+
const getDcqlQueryFailedMatches = (result: DcqlQueryResult) =>
|
94
|
+
Object.entries(result.credential_matches).filter(
|
95
|
+
([, match]) => match.success === false
|
96
|
+
) as [string, DcqlMatchFailure][];
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Extract missing credentials from the DCQL query result.
|
100
|
+
* Note: here we are assuming a failed match is a missing credential,
|
101
|
+
* but there might be other reasons for its failure.
|
102
|
+
*/
|
103
|
+
const extractMissingCredentials = (
|
104
|
+
queryResult: DcqlQueryResult,
|
105
|
+
originalQuery: DcqlQuery
|
106
|
+
): NotFoundDetail[] => {
|
107
|
+
return getDcqlQueryFailedMatches(queryResult).map(([id]) => {
|
108
|
+
const credential = originalQuery.credentials.find((c) => c.id === id);
|
109
|
+
if (credential?.format !== "vc+sd-jwt") {
|
110
|
+
throw new Error("Unsupported format"); // TODO [SIW-2082]: support MDOC credentials
|
111
|
+
}
|
112
|
+
return { id, vctValues: credential.meta?.vct_values };
|
113
|
+
});
|
114
|
+
};
|
115
|
+
|
84
116
|
export const evaluateDcqlQuery: EvaluateDcqlQuery = (
|
85
117
|
credentialsSdJwt,
|
86
118
|
query
|
@@ -97,8 +129,11 @@ export const evaluateDcqlQuery: EvaluateDcqlQuery = (
|
|
97
129
|
const queryResult = DcqlQuery.query(parsedQuery, credentials);
|
98
130
|
|
99
131
|
if (!queryResult.canBeSatisfied) {
|
100
|
-
throw new
|
132
|
+
throw new CredentialsNotFoundError(
|
133
|
+
extractMissingCredentials(queryResult, parsedQuery)
|
134
|
+
);
|
101
135
|
}
|
136
|
+
|
102
137
|
// Build an object vct:credentialJwt to map matched credentials to their JWT
|
103
138
|
const credentialsSdJwtByVct = credentials.reduce(
|
104
139
|
(acc, c, i) => ({ ...acc, [c.vct]: credentialsSdJwt[i]! }),
|
@@ -3,7 +3,7 @@ import { SdJwt4VC, type DisclosureWithEncoded } from "../../sd-jwt/types";
|
|
3
3
|
import { decode, prepareVpToken } from "../../sd-jwt";
|
4
4
|
import { createCryptoContextFor } from "../../utils/crypto";
|
5
5
|
import { JSONPath } from "jsonpath-plus";
|
6
|
-
import {
|
6
|
+
import { CredentialsNotFoundError, MissingDataError } from "./errors";
|
7
7
|
import Ajv from "ajv";
|
8
8
|
|
9
9
|
const ajv = new Ajv({ allErrors: true });
|
@@ -291,9 +291,12 @@ export const findCredentialSdJwt = (
|
|
291
291
|
}
|
292
292
|
}
|
293
293
|
|
294
|
-
throw new
|
295
|
-
|
296
|
-
|
294
|
+
throw new CredentialsNotFoundError([
|
295
|
+
{
|
296
|
+
id: "",
|
297
|
+
reason: "None of the vc+sd-jwt credentials satisfy the requirements.",
|
298
|
+
},
|
299
|
+
]);
|
297
300
|
};
|
298
301
|
|
299
302
|
/**
|
@@ -325,9 +328,12 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
325
328
|
inputDescriptors.map(async (descriptor) => {
|
326
329
|
if (descriptor.format?.["vc+sd-jwt"]) {
|
327
330
|
if (!decodedSdJwtCredentials.length) {
|
328
|
-
throw new
|
329
|
-
|
330
|
-
|
331
|
+
throw new CredentialsNotFoundError([
|
332
|
+
{
|
333
|
+
id: descriptor.id,
|
334
|
+
reason: "vc+sd-jwt credential is not supported.",
|
335
|
+
},
|
336
|
+
]);
|
331
337
|
}
|
332
338
|
|
333
339
|
const { matchedEvaluation, matchedKeyTag, matchedCredential } =
|
@@ -341,9 +347,12 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
341
347
|
};
|
342
348
|
}
|
343
349
|
|
344
|
-
throw new
|
345
|
-
|
346
|
-
|
350
|
+
throw new CredentialsNotFoundError([
|
351
|
+
{
|
352
|
+
id: descriptor.id,
|
353
|
+
reason: `${descriptor.format} format is not supported.`,
|
354
|
+
},
|
355
|
+
]);
|
347
356
|
})
|
348
357
|
);
|
349
358
|
};
|
@@ -385,9 +394,12 @@ export const prepareLegacyRemotePresentations: PrepareLegacyRemotePresentations
|
|
385
394
|
};
|
386
395
|
}
|
387
396
|
|
388
|
-
throw new
|
389
|
-
|
390
|
-
|
397
|
+
throw new CredentialsNotFoundError([
|
398
|
+
{
|
399
|
+
id: descriptor.id,
|
400
|
+
reason: `${descriptor.format} format is not supported.`,
|
401
|
+
},
|
402
|
+
]);
|
391
403
|
})
|
392
404
|
);
|
393
405
|
};
|
@@ -15,12 +15,20 @@ sequenceDiagram
|
|
15
15
|
O->>+I: QR-CODE: Authorization Request (`request_uri`)
|
16
16
|
I->>+O: GET: Verifier's Entity Configuration
|
17
17
|
O->>+I: Respond with metadata (including public keys)
|
18
|
-
I->>+O: GET: Request Object, resolved from
|
18
|
+
I->>+O: GET: Request Object, resolved from `request_uri`
|
19
19
|
O->>+I: Respond with the Request Object
|
20
|
-
I->>+
|
21
|
-
O
|
20
|
+
I->>+I: Validate Request Object and give consent
|
21
|
+
I->>+O: POST: Authorization Response with encrypted VP token
|
22
|
+
O->>+I: Respond with optional `redirect_uri`
|
22
23
|
```
|
23
24
|
|
25
|
+
## Mapped results
|
26
|
+
|
27
|
+
|Error|Description|
|
28
|
+
|-----|-----------|
|
29
|
+
|`ValidationFailed`|The presentation request is not valid, for instance the DCQL query is invalid.|
|
30
|
+
|`CredentialsNotFoundError`|The presentation cannot be completed because the Wallet does not contain all requested credentials. The missing credentials can be found in `details`.|
|
31
|
+
|
24
32
|
|
25
33
|
## Examples
|
26
34
|
|
@@ -35,23 +43,23 @@ const qrCodeParams = decodeQrCode(qrCode)
|
|
35
43
|
|
36
44
|
// Start the issuance flow
|
37
45
|
const {
|
38
|
-
|
39
|
-
|
40
|
-
|
46
|
+
request_uri,
|
47
|
+
client_id,
|
48
|
+
request_uri_method,
|
41
49
|
state
|
42
50
|
} = Credential.Presentation.startFlowFromQR(qrCodeParams);
|
43
51
|
|
44
52
|
// Get the Relying Party's Entity Configuration and evaluate trust
|
45
|
-
const { rpConf } = await Credential.Presentation.evaluateRelyingPartyTrust(
|
53
|
+
const { rpConf } = await Credential.Presentation.evaluateRelyingPartyTrust(client_id);
|
46
54
|
|
47
55
|
// Get the Request Object from the RP
|
48
56
|
const { requestObjectEncodedJwt } =
|
49
|
-
await Credential.Presentation.getRequestObject(
|
57
|
+
await Credential.Presentation.getRequestObject(request_uri);
|
50
58
|
|
51
59
|
// Validate the Request Object
|
52
60
|
const { requestObject } = await Credential.Presentation.verifyRequestObject(
|
53
61
|
requestObjectEncodedJwt,
|
54
|
-
{ clientId, rpConf }
|
62
|
+
{ clientId: client_id, rpConf }
|
55
63
|
);
|
56
64
|
|
57
65
|
// All the credentials that might be requested by the Relying Party
|
@@ -88,18 +88,25 @@ export class MissingDataError extends IoWalletError {
|
|
88
88
|
}
|
89
89
|
}
|
90
90
|
|
91
|
+
export type NotFoundDetail = {
|
92
|
+
id: string;
|
93
|
+
reason?: string;
|
94
|
+
vctValues?: string[];
|
95
|
+
};
|
96
|
+
|
91
97
|
/**
|
92
|
-
*
|
93
|
-
*
|
98
|
+
* Error thrown when one or more credentials cannot be found in the wallet
|
99
|
+
* and the presentation request cannot be satisfied.
|
94
100
|
*/
|
95
|
-
export class
|
96
|
-
code = "
|
101
|
+
export class CredentialsNotFoundError extends IoWalletError {
|
102
|
+
code = "ERR_CREDENTIALS_NOT_FOUND";
|
103
|
+
details: NotFoundDetail[];
|
97
104
|
|
98
105
|
/**
|
99
|
-
* @param
|
106
|
+
* @param details The details of the credentials that could not be found.
|
100
107
|
*/
|
101
|
-
constructor(
|
102
|
-
|
103
|
-
|
108
|
+
constructor(details: NotFoundDetail[]) {
|
109
|
+
super("One or more credentials cannot be found in the wallet");
|
110
|
+
this.details = details;
|
104
111
|
}
|
105
112
|
}
|