@pagopa/io-react-native-wallet 1.5.0 → 1.6.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 (64) hide show
  1. package/lib/commonjs/credential/issuance/06-obtain-credential.js +5 -1
  2. package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
  3. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +33 -21
  4. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  5. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +192 -58
  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 +45 -18
  8. package/lib/commonjs/credential/presentation/08-send-authorization-response.js.map +1 -1
  9. package/lib/commonjs/credential/presentation/types.js +1 -1
  10. package/lib/commonjs/credential/presentation/types.js.map +1 -1
  11. package/lib/commonjs/entity/trust/chain.js.map +1 -1
  12. package/lib/commonjs/mdoc/index.js +45 -13
  13. package/lib/commonjs/mdoc/index.js.map +1 -1
  14. package/lib/commonjs/utils/crypto.js +70 -4
  15. package/lib/commonjs/utils/crypto.js.map +1 -1
  16. package/lib/commonjs/utils/string.js +4 -4
  17. package/lib/commonjs/utils/string.js.map +1 -1
  18. package/lib/module/credential/issuance/06-obtain-credential.js +5 -1
  19. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  20. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +33 -21
  21. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  22. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +186 -55
  23. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  24. package/lib/module/credential/presentation/08-send-authorization-response.js +45 -18
  25. package/lib/module/credential/presentation/08-send-authorization-response.js.map +1 -1
  26. package/lib/module/credential/presentation/types.js +1 -1
  27. package/lib/module/credential/presentation/types.js.map +1 -1
  28. package/lib/module/entity/trust/chain.js.map +1 -1
  29. package/lib/module/mdoc/index.js +43 -12
  30. package/lib/module/mdoc/index.js.map +1 -1
  31. package/lib/module/utils/crypto.js +67 -2
  32. package/lib/module/utils/crypto.js.map +1 -1
  33. package/lib/module/utils/string.js +4 -4
  34. package/lib/module/utils/string.js.map +1 -1
  35. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  36. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +1 -1
  37. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  38. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts +49 -13
  39. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
  40. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts +6 -2
  41. package/lib/typescript/credential/presentation/08-send-authorization-response.d.ts.map +1 -1
  42. package/lib/typescript/credential/presentation/types.d.ts +10 -7
  43. package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
  44. package/lib/typescript/entity/trust/chain.d.ts.map +1 -1
  45. package/lib/typescript/mdoc/index.d.ts +6 -2
  46. package/lib/typescript/mdoc/index.d.ts.map +1 -1
  47. package/lib/typescript/utils/crypto.d.ts +8 -0
  48. package/lib/typescript/utils/crypto.d.ts.map +1 -1
  49. package/lib/typescript/utils/errors.d.ts.map +1 -1
  50. package/lib/typescript/utils/misc.d.ts.map +1 -1
  51. package/lib/typescript/utils/string.d.ts +3 -3
  52. package/lib/typescript/utils/string.d.ts.map +1 -1
  53. package/package.json +14 -12
  54. package/src/credential/issuance/06-obtain-credential.ts +3 -1
  55. package/src/credential/issuance/07-verify-and-parse-credential.ts +37 -16
  56. package/src/credential/presentation/07-evaluate-input-descriptor.ts +278 -97
  57. package/src/credential/presentation/08-send-authorization-response.ts +50 -27
  58. package/src/credential/presentation/types.ts +9 -6
  59. package/src/entity/trust/chain.ts +14 -10
  60. package/src/mdoc/index.ts +72 -15
  61. package/src/utils/crypto.ts +61 -2
  62. package/src/utils/errors.ts +2 -2
  63. package/src/utils/misc.ts +2 -2
  64. package/src/utils/string.ts +4 -4
@@ -9,17 +9,20 @@ import { JWKS } from "../../utils/jwk";
9
9
  export type Presentation = [
10
10
  /* verified credential token */ string,
11
11
  /* claims */ string[],
12
- /* the context for the key associated to the credential */ CryptoContext
12
+ /* the context for the key associated to the credential */ CryptoContext,
13
13
  ];
14
14
 
15
15
  /**
16
16
  * A object that associate the information needed to multiple remote presentation
17
17
  */
18
18
  export type RemotePresentation = {
19
- requestedClaims: string[];
20
- inputDescriptor: InputDescriptor;
21
- format: string;
22
- vpToken: string;
19
+ presentations: {
20
+ requestedClaims: string[];
21
+ inputDescriptor: InputDescriptor;
22
+ format: string;
23
+ vpToken: string;
24
+ }[];
25
+ generatedNonce?: string /* nonce generated by app, used in mdoc presentation */;
23
26
  };
