Skip to content

Commit 7bc14e4

Browse files
authored
feat: [SIW-2206] Update PID data model and issuance flow to 1.0.0 (#217)
1 parent 3f3c697 commit 7bc14e4

31 files changed

+874
-318
lines changed

example/src/store/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type CredentialResultBase = {
5858
>["parsedCredential"];
5959
keyTag: string;
6060
credentialType: SupportedCredentials;
61+
credentialConfigurationId: string;
6162
};
6263

6364
/**

example/src/thunks/pid.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const preparePidFlowParamsThunk = createAppAsyncThunk<
108108
// Start the issuance flow
109109
const startFlow: Credential.Issuance.StartFlow = () => ({
110110
issuerUrl: WALLET_PID_PROVIDER_BASE_URL,
111-
credentialType: "PersonIdentificationData",
111+
credentialType: "dc_sd_jwt_PersonIdentificationData",
112112
});
113113

114114
const { issuerUrl, credentialType } = startFlow();
@@ -123,7 +123,7 @@ export const preparePidFlowParamsThunk = createAppAsyncThunk<
123123
const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
124124
await Credential.Issuance.startUserAuthorization(
125125
issuerConf,
126-
credentialType,
126+
[credentialType],
127127
{
128128
walletInstanceAttestation,
129129
redirectUri: redirectUri,
@@ -213,11 +213,31 @@ export const continuePidFlowThunk = createAppAsyncThunk<
213213
}
214214
);
215215

216-
const { credential, format } = await Credential.Issuance.obtainCredential(
216+
const [pidCredentialDefinition] = credentialDefinition;
217+
218+
const { credential_configuration_id, credential_identifiers } =
219+
accessToken.authorization_details.find(
220+
(authDetails) =>
221+
authDetails.credential_configuration_id ===
222+
pidCredentialDefinition?.credential_configuration_id
223+
) ?? {};
224+
225+
// Get the first credential_identifier from the access token's authorization details
226+
const [credential_identifier] = credential_identifiers ?? [];
227+
228+
if (!credential_configuration_id) {
229+
throw new Error("No credential configuration ID found for PID");
230+
}
231+
232+
// Get the credential identifier that was authorized
233+
const { credential } = await Credential.Issuance.obtainCredential(
217234
issuerConf,
218235
accessToken,
219236
clientId,
220-
credentialDefinition,
237+
{
238+
credential_configuration_id,
239+
credential_identifier,
240+
},
221241
{
222242
credentialCryptoContext,
223243
dPopCryptoContext,
@@ -229,7 +249,7 @@ export const continuePidFlowThunk = createAppAsyncThunk<
229249
await Credential.Issuance.verifyAndParseCredential(
230250
issuerConf,
231251
credential,
232-
format,
252+
credential_configuration_id,
233253
{ credentialCryptoContext }
234254
);
235255

@@ -238,5 +258,6 @@ export const continuePidFlowThunk = createAppAsyncThunk<
238258
credential,
239259
keyTag: credentialKeyTag,
240260
credentialType: "PersonIdentificationData",
261+
credentialConfigurationId: credential_configuration_id,
241262
};
242263
});

example/src/thunks/pidCieID.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const getPidCieIDThunk = createAppAsyncThunk<
5353
const env = selectEnv(getState());
5454
const { WALLET_PID_PROVIDER_BASE_URL, REDIRECT_URI } = getEnv(env);
5555

56-
const { idpHint, credentialType } = args;
56+
const { idpHint } = args;
5757
// Resets the credential state before obtaining a new PID
5858
dispatch(credentialReset());
5959
return await getPidCieID({
@@ -62,6 +62,5 @@ export const getPidCieIDThunk = createAppAsyncThunk<
6262
idpHint,
6363
walletInstanceAttestation,
6464
wiaCryptoContext,
65-
credentialType,
6665
});
6766
});

example/src/utils/credential.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ export const getPidCieID = async ({
2929
redirectUri,
3030
idpHint,
3131
walletInstanceAttestation,
32-
credentialType,
3332
wiaCryptoContext,
3433
}: {
3534
pidIssuerUrl: string;
3635
redirectUri: string;
3736
idpHint: string;
3837
walletInstanceAttestation: string;
3938
wiaCryptoContext: CryptoContext;
40-
credentialType: "PersonIdentificationData";
4139
}): Promise<PidResult> => {
4240
/*
4341
* Create credential crypto context for the PID
@@ -50,11 +48,10 @@ export const getPidCieID = async ({
5048
// Start the issuance flow
5149
const startFlow: Credential.Issuance.StartFlow = () => ({
5250
issuerUrl: pidIssuerUrl,
53-
credentialType: "PersonIdentificationData",
54-
appFetch,
51+
credentialType: "dc_sd_jwt_PersonIdentificationData",
5552
});
5653

57-
const { issuerUrl } = startFlow();
54+
const { issuerUrl, credentialType } = startFlow();
5855

5956
// Evaluate issuer trust
6057
const { issuerConf } = await Credential.Issuance.evaluateIssuerTrust(
@@ -66,7 +63,7 @@ export const getPidCieID = async ({
6663
const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
6764
await Credential.Issuance.startUserAuthorization(
6865
issuerConf,
69-
credentialType,
66+
[credentialType],
7067
{
7168
walletInstanceAttestation,
7269
redirectUri,
@@ -113,12 +110,28 @@ export const getPidCieID = async ({
113110
}
114111
);
115112

113+
const [pidCredentialDefinition] = credentialDefinition;
114+
115+
const { credential_configuration_id, credential_identifiers } =
116+
accessToken.authorization_details.find(
117+
(authDetails) =>
118+
authDetails.credential_configuration_id ===
119+
pidCredentialDefinition?.credential_configuration_id
120+
) ?? {};
121+
122+
// Get the first credential_identifier from the access token's authorization details
123+
const [credential_identifier] = credential_identifiers ?? [];
124+
125+
if (!credential_configuration_id) {
126+
throw new Error("No credential configuration ID found for PID");
127+
}
128+
116129
// Obtain che eID credential
117-
const { credential, format } = await Credential.Issuance.obtainCredential(
130+
const { credential } = await Credential.Issuance.obtainCredential(
118131
issuerConf,
119132
accessToken,
120133
clientId,
121-
credentialDefinition,
134+
{ credential_configuration_id, credential_identifier },
122135
{
123136
credentialCryptoContext,
124137
dPopCryptoContext,
@@ -131,15 +144,16 @@ export const getPidCieID = async ({
131144
await Credential.Issuance.verifyAndParseCredential(
132145
issuerConf,
133146
credential,
134-
format,
147+
credential_configuration_id,
135148
{ credentialCryptoContext }
136149
);
137150

138151
return {
139152
parsedCredential,
140153
credential,
141154
keyTag: credentialKeyTag,
142-
credentialType,
155+
credentialType: "PersonIdentificationData",
156+
credentialConfigurationId: credential_configuration_id,
143157
};
144158
};
145159

@@ -189,10 +203,10 @@ export const getCredential = async ({
189203
await Credential.Issuance.evaluateIssuerTrust(issuerUrl);
190204

191205
// Start user authorization
192-
const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
206+
const { issuerRequestUri, clientId, codeVerifier } =
193207
await Credential.Issuance.startUserAuthorization(
194208
issuerConf,
195-
credentialType,
209+
[credentialType],
196210
{
197211
walletInstanceAttestation,
198212
redirectUri,
@@ -241,7 +255,8 @@ export const getCredential = async ({
241255
issuerConf,
242256
accessToken,
243257
clientId,
244-
credentialDefinition,
258+
// TODO: [SIW-2209] to fix in PR #219
259+
{ credential_configuration_id: "", credential_identifier: "" },
245260
{
246261
credentialCryptoContext,
247262
dPopCryptoContext,
@@ -263,6 +278,7 @@ export const getCredential = async ({
263278
credential,
264279
keyTag: credentialKeyTag,
265280
credentialType,
281+
credentialConfigurationId: "", // TODO: [SIW-2209] to fix in PR #219
266282
};
267283
};
268284

src/credential/issuance/03-start-user-authorization.ts

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { generateRandomAlphaNumericString, type Out } from "../../utils/misc";
44
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
55
import type { StartFlow } from "./01-start-flow";
66
import { AuthorizationDetail, makeParRequest } from "../../utils/par";
7-
import { ASSERTION_TYPE } from "./const";
87
import { LogLevel, Logger } from "../../utils/logging";
98

109
export type StartUserAuthorization = (
1110
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
12-
credentialType: Out<StartFlow>["credentialType"],
11+
credentialTypes: string[],
1312
context: {
1413
wiaCryptoContext: CryptoContext;
1514
walletInstanceAttestation: string;
@@ -20,18 +19,14 @@ export type StartUserAuthorization = (
2019
issuerRequestUri: string;
2120
clientId: string;
2221
codeVerifier: string;
23-
credentialDefinition: AuthorizationDetail;
22+
credentialDefinition: AuthorizationDetail[];
2423
}>;
2524

2625
/**
2726
* Ensures that the credential type requested is supported by the issuer and contained in the
2827
* issuer configuration.
2928
* @param issuerConf The issuer configuration returned by {@link evaluateIssuerTrust}
30-
* @param credentialType The type of the credential to be requested returned by {@link startFlow}
31-
* @param context.wiaCryptoContext The Wallet Instance's crypto context
32-
* @param context.walletInstanceAttestation The Wallet Instance's attestation
33-
* @param context.redirectUri The redirect URI which is the custom URL scheme that the Wallet Instance is registered to handle
34-
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
29+
* @param credentialType The type of the credential to be requested;
3530
* @returns The credential definition to be used in the request which includes the format and the type and its type
3631
*/
3732
const selectCredentialDefinition = (
@@ -43,9 +38,8 @@ const selectCredentialDefinition = (
4338

4439
const [result] = Object.keys(credential_configurations_supported)
4540
.filter((e) => e.includes(credentialType))
46-
.map((e) => ({
41+
.map(() => ({
4742
credential_configuration_id: credentialType,
48-
format: credential_configurations_supported[e]!.format,
4943
type: "openid_credential" as const,
5044
}));
5145

@@ -62,40 +56,61 @@ const selectCredentialDefinition = (
6256
/**
6357
* Ensures that the response mode requested is supported by the issuer and contained in the issuer configuration.
6458
* @param issuerConf The issuer configuration
65-
* @param credentialType The type of the credential to be requested
59+
* @param credentialType The type of the credential(s) to be requested
6660
* @returns The response mode to be used in the request, "query" for PersonIdentificationData and "form_post.jwt" for all other types.
6761
*/
6862
const selectResponseMode = (
6963
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
70-
credentialType: Out<StartFlow>["credentialType"]
64+
credentialTypes: string[]
7165
): ResponseMode => {
7266
const responseModeSupported =
7367
issuerConf.oauth_authorization_server.response_modes_supported;
7468

75-
const responseMode =
76-
credentialType === "PersonIdentificationData" ? "query" : "form_post.jwt";
69+
const responseModeSet = new Set<ResponseMode>();
70+
71+
for (const credentialType of credentialTypes) {
72+
responseModeSet.add(
73+
credentialType.match(/PersonIdentificationData/i)
74+
? "query"
75+
: "form_post.jwt"
76+
);
77+
}
78+
79+
if (responseModeSet.size !== 1) {
80+
Logger.log(
81+
LogLevel.ERROR,
82+
`${credentialTypes} have incompatible response_mode: ${[...responseModeSet.values()]}`
83+
);
84+
throw new Error(
85+
"Requested credentials have incompatible response_mode and cannot be requested with the same PAR request"
86+
);
87+
}
88+
89+
const [responseMode] = responseModeSet.values();
7790

7891
Logger.log(
7992
LogLevel.DEBUG,
80-
`Selected response mode ${responseMode} for credential type ${credentialType}`
93+
`Selected response mode ${responseMode} for credential type ${credentialTypes}`
8194
);
8295

83-
if (!responseModeSupported.includes(responseMode)) {
96+
if (!responseModeSupported.includes(responseMode!)) {
8497
Logger.log(
8598
LogLevel.ERROR,
8699
`Requested response mode ${responseMode} is not supported by the issuer according to its configuration ${JSON.stringify(responseModeSupported)}`
87100
);
88-
throw new Error(`No response mode support the type '${credentialType}'`);
101+
throw new Error(`No response mode support the type '${credentialTypes}'`);
89102
}
90103

91-
return responseMode;
104+
return responseMode!;
92105
};
93106

94107
/**
95108
* WARNING: This function must be called after {@link evaluateIssuerTrust} and {@link startFlow}. The next steam is {@link compeUserAuthorizationWithQueryMode} or {@link compeUserAuthorizationWithFormPostJwtMode}
109+
*
96110
* Creates and sends a PAR request to the /as/par endpoint of the authorization server.
97111
* This starts the authentication flow to obtain an access token.
98-
* This token enables the Wallet Instance to request a digital credential from the Credential Endpoint of the Credential Issuer.
112+
* This token enables the Wallet Instance to request a digital credential from the Credential Endpoint of the Credential Issuer; when multiple credential types are passed,
113+
* it is possible to use the same access token for the issuance of all requested credentials.
99114
* This is an HTTP POST request containing the Wallet Instance identifier (client id), the code challenge and challenge method as specified by PKCE according to RFC 9126
100115
* along with the WTE and its proof of possession (WTE-PoP).
101116
* Additionally, it includes a request object, which is a signed JWT encapsulating the type of digital credential requested (authorization_details),
@@ -105,13 +120,14 @@ const selectResponseMode = (
105120
* to the Wallet Instance's Token Endpoint to obtain the Access Token, and the redirectUri of the Wallet Instance where the Authorization Response
106121
* should be delivered. The redirect is achived by using a custom URL scheme that the Wallet Instance is registered to handle.
107122
* @param issuerConf The issuer configuration
108-
* @param credentialType The type of the credential to be requested returned by {@link selectCredentialDefinition}
123+
* @param credentialTypes The type of the credential(s) to be requested
109124
* @param ctx The context object containing the Wallet Instance's cryptographic context, the Wallet Instance's attestation, the redirect URI and the fetch implementation
110-
* @returns The URI to which the end user should be redirected to start the authentication flow, along with the client id, the code verifier and the credential definition
125+
* @returns The URI to which the end user should be redirected to start the authentication flow, along with the client id, the code verifier and the credential definition(s)
111126
*/
127+
112128
export const startUserAuthorization: StartUserAuthorization = async (
113129
issuerConf,
114-
credentialType,
130+
credentialTypes,
115131
ctx
116132
) => {
117133
const {
@@ -122,6 +138,7 @@ export const startUserAuthorization: StartUserAuthorization = async (
122138
} = ctx;
123139

124140
const clientId = await wiaCryptoContext.getPublicKey().then((_) => _.kid);
141+
125142
if (!clientId) {
126143
Logger.log(
127144
LogLevel.ERROR,
@@ -132,22 +149,23 @@ export const startUserAuthorization: StartUserAuthorization = async (
132149
const codeVerifier = generateRandomAlphaNumericString(64);
133150
const parEndpoint =
134151
issuerConf.oauth_authorization_server.pushed_authorization_request_endpoint;
135-
const credentialDefinition = selectCredentialDefinition(
136-
issuerConf,
137-
credentialType
152+
const aud = issuerConf.openid_credential_issuer.credential_issuer;
153+
const credentialDefinition = credentialTypes.map((c) =>
154+
selectCredentialDefinition(issuerConf, c)
138155
);
139-
const responseMode = selectResponseMode(issuerConf, credentialType);
140-
156+
const responseMode = selectResponseMode(issuerConf, credentialTypes);
141157
const getPar = makeParRequest({ wiaCryptoContext, appFetch });
142158
const issuerRequestUri = await getPar(
143-
clientId,
144-
codeVerifier,
145-
redirectUri,
146-
responseMode,
147159
parEndpoint,
148160
walletInstanceAttestation,
149-
[credentialDefinition],
150-
ASSERTION_TYPE
161+
{
162+
aud,
163+
clientId,
164+
codeVerifier,
165+
redirectUri,
166+
responseMode,
167+
authorizationDetails: credentialDefinition,
168+
}
151169
);
152170

153171
return { issuerRequestUri, clientId, codeVerifier, credentialDefinition };

0 commit comments

Comments
 (0)