@pagopa/io-react-native-wallet 0.1.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.
Files changed (114) hide show
  1. package/README.md +91 -0
  2. package/lib/commonjs/index.js +17 -0
  3. package/lib/commonjs/index.js.map +1 -0
  4. package/lib/commonjs/pid/index.js +11 -0
  5. package/lib/commonjs/pid/index.js.map +1 -0
  6. package/lib/commonjs/pid/sd-jwt/converters.js +29 -0
  7. package/lib/commonjs/pid/sd-jwt/converters.js.map +1 -0
  8. package/lib/commonjs/pid/sd-jwt/index.js +76 -0
  9. package/lib/commonjs/pid/sd-jwt/index.js.map +1 -0
  10. package/lib/commonjs/pid/sd-jwt/types.js +50 -0
  11. package/lib/commonjs/pid/sd-jwt/types.js.map +1 -0
  12. package/lib/commonjs/sd-jwt/__test__/converters.test.js +25 -0
  13. package/lib/commonjs/sd-jwt/__test__/converters.test.js.map +1 -0
  14. package/lib/commonjs/sd-jwt/__test__/types.test.js +70 -0
  15. package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -0
  16. package/lib/commonjs/sd-jwt/converters.js +30 -0
  17. package/lib/commonjs/sd-jwt/converters.js.map +1 -0
  18. package/lib/commonjs/sd-jwt/index.js +77 -0
  19. package/lib/commonjs/sd-jwt/index.js.map +1 -0
  20. package/lib/commonjs/sd-jwt/types.js +53 -0
  21. package/lib/commonjs/sd-jwt/types.js.map +1 -0
  22. package/lib/commonjs/sd-jwt/verifier.js +18 -0
  23. package/lib/commonjs/sd-jwt/verifier.js.map +1 -0
  24. package/lib/commonjs/utils/errors.js +82 -0
  25. package/lib/commonjs/utils/errors.js.map +1 -0
  26. package/lib/commonjs/utils/jwk.js +45 -0
  27. package/lib/commonjs/utils/jwk.js.map +1 -0
  28. package/lib/commonjs/wallet-instance-attestation/index.js +63 -0
  29. package/lib/commonjs/wallet-instance-attestation/index.js.map +1 -0
  30. package/lib/commonjs/wallet-instance-attestation/issuing.js +96 -0
  31. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -0
  32. package/lib/commonjs/wallet-instance-attestation/types.js +65 -0
  33. package/lib/commonjs/wallet-instance-attestation/types.js.map +1 -0
  34. package/lib/module/index.js +7 -0
  35. package/lib/module/index.js.map +1 -0
  36. package/lib/module/pid/index.js +3 -0
  37. package/lib/module/pid/index.js.map +1 -0
  38. package/lib/module/pid/sd-jwt/converters.js +23 -0
  39. package/lib/module/pid/sd-jwt/converters.js.map +1 -0
  40. package/lib/module/pid/sd-jwt/index.js +66 -0
  41. package/lib/module/pid/sd-jwt/index.js.map +1 -0
  42. package/lib/module/pid/sd-jwt/types.js +43 -0
  43. package/lib/module/pid/sd-jwt/types.js.map +1 -0
  44. package/lib/module/sd-jwt/__test__/converters.test.js +23 -0
  45. package/lib/module/sd-jwt/__test__/converters.test.js.map +1 -0
  46. package/lib/module/sd-jwt/__test__/types.test.js +68 -0
  47. package/lib/module/sd-jwt/__test__/types.test.js.map +1 -0
  48. package/lib/module/sd-jwt/converters.js +24 -0
  49. package/lib/module/sd-jwt/converters.js.map +1 -0
  50. package/lib/module/sd-jwt/index.js +71 -0
  51. package/lib/module/sd-jwt/index.js.map +1 -0
  52. package/lib/module/sd-jwt/types.js +44 -0
  53. package/lib/module/sd-jwt/types.js.map +1 -0
  54. package/lib/module/sd-jwt/verifier.js +11 -0
  55. package/lib/module/sd-jwt/verifier.js.map +1 -0
  56. package/lib/module/utils/errors.js +73 -0
  57. package/lib/module/utils/errors.js.map +1 -0
  58. package/lib/module/utils/jwk.js +38 -0
  59. package/lib/module/utils/jwk.js.map +1 -0
  60. package/lib/module/wallet-instance-attestation/index.js +52 -0
  61. package/lib/module/wallet-instance-attestation/index.js.map +1 -0
  62. package/lib/module/wallet-instance-attestation/issuing.js +90 -0
  63. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -0
  64. package/lib/module/wallet-instance-attestation/types.js +55 -0
  65. package/lib/module/wallet-instance-attestation/types.js.map +1 -0
  66. package/lib/typescript/index.d.ts +5 -0
  67. package/lib/typescript/index.d.ts.map +1 -0
  68. package/lib/typescript/pid/index.d.ts +3 -0
  69. package/lib/typescript/pid/index.d.ts.map +1 -0
  70. package/lib/typescript/pid/sd-jwt/converters.d.ts +4 -0
  71. package/lib/typescript/pid/sd-jwt/converters.d.ts.map +1 -0
  72. package/lib/typescript/pid/sd-jwt/index.d.ts +50 -0
  73. package/lib/typescript/pid/sd-jwt/index.d.ts.map +1 -0
  74. package/lib/typescript/pid/sd-jwt/types.d.ts +196 -0
  75. package/lib/typescript/pid/sd-jwt/types.d.ts.map +1 -0
  76. package/lib/typescript/sd-jwt/__test__/converters.test.d.ts +2 -0
  77. package/lib/typescript/sd-jwt/__test__/converters.test.d.ts.map +1 -0
  78. package/lib/typescript/sd-jwt/__test__/types.test.d.ts +2 -0
  79. package/lib/typescript/sd-jwt/__test__/types.test.d.ts.map +1 -0
  80. package/lib/typescript/sd-jwt/converters.d.ts +3 -0
  81. package/lib/typescript/sd-jwt/converters.d.ts.map +1 -0
  82. package/lib/typescript/sd-jwt/index.d.ts +42 -0
  83. package/lib/typescript/sd-jwt/index.d.ts.map +1 -0
  84. package/lib/typescript/sd-jwt/types.d.ts +416 -0
  85. package/lib/typescript/sd-jwt/types.d.ts.map +1 -0
  86. package/lib/typescript/sd-jwt/verifier.d.ts +3 -0
  87. package/lib/typescript/sd-jwt/verifier.d.ts.map +1 -0
  88. package/lib/typescript/utils/errors.d.ts +45 -0
  89. package/lib/typescript/utils/errors.d.ts.map +1 -0
  90. package/lib/typescript/utils/jwk.d.ts +85 -0
  91. package/lib/typescript/utils/jwk.d.ts.map +1 -0
  92. package/lib/typescript/wallet-instance-attestation/index.d.ts +36 -0
  93. package/lib/typescript/wallet-instance-attestation/index.d.ts.map +1 -0
  94. package/lib/typescript/wallet-instance-attestation/issuing.d.ts +32 -0
  95. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -0
  96. package/lib/typescript/wallet-instance-attestation/types.d.ts +733 -0
  97. package/lib/typescript/wallet-instance-attestation/types.d.ts.map +1 -0
  98. package/package.json +108 -0
  99. package/src/index.ts +8 -0
  100. package/src/pid/index.ts +2 -0
  101. package/src/pid/sd-jwt/converters.ts +26 -0
  102. package/src/pid/sd-jwt/index.ts +71 -0
  103. package/src/pid/sd-jwt/types.ts +44 -0
  104. package/src/sd-jwt/__test__/converters.test.ts +27 -0
  105. package/src/sd-jwt/__test__/types.test.ts +85 -0
  106. package/src/sd-jwt/converters.ts +24 -0
  107. package/src/sd-jwt/index.ts +92 -0
  108. package/src/sd-jwt/types.ts +54 -0
  109. package/src/sd-jwt/verifier.ts +20 -0
  110. package/src/utils/errors.ts +74 -0
  111. package/src/utils/jwk.ts +39 -0
  112. package/src/wallet-instance-attestation/index.ts +56 -0
  113. package/src/wallet-instance-attestation/issuing.ts +107 -0
  114. package/src/wallet-instance-attestation/types.ts +77 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/wallet-instance-attestation/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAwBzB,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,mCAAmC,CAC3C,CAAC;AACF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAc9C,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAChD,OAAO,4BAA4B,CACpC,CAAC;AACF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BvC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@pagopa/io-react-native-wallet",
3
+ "version": "0.1.0",
4
+ "description": "Provide data structures, helpers and API for IO Wallet",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/module/index",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index",
9
+ "source": "src/index",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "android",
14
+ "ios",
15
+ "cpp",
16
+ "*.podspec",
17
+ "!lib/typescript/example",
18
+ "!ios/build",
19
+ "!android/build",
20
+ "!android/gradle",
21
+ "!android/gradlew",
22
+ "!android/gradlew.bat",
23
+ "!android/local.properties",
24
+ "!**/__tests__",
25
+ "!**/__fixtures__",
26
+ "!**/__mocks__",
27
+ "!**/.*"
28
+ ],
29
+ "scripts": {
30
+ "test": "jest",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "eslint \"src/**\"",
33
+ "prepack": "bob build",
34
+ "release": "release-it",
35
+ "example": "yarn --cwd example",
36
+ "bootstrap": "yarn example && yarn install",
37
+ "code-review": "yarn lint && yarn typecheck && yarn test"
38
+ },
39
+ "keywords": [
40
+ "react-native",
41
+ "ios",
42
+ "android"
43
+ ],
44
+ "repository": "https://github.com/pagopa/io-react-native-wallet",
45
+ "license": "MIT",
46
+ "bugs": {
47
+ "url": "https://github.com/pagopa/io-react-native-wallet/issues"
48
+ },
49
+ "homepage": "https://github.com/pagopa/io-react-native-wallet#readme",
50
+ "publishConfig": {
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "devDependencies": {
54
+ "@pagopa/eslint-config": "^3.0.0",
55
+ "@pagopa/io-react-native-jwt": "^0.4.0",
56
+ "@react-native-community/eslint-config": "^3.2.0",
57
+ "@rushstack/eslint-patch": "^1.3.2",
58
+ "@types/jest": "^28.1.2",
59
+ "@types/react": "~17.0.21",
60
+ "@types/react-native": "0.70.0",
61
+ "del-cli": "^5.0.0",
62
+ "eslint": "^8.4.1",
63
+ "jest": "^28.1.1",
64
+ "pod-install": "^0.1.0",
65
+ "prettier": "^2.0.5",
66
+ "react": "18.2.0",
67
+ "react-native": "0.71.8",
68
+ "react-native-builder-bob": "^0.20.0",
69
+ "typescript": "^5.0.2"
70
+ },
71
+ "resolutions": {
72
+ "@types/react": "17.0.21"
73
+ },
74
+ "peerDependencies": {
75
+ "@pagopa/io-react-native-jwt": "*",
76
+ "react": "*",
77
+ "react-native": "*"
78
+ },
79
+ "engines": {
80
+ "node": ">= 16.0.0"
81
+ },
82
+ "packageManager": "^yarn@1.22.15",
83
+ "jest": {
84
+ "preset": "react-native",
85
+ "modulePathIgnorePatterns": [
86
+ "<rootDir>/example/node_modules",
87
+ "<rootDir>/lib/"
88
+ ]
89
+ },
90
+ "react-native-builder-bob": {
91
+ "source": "src",
92
+ "output": "lib",
93
+ "targets": [
94
+ "commonjs",
95
+ "module",
96
+ [
97
+ "typescript",
98
+ {
99
+ "project": "tsconfig.build.json"
100
+ }
101
+ ]
102
+ ]
103
+ },
104
+ "dependencies": {
105
+ "react-native-uuid": "^2.0.1",
106
+ "zod": "^3.21.4"
107
+ }
108
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import * as PID from "./pid";
2
+ import * as WalletInstanceAttestation from "./wallet-instance-attestation";
3
+
4
+ export function multiply(a: number, b: number): Promise<number> {
5
+ return Promise.resolve(a * b);
6
+ }
7
+
8
+ export { PID, WalletInstanceAttestation };
@@ -0,0 +1,2 @@
1
+ import * as SdJwt from "./sd-jwt";
2
+ export { SdJwt };
@@ -0,0 +1,26 @@
1
+ import { getValueFromDisclosures } from "../../sd-jwt/converters";
2
+ import type { Disclosure, SdJwt4VC } from "../../sd-jwt/types";
3
+ import { PID } from "./types";
4
+
5
+ export function pidFromToken(sdJwt: SdJwt4VC, disclosures: Disclosure[]): PID {
6
+ return PID.parse({
7
+ issuer: sdJwt.payload.iss,
8
+ issuedAt: new Date(sdJwt.payload.iat * 1000),
9
+ expiration: new Date(sdJwt.payload.exp * 1000),
10
+ verification: {
11
+ trustFramework:
12
+ sdJwt.payload.verified_claims.verification.trust_framework,
13
+ assuranceLevel:
14
+ sdJwt.payload.verified_claims.verification.assurance_level,
15
+ evidence: getValueFromDisclosures(disclosures, "evidence"),
16
+ },
17
+ claims: {
18
+ uniqueId: getValueFromDisclosures(disclosures, "unique_id"),
19
+ givenName: getValueFromDisclosures(disclosures, "given_name"),
20
+ familyName: getValueFromDisclosures(disclosures, "family_name"),
21
+ birthdate: getValueFromDisclosures(disclosures, "birthdate"),
22
+ placeOfBirth: getValueFromDisclosures(disclosures, "place_of_birth"),
23
+ taxIdCode: getValueFromDisclosures(disclosures, "tax_id_number"),
24
+ },
25
+ });
26
+ }
@@ -0,0 +1,71 @@
1
+ import { decode as decodeJwt } from "../../sd-jwt";
2
+ import { verify as verifyJwt } from "../../sd-jwt";
3
+ import { PID } from "./types";
4
+ import { pidFromToken } from "./converters";
5
+ import { Disclosure, SdJwt4VC } from "../../sd-jwt/types";
6
+
7
+ /**
8
+ * Decode a given SD-JWT with Disclosures to get the parsed PID object they define.
9
+ * It ensures provided data is in a valid shape.
10
+ *
11
+ * It DOES NOT verify token signature nor check disclosures are correctly referenced by the SD-JWT.
12
+ * Use {@link verify} instead
13
+ *
14
+ * @function
15
+ * @param token The encoded token that represents a valid sd-jwt for verifiable credentials
16
+ *
17
+ * @returns The validated PID object along with the parsed SD-JWT token and the parsed disclosures
18
+ * @throws A decoding error if the token doesn't resolve in a valid SD-JWT
19
+ * @throws A validation error if the provided data doesn't result in a valid PID
20
+ *
21
+ */
22
+ export function decode(token: string): PidWithToken {
23
+ let { sdJwt, disclosures } = decodeJwt(token, SdJwt4VC);
24
+ const pid = pidFromToken(sdJwt, disclosures);
25
+
26
+ return { pid, sdJwt, disclosures };
27
+ }
28
+
29
+ /**
30
+ * Verify a given SD-JWT with Disclosures to get the parsed PID object they define.
31
+ * Same as {@link decode} plus:
32
+ * - token signature verification
33
+ * - ensure disclosures are well-defined inside the SD-JWT
34
+ *
35
+ * @async @function
36
+ *
37
+ * @todo implement signature validation
38
+ * @todo check disclosures in sd-jwt
39
+ *
40
+ * @param token The encoded token that represents a valid sd-jwt for verifiable credentials
41
+ *
42
+ * @returns {VerifyResult} The validated PID object along with the parsed SD-JWT token and the parsed disclosures
43
+ * @throws A decoding error if the token doesn't resolve in a valid SD-JWT
44
+ * @throws A validation error if the provided data doesn't result in a valid PID
45
+ * @throws A validation error if the provided disclosures are not defined in the SD-JWT
46
+ * @throws Invalid signature error if the token signature is not valid
47
+ *
48
+ */
49
+ export async function verify(token: string): Promise<VerifyResult> {
50
+ const decoded = decode(token);
51
+ const publicKey = decoded.sdJwt.payload.cnf.jwk;
52
+ await verifyJwt(token, publicKey, SdJwt4VC);
53
+
54
+ return decoded;
55
+ }
56
+
57
+ type PidWithToken = {
58
+ // The object with the parsed data for PID
59
+ pid: PID;
60
+ // The object with the parsed SD-JWT token that shipped the PID. It will be needed to present PID data.
61
+ sdJwt: SdJwt4VC;
62
+ // Parsed list of discloures with PID values. It will be needed to present PID data.
63
+ disclosures: Disclosure[];
64
+ };
65
+
66
+ /**
67
+ * Result object for {@link verify}
68
+ */
69
+ export type VerifyResult = PidWithToken;
70
+
71
+ export { PID } from "./types";
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+
3
+ const VerificationEvidence = z.object({
4
+ type: z.string(),
5
+ record: z.object({
6
+ type: z.string(),
7
+ source: z.object({
8
+ organization_name: z.string(),
9
+ organization_id: z.string(),
10
+ country_code: z.string(),
11
+ }),
12
+ }),
13
+ });
14
+ type Verification = z.infer<typeof Verification>;
15
+ const Verification = z.object({
16
+ trustFramework: z.literal("eidas"),
17
+ assuranceLevel: z.string(),
18
+ evidence: z.array(VerificationEvidence),
19
+ });
20
+
21
+ /**
22
+ * Data structure for the PID.
23
+ * It contains PID claims in plain text as well as verification data with the issuer's information
24
+ *
25
+ * @see https://italia.github.io/eidas-it-wallet-docs/en/pid-data-model.html
26
+ */
27
+ export type PID = z.infer<typeof PID>;
28
+ export const PID = z.object({
29
+ issuer: z.string(),
30
+ issuedAt: z.date(),
31
+ expiration: z.date(),
32
+ verification: Verification,
33
+ claims: z.object({
34
+ uniqueId: z.string(),
35
+ givenName: z.string(),
36
+ familyName: z.string(),
37
+ birthdate: z.string(),
38
+ placeOfBirth: z.object({
39
+ country: z.string(),
40
+ locality: z.string(),
41
+ }),
42
+ taxIdCode: z.string(),
43
+ }),
44
+ });
@@ -0,0 +1,27 @@
1
+ import { getValueFromDisclosures } from "../converters";
2
+ import { Disclosure } from "../types";
3
+
4
+ const disclosures: Disclosure[] = [
5
+ ["6w1_soRXFgaHKfpYn3cvfQ", "given_name", "Mario"],
6
+ ["fuNp97Hf3wV6y48y-QZhIg", "birthdate", "1980-10-01"],
7
+ [
8
+ "p-9LzyWHZBVDvhXDWkN2xA",
9
+ "place_of_birth",
10
+ { country: "IT", locality: "Rome" },
11
+ ],
12
+ ];
13
+
14
+ describe("getValueFromDisclosures", () => {
15
+ it("should return correct value for given_name", () => {
16
+ const success = getValueFromDisclosures(disclosures, "given_name");
17
+ expect(success).toBe("Mario");
18
+ });
19
+ it("should return correct value for place_of_birth", () => {
20
+ const success = getValueFromDisclosures(disclosures, "place_of_birth");
21
+ expect(success).toEqual({ country: "IT", locality: "Rome" });
22
+ });
23
+ it("should fail", () => {
24
+ const success = getValueFromDisclosures(disclosures, "given_surname");
25
+ expect(success).toBeUndefined();
26
+ });
27
+ });
@@ -0,0 +1,85 @@
1
+ import { Disclosure, SdJwt4VC } from "../types";
2
+
3
+ describe("SdJwt4VC", () => {
4
+ it("should accept a valid token", () => {
5
+ // example provided at https://italia.github.io/eidas-it-wallet-docs/en/pid-data-model.html
6
+ const token = {
7
+ header: {
8
+ typ: "vc+sd-jwt",
9
+ alg: "RS512",
10
+ kid: "dB67gL7ck3TFiIAf7N6_7SHvqk0MDYMEQcoGGlkUAAw",
11
+ trust_chain: [
12
+ "NEhRdERpYnlHY3M5WldWTWZ2aUhm ...",
13
+ "eyJhbGciOiJSUzI1NiIsImtpZCI6 ...",
14
+ "IkJYdmZybG5oQU11SFIwN2FqVW1B ...",
15
+ ],
16
+ },
17
+ payload: {
18
+ iss: "https://pidprovider.example.org",
19
+ sub: "NzbLsXh8uDCcd7noWXFZAfHkxZsRGC9Xs...",
20
+ jti: "urn:uuid:6c5c0a49-b589-431d-bae7-219122a9ec2c",
21
+ iat: 1541493724,
22
+ exp: 1541493724,
23
+ status: "https://pidprovider.example.org/status",
24
+ cnf: {
25
+ jwk: {
26
+ kty: "RSA",
27
+ use: "sig",
28
+ n: "1Ta-sE …",
29
+ e: "AQAB",
30
+ kid: "YhNFS3YnC9tjiCaivhWLVUJ3AxwGGz_98uRFaqMEEs",
31
+ },
32
+ },
33
+ type: "PersonIdentificationData",
34
+ verified_claims: {
35
+ verification: {
36
+ _sd: ["OGm7ryXgt5Xzlevp-Hu-UTk0a-TxAaPAobqv1pIWMfw"],
37
+ trust_framework: "eidas",
38
+ assurance_level: "high",
39
+ },
40
+ claims: {
41
+ _sd: [
42
+ "8JjozBfovMNvQ3HflmPWy4O19Gpxs61FWHjZebU589E",
43
+ "BoMGktW1rbikntw8Fzx_BeL4YbAndr6AHsdgpatFCig",
44
+ "CFLGzentGNRFngnLVVQVcoAFi05r6RJUX-rdbLdEfew",
45
+ "JU_sTaHCngS32X-0ajHrd1-HCLCkpT5YqgcfQme168w",
46
+ "VQI-S1mT1Kxfq2o8J9io7xMMX2MIxaG9M9PeJVqrMcA",
47
+ "zVdghcmClMVWlUgGsGpSkCPkEHZ4u9oWj1SlIBlCc1o",
48
+ ],
49
+ },
50
+ },
51
+ _sd_alg: "sha-256",
52
+ },
53
+ };
54
+
55
+ const { success } = SdJwt4VC.safeParse(token);
56
+
57
+ expect(success).toBe(true);
58
+ });
59
+ });
60
+
61
+ describe("Disclosure", () => {
62
+ it("should accept a valid disclosure", () => {
63
+ // example provided at https://italia.github.io/eidas-it-wallet-docs/en/pid-data-model.html
64
+ const value = [
65
+ "2GLC42sKQveCfGfryNRN9w",
66
+ "evidence",
67
+ [
68
+ {
69
+ type: "electronic_record",
70
+ record: {
71
+ type: "eidas.it.cie",
72
+ source: {
73
+ organization_name: "Ministero dell'Interno",
74
+ organization_id: "m_it",
75
+ country_code: "IT",
76
+ },
77
+ },
78
+ },
79
+ ],
80
+ ];
81
+
82
+ const { success } = Disclosure.safeParse(value);
83
+ expect(success).toBe(true);
84
+ });
85
+ });
@@ -0,0 +1,24 @@
1
+ import type { Disclosure } from "./types";
2
+
3
+ export function getValueFromDisclosures(
4
+ disclosures: Disclosure[],
5
+ claimName: string
6
+ ) {
7
+ const value = disclosures.find(([, name]) => name === claimName)?.[2];
8
+ // value didn't found, we return nothing
9
+ if (!value) {
10
+ return undefined;
11
+ }
12
+ // value is not a string, it's probably fine
13
+ if (typeof value !== "string") {
14
+ return value;
15
+ }
16
+ // value is a string, we try to parse it
17
+ // maybe it's a serialized object
18
+ try {
19
+ return JSON.parse(value);
20
+ } catch (error) {
21
+ // It's definitely a string
22
+ return value;
23
+ }
24
+ }
@@ -0,0 +1,92 @@
1
+ import { z } from "zod";
2
+
3
+ import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
4
+ import { verify as verifyJwt } from "@pagopa/io-react-native-jwt";
5
+
6
+ import { decodeBase64 } from "@pagopa/io-react-native-jwt";
7
+ import { Disclosure } from "./types";
8
+ import { verifyDisclosure } from "./verifier";
9
+ import type { JWK } from "src/utils/jwk";
10
+
11
+ /**
12
+ * Decode a given SD-JWT with Disclosures to get the parsed SD-JWT object they define.
13
+ * It ensures provided data is in a valid shape.
14
+ *
15
+ * It DOES NOT verify token signature nor check disclosures are correctly referenced by the SD-JWT.
16
+ * Use {@link verify} instead
17
+ *
18
+ * @function
19
+ * @param token The encoded token that represents a valid sd-jwt for verifiable credentials
20
+ * @param schema Schema to use to parse the SD-JWT
21
+ *
22
+ * @returns The parsed SD-JWT token and the parsed disclosures
23
+ *
24
+ */
25
+ export const decode = <S extends z.AnyZodObject>(
26
+ token: string,
27
+ schema: S
28
+ ): { sdJwt: z.infer<S>; disclosures: Disclosure[] } => {
29
+ // token are expected in the form "sd-jwt~disclosure0~disclosure1~...~disclosureN"
30
+ const [rawSdJwt = "", ...rawDisclosures] = token.split("~");
31
+
32
+ // get the sd-jwt as object
33
+ // validate it's a valid SD-JWT for Verifiable Credentials
34
+ const decodedJwt = decodeJwt(rawSdJwt);
35
+ const sdJwt = schema.parse({
36
+ header: decodedJwt.protectedHeader,
37
+ payload: decodedJwt.payload,
38
+ });
39
+
40
+ // get disclosures as list of triples
41
+ // validate each triple
42
+ // throw a validation error if at least one fails to parse
43
+ const disclosures = rawDisclosures
44
+ .map(decodeBase64)
45
+ .map((e) => JSON.parse(e))
46
+ .map((e) => Disclosure.parse(e));
47
+
48
+ return { sdJwt, disclosures };
49
+ };
50
+
51
+ /**
52
+ * Verify a given SD-JWT with Disclosures
53
+ * Same as {@link decode} plus:
54
+ * - token signature verification
55
+ * - ensure disclosures are well-defined inside the SD-JWT
56
+ *
57
+ * @async @function
58
+ *
59
+ *
60
+ * @param token The encoded token that represents a valid sd-jwt for verifiable credentials
61
+ * @param publicKey The public key to validate the signature
62
+ * @param schema Schema to use to parse the SD-JWT
63
+ *
64
+ * @returns The parsed SD-JWT token and the parsed disclosures
65
+ *
66
+ */
67
+ export const verify = async <S extends z.AnyZodObject>(
68
+ token: string,
69
+ publicKey: JWK,
70
+ schema: S
71
+ ): Promise<{ sdJwt: z.infer<S>; disclosures: Disclosure[] }> => {
72
+ // get decoded data
73
+ const [rawSdJwt = ""] = token.split("~");
74
+ const decoded = decode(token, schema);
75
+
76
+ //Check signature
77
+ await verifyJwt(rawSdJwt, publicKey);
78
+
79
+ //Check disclosures in sd-jwt
80
+ const claims = [
81
+ ...decoded.sdJwt.payload.verified_claims.verification._sd,
82
+ ...decoded.sdJwt.payload.verified_claims.claims._sd,
83
+ ];
84
+
85
+ await Promise.all(
86
+ decoded.disclosures.map(
87
+ async (disclosure) => await verifyDisclosure(disclosure, claims)
88
+ )
89
+ );
90
+
91
+ return decoded;
92
+ };
@@ -0,0 +1,54 @@
1
+ import { JWK } from "../utils/jwk";
2
+ import { z } from "zod";
3
+
4
+ export const UnixTime = z.number().min(0).max(2147483647000);
5
+ export type UnixTime = z.infer<typeof UnixTime>;
6
+
7
+ export type ObfuscatedDisclosures = z.infer<typeof ObfuscatedDisclosures>;
8
+ export const ObfuscatedDisclosures = z.object({ _sd: z.array(z.string()) });
9
+
10
+ /**
11
+ * A triple of values in the form of {salt, claim name, claim value} that represent a parsed disclosure.
12
+ *
13
+ * @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-04
14
+ * @see https://vcstuff.github.io/draft-terbu-sd-jwt-vc/draft-terbu-oauth-sd-jwt-vc.html
15
+ */
16
+ export type Disclosure = z.infer<typeof Disclosure>;
17
+ export const Disclosure = z.tuple([
18
+ /* salt */ z.string(),
19
+ /* claim name */ z.string(),
20
+ /* claim value */ z.unknown(),
21
+ ]);
22
+
23
+ export type SdJwt4VC = z.infer<typeof SdJwt4VC>;
24
+ export const SdJwt4VC = z.object({
25
+ header: z.object({
26
+ typ: z.literal("vc+sd-jwt"),
27
+ alg: z.string(),
28
+ kid: z.string(),
29
+ trust_chain: z.array(z.string()),
30
+ }),
31
+ payload: z.object({
32
+ iss: z.string(),
33
+ sub: z.string(),
34
+ jti: z.string(),
35
+ iat: UnixTime,
36
+ exp: UnixTime,
37
+ status: z.string(),
38
+ cnf: z.object({
39
+ jwk: JWK,
40
+ }),
41
+ type: z.literal("PersonIdentificationData"),
42
+ verified_claims: z.object({
43
+ verification: z.intersection(
44
+ z.object({
45
+ trust_framework: z.literal("eidas"),
46
+ assurance_level: z.string(),
47
+ }),
48
+ ObfuscatedDisclosures
49
+ ),
50
+ claims: ObfuscatedDisclosures,
51
+ }),
52
+ _sd_alg: z.literal("sha-256"),
53
+ }),
54
+ });
@@ -0,0 +1,20 @@
1
+ import { encodeBase64, sha256ToBase64 } from "@pagopa/io-react-native-jwt";
2
+
3
+ import { ValidationFailed } from "../utils/errors";
4
+ import type { Disclosure, ObfuscatedDisclosures } from "./types";
5
+
6
+ export const verifyDisclosure = async (
7
+ disclosure: Disclosure,
8
+ claims: ObfuscatedDisclosures["_sd"]
9
+ ) => {
10
+ let disclosureString = JSON.stringify(disclosure);
11
+ let encodedDisclosure = encodeBase64(disclosureString);
12
+ let hash = await sha256ToBase64(encodedDisclosure);
13
+ if (!claims.includes(hash)) {
14
+ throw new ValidationFailed(
15
+ "Validation of disclosure failed",
16
+ `${disclosure}`,
17
+ "Disclosure hash not found in claims"
18
+ );
19
+ }
20
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A generic Error that all other io-wallet specific Error subclasses extend.
3
+ *
4
+ * @example Checking thrown error is a io-wallet one
5
+ *
6
+ * ```js
7
+ * if (err instanceof errors.IoWalletError) {
8
+ * // ...
9
+ * }
10
+ * ```
11
+ */
12
+ export class IoWalletError extends Error {
13
+ /** A unique error code for the particular error subclass. */
14
+ static get code(): string {
15
+ return "ERR_IO_WALLET_GENERIC";
16
+ }
17
+
18
+ /** A unique error code for the particular error subclass. */
19
+ code: string = "ERR_IO_WALLET_GENERIC";
20
+
21
+ constructor(message?: string) {
22
+ super(message);
23
+ this.name = this.constructor.name;
24
+ // @ts-ignore
25
+ Error.captureStackTrace?.(this, this.constructor);
26
+ }
27
+ }
28
+ /**
29
+ * An error subclass thrown when validation fail
30
+ *
31
+ */
32
+ export class ValidationFailed extends IoWalletError {
33
+ static get code(): "ERR_IO_WALLET_VALIDATION_FAILED" {
34
+ return "ERR_IO_WALLET_VALIDATION_FAILED";
35
+ }
36
+
37
+ code = "ERR_IO_WALLET_VALIDATION_FAILED";
38
+
39
+ /** The Claim for which the validation failed. */
40
+ claim: string;
41
+
42
+ /** Reason code for the validation failure. */
43
+ reason: string;
44
+
45
+ constructor(message: string, claim = "unspecified", reason = "unspecified") {
46
+ super(message);
47
+ this.claim = claim;
48
+ this.reason = reason;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * An error subclass thrown when validation fail
54
+ *
55
+ */
56
+ export class WalletInstanceAttestationIssuingError extends IoWalletError {
57
+ static get code(): "ERR_IO_WALLET_INSTANCE_ATTESTATION_ISSUING_FAILED" {
58
+ return "ERR_IO_WALLET_INSTANCE_ATTESTATION_ISSUING_FAILED";
59
+ }
60
+
61
+ code = "ERR_IO_WALLET_INSTANCE_ATTESTATION_ISSUING_FAILED";
62
+
63
+ /** The Claim for which the validation failed. */
64
+ claim: string;
65
+
66
+ /** Reason code for the validation failure. */
67
+ reason: string;
68
+
69
+ constructor(message: string, claim = "unspecified", reason = "unspecified") {
70
+ super(message);
71
+ this.claim = claim;
72
+ this.reason = reason;
73
+ }
74
+ }