@pagopa/io-react-native-wallet 0.2.1 → 0.2.3
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/lib/commonjs/index.js +9 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/pid/issuing.js +28 -0
- package/lib/commonjs/pid/issuing.js.map +1 -1
- package/lib/commonjs/pid/metadata.js +51 -0
- package/lib/commonjs/pid/metadata.js.map +1 -0
- package/lib/commonjs/pid/sd-jwt/index.js +2 -1
- package/lib/commonjs/pid/sd-jwt/index.js.map +1 -1
- package/lib/commonjs/rp/__test__/index.test.js +3 -5
- package/lib/commonjs/rp/__test__/index.test.js.map +1 -1
- package/lib/commonjs/rp/index.js +165 -15
- package/lib/commonjs/rp/index.js.map +1 -1
- package/lib/commonjs/rp/types.js +13 -1
- package/lib/commonjs/rp/types.js.map +1 -1
- package/lib/commonjs/sd-jwt/__test__/index.test.js +119 -0
- package/lib/commonjs/sd-jwt/__test__/index.test.js.map +1 -0
- package/lib/commonjs/sd-jwt/index.js +84 -4
- package/lib/commonjs/sd-jwt/index.js.map +1 -1
- package/lib/commonjs/sd-jwt/types.js +9 -0
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/sd-jwt/verifier.js +7 -5
- package/lib/commonjs/sd-jwt/verifier.js.map +1 -1
- package/lib/commonjs/utils/errors.js +76 -1
- package/lib/commonjs/utils/errors.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/pid/issuing.js +30 -2
- package/lib/module/pid/issuing.js.map +1 -1
- package/lib/module/pid/metadata.js +43 -0
- package/lib/module/pid/metadata.js.map +1 -0
- package/lib/module/pid/sd-jwt/index.js +3 -3
- package/lib/module/pid/sd-jwt/index.js.map +1 -1
- package/lib/module/rp/__test__/index.test.js +3 -5
- package/lib/module/rp/__test__/index.test.js.map +1 -1
- package/lib/module/rp/index.js +168 -18
- package/lib/module/rp/index.js.map +1 -1
- package/lib/module/rp/types.js +11 -0
- package/lib/module/rp/types.js.map +1 -1
- package/lib/module/sd-jwt/__test__/index.test.js +118 -0
- package/lib/module/sd-jwt/__test__/index.test.js.map +1 -0
- package/lib/module/sd-jwt/index.js +83 -3
- package/lib/module/sd-jwt/index.js.map +1 -1
- package/lib/module/sd-jwt/types.js +10 -0
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/sd-jwt/verifier.js +8 -6
- package/lib/module/sd-jwt/verifier.js.map +1 -1
- package/lib/module/utils/errors.js +71 -0
- package/lib/module/utils/errors.js.map +1 -1
- package/lib/typescript/{index.d.ts → src/index.d.ts} +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/pid/index.d.ts.map +1 -0
- package/lib/typescript/{pid → src/pid}/issuing.d.ts +9 -0
- package/lib/typescript/src/pid/issuing.d.ts.map +1 -0
- package/lib/typescript/src/pid/metadata.d.ts +528 -0
- package/lib/typescript/src/pid/metadata.d.ts.map +1 -0
- package/lib/typescript/src/pid/sd-jwt/converters.d.ts.map +1 -0
- package/lib/typescript/{pid → src/pid}/sd-jwt/index.d.ts +1 -1
- package/lib/typescript/src/pid/sd-jwt/index.d.ts.map +1 -0
- package/lib/typescript/src/pid/sd-jwt/types.d.ts.map +1 -0
- package/lib/typescript/src/rp/__test__/index.test.d.ts.map +1 -0
- package/lib/typescript/src/rp/index.d.ts +89 -0
- package/lib/typescript/src/rp/index.d.ts.map +1 -0
- package/lib/typescript/{rp → src/rp}/types.d.ts +71 -47
- package/lib/typescript/{rp → src/rp}/types.d.ts.map +1 -1
- package/lib/typescript/src/sd-jwt/__test__/converters.test.d.ts.map +1 -0
- package/lib/typescript/src/sd-jwt/__test__/index.test.d.ts +2 -0
- package/lib/typescript/src/sd-jwt/__test__/index.test.d.ts.map +1 -0
- package/lib/typescript/src/sd-jwt/__test__/types.test.d.ts.map +1 -0
- package/lib/typescript/src/sd-jwt/converters.d.ts.map +1 -0
- package/lib/typescript/{sd-jwt → src/sd-jwt}/index.d.ts +22 -2
- package/lib/typescript/src/sd-jwt/index.d.ts.map +1 -0
- package/lib/typescript/{sd-jwt → src/sd-jwt}/types.d.ts +12 -0
- package/lib/typescript/src/sd-jwt/types.d.ts.map +1 -0
- package/lib/typescript/src/sd-jwt/verifier.d.ts +3 -0
- package/lib/typescript/src/sd-jwt/verifier.d.ts.map +1 -0
- package/lib/typescript/src/utils/dpop.d.ts.map +1 -0
- package/lib/typescript/{utils → src/utils}/errors.d.ts +41 -0
- package/lib/typescript/src/utils/errors.d.ts.map +1 -0
- package/lib/typescript/src/utils/jwk.d.ts.map +1 -0
- package/lib/typescript/src/wallet-instance-attestation/index.d.ts.map +1 -0
- package/lib/typescript/src/wallet-instance-attestation/issuing.d.ts.map +1 -0
- package/lib/typescript/{wallet-instance-attestation → src/wallet-instance-attestation}/types.d.ts +8 -8
- package/lib/typescript/{wallet-instance-attestation → src/wallet-instance-attestation}/types.d.ts.map +1 -1
- package/package.json +7 -5
- package/src/index.ts +13 -1
- package/src/pid/issuing.ts +38 -1
- package/src/pid/metadata.ts +46 -0
- package/src/pid/sd-jwt/index.ts +7 -4
- package/src/rp/__test__/index.test.ts +5 -9
- package/src/rp/index.ts +208 -24
- package/src/rp/types.ts +16 -0
- package/src/sd-jwt/__test__/index.test.ts +171 -0
- package/src/sd-jwt/index.ts +84 -7
- package/src/sd-jwt/types.ts +13 -0
- package/src/sd-jwt/verifier.ts +5 -7
- package/src/utils/errors.ts +81 -0
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/pid/index.d.ts.map +0 -1
- package/lib/typescript/pid/issuing.d.ts.map +0 -1
- package/lib/typescript/pid/sd-jwt/converters.d.ts.map +0 -1
- package/lib/typescript/pid/sd-jwt/index.d.ts.map +0 -1
- package/lib/typescript/pid/sd-jwt/types.d.ts.map +0 -1
- package/lib/typescript/rp/__test__/index.test.d.ts.map +0 -1
- package/lib/typescript/rp/index.d.ts +0 -43
- package/lib/typescript/rp/index.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/__test__/converters.test.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/__test__/types.test.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/converters.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/index.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/types.d.ts.map +0 -1
- package/lib/typescript/sd-jwt/verifier.d.ts +0 -3
- package/lib/typescript/sd-jwt/verifier.d.ts.map +0 -1
- package/lib/typescript/utils/dpop.d.ts.map +0 -1
- package/lib/typescript/utils/errors.d.ts.map +0 -1
- package/lib/typescript/utils/jwk.d.ts.map +0 -1
- package/lib/typescript/wallet-instance-attestation/index.d.ts.map +0 -1
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +0 -1
- /package/lib/typescript/{pid → src/pid}/index.d.ts +0 -0
- /package/lib/typescript/{pid → src/pid}/sd-jwt/converters.d.ts +0 -0
- /package/lib/typescript/{pid → src/pid}/sd-jwt/types.d.ts +0 -0
- /package/lib/typescript/{rp → src/rp}/__test__/index.test.d.ts +0 -0
- /package/lib/typescript/{sd-jwt → src/sd-jwt}/__test__/converters.test.d.ts +0 -0
- /package/lib/typescript/{sd-jwt → src/sd-jwt}/__test__/types.test.d.ts +0 -0
- /package/lib/typescript/{sd-jwt → src/sd-jwt}/converters.d.ts +0 -0
- /package/lib/typescript/{utils → src/utils}/dpop.d.ts +0 -0
- /package/lib/typescript/{utils → src/utils}/jwk.d.ts +0 -0
- /package/lib/typescript/{wallet-instance-attestation → src/wallet-instance-attestation}/index.d.ts +0 -0
- /package/lib/typescript/{wallet-instance-attestation → src/wallet-instance-attestation}/issuing.d.ts +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { JWK } from "../utils/jwk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export type PidDisplayMetadata = z.infer<typeof PidDisplayMetadata>;
|
|
5
|
+
export const PidDisplayMetadata = z.object({
|
|
6
|
+
name: z.string(),
|
|
7
|
+
locale: z.string(),
|
|
8
|
+
logo: z.object({
|
|
9
|
+
url: z.string(),
|
|
10
|
+
alt_text: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
background_color: z.string(),
|
|
13
|
+
text_color: z.string(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type PidIssuerEntityConfiguration = z.infer<
|
|
17
|
+
typeof PidIssuerEntityConfiguration
|
|
18
|
+
>;
|
|
19
|
+
export const PidIssuerEntityConfiguration = z.object({
|
|
20
|
+
jwks: z.object({ keys: z.array(JWK) }),
|
|
21
|
+
metadata: z.object({
|
|
22
|
+
openid_credential_issuer: z.object({
|
|
23
|
+
credential_issuer: z.string(),
|
|
24
|
+
authorization_endpoint: z.string(),
|
|
25
|
+
token_endpoint: z.string(),
|
|
26
|
+
pushed_authorization_request_endpoint: z.string(),
|
|
27
|
+
dpop_signing_alg_values_supported: z.array(z.string()),
|
|
28
|
+
credential_endpoint: z.string(),
|
|
29
|
+
credentials_supported: z.object({
|
|
30
|
+
"eu.eudiw.pid.it": z.object({
|
|
31
|
+
format: z.literal("vc+sd-jwt"),
|
|
32
|
+
cryptographic_binding_methods_supported: z.array(z.string()),
|
|
33
|
+
cryptographic_suites_supported: z.array(z.string()),
|
|
34
|
+
display: z.array(PidDisplayMetadata),
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
}),
|
|
38
|
+
federation_entity: z.object({
|
|
39
|
+
organization_name: z.string(),
|
|
40
|
+
homepage_uri: z.string(),
|
|
41
|
+
policy_uri: z.string(),
|
|
42
|
+
tos_uri: z.string(),
|
|
43
|
+
logo_uri: z.string(),
|
|
44
|
+
}),
|
|
45
|
+
}),
|
|
46
|
+
});
|
package/src/pid/sd-jwt/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { decode as decodeJwt } from "../../sd-jwt";
|
|
2
|
-
import { verify as verifyJwt } from "../../sd-jwt";
|
|
1
|
+
import { decode as decodeJwt, verify as verifyJwt } from "../../sd-jwt";
|
|
3
2
|
import { PID } from "./types";
|
|
4
3
|
import { pidFromToken } from "./converters";
|
|
5
4
|
import { Disclosure, SdJwt4VC } from "../../sd-jwt/types";
|
|
@@ -20,7 +19,11 @@ import { Disclosure, SdJwt4VC } from "../../sd-jwt/types";
|
|
|
20
19
|
*
|
|
21
20
|
*/
|
|
22
21
|
export function decode(token: string): PidWithToken {
|
|
23
|
-
let { sdJwt, disclosures } = decodeJwt(
|
|
22
|
+
let { sdJwt, disclosures: disclosuresWithOriginal } = decodeJwt(
|
|
23
|
+
token,
|
|
24
|
+
SdJwt4VC
|
|
25
|
+
);
|
|
26
|
+
const disclosures = disclosuresWithOriginal.map((d) => d.decoded);
|
|
24
27
|
const pid = pidFromToken(sdJwt, disclosures);
|
|
25
28
|
|
|
26
29
|
return { pid, sdJwt, disclosures };
|
|
@@ -54,7 +57,7 @@ export async function verify(token: string): Promise<VerifyResult> {
|
|
|
54
57
|
return decoded;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
type PidWithToken = {
|
|
60
|
+
export type PidWithToken = {
|
|
58
61
|
// The object with the parsed data for PID
|
|
59
62
|
pid: PID;
|
|
60
63
|
// The object with the parsed SD-JWT token that shipped the PID. It will be needed to present PID data.
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { RelyingPartySolution } from "..";
|
|
2
2
|
import { AuthRequestDecodeError } from "../../utils/errors";
|
|
3
3
|
|
|
4
|
-
const walletInstanceAttestation =
|
|
5
|
-
"eyJhbGciOiJFUzI1NiIsImtpZCI6IjV0NVlZcEJoTi1FZ0lFRUk1aVV6cjZyME1SMDJMblZRME9tZWttTktjalkiLCJ0cnVzdF9jaGFpbiI6WyJleUpoYkdjaU9pSkZVei4uLjZTMEEiLCJleUpoYkdjaU9pSkZVei4uLmpKTEEiLCJleUpoYkdjaU9pSkZVei4uLkg5Z3ciXSwidHlwIjoidmErand0IiwieDVjIjpbIk1JSUJqRENDIC4uLiBYRmVoZ0tRQT09Il19.eyJpc3MiOiJodHRwczovL3dhbGxldC1wcm92aWRlci5leGFtcGxlLm9yZyIsInN1YiI6InZiZVhKa3NNNDV4cGh0QU5uQ2lHNm1DeXVVNGpmR056b3BHdUt2b2dnOWMiLCJ0eXBlIjoiV2FsbGV0SW5zdGFuY2VBdHRlc3RhdGlvbiIsInBvbGljeV91cmkiOiJodHRwczovL3dhbGxldC1wcm92aWRlci5leGFtcGxlLm9yZy9wcml2YWN5X3BvbGljeSIsInRvc191cmkiOiJodHRwczovL3dhbGxldC1wcm92aWRlci5leGFtcGxlLm9yZy9pbmZvX3BvbGljeSIsImxvZ29fdXJpIjoiaHR0cHM6Ly93YWxsZXQtcHJvdmlkZXIuZXhhbXBsZS5vcmcvbG9nby5zdmciLCJhc2MiOiJodHRwczovL3dhbGxldC1wcm92aWRlci5leGFtcGxlLm9yZy9Mb0EvYmFzaWMiLCJjbmYiOnsiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiNEhOcHRJLXhyMnBqeVJKS0dNbno0V21kblFEX3VKU3E0Ujk1Tmo5OGI0NCIsInkiOiJMSVpuU0IzOXZGSmhZZ1MzazdqWEU0cjMtQ29HRlF3WnRQQklScXBObHJnIiwia2lkIjoidmJlWEprc000NXhwaHRBTm5DaUc2bUN5dVU0amZHTnpvcEd1S3ZvZ2c5YyJ9fSwiYXV0aG9yaXphdGlvbl9lbmRwb2ludCI6ImV1ZGl3OiIsInJlc3BvbnNlX3R5cGVzX3N1cHBvcnRlZCI6WyJ2cF90b2tlbiJdLCJ2cF9mb3JtYXRzX3N1cHBvcnRlZCI6eyJqd3RfdnBfanNvbiI6eyJhbGdfdmFsdWVzX3N1cHBvcnRlZCI6WyJFUzI1NiJdfSwiand0X3ZjX2pzb24iOnsiYWxnX3ZhbHVlc19zdXBwb3J0ZWQiOlsiRVMyNTYiXX19LCJyZXF1ZXN0X29iamVjdF9zaWduaW5nX2FsZ192YWx1ZXNfc3VwcG9ydGVkIjpbIkVTMjU2Il0sInByZXNlbnRhdGlvbl9kZWZpbml0aW9uX3VyaV9zdXBwb3J0ZWQiOmZhbHNlLCJpYXQiOjE2ODcyODExOTUsImV4cCI6MTY4NzI4ODM5NX0.OTuPik6p3o9j6VOx-uCyxRvHwoh1pDiiZcBQFNQt2uE3dK-8izGNflJVETi_uhGSZOf25Enkq-UvEin9NrbJNw";
|
|
6
|
-
const rp = new RelyingPartySolution(
|
|
7
|
-
"http://rp.example",
|
|
8
|
-
walletInstanceAttestation
|
|
9
|
-
);
|
|
10
4
|
describe("decodeAuthRequestQR", () => {
|
|
11
5
|
it("should return authentication request URL", async () => {
|
|
12
6
|
const qrcode =
|
|
13
7
|
"ZXVkaXc6Ly9hdXRob3JpemU/Y2xpZW50X2lkPWh0dHBzOi8vdmVyaWZpZXIuZXhhbXBsZS5vcmcmcmVxdWVzdF91cmk9aHR0cHM6Ly92ZXJpZmllci5leGFtcGxlLm9yZy9yZXF1ZXN0X3VyaQ==";
|
|
14
|
-
const result =
|
|
15
|
-
expect(result).toEqual(
|
|
8
|
+
const result = RelyingPartySolution.decodeAuthRequestQR(qrcode);
|
|
9
|
+
expect(result.requestURI).toEqual(
|
|
10
|
+
"https://verifier.example.org/request_uri"
|
|
11
|
+
);
|
|
16
12
|
});
|
|
17
13
|
it("should throw exception with invalid QR", async () => {
|
|
18
14
|
const qrcode = "aHR0cDovL2dvb2dsZS5pdA==";
|
|
19
|
-
expect(() =>
|
|
15
|
+
expect(() => RelyingPartySolution.decodeAuthRequestQR(qrcode)).toThrowError(
|
|
20
16
|
AuthRequestDecodeError
|
|
21
17
|
);
|
|
22
18
|
});
|
package/src/rp/index.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AuthRequestDecodeError,
|
|
3
|
+
IoWalletError,
|
|
4
|
+
NoSuitableKeysFoundInEntityConfiguration,
|
|
5
|
+
} from "../utils/errors";
|
|
2
6
|
import {
|
|
3
7
|
decode as decodeJwt,
|
|
4
8
|
decodeBase64,
|
|
5
9
|
sha256ToBase64,
|
|
6
10
|
SignJWT,
|
|
11
|
+
EncryptJwe,
|
|
12
|
+
verify,
|
|
7
13
|
} from "@pagopa/io-react-native-jwt";
|
|
8
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
QRCodePayload,
|
|
16
|
+
RequestObject,
|
|
17
|
+
RpEntityConfiguration,
|
|
18
|
+
type Presentation,
|
|
19
|
+
} from "./types";
|
|
9
20
|
|
|
10
21
|
import uuid from "react-native-uuid";
|
|
11
22
|
import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
|
|
23
|
+
import { disclose } from "../sd-jwt";
|
|
12
24
|
|
|
13
25
|
export class RelyingPartySolution {
|
|
14
26
|
relyingPartyBaseUrl: string;
|
|
@@ -33,24 +45,25 @@ export class RelyingPartySolution {
|
|
|
33
45
|
* @returns The authentication request url
|
|
34
46
|
*
|
|
35
47
|
*/
|
|
36
|
-
decodeAuthRequestQR(qrcode: string):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
static decodeAuthRequestQR(qrcode: string): QRCodePayload {
|
|
49
|
+
const decoded = decodeBase64(qrcode);
|
|
50
|
+
const decodedUrl = new URL(decoded);
|
|
51
|
+
const protocol = decodedUrl.protocol;
|
|
52
|
+
const resource = decodedUrl.hostname;
|
|
53
|
+
const requestURI = decodedUrl.searchParams.get("request_uri");
|
|
54
|
+
const clientId = decodedUrl.searchParams.get("client_id");
|
|
55
|
+
|
|
56
|
+
const result = QRCodePayload.safeParse({
|
|
57
|
+
protocol,
|
|
58
|
+
resource,
|
|
59
|
+
requestURI,
|
|
60
|
+
clientId,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.success) {
|
|
64
|
+
return result.data;
|
|
65
|
+
} else {
|
|
66
|
+
throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
/**
|
|
@@ -85,19 +98,21 @@ export class RelyingPartySolution {
|
|
|
85
98
|
|
|
86
99
|
/**
|
|
87
100
|
* Obtain the Request Object for RP authentication
|
|
101
|
+
* @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
|
|
88
102
|
*
|
|
89
|
-
* @function
|
|
103
|
+
* @async @function
|
|
90
104
|
* @param signedWalletInstanceDPoP JWT of the Wallet Instance Attestation DPoP
|
|
91
105
|
*
|
|
92
106
|
* @returns The Request Object JWT
|
|
107
|
+
* @throws {NoSuitableKeysFoundInEntityConfiguration} When the Request Object is signed with a key not listed in RP's entity configuration
|
|
93
108
|
*
|
|
94
109
|
*/
|
|
95
110
|
async getRequestObject(
|
|
96
|
-
signedWalletInstanceDPoP: string
|
|
111
|
+
signedWalletInstanceDPoP: string,
|
|
112
|
+
entity: RpEntityConfiguration
|
|
97
113
|
): Promise<RequestObject> {
|
|
98
114
|
const decodedJwtDPop = await decodeJwt(signedWalletInstanceDPoP);
|
|
99
115
|
const requestUri = decodedJwtDPop.payload.htu as string;
|
|
100
|
-
|
|
101
116
|
const response = await this.appFetch(requestUri, {
|
|
102
117
|
method: "GET",
|
|
103
118
|
headers: {
|
|
@@ -108,11 +123,28 @@ export class RelyingPartySolution {
|
|
|
108
123
|
|
|
109
124
|
if (response.status === 200) {
|
|
110
125
|
const responseText = await response.text();
|
|
111
|
-
const responseJwt =
|
|
126
|
+
const responseJwt = decodeJwt(responseText);
|
|
127
|
+
|
|
128
|
+
// verify token signature according to RP's entity configuration
|
|
129
|
+
// to ensure the request object is authentic
|
|
130
|
+
{
|
|
131
|
+
const pubKey = entity.payload.jwks.keys.find(
|
|
132
|
+
({ kid }) => kid === responseJwt.protectedHeader.kid
|
|
133
|
+
);
|
|
134
|
+
if (!pubKey) {
|
|
135
|
+
throw new NoSuitableKeysFoundInEntityConfiguration(
|
|
136
|
+
"Request Object signature verification"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
await verify(responseText, pubKey);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// parse request object it has the expected shape by specification
|
|
112
143
|
const requestObj = RequestObject.parse({
|
|
113
144
|
header: responseJwt.protectedHeader,
|
|
114
145
|
payload: responseJwt.payload,
|
|
115
146
|
});
|
|
147
|
+
|
|
116
148
|
return requestObj;
|
|
117
149
|
}
|
|
118
150
|
|
|
@@ -121,6 +153,158 @@ export class RelyingPartySolution {
|
|
|
121
153
|
);
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
|
|
158
|
+
* The presentation is prepared by disclosing data from provided credentials, according to requested claims
|
|
159
|
+
* Each Verified Credential come along with the claims the user accepts to disclose from it.
|
|
160
|
+
*
|
|
161
|
+
* The returned token is unsigned (sign should be apply by the caller).
|
|
162
|
+
*
|
|
163
|
+
* @todo accept more than a Verified Credential
|
|
164
|
+
*
|
|
165
|
+
* @param requestObj The incoming request object, which the requirements for the requested authorization
|
|
166
|
+
* @param presentation The Verified Credential containing user data along with the list of claims to be disclosed.
|
|
167
|
+
* @returns The unsigned Verified Presentation token
|
|
168
|
+
* @throws {ClaimsNotFoundBetweenDislosures} If the Verified Credential does not contain one or more requested claims.
|
|
169
|
+
*
|
|
170
|
+
*/
|
|
171
|
+
async prepareVpToken(
|
|
172
|
+
requestObj: RequestObject,
|
|
173
|
+
[vc, claims]: Presentation // TODO: [SIW-353] support multiple presentations
|
|
174
|
+
): Promise<{
|
|
175
|
+
vp_token: string;
|
|
176
|
+
presentation_submission: Record<string, unknown>;
|
|
177
|
+
}> {
|
|
178
|
+
// this throws if vc cannot satisfy all the requested claims
|
|
179
|
+
const { token: vp, paths } = await disclose(vc, claims);
|
|
180
|
+
|
|
181
|
+
// TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
|
|
182
|
+
|
|
183
|
+
const vp_token = new SignJWT({ vp })
|
|
184
|
+
.setAudience(requestObj.payload.response_uri)
|
|
185
|
+
.setExpirationTime("1h")
|
|
186
|
+
.setProtectedHeader({
|
|
187
|
+
typ: "JWT",
|
|
188
|
+
alg: "ES256",
|
|
189
|
+
})
|
|
190
|
+
.toSign();
|
|
191
|
+
|
|
192
|
+
const [definition_id, vc_scope] = requestObj.payload.scope;
|
|
193
|
+
const presentation_submission = {
|
|
194
|
+
definition_id,
|
|
195
|
+
id: `${uuid.v4()}`,
|
|
196
|
+
descriptor_map: paths.map((p) => ({
|
|
197
|
+
id: vc_scope,
|
|
198
|
+
path: `$.vp_token.${p.path}`,
|
|
199
|
+
format: "vc+sd-jwt",
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return { vp_token, presentation_submission };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Compose and send an Authorization Response in the context of an authorization request flow.
|
|
208
|
+
*
|
|
209
|
+
* @todo MUST add presentation_submission
|
|
210
|
+
*
|
|
211
|
+
* @param requestObj The incoming request object, which the requirements for the requested authorization
|
|
212
|
+
* @param vp_token The signed Verified Presentation token with data to send.
|
|
213
|
+
* @param presentation_submission
|
|
214
|
+
* @param entity The RP entity configuration
|
|
215
|
+
* @returns The response from the RP
|
|
216
|
+
* @throws {IoWalletError} if the submission fails.
|
|
217
|
+
* @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key
|
|
218
|
+
*
|
|
219
|
+
*/
|
|
220
|
+
async sendAuthorizationResponse(
|
|
221
|
+
requestObj: RequestObject,
|
|
222
|
+
vp_token: string,
|
|
223
|
+
presentation_submission: Record<string, unknown>,
|
|
224
|
+
entity: RpEntityConfiguration
|
|
225
|
+
): Promise<string> {
|
|
226
|
+
// the request is an unsigned jws without iss, aud, exp
|
|
227
|
+
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-respon
|
|
228
|
+
const jwk = this.choosePublicKeyToEncrypt(entity);
|
|
229
|
+
const enc = this.getEncryptionAlgByJwk(jwk);
|
|
230
|
+
|
|
231
|
+
const authzResponsePayload = JSON.stringify({
|
|
232
|
+
state: requestObj.payload.state,
|
|
233
|
+
presentation_submission,
|
|
234
|
+
vp_token,
|
|
235
|
+
});
|
|
236
|
+
const encrypted = await new EncryptJwe(authzResponsePayload, {
|
|
237
|
+
alg: jwk.alg,
|
|
238
|
+
enc,
|
|
239
|
+
}).encrypt(jwk);
|
|
240
|
+
|
|
241
|
+
const formBody = new URLSearchParams({ response: encrypted });
|
|
242
|
+
const response = await this.appFetch(requestObj.payload.response_uri, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
246
|
+
},
|
|
247
|
+
body: formBody.toString(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (response.status === 200) {
|
|
251
|
+
return response.text();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw new IoWalletError(
|
|
255
|
+
`Unable to send Authorization Response. Response code: ${response.status}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Select a public key from those provided by the RP.
|
|
261
|
+
* Keys with algorithm "RSA-OAEP-256" or "RSA-OAEP" are expected, the firsts to be preferred.
|
|
262
|
+
*
|
|
263
|
+
* @param entity The RP entity configuration
|
|
264
|
+
* @returns A suitable public key with its compatible encryption algorithm
|
|
265
|
+
* @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
|
|
266
|
+
*/
|
|
267
|
+
private choosePublicKeyToEncrypt(
|
|
268
|
+
entity: RpEntityConfiguration
|
|
269
|
+
): (JWK & { alg: "RSA-OAEP-256" }) | (JWK & { alg: "RSA-OAEP" }) {
|
|
270
|
+
// Look for keys using "RSA-OAEP-256", and pick a random one
|
|
271
|
+
const [usingRsa256] = entity.payload.jwks.keys.filter(
|
|
272
|
+
<T>(k: T & { alg?: string }): k is T & { alg: "RSA-OAEP-256" } =>
|
|
273
|
+
typeof k.alg === "string" && k.alg === "RSA-OAEP-256"
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (usingRsa256) {
|
|
277
|
+
return usingRsa256;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Look for keys using "RSA-OAEP", and pick a random one
|
|
281
|
+
const [usingRsa] = entity.payload.jwks.keys.filter(
|
|
282
|
+
<T>(k: T & { alg?: string }): k is T & { alg: "RSA-OAEP" } =>
|
|
283
|
+
typeof k.alg === "string" && k.alg === "RSA-OAEP"
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (usingRsa) {
|
|
287
|
+
return usingRsa;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// No suitable key has been found
|
|
291
|
+
throw new NoSuitableKeysFoundInEntityConfiguration(
|
|
292
|
+
"Encrypt with RP public key"
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private getEncryptionAlgByJwk({
|
|
297
|
+
alg,
|
|
298
|
+
}: (JWK & { alg: "RSA-OAEP-256" }) | (JWK & { alg: "RSA-OAEP" })):
|
|
299
|
+
| "A128CBC-HS256"
|
|
300
|
+
| "A256CBC-HS512" {
|
|
301
|
+
if (alg === "RSA-OAEP-256") return "A256CBC-HS512";
|
|
302
|
+
if (alg === "RSA-OAEP") return "A128CBC-HS256";
|
|
303
|
+
|
|
304
|
+
const _: never = alg;
|
|
305
|
+
throw new Error(`Invalid jwk algorithm: ${_}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
124
308
|
/**
|
|
125
309
|
* Obtain the relying party entity configuration.
|
|
126
310
|
*/
|
package/src/rp/types.ts
CHANGED
|
@@ -62,3 +62,19 @@ export const RpEntityConfiguration = z.object({
|
|
|
62
62
|
authority_hints: z.array(z.string()),
|
|
63
63
|
}),
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
export type QRCodePayload = z.infer<typeof QRCodePayload>;
|
|
67
|
+
export const QRCodePayload = z.object({
|
|
68
|
+
protocol: z.literal("eudiw:"),
|
|
69
|
+
resource: z.string(), // TODO: refine to known paths using literals
|
|
70
|
+
clientId: z.string(),
|
|
71
|
+
requestURI: z.string(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A pair that associate a tokenized Verified Credential with the claims presented or requested to present.
|
|
76
|
+
*/
|
|
77
|
+
export type Presentation = [
|
|
78
|
+
/* verified credential token */ string,
|
|
79
|
+
/* claims */ string[]
|
|
80
|
+
];
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { decode, disclose } from "../index";
|
|
2
|
+
|
|
3
|
+
import { encodeBase64, decodeBase64 } from "@pagopa/io-react-native-jwt";
|
|
4
|
+
import { SdJwt4VC } from "../types";
|
|
5
|
+
|
|
6
|
+
// Examples from https://www.ietf.org/id/draft-terbu-sd-jwt-vc-02.html#name-example-4
|
|
7
|
+
// but adapted to adhere to format declared in https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/pid-eaa-data-model.html#id2
|
|
8
|
+
// In short, the token is a Frankenstein composed as follows:
|
|
9
|
+
// - the header is taken from the italian specification, with kid and alg valued according to the signing keys
|
|
10
|
+
// - disclosures are taken from the SD-JWT-4-VC standard
|
|
11
|
+
// - payload is taken from the italian specification, but _sd are compiled with:
|
|
12
|
+
// - "address" is used as verification._sd
|
|
13
|
+
// - all others disclosures are in claims._sd
|
|
14
|
+
const token =
|
|
15
|
+
"eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImIxODZlYTBjMTkyNTc5MzA5N2JmMDFiOGEyODlhNDVmIiwidHJ1c3RfY2hhaW4iOlsiTkVoUmRFUnBZbmxIWTNNNVdsZFdUV1oyYVVobSAuLi4iLCJleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2IC4uLiIsIklrSllkbVp5Ykc1b1FVMTFTRkl3TjJGcVZXMUIgLi4uIl19.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsInN1YiI6Ik56YkxzWGg4dURDY2Q3bm9XWEZaQWZIa3hac1JHQzlYcy4uLiIsImp0aSI6InVybjp1dWlkOjZjNWMwYTQ5LWI1ODktNDMxZC1iYWU3LTIxOTEyMmE5ZWMyYyIsImlhdCI6MTU0MTQ5MzcyNCwiZXhwIjoxNTQxNDkzNzI0LCJzdGF0dXMiOiJodHRwczovL2V4YW1wbGUuY29tL3N0YXR1cyIsImNuZiI6eyJqd2siOnsia3R5IjoiUlNBIiwidXNlIjoic2lnIiwibiI6IjFUYS1zRSIsImUiOiJBUUFCIiwia2lkIjoiWWhORlMzWW5DOXRqaUNhaXZoV0xWVUozQXh3R0d6Xzk4dVJGYXFNRUVzIn19LCJ0eXBlIjoiUGVyc29uSWRlbnRpZmljYXRpb25EYXRhIiwidmVyaWZpZWRfY2xhaW1zIjp7InZlcmlmaWNhdGlvbiI6eyJfc2QiOlsiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSJdLCJ0cnVzdF9mcmFtZXdvcmsiOiJlaWRhcyIsImFzc3VyYW5jZV9sZXZlbCI6ImhpZ2gifSwiY2xhaW1zIjp7Il9zZCI6WyIwOXZLckpNT2x5VFdNMHNqcHVfcGRPQlZCUTJNMXkzS2hwSDUxNW5Ya3BZIiwiMnJzakdiYUMwa3k4bVQwcEpyUGlvV1RxMF9kYXcxc1g3NnBvVWxnQ3diSSIsIkVrTzhkaFcwZEhFSmJ2VUhsRV9WQ2V1Qzl1UkVMT2llTFpoaDdYYlVUdEEiLCJJbER6SUtlaVpkRHdwcXBLNlpmYnlwaEZ2ejVGZ25XYS1zTjZ3cVFYQ2l3IiwiUG9yRmJwS3VWdTZ4eW1KYWd2a0ZzRlhBYlJvYzJKR2xBVUEyQkE0bzdjSSIsIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCJqZHJURThZY2JZNEVpZnVnaWhpQWVfQlBla3hKUVpJQ2VpVVF3WTlRcXhJIiwianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdfX0sIl9zZF9hbGciOiJzaGEtMjU2In0.8wwSHCd47wCgzRYXvvPTTRXGS-hk9V8jRzy7WSjRBTZxSHxJkGOSWwBVAA-kpJ-IvQS7699aLWxIMqAvr34sOA~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImlzX292ZXJfMTgiLCB0cnVlXQ~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImlzX292ZXJfMjEiLCB0cnVlXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImlzX292ZXJfNjUiLCB0cnVlXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0";
|
|
16
|
+
|
|
17
|
+
const unsigned =
|
|
18
|
+
"eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImIxODZlYTBjMTkyNTc5MzA5N2JmMDFiOGEyODlhNDVmIiwidHJ1c3RfY2hhaW4iOlsiTkVoUmRFUnBZbmxIWTNNNVdsZFdUV1oyYVVobSAuLi4iLCJleUpoYkdjaU9pSlNVekkxTmlJc0ltdHBaQ0k2IC4uLiIsIklrSllkbVp5Ykc1b1FVMTFTRkl3TjJGcVZXMUIgLi4uIl19.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsInN1YiI6Ik56YkxzWGg4dURDY2Q3bm9XWEZaQWZIa3hac1JHQzlYcy4uLiIsImp0aSI6InVybjp1dWlkOjZjNWMwYTQ5LWI1ODktNDMxZC1iYWU3LTIxOTEyMmE5ZWMyYyIsImlhdCI6MTU0MTQ5MzcyNCwiZXhwIjoxNTQxNDkzNzI0LCJzdGF0dXMiOiJodHRwczovL2V4YW1wbGUuY29tL3N0YXR1cyIsImNuZiI6eyJqd2siOnsia3R5IjoiUlNBIiwidXNlIjoic2lnIiwibiI6IjFUYS1zRSIsImUiOiJBUUFCIiwia2lkIjoiWWhORlMzWW5DOXRqaUNhaXZoV0xWVUozQXh3R0d6Xzk4dVJGYXFNRUVzIn19LCJ0eXBlIjoiUGVyc29uSWRlbnRpZmljYXRpb25EYXRhIiwidmVyaWZpZWRfY2xhaW1zIjp7InZlcmlmaWNhdGlvbiI6eyJfc2QiOlsiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSJdLCJ0cnVzdF9mcmFtZXdvcmsiOiJlaWRhcyIsImFzc3VyYW5jZV9sZXZlbCI6ImhpZ2gifSwiY2xhaW1zIjp7Il9zZCI6WyIwOXZLckpNT2x5VFdNMHNqcHVfcGRPQlZCUTJNMXkzS2hwSDUxNW5Ya3BZIiwiMnJzakdiYUMwa3k4bVQwcEpyUGlvV1RxMF9kYXcxc1g3NnBvVWxnQ3diSSIsIkVrTzhkaFcwZEhFSmJ2VUhsRV9WQ2V1Qzl1UkVMT2llTFpoaDdYYlVUdEEiLCJJbER6SUtlaVpkRHdwcXBLNlpmYnlwaEZ2ejVGZ25XYS1zTjZ3cVFYQ2l3IiwiUG9yRmJwS3VWdTZ4eW1KYWd2a0ZzRlhBYlJvYzJKR2xBVUEyQkE0bzdjSSIsIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCJqZHJURThZY2JZNEVpZnVnaWhpQWVfQlBla3hKUVpJQ2VpVVF3WTlRcXhJIiwianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdfX0sIl9zZF9hbGciOiJzaGEtMjU2In0";
|
|
19
|
+
|
|
20
|
+
const signature =
|
|
21
|
+
"8wwSHCd47wCgzRYXvvPTTRXGS-hk9V8jRzy7WSjRBTZxSHxJkGOSWwBVAA-kpJ-IvQS7699aLWxIMqAvr34sOA";
|
|
22
|
+
|
|
23
|
+
const signed = `${unsigned}.${signature}`;
|
|
24
|
+
|
|
25
|
+
const tokenizedDisclosures = [
|
|
26
|
+
"WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd",
|
|
27
|
+
"WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd",
|
|
28
|
+
"WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ",
|
|
29
|
+
"WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ",
|
|
30
|
+
"WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0",
|
|
31
|
+
"WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImlzX292ZXJfMTgiLCB0cnVlXQ",
|
|
32
|
+
"WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImlzX292ZXJfMjEiLCB0cnVlXQ",
|
|
33
|
+
"WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImlzX292ZXJfNjUiLCB0cnVlXQ",
|
|
34
|
+
"WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const sdJwt = {
|
|
38
|
+
header: {
|
|
39
|
+
typ: "vc+sd-jwt",
|
|
40
|
+
alg: "ES256",
|
|
41
|
+
kid: "b186ea0c1925793097bf01b8a289a45f",
|
|
42
|
+
trust_chain: [
|
|
43
|
+
"NEhRdERpYnlHY3M5WldWTWZ2aUhm ...",
|
|
44
|
+
"eyJhbGciOiJSUzI1NiIsImtpZCI6 ...",
|
|
45
|
+
"IkJYdmZybG5oQU11SFIwN2FqVW1B ...",
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
payload: {
|
|
49
|
+
iss: "https://example.com/issuer",
|
|
50
|
+
sub: "NzbLsXh8uDCcd7noWXFZAfHkxZsRGC9Xs...",
|
|
51
|
+
jti: "urn:uuid:6c5c0a49-b589-431d-bae7-219122a9ec2c",
|
|
52
|
+
iat: 1541493724,
|
|
53
|
+
exp: 1541493724,
|
|
54
|
+
status: "https://example.com/status",
|
|
55
|
+
cnf: {
|
|
56
|
+
jwk: {
|
|
57
|
+
kty: "RSA",
|
|
58
|
+
use: "sig",
|
|
59
|
+
n: "1Ta-sE",
|
|
60
|
+
e: "AQAB",
|
|
61
|
+
kid: "YhNFS3YnC9tjiCaivhWLVUJ3AxwGGz_98uRFaqMEEs",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
type: "PersonIdentificationData",
|
|
65
|
+
verified_claims: {
|
|
66
|
+
verification: {
|
|
67
|
+
_sd: ["JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE"],
|
|
68
|
+
trust_framework: "eidas",
|
|
69
|
+
assurance_level: "high",
|
|
70
|
+
},
|
|
71
|
+
claims: {
|
|
72
|
+
_sd: [
|
|
73
|
+
"09vKrJMOlyTWM0sjpu_pdOBVBQ2M1y3KhpH515nXkpY",
|
|
74
|
+
"2rsjGbaC0ky8mT0pJrPioWTq0_daw1sX76poUlgCwbI",
|
|
75
|
+
"EkO8dhW0dHEJbvUHlE_VCeuC9uRELOieLZhh7XbUTtA",
|
|
76
|
+
"IlDzIKeiZdDwpqpK6ZfbyphFvz5FgnWa-sN6wqQXCiw",
|
|
77
|
+
"PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI",
|
|
78
|
+
"TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo",
|
|
79
|
+
"jdrTE8YcbY4EifugihiAe_BPekxJQZICeiUQwY9QqxI",
|
|
80
|
+
"jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4",
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
_sd_alg: "sha-256",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// In the very same order than tokenizedDisclosures
|
|
89
|
+
const disclosures = [
|
|
90
|
+
["2GLC42sKQveCfGfryNRN9w", "given_name", "John"],
|
|
91
|
+
["eluV5Og3gSNII8EYnsxA_A", "family_name", "Doe"],
|
|
92
|
+
["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"],
|
|
93
|
+
["eI8ZWm9QnKPpNPeNenHdhQ", "phone_number", "+1-202-555-0101"],
|
|
94
|
+
["AJx-095VPrpTtN4QMOqROA", "birthdate", "1940-01-01"],
|
|
95
|
+
["Pc33JM2LchcU_lHggv_ufQ", "is_over_18", true],
|
|
96
|
+
["G02NSrQfjFXQ7Io09syajA", "is_over_21", true],
|
|
97
|
+
["lklxF5jMYlGTPUovMNIvCA", "is_over_65", true],
|
|
98
|
+
[
|
|
99
|
+
"Qg_O64zqAxe412a108iroA",
|
|
100
|
+
"address",
|
|
101
|
+
{
|
|
102
|
+
street_address: "123 Main St",
|
|
103
|
+
locality: "Anytown",
|
|
104
|
+
region: "Anystate",
|
|
105
|
+
country: "US",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
];
|
|
109
|
+
it("Ensures example data correctness", () => {
|
|
110
|
+
expect(
|
|
111
|
+
JSON.parse(decodeBase64(encodeBase64(JSON.stringify(sdJwt.header))))
|
|
112
|
+
).toEqual(sdJwt.header);
|
|
113
|
+
expect([signed, ...tokenizedDisclosures].join("~")).toBe(token);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("decode", () => {
|
|
117
|
+
it("should decode a valid token", () => {
|
|
118
|
+
const result = decode(token, SdJwt4VC);
|
|
119
|
+
expect(result).toEqual({
|
|
120
|
+
sdJwt,
|
|
121
|
+
disclosures: disclosures.map((decoded, i) => ({
|
|
122
|
+
decoded,
|
|
123
|
+
encoded: tokenizedDisclosures[i],
|
|
124
|
+
})),
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("disclose", () => {
|
|
130
|
+
it("should encode a valid sdjwt (one claim)", async () => {
|
|
131
|
+
const result = await disclose(token, ["given_name"]);
|
|
132
|
+
const expected = {
|
|
133
|
+
token: `${signed}~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd`,
|
|
134
|
+
paths: [{ claim: "given_name", path: "verified_claims.claims._sd[7]" }],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual(expected);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should encode a valid sdjwt (no claims)", async () => {
|
|
141
|
+
const result = await disclose(token, []);
|
|
142
|
+
const expected = { token: `${signed}`, paths: [] };
|
|
143
|
+
|
|
144
|
+
expect(result).toEqual(expected);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should encode a valid sdjwt (multiple claims)", async () => {
|
|
148
|
+
const result = await disclose(token, ["given_name", "email"]);
|
|
149
|
+
const expected = {
|
|
150
|
+
token: `${signed}~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ`,
|
|
151
|
+
paths: [
|
|
152
|
+
{
|
|
153
|
+
claim: "given_name",
|
|
154
|
+
path: "verified_claims.claims._sd[7]",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
claim: "email",
|
|
158
|
+
path: "verified_claims.verification._sd[0]",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
expect(result).toEqual(expected);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should fail on unknown claim", async () => {
|
|
167
|
+
const fn = async () => disclose(token, ["unknown"]);
|
|
168
|
+
|
|
169
|
+
await expect(fn()).rejects.toEqual(expect.any(Error));
|
|
170
|
+
});
|
|
171
|
+
});
|