24
27
 
25
28
  const Fields = z.object({
@@ -82,7 +85,7 @@ export const RequestObject = z.object({
82
85
  iss: z.string().optional(), //optional by RFC 7519, mandatory for Potential
83
86
  iat: UnixTime.optional(),
84
87
  exp: UnixTime.optional(),
85
- state: z.string(),
88
+ state: z.string().optional(),
86
89
  nonce: z.string(),
87
90
  response_uri: z.string(),
88
91
  response_type: z.literal("vp_token"),
@@ -70,8 +70,8 @@ export async function validateTrustChain(
70
70
  elementIndex === 0
71
71
  ? FirstElementShape
72
72
  : elementIndex === chain.length - 1
73
- ? LastElementShape
74
- : MiddleElementShape;
73
+ ? LastElementShape
74
+ : MiddleElementShape;
75
75
 
76
76
  // select the kid from the current index
77
77
  const selectKid = (currentIndex: number): string => {
@@ -136,15 +136,19 @@ export function renewTrustChain(
136
136
  ec.success
137
137
  ? getSignedEntityConfiguration(ec.data.payload.iss, { appFetch })
138
138
  : es.success
139
- ? getSignedEntityStatement(es.data.payload.iss, es.data.payload.sub, {
140
- appFetch,
141
- })
142
- : // if the element fail to parse in both EntityStatement and EntityConfiguration, raise an error
143
- Promise.reject(
144
- new IoWalletError(
145
- `Cannot renew trust chain because the element #${i} failed to be parsed.`
139
+ ? getSignedEntityStatement(
140
+ es.data.payload.iss,
141
+ es.data.payload.sub,
142
+ {
143
+ appFetch,
144
+ }
145
+ )
146
+ : // if the element fail to parse in both EntityStatement and EntityConfiguration, raise an error
147
+ Promise.reject(
148
+ new IoWalletError(
149
+ `Cannot renew trust chain because the element #${i} failed to be parsed.`
150
+ )
146
151
  )
147
- )
148
152
  )
149
153
  );
150
154
  }
package/src/mdoc/index.ts CHANGED
@@ -1,28 +1,85 @@
1
- import { CBOR } from "@pagopa/io-react-native-cbor";
1
+ import { CBOR, COSE, ISO18013 } from "@pagopa/io-react-native-cbor";
2
2
  import type { JWK } from "../utils/jwk";
3
+ import type { PublicKey } from "@pagopa/io-react-native-crypto";
4
+ import { b64utob64 } from "jsrsasign";
5
+ import {
6
+ convertCertToPem,
7
+ getSigningJwk,
8
+ parsePublicKey,
9
+ } from "../utils/crypto";
10
+ import { type Presentation } from "../credential/presentation/types";
11
+ import { base64ToBase64Url } from "../utils/string";
3
12
 
4
13
  export const verify = async (
5
14
  token: string,
6
- publicKey: JWK | JWK[]
7
- ): Promise<{ mDoc: CBOR.MDOC }> => {
15
+ _: JWK | JWK[]
16
+ ): Promise<{ issuerSigned: CBOR.IssuerSigned }> => {
8
17
  // get decoded data
9
- const documents = await CBOR.decodeDocuments(token);
10
- if (!documents || documents.documents.length === 0) {
11
- throw new Error("Invalid mDoc");
12
- }
13
- const mDoc = documents.documents[0];
14
- if (!mDoc) {
18
+ const issuerSigned = await CBOR.decodeIssuerSigned(token);
19
+ if (!issuerSigned) {
15
20
  throw new Error("Invalid mDoc");
16
21
  }
17
22
 
18
- const sigKey = Array.isArray(publicKey)
19
- ? publicKey.find((k) => k.use === "sig")
20
- : publicKey;
21
- sigKey;
23
+ const cert = issuerSigned.issuerAuth.unprotectedHeader[0]?.keyId;
24
+ if (!cert) throw new Error("Certificate not present in credential");
25
+
26
+ const pemcert = convertCertToPem(b64utob64(cert));
27
+ const publickey = parsePublicKey(pemcert);
28
+ if (!publickey) throw new Error("Certificate not present in credential");
29
+
30
+ const jwk = getSigningJwk(publickey);
31
+
32
+ jwk.x = b64utob64(jwk.x!);
33
+ jwk.y = b64utob64(jwk.y!);
34
+
35
+ const signatureCorrect = await COSE.verify(
36
+ b64utob64(issuerSigned.issuerAuth.rawValue!),
37
+ jwk as PublicKey
38
+ ).catch(() => false);
39
+ if (!signatureCorrect) throw new Error("Invalid mDoc signature");
40
+
41
+ return { issuerSigned };
42
+ };
43
+
44
+ export const prepareVpTokenMdoc = async (
45
+ requestNonce: string,
46
+ generatedNonce: string,
47
+ clientId: string,
48
+ responseUri: string,
49
+ docType: string,
50
+ keyTag: string,
51
+ [verifiableCredential, requestedClaims, _]: Presentation
52
+ ): Promise<{
53
+ vp_token: string;
54
+ }> => {
55
+ /* verifiableCredential is a IssuerSigned structure */
56
+ const documents = [
57
+ {
58
+ issuerSignedContent: verifiableCredential,
59
+ alias: keyTag,
60
+ docType,
61
+ },
62
+ ];
63
+
64
+ /* we map each requested claim as for ex. { "org.iso.18013.5.1.mDL" { <claim-name>: true, ... }} for selective disclosure */
65
+ const fieldRequestedAndAccepted = JSON.stringify({
66
+ [docType]: requestedClaims.reduce((acc, item) => {
67
+ return { ...acc, [item]: true };
68
+ }, {}),
69
+ });
22
70
 
23
- //await COSE.verify(mDoc.issuerSigned.issuerAuth, sigKey as PublicKey);
71
+ /* clientId,responseUri,requestNonce are retrieved by Auth Request Object */
72
+ /* create DeviceResponse as { documents: { docType, issuerSigned, deviceSigned }, version, status } */
73
+ const vp_token = await ISO18013.generateOID4VPDeviceResponse(
74
+ clientId,
75
+ responseUri,
76
+ requestNonce,
77
+ generatedNonce,
78
+ documents,
79
+ fieldRequestedAndAccepted
80
+ );
24
81
 
25
82
  return {
26
- mDoc,
83
+ vp_token: base64ToBase64Url(vp_token),
27
84
  };
28
85
  };
@@ -3,12 +3,14 @@ import {
3
3
  sign,
4
4
  generate,
5
5
  deleteKey,
6
+ type PublicKey,
6
7
  } from "@pagopa/io-react-native-crypto";
7
8
  import uuid from "react-native-uuid";
8
9
  import { thumbprint, type CryptoContext } from "@pagopa/io-react-native-jwt";
9
- import { fixBase64EncodingOnKey } from "./jwk";
10
10
  import { X509, KEYUTIL, RSAKey, KJUR } from "jsrsasign";
11
11
  import { JWK } from "./jwk";
12
+ import { removePadding } from "@pagopa/io-react-native-jwt";
13
+ import { Buffer } from "buffer";
12
14
 
13
15
  /**
14
16
  * Create a CryptoContext bound to a key pair.
@@ -26,7 +28,7 @@ export const createCryptoContextFor = (keytag: string): CryptoContext => {
26
28
  */
27
29
  async getPublicKey() {
28
30
  return getPublicKey(keytag)
29
- .then(fixBase64EncodingOnKey)
31
+ .then(fixBase64WithLeadingZero)
30
32
  .then(async (jwk) => ({
31
33
  ...jwk,
32
34
  // Keys in the TEE are not stored with their KID, which is supposed to be assigned when they are included in JWK sets.
@@ -48,6 +50,45 @@ export const createCryptoContextFor = (keytag: string): CryptoContext => {
48
50
  };
49
51
  };
50
52
 
53
+ /**
54
+ * This function takes a JSON Web Key (JWK) and returns a new JWK with its base64-url properties (x, y, e, n) processed.
55
+ * Each property is passed through the `removeLeadingZeroAndParseb64u` function if it exists, which fixes any unwanted leading zeros.
56
+ *
57
+ * @param key - The input JSON Web Key that may contain properties with potential leading zero issues.
58
+ * @returns A new JSON Web Key with the processed properties.
59
+ */
60
+ const fixBase64WithLeadingZero = (key: JWK): JWK => {
61
+ const { x, y, e, n, ...pk } = key;
62
+
63
+ return {
64
+ ...pk,
65
+ ...(x ? { x: removeLeadingZeroAndParseb64u(x) } : {}),
66
+ ...(y ? { y: removeLeadingZeroAndParseb64u(y) } : {}),
67
+ ...(e ? { e: removeLeadingZeroAndParseb64u(e) } : {}),
68
+ ...(n ? { n: removeLeadingZeroAndParseb64u(n) } : {}),
69
+ };
70
+ };
71
+
72
+ /**
73
+ * This function processes a base64-encoded string to remove any unwanted leading zeros.
74
+ * It converts the input base64 string into a buffer, then to a hex string, checks for a leading "00",
75
+ * and removes it if present. The result is then converted back to a base64-url.
76
+ *
77
+ * @param input - The base64 encoded string to process.
78
+ * @returns A new base64-url encoded string with any leading zero removed.
79
+ */
80
+ const removeLeadingZeroAndParseb64u = (input: string): string => {
81
+ // Decode base64 input into a Buffer
82
+ const buffer = Buffer.from(input, "base64");
83
+ const hex = buffer.toString("hex");
84
+ // If the hex string starts with "00", remove the first two characters
85
+ const fixedHex = hex.startsWith("00") ? hex.slice(2) : hex;
86
+ const newBuffer = Buffer.from(fixedHex, "hex");
87
+
88
+ // removePadding convert base64 string to base64-url
89
+ return removePadding(newBuffer.toString("base64"));
90
+ };
91
+
51
92
  /**
52
93
  * Executes the input function injecting an ephemeral crypto context.
53
94
  * An ephemeral crypto context is a context which is bound to a key
@@ -106,3 +147,21 @@ export const getSigningJwk = (publicKey: RSAKey | KJUR.crypto.ECDSA): JWK => ({
106
147
  ...JWK.parse(KEYUTIL.getJWKFromKey(publicKey)),
107
148
  use: "sig",
108
149
  });
150
+
151
+ /**
152
+ * This function takes two {@link PublicKey} and evaluates and compares their thumbprints
153
+ * @param key1 The first key
154
+ * @param key2 The second key
155
+ * @returns true if the keys' thumbprints are equal, false otherwise
156
+ */
157
+ export const compareKeysByThumbprint = async (
158
+ key1: PublicKey,
159
+ key2: PublicKey
160
+ ) => {
161
+ //Parallel for optimization
162
+ const [thumbprint1, thumbprint2] = await Promise.all([
163
+ thumbprint(key1),
164
+ thumbprint(key2),
165
+ ]);
166
+ return thumbprint1 === thumbprint2;
167
+ };
@@ -189,8 +189,8 @@ export class ResponseErrorBuilder<T extends typeof UnexpectedStatusCodeError> {
189
189
  type ErrorCodeMap<T> = T extends typeof IssuerResponseError
190
190
  ? IssuerResponseErrorCode
191
191
  : T extends typeof WalletProviderResponseError
192
- ? WalletProviderResponseErrorCode
193
- : never;
192
+ ? WalletProviderResponseErrorCode
193
+ : never;
194
194
 
195
195
  type ErrorCase<T> = {
196
196
  code: ErrorCodeMap<T>;
package/src/utils/misc.ts CHANGED
@@ -37,8 +37,8 @@ export const parseRawHttpResponse = <T extends Record<string, unknown>>(
37
37
  export type Out<FN> = FN extends (...args: any[]) => Promise<any>
38
38
  ? Awaited<ReturnType<FN>>
39
39
  : FN extends (...args: any[]) => any
40
- ? ReturnType<FN>
41
- : never;
40
+ ? ReturnType<FN>
41
+ : never;
42
42
 
43
43
  /**
44
44
  * TODO [SIW-1310]: replace this function with a cryptographically secure one.
@@ -45,11 +45,11 @@ export const obfuscateString = (
45
45
  };
46
46
 
47
47
  /**
48
- * Converts a hexadecimal byte string to a Base64 URL-encoded string.
48
+ * Converts a base64 string to a Base64 URL-encoded string.
49
49
  *
50
- * @param byteString - The input string in hexadecimal format.
50
+ * @param byteString - The input string in base64 format.
51
51
  * @returns The Base64 URL-encoded string.
52
52
  */
53
- export const base64ToBase64Url = (byteString: string): string => {
54
- return byteString.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
53
+ export const base64ToBase64Url = (base64: string): string => {
54
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, "");
55
55
  };