@pagopa/io-react-native-wallet 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. package/lib/commonjs/index.js +0 -7
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/pid/index.js +7 -0
  4. package/lib/commonjs/pid/index.js.map +1 -1
  5. package/lib/commonjs/pid/issuing.js +231 -0
  6. package/lib/commonjs/pid/issuing.js.map +1 -0
  7. package/lib/commonjs/sd-jwt/types.js +1 -1
  8. package/lib/commonjs/sd-jwt/types.js.map +1 -1
  9. package/lib/commonjs/utils/dpop.js +1 -1
  10. package/lib/commonjs/utils/dpop.js.map +1 -1
  11. package/lib/commonjs/utils/errors.js +25 -1
  12. package/lib/commonjs/utils/errors.js.map +1 -1
  13. package/lib/module/index.js +1 -2
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/pid/index.js +2 -1
  16. package/lib/module/pid/index.js.map +1 -1
  17. package/lib/module/pid/issuing.js +225 -0
  18. package/lib/module/pid/issuing.js.map +1 -0
  19. package/lib/module/sd-jwt/types.js +1 -1
  20. package/lib/module/sd-jwt/types.js.map +1 -1
  21. package/lib/module/utils/dpop.js +1 -1
  22. package/lib/module/utils/dpop.js.map +1 -1
  23. package/lib/module/utils/errors.js +23 -0
  24. package/lib/module/utils/errors.js.map +1 -1
  25. package/lib/typescript/index.d.ts +1 -2
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/lib/typescript/pid/index.d.ts +2 -1
  28. package/lib/typescript/pid/index.d.ts.map +1 -1
  29. package/lib/typescript/pid/issuing.d.ts +93 -0
  30. package/lib/typescript/pid/issuing.d.ts.map +1 -0
  31. package/lib/typescript/rp/types.d.ts +8 -8
  32. package/lib/typescript/sd-jwt/types.d.ts +5 -5
  33. package/lib/typescript/utils/dpop.d.ts +5 -5
  34. package/lib/typescript/utils/errors.d.ts +13 -0
  35. package/lib/typescript/utils/errors.d.ts.map +1 -1
  36. package/lib/typescript/wallet-instance-attestation/issuing.d.ts +1 -1
  37. package/package.json +4 -2
  38. package/src/index.ts +1 -9
  39. package/src/pid/index.ts +2 -1
  40. package/src/pid/issuing.ts +305 -0
  41. package/src/sd-jwt/types.ts +1 -1
  42. package/src/utils/dpop.ts +1 -1
  43. package/src/utils/errors.ts +24 -0
  44. package/src/wallet-instance-attestation/issuing.ts +1 -1
  45. package/lib/commonjs/utils/signature.js +0 -10
  46. package/lib/commonjs/utils/signature.js.map +0 -1
  47. package/lib/module/utils/signature.js +0 -3
  48. package/lib/module/utils/signature.js.map +0 -1
  49. package/lib/typescript/utils/signature.d.ts +0 -2
  50. package/lib/typescript/utils/signature.d.ts.map +0 -1
  51. package/src/utils/signature.ts +0 -4
