@pagopa/io-react-native-wallet 2.0.0-next.6 → 2.0.0-next.8

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 (69) hide show
  1. package/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js +6 -1
  2. package/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js.map +1 -1
  3. package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
  4. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +189 -9
  5. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  6. package/lib/commonjs/credential/issuance/README.md +7 -2
  7. package/lib/commonjs/mdoc/const.js +9 -0
  8. package/lib/commonjs/mdoc/const.js.map +1 -0
  9. package/lib/commonjs/mdoc/converter.js +26 -0
  10. package/lib/commonjs/mdoc/converter.js.map +1 -0
  11. package/lib/commonjs/mdoc/index.js +74 -0
  12. package/lib/commonjs/mdoc/index.js.map +1 -0
  13. package/lib/commonjs/mdoc/utils.js +14 -0
  14. package/lib/commonjs/mdoc/utils.js.map +1 -0
  15. package/lib/commonjs/trust/types.js.map +1 -1
  16. package/lib/commonjs/utils/crypto.js +33 -1
  17. package/lib/commonjs/utils/crypto.js.map +1 -1
  18. package/lib/module/credential/issuance/02-evaluate-issuer-trust.js +6 -1
  19. package/lib/module/credential/issuance/02-evaluate-issuer-trust.js.map +1 -1
  20. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  21. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +190 -10
  22. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  23. package/lib/module/credential/issuance/README.md +7 -2
  24. package/lib/module/mdoc/const.js +2 -0
  25. package/lib/module/mdoc/const.js.map +1 -0
  26. package/lib/module/mdoc/converter.js +20 -0
  27. package/lib/module/mdoc/converter.js.map +1 -0
  28. package/lib/module/mdoc/index.js +67 -0
  29. package/lib/module/mdoc/index.js.map +1 -0
  30. package/lib/module/mdoc/utils.js +7 -0
  31. package/lib/module/mdoc/utils.js.map +1 -0
  32. package/lib/module/trust/types.js.map +1 -1
  33. package/lib/module/utils/crypto.js +30 -0
  34. package/lib/module/utils/crypto.js.map +1 -1
  35. package/lib/typescript/credential/issuance/02-evaluate-issuer-trust.d.ts.map +1 -1
  36. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +2 -1
  37. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  38. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +8 -9
  39. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  40. package/lib/typescript/mdoc/const.d.ts +2 -0
  41. package/lib/typescript/mdoc/const.d.ts.map +1 -0
  42. package/lib/typescript/mdoc/converter.d.ts +8 -0
  43. package/lib/typescript/mdoc/converter.d.ts.map +1 -0
  44. package/lib/typescript/mdoc/index.d.ts +5 -0
  45. package/lib/typescript/mdoc/index.d.ts.map +1 -0
  46. package/lib/typescript/mdoc/utils.d.ts +7 -0
  47. package/lib/typescript/mdoc/utils.d.ts.map +1 -0
  48. package/lib/typescript/trust/types.d.ts +135 -0
  49. package/lib/typescript/trust/types.d.ts.map +1 -1
  50. package/lib/typescript/utils/crypto.d.ts +16 -0
  51. package/lib/typescript/utils/crypto.d.ts.map +1 -1
  52. package/package.json +8 -4
  53. package/src/credential/issuance/02-evaluate-issuer-trust.ts +2 -1
  54. package/src/credential/issuance/06-obtain-credential.ts +2 -1
  55. package/src/credential/issuance/07-verify-and-parse-credential.ts +258 -22
  56. package/src/credential/issuance/README.md +7 -2
  57. package/src/mdoc/const.ts +1 -0
  58. package/src/mdoc/converter.ts +26 -0
  59. package/src/mdoc/index.ts +93 -0
  60. package/src/mdoc/utils.ts +7 -0
  61. package/src/trust/types.ts +4 -0
  62. package/src/utils/crypto.ts +36 -1
  63. package/lib/commonjs/credential/issuance/const.js +0 -14
  64. package/lib/commonjs/credential/issuance/const.js.map +0 -1
  65. package/lib/module/credential/issuance/const.js +0 -4
  66. package/lib/module/credential/issuance/const.js.map +0 -1
  67. package/lib/typescript/credential/issuance/const.d.ts +0 -5
  68. package/lib/typescript/credential/issuance/const.d.ts.map +0 -1
  69. package/src/credential/issuance/const.ts +0 -11
