@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.
- package/README.md +91 -0
- package/lib/commonjs/index.js +17 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/pid/index.js +11 -0
- package/lib/commonjs/pid/index.js.map +1 -0
- package/lib/commonjs/pid/sd-jwt/converters.js +29 -0
- package/lib/commonjs/pid/sd-jwt/converters.js.map +1 -0
- package/lib/commonjs/pid/sd-jwt/index.js +76 -0
- package/lib/commonjs/pid/sd-jwt/index.js.map +1 -0
- package/lib/commonjs/pid/sd-jwt/types.js +50 -0
- package/lib/commonjs/pid/sd-jwt/types.js.map +1 -0
- package/lib/commonjs/sd-jwt/__test__/converters.test.js +25 -0
- package/lib/commonjs/sd-jwt/__test__/converters.test.js.map +1 -0
- package/lib/commonjs/sd-jwt/__test__/types.test.js +70 -0
- package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -0
- package/lib/commonjs/sd-jwt/converters.js +30 -0
- package/lib/commonjs/sd-jwt/converters.js.map +1 -0
- package/lib/commonjs/sd-jwt/index.js +77 -0
- package/lib/commonjs/sd-jwt/index.js.map +1 -0
- package/lib/commonjs/sd-jwt/types.js +53 -0
- package/lib/commonjs/sd-jwt/types.js.map +1 -0
- package/lib/commonjs/sd-jwt/verifier.js +18 -0
- package/lib/commonjs/sd-jwt/verifier.js.map +1 -0
- package/lib/commonjs/utils/errors.js +82 -0
- package/lib/commonjs/utils/errors.js.map +1 -0
- package/lib/commonjs/utils/jwk.js +45 -0
- package/lib/commonjs/utils/jwk.js.map +1 -0
- package/lib/commonjs/wallet-instance-attestation/index.js +63 -0
- package/lib/commonjs/wallet-instance-attestation/index.js.map +1 -0
- package/lib/commonjs/wallet-instance-attestation/issuing.js +96 -0
- package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -0
- package/lib/commonjs/wallet-instance-attestation/types.js +65 -0
- package/lib/commonjs/wallet-instance-attestation/types.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/pid/index.js +3 -0
- package/lib/module/pid/index.js.map +1 -0
- package/lib/module/pid/sd-jwt/converters.js +23 -0
- package/lib/module/pid/sd-jwt/converters.js.map +1 -0
- package/lib/module/pid/sd-jwt/index.js +66 -0
- package/lib/module/pid/sd-jwt/index.js.map +1 -0
- package/lib/module/pid/sd-jwt/types.js +43 -0
- package/lib/module/pid/sd-jwt/types.js.map +1 -0
- package/lib/module/sd-jwt/__test__/converters.test.js +23 -0
- package/lib/module/sd-jwt/__test__/converters.test.js.map +1 -0
- package/lib/module/sd-jwt/__test__/types.test.js +68 -0
- package/lib/module/sd-jwt/__test__/types.test.js.map +1 -0
- package/lib/module/sd-jwt/converters.js +24 -0
- package/lib/module/sd-jwt/converters.js.map +1 -0
- package/lib/module/sd-jwt/index.js +71 -0
- package/lib/module/sd-jwt/index.js.map +1 -0
- package/lib/module/sd-jwt/types.js +44 -0
- package/lib/module/sd-jwt/types.js.map +1 -0
- package/lib/module/sd-jwt/verifier.js +11 -0
- package/lib/module/sd-jwt/verifier.js.map +1 -0
- package/lib/module/utils/errors.js +73 -0
- package/lib/module/utils/errors.js.map +1 -0
- package/lib/module/utils/jwk.js +38 -0
- package/lib/module/utils/jwk.js.map +1 -0
- package/lib/module/wallet-instance-attestation/index.js +52 -0
- package/lib/module/wallet-instance-attestation/index.js.map +1 -0
- package/lib/module/wallet-instance-attestation/issuing.js +90 -0
- package/lib/module/wallet-instance-attestation/issuing.js.map +1 -0
- package/lib/module/wallet-instance-attestation/types.js +55 -0
- package/lib/module/wallet-instance-attestation/types.js.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/pid/index.d.ts +3 -0
- package/lib/typescript/pid/index.d.ts.map +1 -0
- package/lib/typescript/pid/sd-jwt/converters.d.ts +4 -0
- package/lib/typescript/pid/sd-jwt/converters.d.ts.map +1 -0
- package/lib/typescript/pid/sd-jwt/index.d.ts +50 -0
- package/lib/typescript/pid/sd-jwt/index.d.ts.map +1 -0
- package/lib/typescript/pid/sd-jwt/types.d.ts +196 -0
- package/lib/typescript/pid/sd-jwt/types.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/__test__/converters.test.d.ts +2 -0
- package/lib/typescript/sd-jwt/__test__/converters.test.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/__test__/types.test.d.ts +2 -0
- package/lib/typescript/sd-jwt/__test__/types.test.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/converters.d.ts +3 -0
- package/lib/typescript/sd-jwt/converters.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/index.d.ts +42 -0
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/types.d.ts +416 -0
- package/lib/typescript/sd-jwt/types.d.ts.map +1 -0
- package/lib/typescript/sd-jwt/verifier.d.ts +3 -0
- package/lib/typescript/sd-jwt/verifier.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts +45 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -0
- package/lib/typescript/utils/jwk.d.ts +85 -0
- package/lib/typescript/utils/jwk.d.ts.map +1 -0
- package/lib/typescript/wallet-instance-attestation/index.d.ts +36 -0
- package/lib/typescript/wallet-instance-attestation/index.d.ts.map +1 -0
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts +32 -0
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -0
- package/lib/typescript/wallet-instance-attestation/types.d.ts +733 -0
- package/lib/typescript/wallet-instance-attestation/types.d.ts.map +1 -0
- package/package.json +108 -0
- package/src/index.ts +8 -0
- package/src/pid/index.ts +2 -0
- package/src/pid/sd-jwt/converters.ts +26 -0
- package/src/pid/sd-jwt/index.ts +71 -0
- package/src/pid/sd-jwt/types.ts +44 -0
- package/src/sd-jwt/__test__/converters.test.ts +27 -0
- package/src/sd-jwt/__test__/types.test.ts +85 -0
- package/src/sd-jwt/converters.ts +24 -0
- package/src/sd-jwt/index.ts +92 -0
- package/src/sd-jwt/types.ts +54 -0
- package/src/sd-jwt/verifier.ts +20 -0
- package/src/utils/errors.ts +74 -0
- package/src/utils/jwk.ts +39 -0
- package/src/wallet-instance-attestation/index.ts +56 -0
- package/src/wallet-instance-attestation/issuing.ts +107 -0
- 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
package/src/pid/index.ts
ADDED
@@ -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
|
+
}
|