package/src/pid/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import * as SdJwt from "./sd-jwt";
2
- export { SdJwt };
2
+ import { Issuing } from "./issuing";
3
+ export { SdJwt, Issuing };
@@ -0,0 +1,305 @@
1
+ import {
2
+ decode as decodeJwt,
3
+ sha256ToBase64,
4
+ } from "@pagopa/io-react-native-jwt";
5
+
6
+ import { SignJWT, thumbprint } from "@pagopa/io-react-native-jwt";
7
+ import { JWK } from "../utils/jwk";
8
+ import uuid from "react-native-uuid";
9
+ import { PidIssuingError } from "../utils/errors";
10
+ import { getUnsignedDPop } from "../utils/dpop";
11
+ import { sign, generate, deleteKey } from "@pagopa/io-react-native-crypto";
12
+
13
+ // This is a temporary type that will be used for demo purposes only
14
+ export type CieData = {
15
+ birthDate: string;
16
+ fiscalCode: string;
17
+ name: string;
18
+ surname: string;
19
+ };
20
+
21
+ export type TokenResponse = { access_token: string; c_nonce: string };
22
+ export type PidResponse = {
23
+ credential: string;
24
+ c_nonce: string;
25
+ c_nonce_expires_in: number;
26
+ format: string;
27
+ };
28
+
29
+ export class Issuing {
30
+ pidProviderBaseUrl: string;
31
+ walletProviderBaseUrl: string;
32
+ walletInstanceAttestation: string;
33
+ codeVerifier: string;
34
+ clientId: string;
35
+ state: string;
36
+ authorizationCode: string;
37
+ appFetch: GlobalFetch["fetch"];
38
+
39
+ constructor(
40
+ pidProviderBaseUrl: string,
41
+ walletProviderBaseUrl: string,
42
+ walletInstanceAttestation: string,
43
+ clientId: string,
44
+ appFetch: GlobalFetch["fetch"] = fetch
45
+ ) {
46
+ this.pidProviderBaseUrl = pidProviderBaseUrl;
47
+ this.walletProviderBaseUrl = walletProviderBaseUrl;
48
+ this.state = `${uuid.v4()}`;
49
+ this.codeVerifier = `${uuid.v4()}`;
50
+ this.authorizationCode = `${uuid.v4()}`;
51
+ this.walletInstanceAttestation = walletInstanceAttestation;
52
+ this.clientId = clientId;
53
+ this.appFetch = appFetch;
54
+ }
55
+
56
+ /**
57
+ * Return the unsigned jwt to call the PAR request.
58
+ *
59
+ * @function
60
+ * @param jwk The wallet instance attestation public JWK
61
+ *
62
+ * @returns Unsigned jwt
63
+ *
64
+ */
65
+ async getUnsignedJwtForPar(jwk: JWK): Promise<string> {
66
+ const parsedJwk = JWK.parse(jwk);
67
+ const keyThumbprint = await thumbprint(parsedJwk);
68
+ const publicKey = { ...parsedJwk, kid: keyThumbprint };
69
+ const codeChallenge = await sha256ToBase64(this.codeVerifier);
70
+
71
+ const unsignedJwtForPar = new SignJWT({
72
+ client_assertion_type:
73
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
74
+ authorization_details: [
75
+ {
76
+ credentialDefinition: {
77
+ type: ["eu.eudiw.pid.it"],
78
+ },
79
+ format: "vc+sd-jwt",
80
+ type: "type",
81
+ },
82
+ ],
83
+ response_type: "code",
84
+ code_challenge_method: "s256",
85
+ redirect_uri: this.walletProviderBaseUrl,
86
+ state: this.state,
87
+ client_id: this.clientId,
88
+ code_challenge: codeChallenge,
89
+ })
90
+ .setProtectedHeader({
91
+ alg: "ES256",
92
+ kid: publicKey.kid,
93
+ })
94
+ .setIssuedAt()
95
+ .setExpirationTime("1h")
96
+ .toSign();
97
+
98
+ return unsignedJwtForPar;
99
+ }
100
+
101
+ /**
102
+ * Make a PAR request to the PID issuer and return the response url
103
+ *
104
+ * @function
105
+ * @param unsignedJwtForPar The unsigned JWT for PAR
106
+ * @param signature The JWT for PAR signature
107
+ *
108
+ * @returns Unsigned PAR url
109
+ *
110
+ */
111
+ async getPar(unsignedJwtForPar: string, signature: string): Promise<string> {
112
+ const codeChallenge = await sha256ToBase64(this.codeVerifier);
113
+ const signedJwtForPar = await SignJWT.appendSignature(
114
+ unsignedJwtForPar,
115
+ signature
116
+ );
117
+
118
+ const parUrl = new URL("/as/par", this.pidProviderBaseUrl).href;
119
+
120
+ const requestBody = {
121
+ response_type: "code",
122
+ client_id: this.clientId,
123
+ code_challenge: codeChallenge,
124
+ code_challenge_method: "S256",
125
+ client_assertion_type:
126
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
127
+ client_assertion: this.walletInstanceAttestation,
128
+ request: signedJwtForPar,
129
+ };
130
+
131
+ var formBody = new URLSearchParams(requestBody);
132
+
133
+ const response = await this.appFetch(parUrl, {
134
+ method: "POST",
135
+ headers: {
136
+ "Content-Type": "application/x-www-form-urlencoded",
137
+ },
138
+ body: formBody.toString(),
139
+ });
140
+
141
+ if (response.status === 201) {
142
+ const result = await response.json();
143
+ return result.request_uri;
144
+ }
145
+
146
+ throw new PidIssuingError(
147
+ `Unable to obtain PAR. Response code: ${await response.text()}`
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Return the unsigned jwt for a generic DPoP
153
+ *
154
+ * @function
155
+ * @param jwk the public key for which the DPoP is to be created
156
+ *
157
+ * @returns Unsigned JWT for DPoP
158
+ *
159
+ */
160
+ async getUnsignedDPoP(jwk: JWK): Promise<string> {
161
+ const tokenUrl = new URL("/token", this.pidProviderBaseUrl).href;
162
+ const dPop = getUnsignedDPop(jwk, {
163
+ htm: "POST",
164
+ htu: tokenUrl,
165
+ jti: `${uuid.v4()}`,
166
+ });
167
+ return dPop;
168
+ }
169
+
170
+ /**
171
+ * Make an auth token request to the PID issuer
172
+ *
173
+ * @function
174
+ * @returns a token response
175
+ *
176
+ */
177
+ async getAuthToken(): Promise<TokenResponse> {
178
+ //Generate fresh keys for DPoP
179
+ const dPopKeyTag = `${uuid.v4()}`;
180
+ const dPopKey = await generate(dPopKeyTag);
181
+ const unsignedDPopForToken = await this.getUnsignedDPoP(dPopKey);
182
+ const dPopTokenSignature = await sign(unsignedDPopForToken, dPopKeyTag);
183
+ await deleteKey(dPopKeyTag);
184
+
185
+ const signedDPop = await SignJWT.appendSignature(
186
+ unsignedDPopForToken,
187
+ dPopTokenSignature
188
+ );
189
+ const decodedJwtDPop = decodeJwt(signedDPop);
190
+ const tokenUrl = decodedJwtDPop.payload.htu as string;
191
+ const requestBody = {
192
+ grant_type: "authorization code",
193
+ client_id: this.clientId,
194
+ code: this.authorizationCode,
195
+ code_verifier: this.codeVerifier,
196
+ client_assertion_type:
197
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
198
+ client_assertion: this.walletInstanceAttestation,
199
+ redirect_uri: this.walletProviderBaseUrl,
200
+ };
201
+ var formBody = new URLSearchParams(requestBody);
202
+
203
+ const response = await this.appFetch(tokenUrl, {
204
+ method: "POST",
205
+ headers: {
206
+ "Content-Type": "application/x-www-form-urlencoded",
207
+ DPoP: signedDPop,
208
+ },
209
+ body: formBody.toString(),
210
+ });
211
+
212
+ if (response.status === 200) {
213
+ return await response.json();
214
+ }
215
+
216
+ throw new PidIssuingError(
217
+ `Unable to obtain token. Response code: ${await response.text()}`
218
+ );
219
+ }
220
+
221
+ /**
222
+ * Return the unsigned jwt for nonce proof of possession
223
+ *
224
+ * @function
225
+ * @param nonce the nonce
226
+ *
227
+ * @returns Unsigned JWT for nonce proof
228
+ *
229
+ */
230
+ async getUnsignedNonceProof(nonce: string): Promise<string> {
231
+ const unsignedProof = new SignJWT({
232
+ nonce,
233
+ })
234
+ .setProtectedHeader({
235
+ alg: "ES256",
236
+ type: "openid4vci-proof+jwt",
237
+ })
238
+ .setAudience(this.walletProviderBaseUrl)
239
+ .setIssuer(this.clientId)
240
+ .setIssuedAt()
241
+ .setExpirationTime("1h")
242
+ .toSign();
243
+ return unsignedProof;
244
+ }
245
+
246
+ /**
247
+ * Make the credential issuing request to the PID issuer
248
+ *
249
+ * @function
250
+ * @param unsignedDPopForPid The unsigned JWT for PID DPoP
251
+ * @param dPopPidSignature The JWT for PID DPoP signature
252
+ * @param unsignedNonceProof The unsigned JWT for nonce proof
253
+ * @param nonceProofSignature The JWT for nonce proof signature
254
+ * @param accessToken The access token obtained with getAuthToken
255
+ * @param cieData Personal data read by the CIE
256
+ *
257
+ * @returns a credential
258
+ *
259
+ */
260
+ async getCredential(
261
+ unsignedDPopForPid: string,
262
+ dPopPidSignature: string,
263
+ unsignedNonceProof: string,
264
+ nonceProofSignature: string,
265
+ accessToken: string,
266
+ cieData: CieData
267
+ ): Promise<PidResponse> {
268
+ const signedDPopForPid = await SignJWT.appendSignature(
269
+ unsignedDPopForPid,
270
+ dPopPidSignature
271
+ );
272
+ const signedNonceProof = await SignJWT.appendSignature(
273
+ unsignedNonceProof,
274
+ nonceProofSignature
275
+ );
276
+ const credentialUrl = new URL("/credential", this.pidProviderBaseUrl).href;
277
+
278
+ const requestBody = {
279
+ credential_definition: JSON.stringify({ type: ["eu.eudiw.pid.it"] }),
280
+ format: "vc+sd-jwt",
281
+ proof: JSON.stringify({
282
+ jwt: signedNonceProof,
283
+ cieData,
284
+ proof_type: "jwt",
285
+ }),
286
+ };
287
+ const formBody = new URLSearchParams(requestBody);
288
+
289
+ const response = await this.appFetch(credentialUrl, {
290
+ method: "POST",
291
+ headers: {
292
+ "Content-Type": "application/x-www-form-urlencoded",
293
+ DPoP: signedDPopForPid,
294
+ Authorization: accessToken,
295
+ },
296
+ body: formBody.toString(),
297
+ });
298
+
299
+ if (response.status === 200) {
300
+ return await response.json();
301
+ }
302
+
303
+ throw new PidIssuingError(`Unable to obtain credential!`);
304
+ }
305
+ }
@@ -25,7 +25,7 @@ export const SdJwt4VC = z.object({
25
25
  header: z.object({
26
26
  typ: z.literal("vc+sd-jwt"),
27
27
  alg: z.string(),
28
- kid: z.string(),
28
+ kid: z.string().optional(),
29
29
  trust_chain: z.array(z.string()),
30
30
  }),
31
31
  payload: z.object({
package/src/utils/dpop.ts CHANGED
@@ -21,5 +21,5 @@ export const DPoPPayload = z.object({
21
21
  jti: z.string(),
22
22
  htm: z.union([z.literal("POST"), z.literal("GET")]),
23
23
  htu: z.string(),
24
- ath: z.string(),
24
+ ath: z.string().optional(),
25
25
  });
@@ -96,3 +96,27 @@ export class AuthRequestDecodeError extends IoWalletError {
96
96
  this.reason = reason;
97
97
  }
98
98
  }
99
+
100
+ /**
101
+ * An error subclass thrown when validation fail
102
+ *
103
+ */
104
+ export class PidIssuingError extends IoWalletError {
105
+ static get code(): "ERR_IO_WALLET_PID_ISSUING_FAILED" {
106
+ return "ERR_IO_WALLET_PID_ISSUING_FAILED";
107
+ }
108
+
109
+ code = "ERR_IO_WALLET_PID_ISSUING_FAILED";
110
+
111
+ /** The Claim for which the validation failed. */
112
+ claim: string;
113
+
114
+ /** Reason code for the validation failure. */
115
+ reason: string;
116
+
117
+ constructor(message: string, claim = "unspecified", reason = "unspecified") {
118
+ super(message);
119
+ this.claim = claim;
120
+ this.reason = reason;
121
+ }
122
+ }
@@ -69,7 +69,7 @@ export class Issuing {
69
69
  async getAttestation(
70
70
  attestationRequest: string,
71
71
  signature: string
72
- ): Promise<String> {
72
+ ): Promise<string> {
73
73
  const signedAttestationRequest = await SignJWT.appendSignature(
74
74
  attestationRequest,
75
75
  signature
@@ -1,10 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.getSignedJwt = void 0;
7
- var _ioReactNativeJwt = require("@pagopa/io-react-native-jwt");
8
- const getSignedJwt = async (unsignedJwt, signature) => await _ioReactNativeJwt.SignJWT.appendSignature(unsignedJwt, signature);
9
- exports.getSignedJwt = getSignedJwt;
10
- //# sourceMappingURL=signature.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_ioReactNativeJwt","require","getSignedJwt","unsignedJwt","signature","SignJWT","appendSignature","exports"],"sourceRoot":"../../../src","sources":["utils/signature.ts"],"mappings":";;;;;;AAAA,IAAAA,iBAAA,GAAAC,OAAA;AAEO,MAAMC,YAAY,GAAG,MAAAA,CAAOC,WAAmB,EAAEC,SAAiB,KACvE,MAAMC,yBAAO,CAACC,eAAe,CAACH,WAAW,EAAEC,SAAS,CAAC;AAACG,OAAA,CAAAL,YAAA,GAAAA,YAAA"}
@@ -1,3 +0,0 @@
1
- import { SignJWT } from "@pagopa/io-react-native-jwt";
2
- export const getSignedJwt = async (unsignedJwt, signature) => await SignJWT.appendSignature(unsignedJwt, signature);
3
- //# sourceMappingURL=signature.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["SignJWT","getSignedJwt","unsignedJwt","signature","appendSignature"],"sourceRoot":"../../../src","sources":["utils/signature.ts"],"mappings":"AAAA,SAASA,OAAO,QAAQ,6BAA6B;AAErD,OAAO,MAAMC,YAAY,GAAG,MAAAA,CAAOC,WAAmB,EAAEC,SAAiB,KACvE,MAAMH,OAAO,CAACI,eAAe,CAACF,WAAW,EAAEC,SAAS,CAAC"}
@@ -1,2 +0,0 @@
1
- export declare const getSignedJwt: (unsignedJwt: string, signature: string) => Promise<string>;
2
- //# sourceMappingURL=signature.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../../src/utils/signature.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,gBAAuB,MAAM,aAAa,MAAM,oBAClB,CAAC"}
@@ -1,4 +0,0 @@
1
- import { SignJWT } from "@pagopa/io-react-native-jwt";
2
-
3
- export const getSignedJwt = async (unsignedJwt: string, signature: string) =>
4
- await SignJWT.appendSignature(unsignedJwt, signature);