@@ -6,12 +6,22 @@ import { SdJwt4VC, verify as verifySdJwt } from "../../sd-jwt";
6
6
  import { getValueFromDisclosures } from "../../sd-jwt/converters";
7
7
  import { isSameThumbprint, type JWK } from "../../utils/jwk";
8
8
  import type { ObtainCredential } from "./06-obtain-credential";
9
- import { Logger, LogLevel } from "../../utils/logging";
9
+ import { verify as verifyMdoc } from "../../mdoc";
10
+ import { MDOC_DEFAULT_NAMESPACE } from "../../mdoc/const";
11
+ import { getParsedCredentialClaimKey } from "../../mdoc/utils";
12
+ import { LogLevel, Logger } from "../../utils/logging";
13
+ import { extractElementValueAsDate } from "../../mdoc/converter";
14
+ import type { CBOR } from "@pagopa/io-react-native-iso18013";
15
+ import type { PublicKey } from "@pagopa/io-react-native-crypto";
10
16
 
11
17
  type IssuerConf = Out<EvaluateIssuerTrust>["issuerConf"];
12
18
  type CredentialConf =
13
19
  IssuerConf["openid_credential_issuer"]["credential_configurations_supported"][string];
14
20
 
21
+ type DecodedMDocCredential = Out<typeof verifyMdoc> & {
22
+ issuerSigned: CBOR.IssuerSigned;
23
+ };
24
+
15
25
  export type VerifyAndParseCredential = (
16
26
  issuerConf: IssuerConf,
17
27
  credential: Out<ObtainCredential>["credential"],
@@ -26,7 +36,8 @@ export type VerifyAndParseCredential = (
26
36
  * Include attributes that are not explicitly mapped in the issuer configuration.
27
37
  */
28
38
  includeUndefinedAttributes?: boolean;
29
- }
39
+ },
40
+ x509CertRoot?: string
30
41
  ) => Promise<{
31
42
  parsedCredential: ParsedCredential;
32
43
  expiration: Date;
@@ -34,11 +45,9 @@ export type VerifyAndParseCredential = (
34
45
  }>;
35
46
 
36
47
  // The credential as a collection of attributes in plain value
37
- type ParsedCredential = Record<
48
+ type ParsedCredential = {
38
49
  /** Attribute key */
39
- string,
40
- {
41
- /** Human-readable name of the attribute */
50
+ [claim: string]: {
42
51
  name:
43
52
  | /* if i18n is provided */ Record<
44
53
  string /* locale */,
@@ -46,10 +55,9 @@ type ParsedCredential = Record<
46
55
  >
47
56
  | /* if no i18n is provided */ string
48
57
  | undefined; // Add undefined as a possible value for the name property
49
- /** The actual value of the attribute */
50
58
  value: unknown;
51
- }
52
- >;
59
+ };
60
+ };
53
61
 
54
62
  // handy alias
55
63
  type DecodedSdJwtCredential = Out<typeof verifySdJwt> & {
@@ -139,7 +147,124 @@ const parseCredentialSdJwt = (
139
147
 
140
148
  return definedValues;
141
149
  };
150
+ const parseCredentialMDoc = (
151
+ // the list of supported credentials, as defined in the issuer configuration
152
+ credentialConfig: CredentialConf,
153
+ // credential_type: string,
154
+ { issuerSigned }: DecodedMDocCredential,
155
+ ignoreMissingAttributes: boolean = false,
156
+ includeUndefinedAttributes: boolean = false
157
+ ): ParsedCredential => {
158
+ if (!credentialConfig) {
159
+ throw new IoWalletError("Credential type not supported by the issuer");
160
+ }
161
+
162
+ if (!credentialConfig.claims) {
163
+ throw new IoWalletError("Missing claims in the credential subject");
164
+ }
165
+
166
+ const attrDefinitions = credentialConfig.claims.map<
167
+ [string, string, { name: string; locale: string }[]]
168
+ >(({ path: [namespace, attribute], display }) => [
169
+ namespace as string,
170
+ attribute as string,
171
+ display,
172
+ ]);
173
+
174
+ if (!issuerSigned.nameSpaces) {
175
+ throw new IoWalletError("Missing claims in the credential");
176
+ }
177
+
178
+ const flatNamespaces = Object.entries(issuerSigned.nameSpaces).flatMap(
179
+ ([namespace, values]) =>
180
+ values.map<[string, string, string]>((v) => [
181
+ namespace,
182
+ v.elementIdentifier,
183
+ v.elementValue,
184
+ ])
185
+ );
186
+
187
+ // Check that all mandatory attributes defined in the issuer configuration are present in the disclosure set
188
+ // and filter the non present ones
189
+ const attrsNotInDisclosures = attrDefinitions.filter(
190
+ ([attrDefNamespace, attrKey]) =>
191
+ !flatNamespaces.some(
192
+ ([namespace, claim]) =>
193
+ attrDefNamespace === namespace && attrKey === claim
194
+ )
195
+ );
196
+
197
+ if (attrsNotInDisclosures.length > 0) {
198
+ const missing = attrsNotInDisclosures
199
+ .map(([, attrKey]) => attrKey)
200
+ .join(", ");
201
+ const received = flatNamespaces.map(([, attrKey]) => attrKey).join(", ");
142
202
 
203
+ if (!ignoreMissingAttributes) {
204
+ throw new IoWalletError(
205
+ `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
206
+ );
207
+ }
208
+ }
209
+
210
+ // Attributes defined in the issuer configuration and present in the disclosure set
211
+ const definedValues = attrDefinitions
212
+ // Retrieve the value from the corresponding disclosure
213
+ .map(
214
+ ([attrDefNamespace, attrKey, display]) =>
215
+ [
216
+ attrDefNamespace,
217
+ attrKey,
218
+ {
219
+ display,
220
+ value: flatNamespaces.find(
221
+ ([namespace, name]) =>
222
+ attrDefNamespace === namespace && name === attrKey
223
+ )?.[2],
224
+ },
225
+ ] as const
226
+ )
227
+ //filter the not found elements
228
+ .filter(([_, __, definition]) => definition.value !== undefined)
229
+ // Add a human-readable attribute name, with i18n, in the form { locale: name }
230
+ // Example: { "it-IT": "Nome", "en-EN": "Name", "es-ES": "Nombre" }
231
+ .reduce<ParsedCredential>(
232
+ (acc, [attrDefNamespace, attrKey, { display, value }]) => ({
233
+ ...acc,
234
+ [getParsedCredentialClaimKey(attrDefNamespace, attrKey)]: {
235
+ value,
236
+ name: display.reduce(
237
+ (names, { locale, name }) => ({
238
+ ...names,
239
+ [locale]: name,
240
+ }),
241
+ {}
242
+ ),
243
+ },
244
+ }),
245
+ {}
246
+ );
247
+
248
+ if (includeUndefinedAttributes) {
249
+ const undefinedValues: ParsedCredential = Object.fromEntries(
250
+ Object.values(flatNamespaces)
251
+ .filter(
252
+ ([namespace, key]) =>
253
+ !definedValues[getParsedCredentialClaimKey(namespace, key)]
254
+ )
255
+ .map(([namespace, key, value]) => [
256
+ getParsedCredentialClaimKey(namespace, key),
257
+ { value, name: key },
258
+ ])
259
+ );
260
+ return {
261
+ ...definedValues,
262
+ ...undefinedValues,
263
+ };
264
+ }
265
+
266
+ return definedValues;
267
+ };
143
268
  /**
144
269
  * Given a credential, verify it's in the supported format
145
270
  * and the credential is correctly signed
@@ -176,6 +301,48 @@ async function verifyCredentialSdJwt(
176
301
 
177
302
  return decodedCredential;
178
303
  }
304
+ /**
305
+ * Given a credential, verify it's in the supported format
306
+ * and the credential is correctly signed
307
+ * and it's bound to the given key
308
+ *
309
+ * @param rawCredential The received credential
310
+ * @param issuerKeys The set of public keys of the issuer,
311
+ * which will be used to verify the signature
312
+ * @param holderBindingContext The access to the holder's key
313
+ *
314
+ * @throws If the signature verification fails
315
+ * @throws If the credential is not in the SdJwt4VC format
316
+ * @throws If the holder binding is not properly configured
317
+ *
318
+ */
319
+ async function verifyCredentialMDoc(
320
+ rawCredential: string,
321
+ x509CertRoot: string,
322
+ holderBindingContext: CryptoContext
323
+ ): Promise<DecodedMDocCredential> {
324
+ const [decodedCredential, holderBindingKey] =
325
+ // parallel for optimization
326
+ await Promise.all([
327
+ verifyMdoc(rawCredential, x509CertRoot),
328
+ holderBindingContext.getPublicKey(),
329
+ ]);
330
+
331
+ if (!decodedCredential) {
332
+ throw new IoWalletError("No MDOC credentials found!");
333
+ }
334
+
335
+ const key =
336
+ decodedCredential.issuerSigned.issuerAuth.payload.deviceKeyInfo.deviceKey;
337
+
338
+ if (!(await isSameThumbprint(key, holderBindingKey as PublicKey))) {
339
+ throw new IoWalletError(
340
+ `Failed to verify holder binding, holder binding key and mDoc deviceKey don't match`
341
+ );
342
+ }
343
+
344
+ return decodedCredential;
345
+ }
179
346
 
180
347
  const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
181
348
  issuerConf,
@@ -231,6 +398,60 @@ const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
231
398
  };
232
399
  };
233
400
 
401
+ const verifyAndParseCredentialMDoc: VerifyAndParseCredential = async (
402
+ issuerConf,
403
+ credential,
404
+ credentialConfigurationId,
405
+ { credentialCryptoContext, ignoreMissingAttributes },
406
+ x509CertRoot
407
+ ) => {
408
+ if (!x509CertRoot) {
409
+ throw new IoWalletError("Missing x509CertRoot");
410
+ }
411
+
412
+ const decoded = await verifyCredentialMDoc(
413
+ credential,
414
+ x509CertRoot,
415
+ credentialCryptoContext
416
+ );
417
+
418
+ const credentialConfig =
419
+ issuerConf.openid_credential_issuer.credential_configurations_supported[
420
+ credentialConfigurationId
421
+ ]!;
422
+ const parsedCredential = parseCredentialMDoc(
423
+ credentialConfig,
424
+ decoded,
425
+ ignoreMissingAttributes,
426
+ ignoreMissingAttributes
427
+ );
428
+
429
+ const expirationDate = extractElementValueAsDate(
430
+ parsedCredential?.[
431
+ getParsedCredentialClaimKey(MDOC_DEFAULT_NAMESPACE, "expiry_date")
432
+ ]?.value as string
433
+ );
434
+ if (!expirationDate) {
435
+ throw new IoWalletError(`expirationDate must be present!!`);
436
+ }
437
+ expirationDate.setDate(expirationDate.getDate() + 1);
438
+
439
+ const maybeIssuedAt = extractElementValueAsDate(
440
+ parsedCredential?.[
441
+ getParsedCredentialClaimKey(MDOC_DEFAULT_NAMESPACE, "issue_date")
442
+ ]?.value as string
443
+ );
444
+ maybeIssuedAt?.setDate(maybeIssuedAt.getDate() + 1);
445
+
446
+ return {
447
+ parsedCredential,
448
+ credential,
449
+ credentialConfigurationId,
450
+ expiration: expirationDate,
451
+ issuedAt: maybeIssuedAt ?? undefined,
452
+ };
453
+ };
454
+
234
455
  /**
235
456
  * Verify and parse an encoded credential.
236
457
  * @param issuerConf The Issuer configuration returned by {@link evaluateIssuerTrust}
@@ -248,24 +469,39 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
248
469
  issuerConf,
249
470
  credential,
250
471
  credentialConfigurationId,
251
- context
472
+ context,
473
+ x509CertRoot
252
474
  ) => {
253
475
  const format =
254
476
  issuerConf.openid_credential_issuer.credential_configurations_supported[
255
477
  credentialConfigurationId
256
478
  ]?.format;
257
479
 
258
- if (format === "dc+sd-jwt") {
259
- Logger.log(LogLevel.DEBUG, "Parsing credential in dc+sd-jwt format");
260
- return verifyAndParseCredentialSdJwt(
261
- issuerConf,
262
- credential,
263
- credentialConfigurationId,
264
- context
265
- );
266
- }
480
+ switch (format) {
481
+ case "dc+sd-jwt": {
482
+ Logger.log(LogLevel.DEBUG, "Parsing credential in dc+sd-jwt format");
483
+ return verifyAndParseCredentialSdJwt(
484
+ issuerConf,
485
+ credential,
486
+ credentialConfigurationId,
487
+ context
488
+ );
489
+ }
490
+ case "mso_mdoc": {
491
+ Logger.log(LogLevel.DEBUG, "Parsing credential in mso_mdoc format");
492
+ return verifyAndParseCredentialMDoc(
493
+ issuerConf,
494
+ credential,
495
+ credentialConfigurationId,
496
+ context,
497
+ x509CertRoot
498
+ );
499
+ }
267
500
 
268
- const message = `Unsupported credential format: ${format}`;
269
- Logger.log(LogLevel.ERROR, message);
270
- throw new IoWalletError(message);
501
+ default: {
502
+ const message = `Unsupported credential format: ${format}`;
503
+ Logger.log(LogLevel.ERROR, message);
504
+ throw new IoWalletError(message);
505
+ }
506
+ }
271
507
  };
@@ -171,7 +171,7 @@ const { credential_configuration_id, credential_identifiers } =
171
171
  accessToken.authorization_details[0]!;
172
172
 
173
173
  // Obtain the credential
174
- const { credential } = await Credential.Issuance.obtainCredential(
174
+ const { credential, format } = await Credential.Issuance.obtainCredential(
175
175
  issuerConf,
176
176
  accessToken,
177
177
  clientId,
@@ -186,6 +186,10 @@ const { credential } = await Credential.Issuance.obtainCredential(
186
186
  }
187
187
  );
188
188
 
189
+ // The certificate below is required to perform the `x5chain` validation of credentials in `mdoc` format.
190
+ // In a real-world scenario, it must be obtained from the appropriate endpoint exposed by the Trust Anchor
191
+ const mockX509CertRoot = format === "mso_mdoc" ? "base64encodedX509CertRoot" : undefined
192
+
189
193
  /*
190
194
  * Parse and verify the credential. The ignoreMissingAttributes flag must be set to false or omitted in production.
191
195
  * WARNING: includeUndefinedAttributes should not be set to true in production in order to get only claims explicitly declared by the issuer.
@@ -199,7 +203,8 @@ const { parsedCredential } =
199
203
  credentialCryptoContext,
200
204
  ignoreMissingAttributes: true,
201
205
  includeUndefinedAttributes: false
202
- }
206
+ },
207
+ mockX509CertRoot
203
208
  );
204
209
 
205
210
  const credentialType =
@@ -0,0 +1 @@
1
+ export const MDOC_DEFAULT_NAMESPACE = "org.iso.18013.5.1";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Extracts the date value of a given elementIdentifier from an MDOC object.
3
+ * Searches through the issuerSigned namespaces and attempts to parse the value as a Date.
4
+ * The expected date format is "YYYY-MM-DD".
5
+ * Returns the Date object if found, otherwise returns null.
6
+ */
7
+ export function extractElementValueAsDate(elementValue: string): Date | null {
8
+ if (typeof elementValue === "string") {
9
+ const dateParts = elementValue.split("-");
10
+ if (dateParts.length === 3) {
11
+ const [year, month, day] = dateParts.map(Number);
12
+ if (
13
+ day !== undefined &&
14
+ month !== undefined &&
15
+ year !== undefined &&
16
+ !isNaN(day) &&
17
+ !isNaN(month) &&
18
+ !isNaN(year)
19
+ ) {
20
+ return new Date(year, month - 1, day); // Month is zero-based in JS Date
21
+ }
22
+ }
23
+ }
24
+
25
+ return null; // Return null if no matching element is found or it's not a valid date
26
+ }
@@ -0,0 +1,93 @@
1
+ import { CBOR, COSE } from "@pagopa/io-react-native-iso18013";
2
+ import { b64utob64 } from "jsrsasign";
3
+ import {
4
+ verifyCertificateChain,
5
+ type CertificateValidationResult,
6
+ type PublicKey,
7
+ type X509CertificateOptions,
8
+ } from "@pagopa/io-react-native-crypto";
9
+ import { MissingX509CertsError, X509ValidationError } from "../trust/errors";
10
+ import { IoWalletError } from "../utils/errors";
11
+ import { convertBase64DerToPem, getSigninJwkFromCert } from "../utils/crypto";
12
+
13
+ export const verify = async (
14
+ token: string,
15
+ x509CertRoot: string
16
+ ): Promise<{ issuerSigned: CBOR.IssuerSigned }> => {
17
+ // get decoded data
18
+ const issuerSigned = await CBOR.decodeIssuerSigned(token);
19
+
20
+ if (!issuerSigned) {
21
+ throw new IoWalletError("Invalid mDoc");
22
+ }
23
+
24
+ if (
25
+ !issuerSigned.issuerAuth.unprotectedHeader?.x5chain &&
26
+ (!Array.isArray(issuerSigned.issuerAuth.unprotectedHeader.x5chain) ||
27
+ issuerSigned.issuerAuth.unprotectedHeader.x5chain.length === 0)
28
+ ) {
29
+ throw new MissingX509CertsError("Missing x509 certificates");
30
+ }
31
+ const x5chain =
32
+ issuerSigned.issuerAuth.unprotectedHeader.x5chain.map(b64utob64);
33
+ // Verify the x5chain
34
+ await verifyX5chain(x5chain, x509CertRoot);
35
+
36
+ const coseSign1 = issuerSigned.issuerAuth.rawValue;
37
+
38
+ if (!coseSign1) {
39
+ throw new IoWalletError("Missing coseSign1");
40
+ }
41
+ // Once the x5chain is verified, the signatures verification can be performed
42
+ await verifyMdocSignature(coseSign1, x5chain[0]!);
43
+
44
+ return { issuerSigned };
45
+ };
46
+
47
+ /**
48
+ * This function checks whether the x509 certificate chain is valid against a specified Certificate Authority (CA)
49
+ *
50
+ * @param x5chain The mdoc's x509 certificate chain
51
+ * @param x509CertRoot The Trust Anchor CA
52
+ * @param options Options for certificate validation
53
+ */
54
+ const verifyX5chain = async (
55
+ x5chain: string[],
56
+ x509CertRoot: string,
57
+ options: X509CertificateOptions = {
58
+ connectTimeout: 10000,
59
+ readTimeout: 10000,
60
+ requireCrl: true,
61
+ }
62
+ ) => {
63
+ const x509ValidationResult: CertificateValidationResult =
64
+ await verifyCertificateChain(x5chain, x509CertRoot, options);
65
+
66
+ if (!x509ValidationResult.isValid) {
67
+ throw new X509ValidationError(
68
+ `X.509 certificate chain validation failed. Status: ${x509ValidationResult.validationStatus}. Error: ${x509ValidationResult.errorMessage}`,
69
+ {
70
+ x509ValidationStatus: x509ValidationResult.validationStatus,
71
+ x509ErrorMessage: x509ValidationResult.errorMessage,
72
+ }
73
+ );
74
+ }
75
+ };
76
+ /**
77
+ * This function verifies that the signature is valid for the given certificate.
78
+ * If not, it throws an error
79
+ *
80
+ * @param coseSign1 The COSE-Sign1 object encoded in base64 or base64url
81
+ * @param cert The `x5chain`'s leaf certificate
82
+ */
83
+ const verifyMdocSignature = async (coseSign1: string, cert: string) => {
84
+ const pemcert = convertBase64DerToPem(cert);
85
+ const jwk = getSigninJwkFromCert(pemcert);
86
+
87
+ jwk.x = b64utob64(jwk.x!);
88
+ jwk.y = b64utob64(jwk.y!);
89
+
90
+ const signatureCorrect = await COSE.verify(coseSign1, jwk as PublicKey);
91
+
92
+ if (!signatureCorrect) throw new Error("Invalid mDoc signature");
93
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param namespace The mdoc credential `namespace`
3
+ * @param key The claim attribute key
4
+ * @returns A string consisting of the concatenation of the namespace and the claim key, separated by a colon
5
+ */
6
+ export const getParsedCredentialClaimKey = (namespace: string, key: string) =>
7
+ `${namespace}:${key}`;
@@ -71,6 +71,10 @@ const SupportedCredentialMetadata = z.intersection(
71
71
  })
72
72
  );
