@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/pid/issuing.ts
CHANGED
@@ -1,17 +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
|
-
|
12
|
+
import {
|
13
|
+
createCryptoContextFor,
|
14
|
+
getEntityConfiguration as getGenericEntityConfiguration,
|
15
|
+
} from "..";
|
16
|
+
import { generate, deleteKey } from "@pagopa/io-react-native-crypto";
|
17
|
+
import { SdJwt } from ".";
|
15
18
|
// This is a temporary type that will be used for demo purposes only
|
16
19
|
export type CieData = {
|
17
20
|
birthDate: string;
|
@@ -20,7 +23,15 @@ export type CieData = {
|
|
20
23
|
surname: string;
|
21
24
|
};
|
22
25
|
|
23
|
-
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
|
+
|
24
35
|
export type PidResponse = {
|
25
36
|
credential: string;
|
26
37
|
c_nonce: string;
|
@@ -28,111 +39,93 @@ export type PidResponse = {
|
|
28
39
|
format: string;
|
29
40
|
};
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
+
};
|
40
54
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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 (
|
45
67
|
clientId: string,
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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);
|
57
81
|
|
58
|
-
|
59
|
-
* Return the unsigned jwt to call the PAR request.
|
60
|
-
*
|
61
|
-
* @function
|
62
|
-
* @param jwk The wallet instance attestation public JWK
|
63
|
-
*
|
64
|
-
* @returns Unsigned jwt
|
65
|
-
*
|
66
|
-
*/
|
67
|
-
async getUnsignedJwtForPar(jwk: JWK): Promise<string> {
|
68
|
-
const parsedJwk = JWK.parse(jwk);
|
69
|
-
const keyThumbprint = await thumbprint(parsedJwk);
|
70
|
-
const publicKey = { ...parsedJwk, kid: keyThumbprint };
|
71
|
-
const codeChallenge = await sha256ToBase64(this.codeVerifier);
|
82
|
+
const codeChallenge = await sha256ToBase64(codeVerifier);
|
72
83
|
|
73
|
-
const
|
74
|
-
client_assertion_type:
|
75
|
-
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
76
|
-
authorization_details: [
|
77
|
-
{
|
78
|
-
credentialDefinition: {
|
79
|
-
type: ["eu.eudiw.pid.it"],
|
80
|
-
},
|
81
|
-
format: "vc+sd-jwt",
|
82
|
-
type: "type",
|
83
|
-
},
|
84
|
-
],
|
85
|
-
response_type: "code",
|
86
|
-
code_challenge_method: "s256",
|
87
|
-
redirect_uri: this.walletProviderBaseUrl,
|
88
|
-
state: this.state,
|
89
|
-
client_id: this.clientId,
|
90
|
-
code_challenge: codeChallenge,
|
91
|
-
})
|
84
|
+
const signedJwtForPar = await new SignJWT(wiaCryptoContext)
|
92
85
|
.setProtectedHeader({
|
93
|
-
|
94
|
-
|
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,
|
95
106
|
})
|
96
107
|
.setIssuedAt()
|
97
108
|
.setExpirationTime("1h")
|
98
|
-
.
|
99
|
-
|
100
|
-
return unsignedJwtForPar;
|
101
|
-
}
|
109
|
+
.sign();
|
102
110
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
* @function
|
107
|
-
* @param unsignedJwtForPar The unsigned JWT for PAR
|
108
|
-
* @param signature The JWT for PAR signature
|
109
|
-
*
|
110
|
-
* @returns Unsigned PAR url
|
111
|
-
*
|
112
|
-
*/
|
113
|
-
async getPar(unsignedJwtForPar: string, signature: string): Promise<string> {
|
114
|
-
const codeChallenge = await sha256ToBase64(this.codeVerifier);
|
115
|
-
const signedJwtForPar = await SignJWT.appendSignature(
|
116
|
-
unsignedJwtForPar,
|
117
|
-
signature
|
118
|
-
);
|
119
|
-
|
120
|
-
const parUrl = new URL("/as/par", this.pidProviderBaseUrl).href;
|
111
|
+
const parUrl =
|
112
|
+
pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
|
113
|
+
.pushed_authorization_request_endpoint;
|
121
114
|
|
122
115
|
const requestBody = {
|
123
116
|
response_type: "code",
|
124
|
-
client_id:
|
117
|
+
client_id: clientId,
|
125
118
|
code_challenge: codeChallenge,
|
126
119
|
code_challenge_method: "S256",
|
127
120
|
client_assertion_type:
|
128
121
|
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
129
|
-
client_assertion:
|
122
|
+
client_assertion: walletInstanceAttestation,
|
130
123
|
request: signedJwtForPar,
|
131
124
|
};
|
132
125
|
|
133
126
|
var formBody = new URLSearchParams(requestBody);
|
134
127
|
|
135
|
-
const response = await
|
128
|
+
const response = await appFetch(parUrl, {
|
136
129
|
method: "POST",
|
137
130
|
headers: {
|
138
131
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -148,61 +141,76 @@ export class Issuing {
|
|
148
141
|
throw new PidIssuingError(
|
149
142
|
`Unable to obtain PAR. Response code: ${await response.text()}`
|
150
143
|
);
|
151
|
-
}
|
144
|
+
};
|
152
145
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
}
|
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
|
+
);
|
171
184
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
* @returns a token response
|
177
|
-
*
|
178
|
-
*/
|
179
|
-
async getAuthToken(): Promise<TokenResponse> {
|
180
|
-
//Generate fresh keys for DPoP
|
181
|
-
const dPopKeyTag = `${uuid.v4()}`;
|
182
|
-
const dPopKey = await generate(dPopKeyTag);
|
183
|
-
const unsignedDPopForToken = await this.getUnsignedDPoP(dPopKey);
|
184
|
-
const dPopTokenSignature = await sign(unsignedDPopForToken, dPopKeyTag);
|
185
|
-
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);
|
186
189
|
|
187
|
-
const signedDPop = await
|
188
|
-
|
189
|
-
|
190
|
+
const signedDPop = await createDPopToken(
|
191
|
+
{
|
192
|
+
htm: "POST",
|
193
|
+
htu: tokenUrl,
|
194
|
+
jti: `${uuid.v4()}`,
|
195
|
+
},
|
196
|
+
ephemeralContext
|
190
197
|
);
|
191
|
-
|
192
|
-
|
198
|
+
|
199
|
+
await deleteKey(keytag);
|
200
|
+
|
193
201
|
const requestBody = {
|
194
202
|
grant_type: "authorization code",
|
195
|
-
client_id:
|
196
|
-
code:
|
197
|
-
code_verifier:
|
203
|
+
client_id: clientId,
|
204
|
+
code: authorizationCode,
|
205
|
+
code_verifier: codeVerifier,
|
198
206
|
client_assertion_type:
|
199
207
|
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
200
|
-
client_assertion:
|
201
|
-
redirect_uri:
|
208
|
+
client_assertion: walletInstanceAttestation,
|
209
|
+
redirect_uri: walletProviderBaseUrl,
|
202
210
|
};
|
203
211
|
var formBody = new URLSearchParams(requestBody);
|
204
212
|
|
205
|
-
const response = await
|
213
|
+
const response = await appFetch(tokenUrl, {
|
206
214
|
method: "POST",
|
207
215
|
headers: {
|
208
216
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -212,70 +220,86 @@ export class Issuing {
|
|
212
220
|
});
|
213
221
|
|
214
222
|
if (response.status === 200) {
|
215
|
-
|
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
|
+
};
|
216
232
|
}
|
217
233
|
|
218
234
|
throw new PidIssuingError(
|
219
235
|
`Unable to obtain token. Response code: ${await response.text()}`
|
220
236
|
);
|
221
|
-
}
|
237
|
+
};
|
222
238
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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({
|
234
250
|
nonce,
|
235
251
|
})
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
return unsignedProof;
|
246
|
-
}
|
252
|
+
.setProtectedHeader({
|
253
|
+
type: "openid4vci-proof+jwt",
|
254
|
+
})
|
255
|
+
.setAudience(audience)
|
256
|
+
.setIssuer(issuer)
|
257
|
+
.setIssuedAt()
|
258
|
+
.setExpirationTime("1h")
|
259
|
+
.sign();
|
260
|
+
};
|
247
261
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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,
|
268
282
|
cieData: CieData
|
269
|
-
): Promise<PidResponse> {
|
270
|
-
const signedDPopForPid = await
|
271
|
-
|
272
|
-
|
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
|
273
292
|
);
|
274
|
-
const signedNonceProof = await
|
275
|
-
|
276
|
-
|
293
|
+
const signedNonceProof = await createNonceProof(
|
294
|
+
nonce,
|
295
|
+
clientId,
|
296
|
+
walletProviderBaseUrl,
|
297
|
+
pidCryptoContext
|
277
298
|
);
|
278
|
-
|
299
|
+
|
300
|
+
const credentialUrl =
|
301
|
+
pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
|
302
|
+
.credential_endpoint;
|
279
303
|
|
280
304
|
const requestBody = {
|
281
305
|
credential_definition: JSON.stringify({ type: ["eu.eudiw.pid.it"] }),
|
@@ -288,7 +312,7 @@ export class Issuing {
|
|
288
312
|
};
|
289
313
|
const formBody = new URLSearchParams(requestBody);
|
290
314
|
|
291
|
-
const response = await
|
315
|
+
const response = await appFetch(credentialUrl, {
|
292
316
|
method: "POST",
|
293
317
|
headers: {
|
294
318
|
"Content-Type": "application/x-www-form-urlencoded",
|
@@ -299,44 +323,28 @@ export class Issuing {
|
|
299
323
|
});
|
300
324
|
|
301
325
|
if (response.status === 200) {
|
302
|
-
|
326
|
+
const pidResponse = (await response.json()) as PidResponse;
|
327
|
+
await validatePid(pidResponse.credential, pidCryptoContext);
|
328
|
+
return pidResponse;
|
303
329
|
}
|
304
330
|
|
305
|
-
throw new PidIssuingError(
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
* @function
|
312
|
-
* @returns PID issuer metadata
|
313
|
-
*
|
314
|
-
*/
|
315
|
-
async getEntityConfiguration(): Promise<PidIssuerEntityConfiguration> {
|
316
|
-
const metadataUrl = new URL(
|
317
|
-
"ci/.well-known/openid-federation",
|
318
|
-
this.pidProviderBaseUrl
|
319
|
-
).href;
|
320
|
-
|
321
|
-
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
|
+
};
|
322
337
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
if (result.success) {
|
328
|
-
const parsedMetadata = result.data;
|
329
|
-
await verifyJwt(jwtMetadata, parsedMetadata.jwks.keys);
|
330
|
-
return parsedMetadata;
|
331
|
-
} else {
|
332
|
-
throw new PidMetadataError(result.error.message);
|
333
|
-
}
|
334
|
-
}
|
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;
|
335
342
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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)}`
|
340
348
|
);
|
341
349
|
}
|
342
|
-
}
|
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
|
+
);
|