@pagopa/io-react-native-wallet 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +98 -22
- package/lib/commonjs/index.js +12 -8
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/pid/index.js +3 -8
- package/lib/commonjs/pid/index.js.map +1 -1
- package/lib/commonjs/pid/issuing.js +152 -168
- package/lib/commonjs/pid/issuing.js.map +1 -1
- package/lib/commonjs/pid/metadata.js +28 -25
- package/lib/commonjs/pid/metadata.js.map +1 -1
- package/lib/commonjs/rp/__test__/index.test.js +5 -3
- package/lib/commonjs/rp/__test__/index.test.js.map +1 -1
- package/lib/commonjs/rp/index.js +158 -154
- package/lib/commonjs/rp/index.js.map +1 -1
- package/lib/commonjs/trust/types.js +9 -7
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/utils/crypto.js +46 -0
- package/lib/commonjs/utils/crypto.js.map +1 -0
- package/lib/commonjs/utils/dpop.js +14 -7
- package/lib/commonjs/utils/dpop.js.map +1 -1
- package/lib/commonjs/wallet-instance-attestation/index.js +3 -3
- package/lib/commonjs/wallet-instance-attestation/issuing.js +50 -60
- package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/module/index.js +4 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/pid/index.js +1 -1
- package/lib/module/pid/index.js.map +1 -1
- package/lib/module/pid/issuing.js +151 -171
- package/lib/module/pid/issuing.js.map +1 -1
- package/lib/module/pid/metadata.js +28 -25
- package/lib/module/pid/metadata.js.map +1 -1
- package/lib/module/rp/__test__/index.test.js +1 -1
- package/lib/module/rp/__test__/index.test.js.map +1 -1
- package/lib/module/rp/index.js +155 -153
- package/lib/module/rp/index.js.map +1 -1
- package/lib/module/trust/types.js +7 -6
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/utils/crypto.js +40 -0
- package/lib/module/utils/crypto.js.map +1 -0
- package/lib/module/utils/dpop.js +13 -5
- package/lib/module/utils/dpop.js.map +1 -1
- package/lib/module/wallet-instance-attestation/index.js +2 -2
- package/lib/module/wallet-instance-attestation/index.js.map +1 -1
- package/lib/module/wallet-instance-attestation/issuing.js +48 -58
- package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
- package/lib/typescript/index.d.ts +4 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/pid/index.d.ts +1 -1
- package/lib/typescript/pid/index.d.ts.map +1 -1
- package/lib/typescript/pid/issuing.d.ts +51 -87
- package/lib/typescript/pid/issuing.d.ts.map +1 -1
- package/lib/typescript/pid/metadata.d.ts +1338 -408
- package/lib/typescript/pid/metadata.d.ts.map +1 -1
- package/lib/typescript/rp/index.d.ts +48 -86
- package/lib/typescript/rp/index.d.ts.map +1 -1
- package/lib/typescript/rp/types.d.ts +413 -57
- package/lib/typescript/rp/types.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/index.d.ts +1 -1
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
- package/lib/typescript/trust/types.d.ts +1000 -274
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/utils/crypto.d.ts +10 -0
- package/lib/typescript/utils/crypto.d.ts.map +1 -0
- package/lib/typescript/utils/dpop.d.ts +10 -2
- package/lib/typescript/utils/dpop.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/index.d.ts +2 -2
- package/lib/typescript/wallet-instance-attestation/index.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts +17 -31
- package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +5 -3
- package/src/pid/index.ts +1 -1
- package/src/pid/issuing.ts +233 -225
- package/src/pid/metadata.ts +32 -27
- package/src/rp/__test__/index.test.ts +1 -1
- package/src/rp/index.ts +180 -188
- package/src/sd-jwt/index.ts +1 -1
- package/src/trust/types.ts +39 -32
- package/src/utils/crypto.ts +41 -0
- package/src/utils/dpop.ts +17 -7
- package/src/wallet-instance-attestation/index.ts +2 -2
- package/src/wallet-instance-attestation/issuing.ts +55 -62
package/src/rp/index.ts
CHANGED
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
SignJWT,
|
11
11
|
EncryptJwe,
|
12
12
|
verify,
|
13
|
+
type CryptoContext,
|
13
14
|
} from "@pagopa/io-react-native-jwt";
|
14
15
|
import {
|
15
16
|
QRCodePayload,
|
@@ -21,102 +22,111 @@ import {
|
|
21
22
|
import uuid from "react-native-uuid";
|
22
23
|
import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
|
23
24
|
import { disclose } from "../sd-jwt";
|
24
|
-
import { getEntityConfiguration } from "../trust";
|
25
|
+
import { getEntityConfiguration as getGenericEntityConfiguration } from "../trust";
|
26
|
+
import { createDPopToken } from "../utils/dpop";
|
27
|
+
import { WalletInstanceAttestation } from "..";
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
/**
|
30
|
+
* Select a RSA public key from those provided by the RP to encrypt.
|
31
|
+
*
|
32
|
+
* @param entity The RP entity configuration
|
33
|
+
* @returns A suitable public key with its compatible encryption algorithm
|
34
|
+
* @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
|
35
|
+
*/
|
36
|
+
const chooseRSAPublicKeyToEncrypt = (entity: RpEntityConfiguration): JWK => {
|
37
|
+
const [usingRsa256] =
|
38
|
+
entity.payload.metadata.wallet_relying_party.jwks.filter(
|
39
|
+
(jwk) => jwk.use === "enc" && jwk.kty === "RSA"
|
40
|
+
);
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
walletInstanceAttestation: string,
|
34
|
-
appFetch: GlobalFetch["fetch"] = fetch
|
35
|
-
) {
|
36
|
-
this.relyingPartyBaseUrl = relyingPartyBaseUrl;
|
37
|
-
this.walletInstanceAttestation = walletInstanceAttestation;
|
38
|
-
this.appFetch = appFetch;
|
42
|
+
if (usingRsa256) {
|
43
|
+
return usingRsa256;
|
39
44
|
}
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
* @returns The authentication request url
|
47
|
-
*
|
48
|
-
*/
|
49
|
-
static decodeAuthRequestQR(qrcode: string): QRCodePayload {
|
50
|
-
const decoded = decodeBase64(qrcode);
|
51
|
-
const decodedUrl = new URL(decoded);
|
52
|
-
const protocol = decodedUrl.protocol;
|
53
|
-
const resource = decodedUrl.hostname;
|
54
|
-
const requestURI = decodedUrl.searchParams.get("request_uri");
|
55
|
-
const clientId = decodedUrl.searchParams.get("client_id");
|
46
|
+
// No suitable key has been found
|
47
|
+
throw new NoSuitableKeysFoundInEntityConfiguration(
|
48
|
+
"Encrypt with RP public key"
|
49
|
+
);
|
50
|
+
};
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
52
|
+
/**
|
53
|
+
* Obtain the relying party entity configuration.
|
54
|
+
*/
|
55
|
+
export const getEntityConfiguration =
|
56
|
+
({ appFetch = fetch }: { appFetch?: GlobalFetch["fetch"] } = {}) =>
|
57
|
+
async (relyingPartyBaseUrl: string): Promise<RpEntityConfiguration> => {
|
58
|
+
return getGenericEntityConfiguration(relyingPartyBaseUrl, {
|
59
|
+
appFetch: appFetch,
|
60
|
+
}).then(RpEntityConfiguration.parse);
|
61
|
+
};
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
alg: "ES256",
|
92
|
-
jwk: walletInstanceAttestationJwk,
|
93
|
-
typ: "dpop+jwt",
|
94
|
-
})
|
95
|
-
.setIssuedAt()
|
96
|
-
.setExpirationTime("1h")
|
97
|
-
.toSign();
|
63
|
+
/**
|
64
|
+
* Decode a QR code content to an authentication request url.
|
65
|
+
* @function
|
66
|
+
* @param qrcode QR code content
|
67
|
+
*
|
68
|
+
* @returns The authentication request url
|
69
|
+
*
|
70
|
+
*/
|
71
|
+
export const decodeAuthRequestQR = (qrcode: string): QRCodePayload => {
|
72
|
+
const decoded = decodeBase64(qrcode);
|
73
|
+
const decodedUrl = new URL(decoded);
|
74
|
+
const protocol = decodedUrl.protocol;
|
75
|
+
const resource = decodedUrl.hostname;
|
76
|
+
const requestURI = decodedUrl.searchParams.get("request_uri");
|
77
|
+
const clientId = decodedUrl.searchParams.get("client_id");
|
78
|
+
|
79
|
+
const result = QRCodePayload.safeParse({
|
80
|
+
protocol,
|
81
|
+
resource,
|
82
|
+
requestURI,
|
83
|
+
clientId,
|
84
|
+
});
|
85
|
+
|
86
|
+
if (result.success) {
|
87
|
+
return result.data;
|
88
|
+
} else {
|
89
|
+
throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
|
98
90
|
}
|
91
|
+
};
|
92
|
+
|
93
|
+
export type RequestObjectConf = {
|
94
|
+
requestObject: RequestObject;
|
95
|
+
rpEntityConfiguration: RpEntityConfiguration;
|
96
|
+
walletInstanceAttestation: string;
|
97
|
+
};
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
99
|
+
/**
|
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
|
102
|
+
*/
|
103
|
+
export const getRequestObject =
|
104
|
+
({
|
105
|
+
wiaCryptoContext,
|
106
|
+
appFetch = fetch,
|
107
|
+
}: {
|
108
|
+
wiaCryptoContext: CryptoContext;
|
109
|
+
appFetch?: GlobalFetch["fetch"];
|
110
|
+
}) =>
|
111
|
+
async (
|
112
|
+
walletInstanceAttestation: string,
|
113
113
|
requestUri: string,
|
114
|
-
|
115
|
-
): Promise<
|
116
|
-
const
|
114
|
+
rpEntityConfiguration: RpEntityConfiguration
|
115
|
+
): Promise<RequestObjectConf> => {
|
116
|
+
const signedWalletInstanceDPoP = await createDPopToken(
|
117
|
+
{
|
118
|
+
jti: `${uuid.v4()}`,
|
119
|
+
htm: "GET",
|
120
|
+
htu: requestUri,
|
121
|
+
ath: await sha256ToBase64(walletInstanceAttestation),
|
122
|
+
},
|
123
|
+
wiaCryptoContext
|
124
|
+
);
|
125
|
+
|
126
|
+
const response = await appFetch(requestUri, {
|
117
127
|
method: "GET",
|
118
128
|
headers: {
|
119
|
-
Authorization: `DPoP ${
|
129
|
+
Authorization: `DPoP ${walletInstanceAttestation}`,
|
120
130
|
DPoP: signedWalletInstanceDPoP,
|
121
131
|
},
|
122
132
|
});
|
@@ -130,9 +140,10 @@ export class RelyingPartySolution {
|
|
130
140
|
// verify token signature according to RP's entity configuration
|
131
141
|
// to ensure the request object is authentic
|
132
142
|
{
|
133
|
-
const pubKey =
|
134
|
-
|
135
|
-
|
143
|
+
const pubKey =
|
144
|
+
rpEntityConfiguration.payload.metadata.wallet_relying_party.jwks.find(
|
145
|
+
({ kid }) => kid === responseJwt.protectedHeader.kid
|
146
|
+
);
|
136
147
|
if (!pubKey) {
|
137
148
|
throw new NoSuitableKeysFoundInEntityConfiguration(
|
138
149
|
"Request Object signature verification"
|
@@ -142,67 +153,68 @@ export class RelyingPartySolution {
|
|
142
153
|
}
|
143
154
|
|
144
155
|
// parse request object it has the expected shape by specification
|
145
|
-
const
|
156
|
+
const requestObject = RequestObject.parse({
|
146
157
|
header: responseJwt.protectedHeader,
|
147
158
|
payload: responseJwt.payload,
|
148
159
|
});
|
149
160
|
|
150
|
-
return
|
161
|
+
return {
|
162
|
+
requestObject,
|
163
|
+
rpEntityConfiguration,
|
164
|
+
walletInstanceAttestation,
|
165
|
+
};
|
151
166
|
}
|
152
167
|
|
153
168
|
throw new IoWalletError(
|
154
|
-
`Unable to obtain Request Object. Response code: ${response.status}
|
169
|
+
`Unable to obtain Request Object. Response code: ${response.status}
|
170
|
+
${await response.text()}`
|
155
171
|
);
|
156
|
-
}
|
172
|
+
};
|
157
173
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
* @param signKeyId The kid of the key that will be used to sign
|
171
|
-
* @returns The unsigned Verified Presentation token
|
172
|
-
* @throws {ClaimsNotFoundBetweenDislosures} If the Verified Credential does not contain one or more requested claims.
|
173
|
-
*
|
174
|
-
*/
|
175
|
-
async prepareVpToken(
|
176
|
-
requestObj: RequestObject,
|
177
|
-
walletInstanceIdentifier: string,
|
178
|
-
[vc, claims]: Presentation, // TODO: [SIW-353] support multiple presentations,
|
179
|
-
signKeyId: string
|
174
|
+
/**
|
175
|
+
* Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
|
176
|
+
* The presentation is prepared by disclosing data from provided credentials, according to requested claims
|
177
|
+
* Each Verified Credential come along with the claims the user accepts to disclose from it.
|
178
|
+
*
|
179
|
+
* @todo accept more than a Verified Credential
|
180
|
+
*/
|
181
|
+
const prepareVpToken =
|
182
|
+
({ pidCryptoContext }: { pidCryptoContext: CryptoContext }) =>
|
183
|
+
async (
|
184
|
+
{ requestObject, walletInstanceAttestation }: RequestObjectConf,
|
185
|
+
[vc, claims]: Presentation // TODO: [SIW-353] support multiple presentations,
|
180
186
|
): Promise<{
|
181
187
|
vp_token: string;
|
182
188
|
presentation_submission: Record<string, unknown>;
|
183
|
-
}> {
|
189
|
+
}> => {
|
184
190
|
// this throws if vc cannot satisfy all the requested claims
|
185
191
|
const { token: vp, paths } = await disclose(vc, claims);
|
186
192
|
|
187
|
-
//
|
193
|
+
// obtain issuer from Wallet Instance
|
194
|
+
const {
|
195
|
+
payload: { iss },
|
196
|
+
} = WalletInstanceAttestation.decode(walletInstanceAttestation);
|
188
197
|
|
189
|
-
const
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
nonce: requestObj.payload.nonce,
|
194
|
-
})
|
195
|
-
.setAudience(requestObj.payload.response_uri)
|
196
|
-
.setIssuedAt()
|
197
|
-
.setExpirationTime("1h")
|
198
|
+
const pidKid = await pidCryptoContext.getPublicKey().then((_) => _.kid);
|
199
|
+
|
200
|
+
// TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
|
201
|
+
const vp_token = await new SignJWT(pidCryptoContext)
|
198
202
|
.setProtectedHeader({
|
199
203
|
typ: "JWT",
|
200
|
-
|
201
|
-
|
204
|
+
kid: pidKid,
|
205
|
+
})
|
206
|
+
.setPayload({
|
207
|
+
vp: vp,
|
208
|
+
jti: `${uuid.v4()}`,
|
209
|
+
iss,
|
210
|
+
nonce: requestObject.payload.nonce,
|
202
211
|
})
|
203
|
-
.
|
212
|
+
.setAudience(requestObject.payload.response_uri)
|
213
|
+
.setIssuedAt()
|
214
|
+
.setExpirationTime("1h")
|
215
|
+
.sign();
|
204
216
|
|
205
|
-
const vc_scope =
|
217
|
+
const vc_scope = requestObject.payload.scope;
|
206
218
|
const presentation_submission = {
|
207
219
|
definition_id: `${uuid.v4()}`,
|
208
220
|
id: `${uuid.v4()}`,
|
@@ -214,36 +226,49 @@ export class RelyingPartySolution {
|
|
214
226
|
};
|
215
227
|
|
216
228
|
return { vp_token, presentation_submission };
|
217
|
-
}
|
229
|
+
};
|
218
230
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
async
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
231
|
+
/**
|
232
|
+
* Compose and send an Authorization Response in the context of an authorization request flow.
|
233
|
+
*
|
234
|
+
* @todo MUST add presentation_submission
|
235
|
+
*
|
236
|
+
*/
|
237
|
+
export const sendAuthorizationResponse =
|
238
|
+
({
|
239
|
+
pidCryptoContext,
|
240
|
+
appFetch = fetch,
|
241
|
+
}: {
|
242
|
+
pidCryptoContext: CryptoContext;
|
243
|
+
appFetch?: GlobalFetch["fetch"];
|
244
|
+
}) =>
|
245
|
+
async (
|
246
|
+
{
|
247
|
+
requestObject,
|
248
|
+
rpEntityConfiguration,
|
249
|
+
walletInstanceAttestation,
|
250
|
+
}: RequestObjectConf,
|
251
|
+
presentation: Presentation // TODO: [SIW-353] support multiple presentations,
|
252
|
+
): Promise<string> => {
|
239
253
|
// the request is an unsigned jws without iss, aud, exp
|
240
254
|
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-respon
|
241
|
-
const jwk =
|
255
|
+
const jwk = chooseRSAPublicKeyToEncrypt(rpEntityConfiguration);
|
256
|
+
|
257
|
+
const { vp_token, presentation_submission } = await prepareVpToken({
|
258
|
+
pidCryptoContext,
|
259
|
+
})(
|
260
|
+
{
|
261
|
+
requestObject,
|
262
|
+
rpEntityConfiguration,
|
263
|
+
walletInstanceAttestation,
|
264
|
+
},
|
265
|
+
presentation
|
266
|
+
);
|
242
267
|
|
243
268
|
const authzResponsePayload = JSON.stringify({
|
244
|
-
state:
|
269
|
+
state: requestObject.payload.state,
|
245
270
|
presentation_submission,
|
246
|
-
nonce:
|
271
|
+
nonce: requestObject.payload.nonce,
|
247
272
|
vp_token,
|
248
273
|
});
|
249
274
|
|
@@ -256,7 +281,7 @@ export class RelyingPartySolution {
|
|
256
281
|
const formBody = new URLSearchParams({ response: encrypted });
|
257
282
|
const body = formBody.toString();
|
258
283
|
|
259
|
-
const response = await
|
284
|
+
const response = await appFetch(requestObject.payload.response_uri, {
|
260
285
|
method: "POST",
|
261
286
|
headers: {
|
262
287
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -273,37 +298,4 @@ export class RelyingPartySolution {
|
|
273
298
|
response.status
|
274
299
|
}`
|
275
300
|
);
|
276
|
-
}
|
277
|
-
|
278
|
-
/**
|
279
|
-
* Select a RSA public key from those provided by the RP to encrypt.
|
280
|
-
*
|
281
|
-
* @param entity The RP entity configuration
|
282
|
-
* @returns A suitable public key with its compatible encryption algorithm
|
283
|
-
* @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
|
284
|
-
*/
|
285
|
-
private chooseRSAPublicKeyToEncrypt(entity: RpEntityConfiguration): JWK {
|
286
|
-
const [usingRsa256] =
|
287
|
-
entity.payload.metadata.wallet_relying_party.jwks.filter(
|
288
|
-
(jwk) => jwk.use === "enc" && jwk.kty === "RSA"
|
289
|
-
);
|
290
|
-
|
291
|
-
if (usingRsa256) {
|
292
|
-
return usingRsa256;
|
293
|
-
}
|
294
|
-
|
295
|
-
// No suitable key has been found
|
296
|
-
throw new NoSuitableKeysFoundInEntityConfiguration(
|
297
|
-
"Encrypt with RP public key"
|
298
|
-
);
|
299
|
-
}
|
300
|
-
|
301
|
-
/**
|
302
|
-
* Obtain the relying party entity configuration.
|
303
|
-
*/
|
304
|
-
async getEntityConfiguration(): Promise<RpEntityConfiguration> {
|
305
|
-
return getEntityConfiguration(this.relyingPartyBaseUrl, {
|
306
|
-
appFetch: this.appFetch,
|
307
|
-
}).then(RpEntityConfiguration.parse);
|
308
|
-
}
|
309
|
-
}
|
301
|
+
};
|
package/src/sd-jwt/index.ts
CHANGED
@@ -7,7 +7,7 @@ import { sha256ToBase64 } from "@pagopa/io-react-native-jwt";
|
|
7
7
|
import { decodeBase64 } from "@pagopa/io-react-native-jwt";
|
8
8
|
import { Disclosure, SdJwt4VC, type DisclosureWithEncoded } from "./types";
|
9
9
|
import { verifyDisclosure } from "./verifier";
|
10
|
-
import type { JWK } from "
|
10
|
+
import type { JWK } from "../utils/jwk";
|
11
11
|
import {
|
12
12
|
ClaimsNotFoundBetweenDislosures,
|
13
13
|
ClaimsNotFoundInToken,
|
package/src/trust/types.ts
CHANGED
@@ -22,40 +22,47 @@ export const EntityStatement = z.object({
|
|
22
22
|
}),
|
23
23
|
});
|
24
24
|
|
25
|
+
export type EntityConfigurationHeader = z.infer<
|
26
|
+
typeof EntityConfigurationHeader
|
27
|
+
>;
|
28
|
+
export const EntityConfigurationHeader = z.object({
|
29
|
+
typ: z.literal("entity-statement+jwt"),
|
30
|
+
alg: z.string(),
|
31
|
+
kid: z.string(),
|
32
|
+
});
|
33
|
+
|
25
34
|
export type EntityConfiguration = z.infer<typeof EntityConfiguration>;
|
26
35
|
export const EntityConfiguration = z.object({
|
27
|
-
header:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
authority_hints: z.array(z.string()).optional(),
|
58
|
-
}),
|
36
|
+
header: EntityConfigurationHeader,
|
37
|
+
payload: z
|
38
|
+
.object({
|
39
|
+
exp: UnixTime,
|
40
|
+
iat: UnixTime,
|
41
|
+
iss: z.string(),
|
42
|
+
sub: z.string(),
|
43
|
+
jwks: z.object({
|
44
|
+
keys: z.array(JWK),
|
45
|
+
}),
|
46
|
+
metadata: z
|
47
|
+
.object({
|
48
|
+
federation_entity: z
|
49
|
+
.object({
|
50
|
+
federation_fetch_endpoint: z.string().optional(),
|
51
|
+
federation_list_endpoint: z.string().optional(),
|
52
|
+
federation_resolve_endpoint: z.string().optional(),
|
53
|
+
federation_trust_mark_status_endpoint: z.string().optional(),
|
54
|
+
federation_trust_mark_list_endpoint: z.string().optional(),
|
55
|
+
homepage_uri: z.string().optional(),
|
56
|
+
policy_uri: z.string().optional(),
|
57
|
+
logo_uri: z.string().optional(),
|
58
|
+
contacts: z.array(z.string()).optional(),
|
59
|
+
})
|
60
|
+
.passthrough(),
|
61
|
+
})
|
62
|
+
.passthrough(),
|
63
|
+
authority_hints: z.array(z.string()).optional(),
|
64
|
+
})
|
65
|
+
.passthrough(),
|
59
66
|
});
|
60
67
|
|
61
68
|
export type TrustAnchorEntityConfiguration = z.infer<
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { getPublicKey, sign } from "@pagopa/io-react-native-crypto";
|
2
|
+
import { thumbprint, type CryptoContext } from "@pagopa/io-react-native-jwt";
|
3
|
+
import { fixBase64EncodingOnKey } from "./jwk";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Create a CryptoContext bound to a key pair.
|
7
|
+
* Key pair is supposed to exist already in the device's keychain.
|
8
|
+
* It's identified by its unique keytag.
|
9
|
+
*
|
10
|
+
* @returns the crypto context
|
11
|
+
*/
|
12
|
+
export const createCryptoContextFor = (keytag: string): CryptoContext => {
|
13
|
+
return {
|
14
|
+
/**
|
15
|
+
* Retrieve the public key of the pair.
|
16
|
+
* If the key pair doesn't exist yet, an error is raised
|
17
|
+
* @returns The public key.
|
18
|
+
*/
|
19
|
+
async getPublicKey() {
|
20
|
+
return getPublicKey(keytag)
|
21
|
+
.then(fixBase64EncodingOnKey)
|
22
|
+
.then(async (jwk) => ({
|
23
|
+
...jwk,
|
24
|
+
// Keys in the TEE are not stored with their KID, which is supposed to be assigned when they are included in JWK sets.
|
25
|
+
// (that is, KID is not a propoerty of the key itself, but it's property used to identify a key in a set).
|
26
|
+
// We assume the convention we use the thumbprint of the public key as KID, thus for easy development we decided to evaluate KID here
|
27
|
+
// However the values is an arbitrary string that might be anything
|
28
|
+
kid: await thumbprint(jwk),
|
29
|
+
}));
|
30
|
+
},
|
31
|
+
/**
|
32
|
+
* Get a signature for a provided value.
|
33
|
+
* If the key pair doesn't exist yet, an error is raised.
|
34
|
+
* @param value
|
35
|
+
* @returns The signature for the value
|
36
|
+
*/
|
37
|
+
async getSignature(value: string) {
|
38
|
+
return sign(value, keytag);
|
39
|
+
},
|
40
|
+
};
|
41
|
+
};
|
package/src/utils/dpop.ts
CHANGED
@@ -1,19 +1,29 @@
|
|
1
1
|
import * as z from "zod";
|
2
2
|
|
3
|
-
import { SignJWT } from "@pagopa/io-react-native-jwt";
|
4
|
-
import type { JWK } from "./jwk";
|
3
|
+
import { SignJWT, type CryptoContext } from "@pagopa/io-react-native-jwt";
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
/**
|
6
|
+
* Create a signed DPoP token
|
7
|
+
*
|
8
|
+
* @param payload The payload to be included in the token.
|
9
|
+
* @param crypto The crypto context that handles the key bound to the DPoP.
|
10
|
+
*
|
11
|
+
* @returns The signed crypto token.
|
12
|
+
*/
|
13
|
+
export const createDPopToken = async (
|
14
|
+
payload: DPoPPayload,
|
15
|
+
crypto: CryptoContext
|
16
|
+
): Promise<string> => {
|
17
|
+
const jwk = await crypto.getPublicKey();
|
18
|
+
return new SignJWT(crypto)
|
19
|
+
.setPayload(payload)
|
8
20
|
.setProtectedHeader({
|
9
|
-
alg: "ES256",
|
10
21
|
typ: "dpop+jwt",
|
11
22
|
jwk,
|
12
23
|
})
|
13
24
|
.setIssuedAt()
|
14
25
|
.setExpirationTime("1h")
|
15
|
-
.
|
16
|
-
return dPop;
|
26
|
+
.sign();
|
17
27
|
};
|
18
28
|
|
19
29
|
export type DPoPPayload = z.infer<typeof DPoPPayload>;
|
@@ -2,8 +2,8 @@ import { WalletInstanceAttestationJwt } from "./types";
|
|
2
2
|
import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
|
3
3
|
import { verify as verifyJwt } from "@pagopa/io-react-native-jwt";
|
4
4
|
|
5
|
-
import {
|
6
|
-
export {
|
5
|
+
import { getAttestation } from "./issuing";
|
6
|
+
export { getAttestation };
|
7
7
|
/**
|
8
8
|
* Decode a given JWT to get the parsed Wallet Instance Attestation object they define.
|
9
9
|
* It ensures provided data is in a valid shape.
|