@pagopa/io-react-native-wallet 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +69 -35
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +181 -0
- package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -0
- package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +3 -67
- package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
- package/lib/commonjs/credential/presentation/08-send-authorization-response.js +98 -2
- package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
- package/lib/commonjs/credential/presentation/index.js +14 -1
- package/lib/commonjs/credential/presentation/index.js.map +1 -1
- package/lib/commonjs/credential/presentation/types.js +9 -2
- package/lib/commonjs/credential/presentation/types.js.map +1 -1
- package/lib/commonjs/mdoc/index.js +1 -1
- package/lib/commonjs/mdoc/index.js.map +1 -1
- package/lib/commonjs/sd-jwt/__test__/index.test.js +24 -21
- package/lib/commonjs/sd-jwt/__test__/index.test.js.map +1 -1
- package/lib/commonjs/sd-jwt/__test__/types.test.js +3 -0
- package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -1
- package/lib/commonjs/sd-jwt/types.js +4 -1
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js +469 -0
- package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js.map +1 -0
- package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-utils.js +90 -0
- package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-utils.js.map +1 -0
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +67 -35
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js +175 -0
- package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -0
- package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +3 -66
- package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
- package/lib/module/credential/presentation/08-send-authorization-response.js +97 -2
- package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
- package/lib/module/credential/presentation/index.js +4 -3
- package/lib/module/credential/presentation/index.js.map +1 -1
- package/lib/module/credential/presentation/types.js +9 -2
- package/lib/module/credential/presentation/types.js.map +1 -1
- package/lib/module/mdoc/index.js +1 -1
- package/lib/module/mdoc/index.js.map +1 -1
- package/lib/module/sd-jwt/__test__/index.test.js +24 -21
- package/lib/module/sd-jwt/__test__/index.test.js.map +1 -1
- package/lib/module/sd-jwt/__test__/types.test.js +3 -0
- package/lib/module/sd-jwt/__test__/types.test.js.map +1 -1
- package/lib/module/sd-jwt/types.js +4 -1
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js +462 -0
- package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js.map +1 -0
- package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-utils.js +83 -0
- package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-utils.js.map +1 -0
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +12 -0
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts +27 -0
- package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts +2 -28
- package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +23 -1
- package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/index.d.ts +6 -4
- package/lib/typescript/credential/presentation/index.d.ts.map +1 -1
- package/lib/typescript/credential/presentation/types.d.ts +26 -7
- package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/index.d.ts +12 -0
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/types.d.ts +15 -0
- package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
- package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-inputs.d.ts +3 -0
- package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-inputs.d.ts.map +1 -0
- package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-utils.d.ts +42 -0
- package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-utils.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/credential/issuance/07-verify-and-parse-credential.ts +35 -3
- package/src/credential/presentation/07-evaluate-dcql-query.ts +228 -0
- package/src/credential/presentation/07-evaluate-input-descriptor.ts +6 -103
- package/src/credential/presentation/08-send-authorization-response.ts +137 -2
- package/src/credential/presentation/index.ts +12 -2
- package/src/credential/presentation/types.ts +33 -3
- package/src/mdoc/index.ts +1 -1
- package/src/sd-jwt/__test__/index.test.ts +45 -36
- package/src/sd-jwt/__test__/types.test.ts +3 -0
- package/src/sd-jwt/types.ts +3 -0
- package/src/utils/credential/issuance/07-verify-and-parse-credentials-inputs.ts +615 -0
- package/src/utils/credential/issuance/07-verify-and-parse-credentials-utils.ts +171 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
import {
|
2
|
+
DcqlQuery,
|
3
|
+
DcqlError,
|
4
|
+
DcqlCredentialSetError,
|
5
|
+
DcqlQueryResult,
|
6
|
+
DcqlCredential,
|
7
|
+
} from "dcql";
|
8
|
+
import { isValiError } from "valibot";
|
9
|
+
import { decode } from "../../sd-jwt";
|
10
|
+
import type { Disclosure } from "../../sd-jwt/types";
|
11
|
+
import { ValidationFailed } from "../../utils/errors";
|
12
|
+
import { CredentialNotFoundError } from "./errors";
|
13
|
+
import type { CredentialFormat, EvaluatedDisclosure } from "./types";
|
14
|
+
import { CBOR } from "@pagopa/io-react-native-cbor";
|
15
|
+
|
16
|
+
/**
|
17
|
+
* The purpose for the credential request by the RP.
|
18
|
+
*/
|
19
|
+
type CredentialPurpose = {
|
20
|
+
required: boolean;
|
21
|
+
description?: string;
|
22
|
+
};
|
23
|
+
|
24
|
+
export type EvaluateDcqlQuery = (
|
25
|
+
query: DcqlQuery.Input,
|
26
|
+
credentialsSdJwt: [
|
27
|
+
string /* type */,
|
28
|
+
string /* keyTag */,
|
29
|
+
string /* credential */,
|
30
|
+
][],
|
31
|
+
credentialsMdoc: [
|
32
|
+
string /* type */,
|
33
|
+
string /* keyTag */,
|
34
|
+
string /* credential */,
|
35
|
+
][]
|
36
|
+
) => Promise<
|
37
|
+
({
|
38
|
+
id: string;
|
39
|
+
credential: string;
|
40
|
+
keyTag: string;
|
41
|
+
requiredDisclosures: EvaluatedDisclosure[];
|
42
|
+
purposes: CredentialPurpose[];
|
43
|
+
} & CredentialFormat)[]
|
44
|
+
>;
|
45
|
+
|
46
|
+
type DcqlMatchSuccess = Extract<
|
47
|
+
DcqlQueryResult.CredentialMatch,
|
48
|
+
{ success: true }
|
49
|
+
>;
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Convert a credential in SD-JWT format to an object with claims
|
53
|
+
* for correct parsing by the `dcql` library.
|
54
|
+
*/
|
55
|
+
const mapCredentialSdJwtToObj = (credentials: [string, string, string][]) =>
|
56
|
+
credentials.map(([, , jwt]) => {
|
57
|
+
const { sdJwt, disclosures } = decode(jwt);
|
58
|
+
const credentialFormat = sdJwt.header.typ;
|
59
|
+
|
60
|
+
return {
|
61
|
+
vct: sdJwt.payload.vct,
|
62
|
+
credential_format: credentialFormat,
|
63
|
+
claims: disclosures.reduce(
|
64
|
+
(acc, disclosure) => ({
|
65
|
+
...acc,
|
66
|
+
[disclosure.decoded[1]]: disclosure.decoded,
|
67
|
+
}),
|
68
|
+
{} as Record<string, Disclosure>
|
69
|
+
),
|
70
|
+
} as DcqlCredential;
|
71
|
+
});
|
72
|
+
|
73
|
+
/**
|
74
|
+
* Convert a credential in Mdoc format to an object with claims
|
75
|
+
* for correct parsing by the `dcql` library.
|
76
|
+
*/
|
77
|
+
const mapCredentialsMdocToObj = async (
|
78
|
+
credentialsMdoc: [string, string, string][]
|
79
|
+
) => {
|
80
|
+
return await Promise.all(
|
81
|
+
credentialsMdoc?.map(async ([type, _, credential]) => {
|
82
|
+
const issuerSigned = credential
|
83
|
+
? await CBOR.decodeIssuerSigned(credential)
|
84
|
+
: undefined;
|
85
|
+
if (!issuerSigned) {
|
86
|
+
throw new CredentialNotFoundError(
|
87
|
+
"mso_mdoc credential is not present."
|
88
|
+
);
|
89
|
+
}
|
90
|
+
|
91
|
+
const namespaces = Object.entries(issuerSigned.nameSpaces).reduce(
|
92
|
+
(acc, [ns, nsClaims]) => {
|
93
|
+
const flattenNsClaims = Object.entries(nsClaims).reduce(
|
94
|
+
(ac, [, el]) => ({
|
95
|
+
...ac,
|
96
|
+
[el.elementIdentifier]: el.elementValue,
|
97
|
+
}),
|
98
|
+
{} as Record<string, unknown>
|
99
|
+
);
|
100
|
+
|
101
|
+
return {
|
102
|
+
...acc,
|
103
|
+
[ns]: flattenNsClaims,
|
104
|
+
};
|
105
|
+
},
|
106
|
+
{} as Record<string, unknown>
|
107
|
+
);
|
108
|
+
|
109
|
+
return {
|
110
|
+
credential_format: "mso_mdoc",
|
111
|
+
doctype: type,
|
112
|
+
namespaces,
|
113
|
+
} as DcqlCredential;
|
114
|
+
})
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Extract only successful matches from the DCQL query result.
|
120
|
+
*/
|
121
|
+
const getDcqlQueryMatches = (result: DcqlQueryResult) =>
|
122
|
+
Object.entries(result.credential_matches).filter(
|
123
|
+
([, match]) => match.success === true
|
124
|
+
) as [string, DcqlMatchSuccess][];
|
125
|
+
|
126
|
+
export const evaluateDcqlQuery: EvaluateDcqlQuery = async (
|
127
|
+
query,
|
128
|
+
credentialsSdJwt,
|
129
|
+
credentialsMdoc
|
130
|
+
) => {
|
131
|
+
const credentials = [] as DcqlCredential[];
|
132
|
+
credentials.push(...mapCredentialSdJwtToObj(credentialsSdJwt));
|
133
|
+
credentials.push(...(await mapCredentialsMdocToObj(credentialsMdoc)));
|
134
|
+
|
135
|
+
try {
|
136
|
+
// Validate the query
|
137
|
+
const parsedQuery = DcqlQuery.parse(query);
|
138
|
+
DcqlQuery.validate(parsedQuery);
|
139
|
+
|
140
|
+
const queryResult = DcqlQuery.query(parsedQuery, credentials);
|
141
|
+
|
142
|
+
if (!queryResult.canBeSatisfied) {
|
143
|
+
throw new Error("No credential can satisfy the provided DCQL query");
|
144
|
+
}
|
145
|
+
|
146
|
+
return getDcqlQueryMatches(queryResult).map(([id, match]) => {
|
147
|
+
const purposes = queryResult.credential_sets
|
148
|
+
?.filter((set) => set.matching_options?.flat().includes(id))
|
149
|
+
?.map<CredentialPurpose>((credentialSet) => ({
|
150
|
+
description: credentialSet.purpose?.toString(),
|
151
|
+
required: Boolean(credentialSet.required),
|
152
|
+
}));
|
153
|
+
|
154
|
+
if (match.output.credential_format === "vc+sd-jwt") {
|
155
|
+
const { vct, claims } = match.output;
|
156
|
+
|
157
|
+
const [, keyTag, credential] = credentialsSdJwt.find(
|
158
|
+
([type]) => type === vct
|
159
|
+
)!;
|
160
|
+
const requiredDisclosures = Object.values(
|
161
|
+
claims
|
162
|
+
) as EvaluatedDisclosure[];
|
163
|
+
return {
|
164
|
+
id,
|
165
|
+
vct,
|
166
|
+
keyTag,
|
167
|
+
format: match.output.credential_format,
|
168
|
+
credential,
|
169
|
+
requiredDisclosures,
|
170
|
+
// When it is a match but no credential_sets are found, the credential is required by default
|
171
|
+
// See https://openid.net/specs/openid-4-verifiable-presentations-1_0-24.html#section-6.3.1.2-2.1
|
172
|
+
purposes: purposes ?? [{ required: true }],
|
173
|
+
};
|
174
|
+
}
|
175
|
+
|
176
|
+
if (match.output.credential_format === "mso_mdoc") {
|
177
|
+
const { doctype, namespaces } = match.output;
|
178
|
+
|
179
|
+
const [, keyTag, credential] = credentialsMdoc.find(
|
180
|
+
([type]) => type === doctype
|
181
|
+
)!;
|
182
|
+
const requiredDisclosures = Object.entries(namespaces).reduce(
|
183
|
+
(acc, [ns, nsClaims]) => [
|
184
|
+
...acc,
|
185
|
+
...Object.entries(nsClaims).map(([claimName]) => ({
|
186
|
+
namespace: ns,
|
187
|
+
name: claimName,
|
188
|
+
value: nsClaims[claimName],
|
189
|
+
})),
|
190
|
+
],
|
191
|
+
[] as EvaluatedDisclosure[]
|
192
|
+
);
|
193
|
+
|
194
|
+
return {
|
195
|
+
id,
|
196
|
+
keyTag,
|
197
|
+
format: match.output.credential_format,
|
198
|
+
credential,
|
199
|
+
requiredDisclosures,
|
200
|
+
// When it is a match but no credential_sets are found, the credential is required by default
|
201
|
+
// See https://openid.net/specs/openid-4-verifiable-presentations-1_0-24.html#section-6.3.1.2-2.1
|
202
|
+
purposes: purposes ?? [{ required: true }],
|
203
|
+
doctype,
|
204
|
+
};
|
205
|
+
}
|
206
|
+
|
207
|
+
throw new Error(
|
208
|
+
`Unsupported credential format: ${match.output.credential_format}`
|
209
|
+
);
|
210
|
+
});
|
211
|
+
} catch (error) {
|
212
|
+
// Invalid DCQL query structure
|
213
|
+
if (isValiError(error)) {
|
214
|
+
throw new ValidationFailed({
|
215
|
+
message: "Invalid DCQL query",
|
216
|
+
reason: error.issues.map((issue) => issue.message).join(", "),
|
217
|
+
});
|
218
|
+
}
|
219
|
+
|
220
|
+
if (error instanceof DcqlError) {
|
221
|
+
// TODO [SIW-2110]: handle invalid DQCL query or let the error propagate
|
222
|
+
}
|
223
|
+
if (error instanceof DcqlCredentialSetError) {
|
224
|
+
// TODO [SIW-2110]: handle missing credentials or let the error propagate
|
225
|
+
}
|
226
|
+
throw error;
|
227
|
+
}
|
228
|
+
};
|
@@ -1,14 +1,10 @@
|
|
1
|
-
import { InputDescriptor, type
|
2
|
-
import { decode
|
1
|
+
import { InputDescriptor, type EvaluatedDisclosure } from "./types";
|
2
|
+
import { decode } from "../../sd-jwt";
|
3
3
|
import { SdJwt4VC, type DisclosureWithEncoded } from "../../sd-jwt/types";
|
4
|
-
import { createCryptoContextFor } from "../../utils/crypto";
|
5
4
|
import { JSONPath } from "jsonpath-plus";
|
6
5
|
import { MissingDataError, CredentialNotFoundError } from "./errors";
|
7
|
-
import { type EvaluatedDisclosure } from "./types";
|
8
6
|
import Ajv from "ajv";
|
9
7
|
import { CBOR } from "@pagopa/io-react-native-cbor";
|
10
|
-
import { prepareVpTokenMdoc } from "../../mdoc";
|
11
|
-
import { generateRandomAlphaNumericString } from "../../utils/misc";
|
12
8
|
|
13
9
|
const ajv = new Ajv({ allErrors: true });
|
14
10
|
|
@@ -30,8 +26,8 @@ type EvaluateInputDescriptorMdoc = (
|
|
30
26
|
|
31
27
|
export type EvaluateInputDescriptors = (
|
32
28
|
descriptors: InputDescriptor[],
|
33
|
-
credentialsSdJwt: [string /* keyTag */, string /* credential */][],
|
34
|
-
credentialsMdoc: [string /* keyTag */, string /* credential */][]
|
29
|
+
credentialsSdJwt: [string, string /* keyTag */, string /* credential */][],
|
30
|
+
credentialsMdoc: [string, string /* keyTag */, string /* credential */][]
|
35
31
|
) => Promise<
|
36
32
|
{
|
37
33
|
evaluatedDisclosure: EvaluatedDisclosures;
|
@@ -41,20 +37,6 @@ export type EvaluateInputDescriptors = (
|
|
41
37
|
}[]
|
42
38
|
>;
|
43
39
|
|
44
|
-
export type PrepareRemotePresentations = (
|
45
|
-
credentialAndDescriptors: {
|
46
|
-
requestedClaims: EvaluatedDisclosure[];
|
47
|
-
inputDescriptor: InputDescriptor;
|
48
|
-
credential: string;
|
49
|
-
keyTag: string;
|
50
|
-
}[],
|
51
|
-
authRequestObject: {
|
52
|
-
nonce: string;
|
53
|
-
clientId: string;
|
54
|
-
responseUri: string;
|
55
|
-
}
|
56
|
-
) => Promise<RemotePresentation>;
|
57
|
-
|
58
40
|
export const disclosureWithEncodedToEvaluatedDisclosure = (
|
59
41
|
disclosure: DisclosureWithEncoded
|
60
42
|
): EvaluatedDisclosure => {
|
@@ -474,7 +456,7 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
474
456
|
) => {
|
475
457
|
// We need decode SD-JWT credentials for evaluation
|
476
458
|
const decodedSdJwtCredentials =
|
477
|
-
credentialsSdJwt?.map(([keyTag, credential]) => {
|
459
|
+
credentialsSdJwt?.map(([, keyTag, credential]) => {
|
478
460
|
const { sdJwt, disclosures } = decode(credential);
|
479
461
|
return { keyTag, credential, sdJwt, disclosures };
|
480
462
|
}) || [];
|
@@ -482,7 +464,7 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
482
464
|
// We need decode Mdoc credentials for evaluation
|
483
465
|
const decodedMdocCredentials =
|
484
466
|
(await Promise.all(
|
485
|
-
credentialsMdoc?.map(async ([keyTag, credential]) => {
|
467
|
+
credentialsMdoc?.map(async ([, keyTag, credential]) => {
|
486
468
|
const issuerSigned = await CBOR.decodeIssuerSigned(credential);
|
487
469
|
if (!issuerSigned) {
|
488
470
|
throw new CredentialNotFoundError(
|
@@ -539,82 +521,3 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
539
521
|
|
540
522
|
return results;
|
541
523
|
};
|
542
|
-
|
543
|
-
/**
|
544
|
-
* Prepares remote presentations for a set of credentials based on input descriptors.
|
545
|
-
*
|
546
|
-
* For each credential and its corresponding input descriptor, this function:
|
547
|
-
* - Validates the credential format.
|
548
|
-
* - Generates a verifiable presentation token (vpToken) using the provided nonce and client identifier.
|
549
|
-
*
|
550
|
-
* @param credentialAndDescriptors - An array containing objects with requested claims,
|
551
|
-
* input descriptor, credential, and keyTag.
|
552
|
-
* @param nonce - A unique nonce for the verifiable presentation token.
|
553
|
-
* @param client_id - The client identifier.
|
554
|
-
* @returns A promise that resolves to an array of RemotePresentation objects.
|
555
|
-
* @throws {CredentialNotFoundError} When the credential format is unsupported.
|
556
|
-
*/
|
557
|
-
export const prepareRemotePresentations: PrepareRemotePresentations = async (
|
558
|
-
credentialAndDescriptors,
|
559
|
-
authRequestObject
|
560
|
-
) => {
|
561
|
-
/* In case of ISO 18013-7 we need a nonce, it shall have a minimum entropy of 16 */
|
562
|
-
const generatedNonce = generateRandomAlphaNumericString(16);
|
563
|
-
|
564
|
-
const presentations = await Promise.all(
|
565
|
-
credentialAndDescriptors.map(async (item) => {
|
566
|
-
const descriptor = item.inputDescriptor;
|
567
|
-
|
568
|
-
if (descriptor.format?.mso_mdoc) {
|
569
|
-
const { vp_token } = await prepareVpTokenMdoc(
|
570
|
-
authRequestObject.nonce,
|
571
|
-
generatedNonce,
|
572
|
-
authRequestObject.clientId,
|
573
|
-
authRequestObject.responseUri,
|
574
|
-
descriptor.id,
|
575
|
-
item.keyTag,
|
576
|
-
[
|
577
|
-
item.credential,
|
578
|
-
item.requestedClaims,
|
579
|
-
createCryptoContextFor(item.keyTag),
|
580
|
-
]
|
581
|
-
);
|
582
|
-
|
583
|
-
return {
|
584
|
-
requestedClaims: [...item.requestedClaims.map(({ name }) => name)],
|
585
|
-
inputDescriptor: descriptor,
|
586
|
-
vpToken: vp_token,
|
587
|
-
format: "mso_mdoc",
|
588
|
-
};
|
589
|
-
}
|
590
|
-
|
591
|
-
if (descriptor.format?.["vc+sd-jwt"]) {
|
592
|
-
const { vp_token } = await prepareVpToken(
|
593
|
-
authRequestObject.nonce,
|
594
|
-
authRequestObject.clientId,
|
595
|
-
[
|
596
|
-
item.credential,
|
597
|
-
item.requestedClaims,
|
598
|
-
createCryptoContextFor(item.keyTag),
|
599
|
-
]
|
600
|
-
);
|
601
|
-
|
602
|
-
return {
|
603
|
-
requestedClaims: [...item.requestedClaims.map(({ name }) => name)],
|
604
|
-
inputDescriptor: descriptor,
|
605
|
-
vpToken: vp_token,
|
606
|
-
format: "vc+sd-jwt",
|
607
|
-
};
|
608
|
-
}
|
609
|
-
|
610
|
-
throw new CredentialNotFoundError(
|
611
|
-
`${descriptor.format} format is not supported.`
|
612
|
-
);
|
613
|
-
})
|
614
|
-
);
|
615
|
-
|
616
|
-
return {
|
617
|
-
presentations,
|
618
|
-
generatedNonce,
|
619
|
-
};
|
620
|
-
};
|
@@ -2,17 +2,26 @@ import { EncryptJwe } from "@pagopa/io-react-native-jwt";
|
|
2
2
|
import uuid from "react-native-uuid";
|
3
3
|
import type { FetchJwks } from "./04-retrieve-rp-jwks";
|
4
4
|
import type { VerifyRequestObjectSignature } from "./05-verify-request-object";
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
NoSuitableKeysFoundInEntityConfiguration,
|
7
|
+
CredentialNotFoundError,
|
8
|
+
} from "./errors";
|
6
9
|
import { hasStatusOrThrow, type Out } from "../../utils/misc";
|
7
10
|
import {
|
8
11
|
DirectAuthorizationBodyPayload,
|
9
12
|
ErrorResponse,
|
10
13
|
type RemotePresentation,
|
14
|
+
type PrepareRemotePresentations,
|
11
15
|
} from "./types";
|
12
16
|
import * as z from "zod";
|
13
17
|
import type { JWK } from "../../utils/jwk";
|
14
18
|
import { Base64 } from "js-base64";
|
15
19
|
|
20
|
+
import { prepareVpTokenMdoc } from "../../mdoc";
|
21
|
+
import { generateRandomAlphaNumericString } from "../../utils/misc";
|
22
|
+
import { createCryptoContextFor } from "../../utils/crypto";
|
23
|
+
import { prepareVpToken } from "../../sd-jwt";
|
24
|
+
|
16
25
|
export type AuthorizationResponse = z.infer<typeof AuthorizationResponse>;
|
17
26
|
export const AuthorizationResponse = z.object({
|
18
27
|
status: z.string().optional(),
|
@@ -153,6 +162,19 @@ export type SendAuthorizationResponse = (
|
|
153
162
|
}
|
154
163
|
) => Promise<AuthorizationResponse>;
|
155
164
|
|
165
|
+
/**
|
166
|
+
* Type definition for the function that sends the authorization response
|
167
|
+
* to the Relying Party, completing the presentation flow.
|
168
|
+
*/
|
169
|
+
export type SendAuthorizationResponseDcql = (
|
170
|
+
requestObject: Out<VerifyRequestObjectSignature>["requestObject"],
|
171
|
+
jwkKeys: Out<FetchJwks>["keys"],
|
172
|
+
remotePresentation: RemotePresentation,
|
173
|
+
context?: {
|
174
|
+
appFetch?: GlobalFetch["fetch"];
|
175
|
+
}
|
176
|
+
) => Promise<AuthorizationResponse>;
|
177
|
+
|
156
178
|
/**
|
157
179
|
* Sends the authorization response to the Relying Party (RP) using the specified `response_mode`.
|
158
180
|
* This function completes the presentation flow in an OpenID 4 Verifiable Presentations scenario.
|
@@ -183,7 +205,7 @@ export const sendAuthorizationResponse: SendAuthorizationResponse = async (
|
|
183
205
|
: presentations.map((presentation) => presentation.vpToken);
|
184
206
|
|
185
207
|
const descriptor_map = presentations.map((presentation, index) => ({
|
186
|
-
id: presentation.
|
208
|
+
id: presentation.credentialId,
|
187
209
|
path: presentations?.length === 1 ? `$` : `$[${index}]`,
|
188
210
|
format: presentation.format,
|
189
211
|
}));
|
@@ -274,3 +296,116 @@ export const sendAuthorizationErrorResponse: SendAuthorizationErrorResponse =
|
|
274
296
|
.then((res) => res.json())
|
275
297
|
.then(AuthorizationResponse.parse);
|
276
298
|
};
|
299
|
+
|
300
|
+
export const sendAuthorizationResponseDcql: SendAuthorizationResponseDcql =
|
301
|
+
async (
|
302
|
+
requestObject,
|
303
|
+
jwkKeys,
|
304
|
+
remotePresentation,
|
305
|
+
{ appFetch = fetch } = {}
|
306
|
+
): Promise<AuthorizationResponse> => {
|
307
|
+
const { generatedNonce, presentations } = remotePresentation;
|
308
|
+
// 1. Prepare the VP token as a JSON object with keys corresponding to the DCQL query credential IDs
|
309
|
+
const requestBody = await buildDirectPostJwtBody(
|
310
|
+
jwkKeys,
|
311
|
+
requestObject,
|
312
|
+
{
|
313
|
+
vp_token: presentations.reduce(
|
314
|
+
(acc, presentation) => ({
|
315
|
+
...acc,
|
316
|
+
[presentation.credentialId]: presentation.vpToken,
|
317
|
+
}),
|
318
|
+
{} as Record<string, string>
|
319
|
+
),
|
320
|
+
},
|
321
|
+
generatedNonce
|
322
|
+
);
|
323
|
+
|
324
|
+
// 2. Send the authorization response via HTTP POST and validate the response
|
325
|
+
return await appFetch(requestObject.response_uri, {
|
326
|
+
method: "POST",
|
327
|
+
headers: {
|
328
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
329
|
+
},
|
330
|
+
body: requestBody,
|
331
|
+
})
|
332
|
+
.then(hasStatusOrThrow(200))
|
333
|
+
.then((res) => res.json())
|
334
|
+
.then(AuthorizationResponse.parse);
|
335
|
+
};
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Prepares remote presentations for a set of credentials.
|
339
|
+
*
|
340
|
+
* For each credential, this function:
|
341
|
+
* - Validates the credential format (currently supports 'mso_mdoc' and 'vc+sd-jwt').
|
342
|
+
* - Generates a verifiable presentation token (vpToken) using the appropriate method.
|
343
|
+
* - For ISO 18013-7, generates a special nonce with minimum entropy of 16.
|
344
|
+
*
|
345
|
+
* @param credentials - An array of credential items containing format, credential data, requested claims, and key information.
|
346
|
+
* @param authRequestObject - The authentication request object containing nonce, clientId, and responseUri.
|
347
|
+
* @returns A promise that resolves to an object containing an array of presentations and the generated nonce.
|
348
|
+
* @throws {CredentialNotFoundError} When the credential format is unsupported.
|
349
|
+
*/
|
350
|
+
export const prepareRemotePresentations: PrepareRemotePresentations = async (
|
351
|
+
credentials,
|
352
|
+
authRequestObject
|
353
|
+
) => {
|
354
|
+
/* In case of ISO 18013-7 we need a nonce, it shall have a minimum entropy of 16 */
|
355
|
+
const generatedNonce = generateRandomAlphaNumericString(16);
|
356
|
+
|
357
|
+
const presentations = await Promise.all(
|
358
|
+
credentials.map(async (item) => {
|
359
|
+
const { credentialInputId, format } = item;
|
360
|
+
|
361
|
+
if (format === "mso_mdoc") {
|
362
|
+
const { vp_token } = await prepareVpTokenMdoc(
|
363
|
+
authRequestObject.nonce,
|
364
|
+
generatedNonce,
|
365
|
+
authRequestObject.clientId,
|
366
|
+
authRequestObject.responseUri,
|
367
|
+
item.doctype,
|
368
|
+
item.keyTag,
|
369
|
+
[
|
370
|
+
item.credential,
|
371
|
+
item.requestedClaims,
|
372
|
+
createCryptoContextFor(item.keyTag),
|
373
|
+
]
|
374
|
+
);
|
375
|
+
|
376
|
+
return {
|
377
|
+
requestedClaims: [...item.requestedClaims.map(({ name }) => name)],
|
378
|
+
credentialId: credentialInputId,
|
379
|
+
vpToken: vp_token,
|
380
|
+
format: "mso_mdoc",
|
381
|
+
};
|
382
|
+
}
|
383
|
+
|
384
|
+
if (format === "vc+sd-jwt") {
|
385
|
+
const { vp_token } = await prepareVpToken(
|
386
|
+
authRequestObject.nonce,
|
387
|
+
authRequestObject.clientId,
|
388
|
+
[
|
389
|
+
item.credential,
|
390
|
+
item.requestedClaims,
|
391
|
+
createCryptoContextFor(item.keyTag),
|
392
|
+
]
|
393
|
+
);
|
394
|
+
|
395
|
+
return {
|
396
|
+
requestedClaims: [...item.requestedClaims.map(({ name }) => name)],
|
397
|
+
credentialId: credentialInputId,
|
398
|
+
vpToken: vp_token,
|
399
|
+
format: "vc+sd-jwt",
|
400
|
+
};
|
401
|
+
}
|
402
|
+
|
403
|
+
throw new CredentialNotFoundError(`${format} format is not supported.`);
|
404
|
+
})
|
405
|
+
);
|
406
|
+
|
407
|
+
return {
|
408
|
+
presentations,
|
409
|
+
generatedNonce,
|
410
|
+
};
|
411
|
+
};
|
@@ -22,17 +22,23 @@ import {
|
|
22
22
|
} from "./06-fetch-presentation-definition";
|
23
23
|
import {
|
24
24
|
evaluateInputDescriptors,
|
25
|
-
prepareRemotePresentations,
|
26
25
|
type EvaluateInputDescriptors,
|
27
|
-
type PrepareRemotePresentations,
|
28
26
|
} from "./07-evaluate-input-descriptor";
|
29
27
|
import {
|
28
|
+
evaluateDcqlQuery,
|
29
|
+
type EvaluateDcqlQuery,
|
30
|
+
} from "./07-evaluate-dcql-query";
|
31
|
+
import {
|
32
|
+
prepareRemotePresentations,
|
30
33
|
sendAuthorizationResponse,
|
31
34
|
type SendAuthorizationResponse,
|
32
35
|
sendAuthorizationErrorResponse,
|
33
36
|
type SendAuthorizationErrorResponse,
|
37
|
+
sendAuthorizationResponseDcql,
|
38
|
+
type SendAuthorizationResponseDcql,
|
34
39
|
} from "./08-send-authorization-response";
|
35
40
|
import * as Errors from "./errors";
|
41
|
+
import type { PrepareRemotePresentations } from "./types";
|
36
42
|
|
37
43
|
export {
|
38
44
|
startFlowFromQR,
|
@@ -43,8 +49,10 @@ export {
|
|
43
49
|
verifyRequestObjectSignature,
|
44
50
|
fetchPresentDefinition,
|
45
51
|
evaluateInputDescriptors,
|
52
|
+
evaluateDcqlQuery,
|
46
53
|
sendAuthorizationResponse,
|
47
54
|
sendAuthorizationErrorResponse,
|
55
|
+
sendAuthorizationResponseDcql,
|
48
56
|
prepareRemotePresentations,
|
49
57
|
Errors,
|
50
58
|
};
|
@@ -58,5 +66,7 @@ export type {
|
|
58
66
|
EvaluateInputDescriptors,
|
59
67
|
PrepareRemotePresentations,
|
60
68
|
SendAuthorizationResponse,
|
69
|
+
SendAuthorizationResponseDcql,
|
61
70
|
SendAuthorizationErrorResponse,
|
71
|
+
EvaluateDcqlQuery,
|
62
72
|
};
|
@@ -9,6 +9,15 @@ export type EvaluatedDisclosure = {
|
|
9
9
|
value: unknown;
|
10
10
|
};
|
11
11
|
|
12
|
+
export type CredentialFormat =
|
13
|
+
| {
|
14
|
+
format: "vc+sd-jwt";
|
15
|
+
}
|
16
|
+
| {
|
17
|
+
format: "mso_mdoc";
|
18
|
+
doctype: string;
|
19
|
+
};
|
20
|
+
|
12
21
|
/**
|
13
22
|
* A pair that associate a tokenized Verified Credential with the claims presented or requested to present.
|
14
23
|
*/
|
@@ -24,13 +33,27 @@ export type Presentation = [
|
|
24
33
|
export type RemotePresentation = {
|
25
34
|
presentations: {
|
26
35
|
requestedClaims: string[];
|
27
|
-
|
36
|
+
credentialId: string;
|
28
37
|
format: string;
|
29
38
|
vpToken: string;
|
30
39
|
}[];
|
31
40
|
generatedNonce?: string /* nonce generated by app, used in mdoc presentation */;
|
32
41
|
};
|
33
42
|
|
43
|
+
export type PrepareRemotePresentations = (
|
44
|
+
credentials: ({
|
45
|
+
requestedClaims: EvaluatedDisclosure[];
|
46
|
+
credentialInputId: string; // The credential ID descriptor in the presentation definition or DCQL query
|
47
|
+
credential: string;
|
48
|
+
keyTag: string;
|
49
|
+
} & CredentialFormat)[],
|
50
|
+
authRequestObject: {
|
51
|
+
nonce: string;
|
52
|
+
clientId: string;
|
53
|
+
responseUri: string;
|
54
|
+
}
|
55
|
+
) => Promise<RemotePresentation>;
|
56
|
+
|
34
57
|
const Fields = z.object({
|
35
58
|
path: z.array(z.string().min(1)), // Array of JSONPath string expressions
|
36
59
|
id: z.string().optional(), // Unique string ID
|
@@ -106,6 +129,7 @@ export const RequestObject = z.object({
|
|
106
129
|
jwks: JWKS.optional(),
|
107
130
|
})
|
108
131
|
.optional(), // previous z.literal("entity_id"),
|
132
|
+
dcql_query: z.record(z.string(), z.any()).optional(), // Validation happens within the `dcql` library, no need to duplicate it here
|
109
133
|
scope: z.string().optional(),
|
110
134
|
presentation_definition: PresentationDefinition.optional(),
|
111
135
|
});
|
@@ -130,8 +154,14 @@ export type DirectAuthorizationBodyPayload = z.infer<
|
|
130
154
|
>;
|
131
155
|
export const DirectAuthorizationBodyPayload = z.union([
|
132
156
|
z.object({
|
133
|
-
vp_token: z
|
134
|
-
|
157
|
+
vp_token: z
|
158
|
+
.union([
|
159
|
+
z.string(), // Presentation Definition with one credential
|
160
|
+
z.array(z.string()), // Presentation Definition with more credential
|
161
|
+
z.record(z.string(), z.string()), // DCQL query
|
162
|
+
])
|
163
|
+
.optional(),
|
164
|
+
presentation_submission: z.record(z.string(), z.unknown()).optional(),
|
135
165
|
}),
|
136
166
|
z.object({ error: ErrorResponse }),
|
137
167
|
]);
|
package/src/mdoc/index.ts
CHANGED
@@ -20,7 +20,7 @@ export const verify = async (
|
|
20
20
|
throw new Error("Invalid mDoc");
|
21
21
|
}
|
22
22
|
|
23
|
-
const cert = issuerSigned.issuerAuth.unprotectedHeader[0]?.
|
23
|
+
const cert = issuerSigned.issuerAuth.unprotectedHeader[0]?.x5chain?.[0];
|
24
24
|
if (!cert) throw new Error("Certificate not present in credential");
|
25
25
|
|
26
26
|
const pemcert = convertCertToPem(b64utob64(cert));
|