@pagopa/io-react-native-wallet 1.6.1 → 1.7.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/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 -57
- 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 +14 -6
- 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/index.js +7 -1
- package/lib/commonjs/sd-jwt/index.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 -56
- 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 +14 -6
- 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/index.js +7 -1
- package/lib/module/sd-jwt/index.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 -32
- 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 +32 -8
- package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
- package/lib/typescript/mdoc/index.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 +4 -3
- 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 -108
- 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 +40 -4
- package/src/mdoc/index.ts +14 -4
- package/src/sd-jwt/__test__/index.test.ts +45 -36
- package/src/sd-jwt/__test__/types.test.ts +3 -0
- package/src/sd-jwt/index.ts +5 -1
- 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,13 +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
6
|
import Ajv from "ajv";
|
8
7
|
import { CBOR } from "@pagopa/io-react-native-cbor";
|
9
|
-
import { prepareVpTokenMdoc } from "../../mdoc";
|
10
|
-
import { generateRandomAlphaNumericString } from "../../utils/misc";
|
11
8
|
|
12
9
|
const ajv = new Ajv({ allErrors: true });
|
13
10
|
|
@@ -16,12 +13,6 @@ type EvaluatedDisclosures = {
|
|
16
13
|
optionalDisclosures: EvaluatedDisclosure[];
|
17
14
|
};
|
18
15
|
|
19
|
-
export type EvaluatedDisclosure = {
|
20
|
-
namespace?: string;
|
21
|
-
name: string;
|
22
|
-
value: unknown;
|
23
|
-
};
|
24
|
-
|
25
16
|
type EvaluateInputDescriptorSdJwt4VC = (
|
26
17
|
inputDescriptor: InputDescriptor,
|
27
18
|
payloadCredential: SdJwt4VC["payload"],
|
@@ -35,8 +26,8 @@ type EvaluateInputDescriptorMdoc = (
|
|
35
26
|
|
36
27
|
export type EvaluateInputDescriptors = (
|
37
28
|
descriptors: InputDescriptor[],
|
38
|
-
credentialsSdJwt: [string /* keyTag */, string /* credential */][],
|
39
|
-
credentialsMdoc: [string /* keyTag */, string /* credential */][]
|
29
|
+
credentialsSdJwt: [string, string /* keyTag */, string /* credential */][],
|
30
|
+
credentialsMdoc: [string, string /* keyTag */, string /* credential */][]
|
40
31
|
) => Promise<
|
41
32
|
{
|
42
33
|
evaluatedDisclosure: EvaluatedDisclosures;
|
@@ -46,20 +37,6 @@ export type EvaluateInputDescriptors = (
|
|
46
37
|
}[]
|
47
38
|
>;
|
48
39
|
|
49
|
-
export type PrepareRemotePresentations = (
|
50
|
-
credentialAndDescriptors: {
|
51
|
-
requestedClaims: string[];
|
52
|
-
inputDescriptor: InputDescriptor;
|
53
|
-
credential: string;
|
54
|
-
keyTag: string;
|
55
|
-
}[],
|
56
|
-
authRequestObject: {
|
57
|
-
nonce: string;
|
58
|
-
clientId: string;
|
59
|
-
responseUri: string;
|
60
|
-
}
|
61
|
-
) => Promise<RemotePresentation>;
|
62
|
-
|
63
40
|
export const disclosureWithEncodedToEvaluatedDisclosure = (
|
64
41
|
disclosure: DisclosureWithEncoded
|
65
42
|
): EvaluatedDisclosure => {
|
@@ -479,7 +456,7 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
479
456
|
) => {
|
480
457
|
// We need decode SD-JWT credentials for evaluation
|
481
458
|
const decodedSdJwtCredentials =
|
482
|
-
credentialsSdJwt?.map(([keyTag, credential]) => {
|
459
|
+
credentialsSdJwt?.map(([, keyTag, credential]) => {
|
483
460
|
const { sdJwt, disclosures } = decode(credential);
|
484
461
|
return { keyTag, credential, sdJwt, disclosures };
|
485
462
|
}) || [];
|
@@ -487,7 +464,7 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
487
464
|
// We need decode Mdoc credentials for evaluation
|
488
465
|
const decodedMdocCredentials =
|
489
466
|
(await Promise.all(
|
490
|
-
credentialsMdoc?.map(async ([keyTag, credential]) => {
|
467
|
+
credentialsMdoc?.map(async ([, keyTag, credential]) => {
|
491
468
|
const issuerSigned = await CBOR.decodeIssuerSigned(credential);
|
492
469
|
if (!issuerSigned) {
|
493
470
|
throw new CredentialNotFoundError(
|
@@ -544,82 +521,3 @@ export const evaluateInputDescriptors: EvaluateInputDescriptors = async (
|
|
544
521
|
|
545
522
|
return results;
|
546
523
|
};
|
547
|
-
|
548
|
-
/**
|
549
|
-
* Prepares remote presentations for a set of credentials based on input descriptors.
|
550
|
-
*
|
551
|
-
* For each credential and its corresponding input descriptor, this function:
|
552
|
-
* - Validates the credential format.
|
553
|
-
* - Generates a verifiable presentation token (vpToken) using the provided nonce and client identifier.
|
554
|
-
*
|
555
|
-
* @param credentialAndDescriptors - An array containing objects with requested claims,
|
556
|
-
* input descriptor, credential, and keyTag.
|
557
|
-
* @param nonce - A unique nonce for the verifiable presentation token.
|
558
|
-
* @param client_id - The client identifier.
|
559
|
-
* @returns A promise that resolves to an array of RemotePresentation objects.
|
560
|
-
* @throws {CredentialNotFoundError} When the credential format is unsupported.
|
561
|
-
*/
|
562
|
-
export const prepareRemotePresentations: PrepareRemotePresentations = async (
|
563
|
-
credentialAndDescriptors,
|
564
|
-
authRequestObject
|
565
|
-
) => {
|
566
|
-
/* In case of ISO 18013-7 we need a nonce, it shall have a minimum entropy of 16 */
|
567
|
-
const generatedNonce = generateRandomAlphaNumericString(16);
|
568
|
-
|
569
|
-
const presentations = await Promise.all(
|
570
|
-
credentialAndDescriptors.map(async (item) => {
|
571
|
-
const descriptor = item.inputDescriptor;
|
572
|
-
|
573
|
-
if (descriptor.format?.mso_mdoc) {
|
574
|
-
const { vp_token } = await prepareVpTokenMdoc(
|
575
|
-
authRequestObject.nonce,
|
576
|
-
generatedNonce,
|
577
|
-
authRequestObject.clientId,
|
578
|
-
authRequestObject.responseUri,
|
579
|
-
descriptor.id,
|
580
|
-
item.keyTag,
|
581
|
-
[
|
582
|
-
item.credential,
|
583
|
-
item.requestedClaims,
|
584
|
-
createCryptoContextFor(item.keyTag),
|
585
|
-
]
|
586
|
-
);
|
587
|
-
|
588
|
-
return {
|
589
|
-
requestedClaims: item.requestedClaims,
|
590
|
-
inputDescriptor: descriptor,
|
591
|
-
vpToken: vp_token,
|
592
|
-
format: "mso_mdoc",
|
593
|
-
};
|
594
|
-
}
|
595
|
-
|
596
|
-
if (descriptor.format?.["vc+sd-jwt"]) {
|
597
|
-
const { vp_token } = await prepareVpToken(
|
598
|
-
authRequestObject.nonce,
|
599
|
-
authRequestObject.clientId,
|
600
|
-
[
|
601
|
-
item.credential,
|
602
|
-
item.requestedClaims,
|
603
|
-
createCryptoContextFor(item.keyTag),
|
604
|
-
]
|
605
|
-
);
|
606
|
-
|
607
|
-
return {
|
608
|
-
requestedClaims: item.requestedClaims,
|
609
|
-
inputDescriptor: descriptor,
|
610
|
-
vpToken: vp_token,
|
611
|
-
format: "vc+sd-jwt",
|
612
|
-
};
|
613
|
-
}
|
614
|
-
|
615
|
-
throw new CredentialNotFoundError(
|
616
|
-
`${descriptor.format} format is not supported.`
|
617
|
-
);
|
618
|
-
})
|
619
|
-
);
|
620
|
-
|
621
|
-
return {
|
622
|
-
presentations,
|
623
|
-
generatedNonce,
|
624
|
-
};
|
625
|
-
};
|
@@ -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
|
};
|
@@ -3,12 +3,27 @@ import { UnixTime } from "../../sd-jwt/types";
|
|
3
3
|
import * as z from "zod";
|
4
4
|
import { JWKS } from "../../utils/jwk";
|
5
5
|
|
6
|
+
export type EvaluatedDisclosure = {
|
7
|
+
namespace?: string;
|
8
|
+
name: string;
|
9
|
+
value: unknown;
|
10
|
+
};
|
11
|
+
|
12
|
+
export type CredentialFormat =
|
13
|
+
| {
|
14
|
+
format: "vc+sd-jwt";
|
15
|
+
}
|
16
|
+
| {
|
17
|
+
format: "mso_mdoc";
|
18
|
+
doctype: string;
|
19
|
+
};
|
20
|
+
|
6
21
|
/**
|
7
22
|
* A pair that associate a tokenized Verified Credential with the claims presented or requested to present.
|
8
23
|
*/
|
9
24
|
export type Presentation = [
|
10
25
|
/* verified credential token */ string,
|
11
|
-
/* claims */
|
26
|
+
/* claims */ EvaluatedDisclosure[],
|
12
27
|
/* the context for the key associated to the credential */ CryptoContext,
|
13
28
|
];
|
14
29
|
|
@@ -18,13 +33,27 @@ export type Presentation = [
|
|
18
33
|
export type RemotePresentation = {
|
19
34
|
presentations: {
|
20
35
|
requestedClaims: string[];
|
21
|
-
|
36
|
+
credentialId: string;
|
22
37
|
format: string;
|
23
38
|
vpToken: string;
|
24
39
|
}[];
|
25
40
|
generatedNonce?: string /* nonce generated by app, used in mdoc presentation */;
|
26
41
|
};
|
27
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
|
+
|
28
57
|
const Fields = z.object({
|
29
58
|
path: z.array(z.string().min(1)), // Array of JSONPath string expressions
|
30
59
|
id: z.string().optional(), // Unique string ID
|
@@ -100,6 +129,7 @@ export const RequestObject = z.object({
|
|
100
129
|
jwks: JWKS.optional(),
|
101
130
|
})
|
102
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
|
103
133
|
scope: z.string().optional(),
|
104
134
|
presentation_definition: PresentationDefinition.optional(),
|
105
135
|
});
|
@@ -124,8 +154,14 @@ export type DirectAuthorizationBodyPayload = z.infer<
|
|
124
154
|
>;
|
125
155
|
export const DirectAuthorizationBodyPayload = z.union([
|
126
156
|
z.object({
|
127
|
-
vp_token: z
|
128
|
-
|
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(),
|
129
165
|
}),
|
130
166
|
z.object({ error: ErrorResponse }),
|
131
167
|
]);
|