73
73
 
74
+ export type SupportedCredentialFormat = z.infer<
75
+ typeof SupportedCredentialMetadata
76
+ >["format"];
77
+
74
78
  export type EntityStatement = z.infer<typeof EntityStatement>;
75
79
  export const EntityStatement = z.object({
76
80
  header: z.object({
@@ -5,7 +5,10 @@ import {
5
5
  sign,
6
6
  } from "@pagopa/io-react-native-crypto";
7
7
  import { v4 as uuidv4 } from "uuid";
8
- import { type CryptoContext, thumbprint } from "@pagopa/io-react-native-jwt";
8
+ import { thumbprint, type CryptoContext } from "@pagopa/io-react-native-jwt";
9
+ import { JWK } from "./jwk";
10
+ import { KEYUTIL, KJUR, RSAKey, X509 } from "jsrsasign";
11
+ import { IoWalletError } from "./errors";
9
12
 
10
13
  /**
11
14
  * Create a CryptoContext bound to a key pair.
@@ -55,3 +58,35 @@ export const withEphemeralKey = async <R>(
55
58
  const ephemeralContext = createCryptoContextFor(keytag);
56
59
  return fn(ephemeralContext).finally(() => deleteKey(keytag));
57
60
  };
61
+ /**
62
+ * Converts a base64-encoded DER certificate to PEM format.
63
+ *
64
+ * @param certificate - The base64-encoded DER certificate.
65
+ * @returns The PEM-formatted certificate.
66
+ */
67
+ export const convertBase64DerToPem = (certificate: string): string =>
68
+ `-----BEGIN CERTIFICATE-----\n${certificate}\n-----END CERTIFICATE-----`;
69
+
70
+ /**
71
+ * Retrieves the signing JWK from a PEM-formatted certificate.
72
+ *
73
+ * @param pemCert - The PEM-formatted certificate.
74
+ * @returns The signing JWK.
75
+ * @throws Will throw an error if the public key is unsupported.
76
+ */
77
+ export const getSigninJwkFromCert = (pemCert: string): JWK => {
78
+ const x509 = new X509();
79
+ x509.readCertPEM(pemCert);
80
+ const publicKey = x509.getPublicKey();
81
+
82
+ if (publicKey instanceof RSAKey || publicKey instanceof KJUR.crypto.ECDSA) {
83
+ return {
84
+ ...JWK.parse(KEYUTIL.getJWKFromKey(publicKey)),
85
+ use: "sig",
86
+ };
87
+ }
88
+
89
+ throw new IoWalletError(
90
+ "Unable to find the signing key inside the PEM certificate"
91
+ );
92
+ };
@@ -1,14 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.SupportedCredentialFormat = exports.ASSERTION_TYPE = void 0;
7
- var z = _interopRequireWildcard(require("zod"));
8
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
9
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
10
- const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
11
- exports.ASSERTION_TYPE = ASSERTION_TYPE;
12
- const SupportedCredentialFormat = z.union([z.literal("dc+sd-jwt"), z.literal("vc+mdoc-cbor")]);
13
- exports.SupportedCredentialFormat = SupportedCredentialFormat;
14
- //# sourceMappingURL=const.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["z","_interopRequireWildcard","require","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set","ASSERTION_TYPE","exports","SupportedCredentialFormat","union","literal"],"sourceRoot":"../../../../src","sources":["credential/issuance/const.ts"],"mappings":";;;;;;AAAA,IAAAA,CAAA,GAAAC,uBAAA,CAAAC,OAAA;AAAyB,SAAAC,yBAAAC,WAAA,eAAAC,OAAA,kCAAAC,iBAAA,OAAAD,OAAA,QAAAE,gBAAA,OAAAF,OAAA,YAAAF,wBAAA,YAAAA,CAAAC,WAAA,WAAAA,WAAA,GAAAG,gBAAA,GAAAD,iBAAA,KAAAF,WAAA;AAAA,SAAAH,wBAAAO,GAAA,EAAAJ,WAAA,SAAAA,WAAA,IAAAI,GAAA,IAAAA,GAAA,CAAAC,UAAA,WAAAD,GAAA,QAAAA,GAAA,oBAAAA,GAAA,wBAAAA,GAAA,4BAAAE,OAAA,EAAAF,GAAA,UAAAG,KAAA,GAAAR,wBAAA,CAAAC,WAAA,OAAAO,KAAA,IAAAA,KAAA,CAAAC,GAAA,CAAAJ,GAAA,YAAAG,KAAA,CAAAE,GAAA,CAAAL,GAAA,SAAAM,MAAA,WAAAC,qBAAA,GAAAC,MAAA,CAAAC,cAAA,IAAAD,MAAA,CAAAE,wBAAA,WAAAC,GAAA,IAAAX,GAAA,QAAAW,GAAA,kBAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAd,GAAA,EAAAW,GAAA,SAAAI,IAAA,GAAAR,qBAAA,GAAAC,MAAA,CAAAE,wBAAA,CAAAV,GAAA,EAAAW,GAAA,cAAAI,IAAA,KAAAA,IAAA,CAAAV,GAAA,IAAAU,IAAA,CAAAC,GAAA,KAAAR,MAAA,CAAAC,cAAA,CAAAH,MAAA,EAAAK,GAAA,EAAAI,IAAA,YAAAT,MAAA,CAAAK,GAAA,IAAAX,GAAA,CAAAW,GAAA,SAAAL,MAAA,CAAAJ,OAAA,GAAAF,GAAA,MAAAG,KAAA,IAAAA,KAAA,CAAAa,GAAA,CAAAhB,GAAA,EAAAM,MAAA,YAAAA,MAAA;AAClB,MAAMW,cAAc,GACzB,oEAAoE;AAACC,OAAA,CAAAD,cAAA,GAAAA,cAAA;AAKhE,MAAME,yBAAyB,GAAG3B,CAAC,CAAC4B,KAAK,CAAC,CAC/C5B,CAAC,CAAC6B,OAAO,CAAC,WAAW,CAAC,EACtB7B,CAAC,CAAC6B,OAAO,CAAC,cAAc,CAAC,CAC1B,CAAC;AAACH,OAAA,CAAAC,yBAAA,GAAAA,yBAAA"}
@@ -1,4 +0,0 @@
1
- import * as z from "zod";
2
- export const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
3
- export const SupportedCredentialFormat = z.union([z.literal("dc+sd-jwt"), z.literal("vc+mdoc-cbor")]);
4
- //# sourceMappingURL=const.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["z","ASSERTION_TYPE","SupportedCredentialFormat","union","literal"],"sourceRoot":"../../../../src","sources":["credential/issuance/const.ts"],"mappings":"AAAA,OAAO,KAAKA,CAAC,MAAM,KAAK;AACxB,OAAO,MAAMC,cAAc,GACzB,oEAAoE;AAKtE,OAAO,MAAMC,yBAAyB,GAAGF,CAAC,CAACG,KAAK,CAAC,CAC/CH,CAAC,CAACI,OAAO,CAAC,WAAW,CAAC,EACtBJ,CAAC,CAACI,OAAO,CAAC,cAAc,CAAC,CAC1B,CAAC"}
@@ -1,5 +0,0 @@
1
- import * as z from "zod";
2
- export declare const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
3
- export type SupportedCredentialFormat = z.infer<typeof SupportedCredentialFormat>;
4
- export declare const SupportedCredentialFormat: z.ZodUnion<[z.ZodLiteral<"dc+sd-jwt">, z.ZodLiteral<"vc+mdoc-cbor">]>;
5
- //# sourceMappingURL=const.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../../../src/credential/issuance/const.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,cAAc,uEAC2C,CAAC;AAEvE,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,yBAAyB,CACjC,CAAC;AACF,eAAO,MAAM,yBAAyB,uEAGpC,CAAC"}
@@ -1,11 +0,0 @@
1
- import * as z from "zod";
2
- export const ASSERTION_TYPE =
3
- "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
4
-
5
- export type SupportedCredentialFormat = z.infer<
6
- typeof SupportedCredentialFormat
7
- >;
8
- export const SupportedCredentialFormat = z.union([
9
- z.literal("dc+sd-jwt"),
10
- z.literal("vc+mdoc-cbor"),
11
- ]);