@pagopa/io-react-native-wallet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }