@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.
Files changed (81) hide show
  1. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +69 -35
  2. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  3. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +181 -0
  4. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -0
  5. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +3 -67
  6. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  7. package/lib/commonjs/credential/presentation/08-send-authorization-response.js +98 -2
  8. package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
  9. package/lib/commonjs/credential/presentation/index.js +14 -1
  10. package/lib/commonjs/credential/presentation/index.js.map +1 -1
  11. package/lib/commonjs/credential/presentation/types.js +9 -2
  12. package/lib/commonjs/credential/presentation/types.js.map +1 -1
  13. package/lib/commonjs/mdoc/index.js +1 -1
  14. package/lib/commonjs/mdoc/index.js.map +1 -1
  15. package/lib/commonjs/sd-jwt/__test__/index.test.js +24 -21
  16. package/lib/commonjs/sd-jwt/__test__/index.test.js.map +1 -1
  17. package/lib/commonjs/sd-jwt/__test__/types.test.js +3 -0
  18. package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -1
  19. package/lib/commonjs/sd-jwt/types.js +4 -1
  20. package/lib/commonjs/sd-jwt/types.js.map +1 -1
  21. package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js +469 -0
  22. package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js.map +1 -0
  23. package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-utils.js +90 -0
  24. package/lib/commonjs/utils/credential/issuance/07-verify-and-parse-credentials-utils.js.map +1 -0
  25. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +67 -35
  26. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  27. package/lib/module/credential/presentation/07-evaluate-dcql-query.js +175 -0
  28. package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -0
  29. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +3 -66
  30. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  31. package/lib/module/credential/presentation/08-send-authorization-response.js +97 -2
  32. package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
  33. package/lib/module/credential/presentation/index.js +4 -3
  34. package/lib/module/credential/presentation/index.js.map +1 -1
  35. package/lib/module/credential/presentation/types.js +9 -2
  36. package/lib/module/credential/presentation/types.js.map +1 -1
  37. package/lib/module/mdoc/index.js +1 -1
  38. package/lib/module/mdoc/index.js.map +1 -1
  39. package/lib/module/sd-jwt/__test__/index.test.js +24 -21
  40. package/lib/module/sd-jwt/__test__/index.test.js.map +1 -1
  41. package/lib/module/sd-jwt/__test__/types.test.js +3 -0
  42. package/lib/module/sd-jwt/__test__/types.test.js.map +1 -1
  43. package/lib/module/sd-jwt/types.js +4 -1
  44. package/lib/module/sd-jwt/types.js.map +1 -1
  45. package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js +462 -0
  46. package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-inputs.js.map +1 -0
  47. package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-utils.js +83 -0
  48. package/lib/module/utils/credential/issuance/07-verify-and-parse-credentials-utils.js.map +1 -0
  49. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +12 -0
  50. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  51. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts +27 -0
  52. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -0
  53. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts +2 -28
  54. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
  55. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +23 -1
  56. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
  57. package/lib/typescript/credential/presentation/index.d.ts +6 -4
  58. package/lib/typescript/credential/presentation/index.d.ts.map +1 -1
  59. package/lib/typescript/credential/presentation/types.d.ts +26 -7
  60. package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
  61. package/lib/typescript/sd-jwt/index.d.ts +12 -0
  62. package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
  63. package/lib/typescript/sd-jwt/types.d.ts +15 -0
  64. package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
  65. package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-inputs.d.ts +3 -0
  66. package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-inputs.d.ts.map +1 -0
  67. package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-utils.d.ts +42 -0
  68. package/lib/typescript/utils/credential/issuance/07-verify-and-parse-credentials-utils.d.ts.map +1 -0
  69. package/package.json +3 -2
  70. package/src/credential/issuance/07-verify-and-parse-credential.ts +35 -3
  71. package/src/credential/presentation/07-evaluate-dcql-query.ts +228 -0
  72. package/src/credential/presentation/07-evaluate-input-descriptor.ts +6 -103
  73. package/src/credential/presentation/08-send-authorization-response.ts +137 -2
  74. package/src/credential/presentation/index.ts +12 -2
  75. package/src/credential/presentation/types.ts +33 -3
  76. package/src/mdoc/index.ts +1 -1
  77. package/src/sd-jwt/__test__/index.test.ts +45 -36
  78. package/src/sd-jwt/__test__/types.test.ts +3 -0
  79. package/src/sd-jwt/types.ts +3 -0
  80. package/src/utils/credential/issuance/07-verify-and-parse-credentials-inputs.ts +615 -0
  81. 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 RemotePresentation } from "./types";
2
- import { decode, prepareVpToken } from "../../sd-jwt";
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 { NoSuitableKeysFoundInEntityConfiguration } from "./errors";
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.inputDescriptor.id,
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
- inputDescriptor: InputDescriptor;
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.union([z.string(), z.array(z.string())]).optional(),
134
- presentation_submission: z.record(z.string(), z.unknown()),
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]?.keyId;
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));