@pagopa/io-react-native-wallet 0.4.3 → 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 -169
- 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 -172
- 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 -226
- 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/pid/issuing.ts
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
import {
|
2
|
-
decode as decodeJwt,
|
3
|
-
verify as verifyJwt,
|
4
2
|
sha256ToBase64,
|
3
|
+
type CryptoContext,
|
4
|
+
SignJWT,
|
5
|
+
thumbprint,
|
5
6
|
} from "@pagopa/io-react-native-jwt";
|
6
|
-
|
7
|
-
import { SignJWT, thumbprint } from "@pagopa/io-react-native-jwt";
|
8
7
|
import { JWK } from "../utils/jwk";
|
9
8
|
import uuid from "react-native-uuid";
|
10
|
-
import { PidIssuingError
|
11
|
-
import {
|
12
|
-
import { sign, generate, deleteKey } from "@pagopa/io-react-native-crypto";
|
9
|
+
import { PidIssuingError } from "../utils/errors";
|
10
|
+
import { createDPopToken } from "../utils/dpop";
|
13
11
|
import { PidIssuerEntityConfiguration } from "./metadata";
|
14
|
-
import {
|
15
|
-
|
12
|
+
import {
|
13
|
+
createCryptoContextFor,
|
14
|
+
getEntityConfiguration as getGenericEntityConfiguration,
|
15
|
+
} from "..";
|
16
|
+
import { generate, deleteKey } from "@pagopa/io-react-native-crypto";
|
17
|
+
import { SdJwt } from ".";
|
16
18
|
// This is a temporary type that will be used for demo purposes only
|
17
19
|
export type CieData = {
|
18
20
|
birthDate: string;
|
@@ -21,7 +23,15 @@ export type CieData = {
|
|
21
23
|
surname: string;
|
22
24
|
};
|
23
25
|
|
24
|
-
export type
|
26
|
+
export type AuthorizationConf = {
|
27
|
+
accessToken: string;
|
28
|
+
nonce: string;
|
29
|
+
clientId: string;
|
30
|
+
authorizationCode: string;
|
31
|
+
codeVerifier: string;
|
32
|
+
walletProviderBaseUrl: string;
|
33
|
+
};
|
34
|
+
|
25
35
|
export type PidResponse = {
|
26
36
|
credential: string;
|
27
37
|
c_nonce: string;
|
@@ -29,111 +39,93 @@ export type PidResponse = {
|
|
29
39
|
format: string;
|
30
40
|
};
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
/**
|
43
|
+
* Obtain the PID provider entity configuration.
|
44
|
+
*/
|
45
|
+
export const getEntityConfiguration =
|
46
|
+
({ appFetch = fetch }: { appFetch?: GlobalFetch["fetch"] } = {}) =>
|
47
|
+
async (
|
48
|
+
relyingPartyBaseUrl: string
|
49
|
+
): Promise<PidIssuerEntityConfiguration> => {
|
50
|
+
return getGenericEntityConfiguration(relyingPartyBaseUrl, {
|
51
|
+
appFetch: appFetch,
|
52
|
+
}).then(PidIssuerEntityConfiguration.parse);
|
53
|
+
};
|
41
54
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
55
|
+
/**
|
56
|
+
* Make a PAR request to the PID issuer and return the response url
|
57
|
+
*/
|
58
|
+
const getPar =
|
59
|
+
({
|
60
|
+
wiaCryptoContext,
|
61
|
+
appFetch = fetch,
|
62
|
+
}: {
|
63
|
+
wiaCryptoContext: CryptoContext;
|
64
|
+
appFetch?: GlobalFetch["fetch"];
|
65
|
+
}) =>
|
66
|
+
async (
|
46
67
|
clientId: string,
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
68
|
+
codeVerifier: string,
|
69
|
+
walletProviderBaseUrl: string,
|
70
|
+
pidProviderEntityConfiguration: PidIssuerEntityConfiguration,
|
71
|
+
walletInstanceAttestation: string
|
72
|
+
): Promise<string> => {
|
73
|
+
// Calculate the thumbprint of the public key of the Wallet Instance Attestation.
|
74
|
+
// The PAR request token is signed used the Wallet Instance Attestation key.
|
75
|
+
// The signature can be verified by reading the public key from the key set shippet with the it will ship the Wallet Instance Attestation;
|
76
|
+
// key is matched by its kid, which is supposed to be the thumbprint of its public key.
|
77
|
+
const keyThumbprint = await wiaCryptoContext
|
78
|
+
.getPublicKey()
|
79
|
+
.then(JWK.parse)
|
80
|
+
.then(thumbprint);
|
58
81
|
|
59
|
-
|
60
|
-
* Return the unsigned jwt to call the PAR request.
|
61
|
-
*
|
62
|
-
* @function
|
63
|
-
* @param jwk The wallet instance attestation public JWK
|
64
|
-
*
|
65
|
-
* @returns Unsigned jwt
|
66
|
-
*
|
67
|
-
*/
|
68
|
-
async getUnsignedJwtForPar(jwk: JWK): Promise<string> {
|
69
|
-
const parsedJwk = JWK.parse(jwk);
|
70
|
-
const keyThumbprint = await thumbprint(parsedJwk);
|
71
|
-
const publicKey = { ...parsedJwk, kid: keyThumbprint };
|
72
|
-
const codeChallenge = await sha256ToBase64(this.codeVerifier);
|
82
|
+
const codeChallenge = await sha256ToBase64(codeVerifier);
|
73
83
|
|
74
|
-
const
|
75
|
-
client_assertion_type:
|
76
|
-
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
77
|
-
authorization_details: [
|
78
|
-
{
|
79
|
-
credentialDefinition: {
|
80
|
-
type: ["eu.eudiw.pid.it"],
|
81
|
-
},
|
82
|
-
format: "vc+sd-jwt",
|
83
|
-
type: "type",
|
84
|
-
},
|
85
|
-
],
|
86
|
-
response_type: "code",
|
87
|
-
code_challenge_method: "s256",
|
88
|
-
redirect_uri: this.walletProviderBaseUrl,
|
89
|
-
state: this.state,
|
90
|
-
client_id: this.clientId,
|
91
|
-
code_challenge: codeChallenge,
|
92
|
-
})
|
84
|
+
const signedJwtForPar = await new SignJWT(wiaCryptoContext)
|
93
85
|
.setProtectedHeader({
|
94
|
-
|
95
|
-
|
86
|
+
kid: keyThumbprint,
|
87
|
+
})
|
88
|
+
.setPayload({
|
89
|
+
client_assertion_type:
|
90
|
+
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
91
|
+
authorization_details: [
|
92
|
+
{
|
93
|
+
credentialDefinition: {
|
94
|
+
type: ["eu.eudiw.pid.it"],
|
95
|
+
},
|
96
|
+
format: "vc+sd-jwt",
|
97
|
+
type: "type",
|
98
|
+
},
|
99
|
+
],
|
100
|
+
response_type: "code",
|
101
|
+
code_challenge_method: "s256",
|
102
|
+
redirect_uri: walletProviderBaseUrl,
|
103
|
+
state: `${uuid.v4()}`,
|
104
|
+
client_id: clientId,
|
105
|
+
code_challenge: codeChallenge,
|
96
106
|
})
|
97
107
|
.setIssuedAt()
|
98
108
|
.setExpirationTime("1h")
|
99
|
-
.
|
100
|
-
|
101
|
-
return unsignedJwtForPar;
|
102
|
-
}
|
109
|
+
.sign();
|
103
110
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
* @function
|
108
|
-
* @param unsignedJwtForPar The unsigned JWT for PAR
|
109
|
-
* @param signature The JWT for PAR signature
|
110
|
-
*
|
111
|
-
* @returns Unsigned PAR url
|
112
|
-
*
|
113
|
-
*/
|
114
|
-
async getPar(unsignedJwtForPar: string, signature: string): Promise<string> {
|
115
|
-
const codeChallenge = await sha256ToBase64(this.codeVerifier);
|
116
|
-
const signedJwtForPar = await SignJWT.appendSignature(
|
117
|
-
unsignedJwtForPar,
|
118
|
-
signature
|
119
|
-
);
|
120
|
-
|
121
|
-
const parUrl = new URL("/as/par", this.pidProviderBaseUrl).href;
|
111
|
+
const parUrl =
|
112
|
+
pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
|
113
|
+
.pushed_authorization_request_endpoint;
|
122
114
|
|
123
115
|
const requestBody = {
|
124
116
|
response_type: "code",
|
125
|
-
client_id:
|
117
|
+
client_id: clientId,
|
126
118
|
code_challenge: codeChallenge,
|
127
119
|
code_challenge_method: "S256",
|
128
120
|
client_assertion_type:
|
129
121
|
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
130
|
-
client_assertion:
|
122
|
+
client_assertion: walletInstanceAttestation,
|
131
123
|
request: signedJwtForPar,
|
132
124
|
};
|
133
125
|
|
134
126
|
var formBody = new URLSearchParams(requestBody);
|
135
127
|
|
136
|
-
const response = await
|
128
|
+
const response = await appFetch(parUrl, {
|
137
129
|
method: "POST",
|
138
130
|
headers: {
|
139
131
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -149,61 +141,76 @@ export class Issuing {
|
|
149
141
|
throw new PidIssuingError(
|
150
142
|
`Unable to obtain PAR. Response code: ${await response.text()}`
|
151
143
|
);
|
152
|
-
}
|
144
|
+
};
|
153
145
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
}
|
146
|
+
/**
|
147
|
+
* Start the issuing flow by generating an authorization request to the PID Provider. Obtain from the PID Provider an access token to be used to complete the issuing flow.
|
148
|
+
*
|
149
|
+
* @param params.wiaCryptoContext The key pair associated with the WIA. Will be use to prove the ownership of the attestation.
|
150
|
+
* @param params.appFetch (optional) Http client
|
151
|
+
* @param walletInstanceAttestation Wallet Instance Attestation token.
|
152
|
+
* @param walletProviderBaseUrl Base url for the Wallet Provider
|
153
|
+
* @param pidProviderEntityConfiguration The Entity Configuration of the PID Provider, from which discover public endooints.
|
154
|
+
* @returns The access token along with the values that identify the issuing session.
|
155
|
+
*/
|
156
|
+
export const authorizeIssuing =
|
157
|
+
({
|
158
|
+
wiaCryptoContext,
|
159
|
+
appFetch = fetch,
|
160
|
+
}: {
|
161
|
+
wiaCryptoContext: CryptoContext;
|
162
|
+
appFetch?: GlobalFetch["fetch"];
|
163
|
+
}) =>
|
164
|
+
async (
|
165
|
+
walletInstanceAttestation: string,
|
166
|
+
walletProviderBaseUrl: string,
|
167
|
+
pidProviderEntityConfiguration: PidIssuerEntityConfiguration
|
168
|
+
): Promise<AuthorizationConf> => {
|
169
|
+
// FIXME: do better
|
170
|
+
const clientId = await wiaCryptoContext.getPublicKey().then((_) => _.kid);
|
171
|
+
const codeVerifier = `${uuid.v4()}`;
|
172
|
+
const authorizationCode = `${uuid.v4()}`;
|
173
|
+
const tokenUrl =
|
174
|
+
pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
|
175
|
+
.token_endpoint;
|
176
|
+
|
177
|
+
await getPar({ wiaCryptoContext, appFetch })(
|
178
|
+
clientId,
|
179
|
+
codeVerifier,
|
180
|
+
walletProviderBaseUrl,
|
181
|
+
pidProviderEntityConfiguration,
|
182
|
+
walletInstanceAttestation
|
183
|
+
);
|
172
184
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
* @returns a token response
|
178
|
-
*
|
179
|
-
*/
|
180
|
-
async getAuthToken(): Promise<TokenResponse> {
|
181
|
-
//Generate fresh keys for DPoP
|
182
|
-
const dPopKeyTag = `${uuid.v4()}`;
|
183
|
-
const dPopKey = await generate(dPopKeyTag);
|
184
|
-
const unsignedDPopForToken = await this.getUnsignedDPoP(dPopKey);
|
185
|
-
const dPopTokenSignature = await sign(unsignedDPopForToken, dPopKeyTag);
|
186
|
-
await deleteKey(dPopKeyTag);
|
185
|
+
// Use an ephemeral key to be destroyed after use
|
186
|
+
const keytag = `ephemeral-${uuid.v4()}`;
|
187
|
+
await generate(keytag);
|
188
|
+
const ephemeralContext = createCryptoContextFor(keytag);
|
187
189
|
|
188
|
-
const signedDPop = await
|
189
|
-
|
190
|
-
|
190
|
+
const signedDPop = await createDPopToken(
|
191
|
+
{
|
192
|
+
htm: "POST",
|
193
|
+
htu: tokenUrl,
|
194
|
+
jti: `${uuid.v4()}`,
|
195
|
+
},
|
196
|
+
ephemeralContext
|
191
197
|
);
|
192
|
-
|
193
|
-
|
198
|
+
|
199
|
+
await deleteKey(keytag);
|
200
|
+
|
194
201
|
const requestBody = {
|
195
202
|
grant_type: "authorization code",
|
196
|
-
client_id:
|
197
|
-
code:
|
198
|
-
code_verifier:
|
203
|
+
client_id: clientId,
|
204
|
+
code: authorizationCode,
|
205
|
+
code_verifier: codeVerifier,
|
199
206
|
client_assertion_type:
|
200
207
|
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
201
|
-
client_assertion:
|
202
|
-
redirect_uri:
|
208
|
+
client_assertion: walletInstanceAttestation,
|
209
|
+
redirect_uri: walletProviderBaseUrl,
|
203
210
|
};
|
204
211
|
var formBody = new URLSearchParams(requestBody);
|
205
212
|
|
206
|
-
const response = await
|
213
|
+
const response = await appFetch(tokenUrl, {
|
207
214
|
method: "POST",
|
208
215
|
headers: {
|
209
216
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -213,70 +220,86 @@ export class Issuing {
|
|
213
220
|
});
|
214
221
|
|
215
222
|
if (response.status === 200) {
|
216
|
-
|
223
|
+
const { c_nonce, access_token } = await response.json();
|
224
|
+
return {
|
225
|
+
accessToken: access_token,
|
226
|
+
nonce: c_nonce,
|
227
|
+
clientId,
|
228
|
+
codeVerifier,
|
229
|
+
authorizationCode,
|
230
|
+
walletProviderBaseUrl,
|
231
|
+
};
|
217
232
|
}
|
218
233
|
|
219
234
|
throw new PidIssuingError(
|
220
235
|
`Unable to obtain token. Response code: ${await response.text()}`
|
221
236
|
);
|
222
|
-
}
|
237
|
+
};
|
223
238
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
239
|
+
/**
|
240
|
+
* Return the signed jwt for nonce proof of possession
|
241
|
+
*/
|
242
|
+
const createNonceProof = async (
|
243
|
+
nonce: string,
|
244
|
+
issuer: string,
|
245
|
+
audience: string,
|
246
|
+
ctx: CryptoContext
|
247
|
+
): Promise<string> => {
|
248
|
+
return new SignJWT(ctx)
|
249
|
+
.setPayload({
|
235
250
|
nonce,
|
236
251
|
})
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
return unsignedProof;
|
247
|
-
}
|
252
|
+
.setProtectedHeader({
|
253
|
+
type: "openid4vci-proof+jwt",
|
254
|
+
})
|
255
|
+
.setAudience(audience)
|
256
|
+
.setIssuer(issuer)
|
257
|
+
.setIssuedAt()
|
258
|
+
.setExpirationTime("1h")
|
259
|
+
.sign();
|
260
|
+
};
|
248
261
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
262
|
+
/**
|
263
|
+
* Complete the issuing flow and get the PID credential.
|
264
|
+
*
|
265
|
+
* @param params.pidCryptoContext The key pair associated with the PID. Will be use to prove the ownership of the credential.
|
266
|
+
* @param params.appFetch (optional) Http client
|
267
|
+
* @param authConf The authorization configuration retrieved with the access token
|
268
|
+
* @param cieData Data red from the CIE login process
|
269
|
+
* @returns The PID credential token
|
270
|
+
*/
|
271
|
+
export const getCredential =
|
272
|
+
({
|
273
|
+
pidCryptoContext,
|
274
|
+
appFetch = fetch,
|
275
|
+
}: {
|
276
|
+
pidCryptoContext: CryptoContext;
|
277
|
+
appFetch?: GlobalFetch["fetch"];
|
278
|
+
}) =>
|
279
|
+
async (
|
280
|
+
{ nonce, accessToken, clientId, walletProviderBaseUrl }: AuthorizationConf,
|
281
|
+
pidProviderEntityConfiguration: PidIssuerEntityConfiguration,
|
269
282
|
cieData: CieData
|
270
|
-
): Promise<PidResponse> {
|
271
|
-
const signedDPopForPid = await
|
272
|
-
|
273
|
-
|
283
|
+
): Promise<PidResponse> => {
|
284
|
+
const signedDPopForPid = await createDPopToken(
|
285
|
+
{
|
286
|
+
htm: "POST",
|
287
|
+
htu: pidProviderEntityConfiguration.payload.metadata
|
288
|
+
.openid_credential_issuer.token_endpoint,
|
289
|
+
jti: `${uuid.v4()}`,
|
290
|
+
},
|
291
|
+
pidCryptoContext
|
274
292
|
);
|
275
|
-
const signedNonceProof = await
|
276
|
-
|
277
|
-
|
293
|
+
const signedNonceProof = await createNonceProof(
|
294
|
+
nonce,
|
295
|
+
clientId,
|
296
|
+
walletProviderBaseUrl,
|
297
|
+
pidCryptoContext
|
278
298
|
);
|
279
|
-
|
299
|
+
|
300
|
+
const credentialUrl =
|
301
|
+
pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
|
302
|
+
.credential_endpoint;
|
280
303
|
|
281
304
|
const requestBody = {
|
282
305
|
credential_definition: JSON.stringify({ type: ["eu.eudiw.pid.it"] }),
|
@@ -289,7 +312,7 @@ export class Issuing {
|
|
289
312
|
};
|
290
313
|
const formBody = new URLSearchParams(requestBody);
|
291
314
|
|
292
|
-
const response = await
|
315
|
+
const response = await appFetch(credentialUrl, {
|
293
316
|
method: "POST",
|
294
317
|
headers: {
|
295
318
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -300,44 +323,28 @@ export class Issuing {
|
|
300
323
|
});
|
301
324
|
|
302
325
|
if (response.status === 200) {
|
303
|
-
|
326
|
+
const pidResponse = (await response.json()) as PidResponse;
|
327
|
+
await validatePid(pidResponse.credential, pidCryptoContext);
|
328
|
+
return pidResponse;
|
304
329
|
}
|
305
330
|
|
306
|
-
throw new PidIssuingError(
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
* @function
|
313
|
-
* @returns PID issuer metadata
|
314
|
-
*
|
315
|
-
*/
|
316
|
-
async getEntityConfiguration(): Promise<PidIssuerEntityConfiguration> {
|
317
|
-
const metadataUrl = new URL(
|
318
|
-
"ci/.well-known/openid-federation",
|
319
|
-
this.pidProviderBaseUrl
|
320
|
-
).href;
|
321
|
-
|
322
|
-
const response = await this.appFetch(metadataUrl);
|
331
|
+
throw new PidIssuingError(
|
332
|
+
`Unable to obtain credential! url=${credentialUrl} status=${
|
333
|
+
response.status
|
334
|
+
} body=${await response.text()}`
|
335
|
+
);
|
336
|
+
};
|
323
337
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
if (result.success) {
|
329
|
-
const parsedMetadata = result.data;
|
330
|
-
await verifyJwt(jwtMetadata, parsedMetadata.jwks.keys);
|
331
|
-
return parsedMetadata;
|
332
|
-
} else {
|
333
|
-
throw new PidMetadataError(result.error.message);
|
334
|
-
}
|
335
|
-
}
|
338
|
+
const validatePid = async (pidJwt: string, pidCryptoContext: CryptoContext) => {
|
339
|
+
const decoded = SdJwt.decode(pidJwt);
|
340
|
+
const pidKey = await pidCryptoContext.getPublicKey();
|
341
|
+
const holderBindedKey = decoded.sdJwt.payload.cnf.jwk;
|
336
342
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
343
|
+
if ((await thumbprint(pidKey)) !== (await thumbprint(holderBindedKey))) {
|
344
|
+
throw new PidIssuingError(
|
345
|
+
`The obtained pid does not seem to be valid according to your configuration. Your PID public key is: ${JSON.stringify(
|
346
|
+
pidKey
|
347
|
+
)} but PID holder binded key is: ${JSON.stringify(holderBindedKey)}`
|
341
348
|
);
|
342
349
|
}
|
343
|
-
}
|
350
|
+
};
|
package/src/pid/metadata.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { EntityConfiguration } from "../trust/types";
|
1
2
|
import { JWK } from "../utils/jwk";
|
2
3
|
import { z } from "zod";
|
3
4
|
|
@@ -16,31 +17,35 @@ export const PidDisplayMetadata = z.object({
|
|
16
17
|
export type PidIssuerEntityConfiguration = z.infer<
|
17
18
|
typeof PidIssuerEntityConfiguration
|
18
19
|
>;
|
19
|
-
export const PidIssuerEntityConfiguration =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
export const PidIssuerEntityConfiguration = EntityConfiguration.and(
|
21
|
+
z.object({
|
22
|
+
payload: z.object({
|
23
|
+
jwks: z.object({ keys: z.array(JWK) }),
|
24
|
+
metadata: z.object({
|
25
|
+
openid_credential_issuer: z.object({
|
26
|
+
credential_issuer: z.string(),
|
27
|
+
authorization_endpoint: z.string(),
|
28
|
+
token_endpoint: z.string(),
|
29
|
+
pushed_authorization_request_endpoint: z.string(),
|
30
|
+
dpop_signing_alg_values_supported: z.array(z.string()),
|
31
|
+
credential_endpoint: z.string(),
|
32
|
+
credentials_supported: z.array(
|
33
|
+
z.object({
|
34
|
+
format: z.literal("vc+sd-jwt"),
|
35
|
+
cryptographic_binding_methods_supported: z.array(z.string()),
|
36
|
+
cryptographic_suites_supported: z.array(z.string()),
|
37
|
+
display: z.array(PidDisplayMetadata),
|
38
|
+
})
|
39
|
+
),
|
40
|
+
}),
|
41
|
+
federation_entity: z.object({
|
42
|
+
organization_name: z.string(),
|
43
|
+
homepage_uri: z.string(),
|
44
|
+
policy_uri: z.string(),
|
45
|
+
tos_uri: z.string(),
|
46
|
+
logo_uri: z.string(),
|
47
|
+
}),
|
48
|
+
}),
|
37
49
|
}),
|
38
|
-
|
39
|
-
|
40
|
-
homepage_uri: z.string(),
|
41
|
-
policy_uri: z.string(),
|
42
|
-
tos_uri: z.string(),
|
43
|
-
logo_uri: z.string(),
|
44
|
-
}),
|
45
|
-
}),
|
46
|
-
});
|
50
|
+
})
|
51
|
+
);
|