@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.
- 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
|
+
}
|