@pagopa/io-react-native-wallet 0.4.2 → 0.5.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 +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.
|