Skip to content

Commit 2759750

Browse files
committed
chore: better EBSI detection. Hookup client assertion to EBSI as well
1 parent 03caf09 commit 2759750

6 files changed

Lines changed: 81 additions & 60 deletions

File tree

packages/client/lib/OpenID4VCIClient.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ import { createAuthorizationRequestUrl } from './AuthorizationCodeClient';
4242
import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11';
4343
import { CredentialOfferClient } from './CredentialOfferClient';
4444
import { CredentialRequestOpts } from './CredentialRequestClient';
45-
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
4645
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
46+
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
4747
import { MetadataClient } from './MetadataClient';
4848
import { OpenID4VCIClientStateV1_0_11 } from './OpenID4VCIClientV1_0_11';
4949
import { OpenID4VCIClientStateV1_0_13 } from './OpenID4VCIClientV1_0_13';
@@ -298,7 +298,10 @@ export class OpenID4VCIClient {
298298
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
299299
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
300300
: undefined);
301-
if (clientId) {
301+
if (this.isEBSI() || (clientId && kid)) {
302+
if (!clientId) {
303+
throw Error(`Client id expected for EBSI`);
304+
}
302305
asOpts.clientOpts = {
303306
...asOpts.clientOpts,
304307
clientId,
@@ -667,6 +670,9 @@ export class OpenID4VCIClient {
667670
}
668671
// this.assertIssuerData();
669672
return (
673+
this.clientId?.includes('ebsi') ||
674+
this._state.kid?.includes('did:ebsi:') ||
675+
this.getIssuer().includes('ebsi') ||
670676
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
671677
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
672678
);

packages/client/lib/OpenID4VCIClientV1_0_11.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,10 @@ export class OpenID4VCIClientV1_0_11 {
297297
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
298298
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
299299
: undefined);
300-
if (clientId) {
300+
if (this.isEBSI() || (clientId && kid)) {
301+
if (!clientId) {
302+
throw Error(`Client id expected for EBSI`);
303+
}
301304
asOpts.clientOpts = {
302305
...asOpts.clientOpts,
303306
clientId,
@@ -602,8 +605,8 @@ export class OpenID4VCIClientV1_0_11 {
602605
*/
603606
public isEBSI() {
604607
if (
605-
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)['credentials'] &&
606-
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11).credentials.find(
608+
this.credentialOffer &&
609+
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)?.credentials?.find(
607610
(cred) =>
608611
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
609612
// @ts-ignore
@@ -612,8 +615,14 @@ export class OpenID4VCIClientV1_0_11 {
612615
) {
613616
return true;
614617
}
615-
this.assertIssuerData();
616-
return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') === true;
618+
// this.assertIssuerData();
619+
return (
620+
this.clientId?.includes('ebsi') ||
621+
this._state.kid?.includes('did:ebsi:') ||
622+
this.getIssuer().includes('ebsi') ||
623+
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
624+
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
625+
);
617626
}
618627

619628
private assertIssuerData(): void {

packages/client/lib/OpenID4VCIClientV1_0_13.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ import { CredentialRequestOpts } from './CredentialRequestClient';
3737
import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
3838
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
3939
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
40-
import { generateMissingPKCEOpts } from './functions';
41-
import { sendNotification } from './functions';
40+
import { generateMissingPKCEOpts, sendNotification } from './functions';
4241

4342
const debug = Debug('sphereon:oid4vci');
4443

@@ -288,7 +287,10 @@ export class OpenID4VCIClientV1_0_13 {
288287
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
289288
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
290289
: undefined);
291-
if (clientId) {
290+
if (this.isEBSI() || (clientId && kid)) {
291+
if (!clientId) {
292+
throw Error(`Client id expected for EBSI`);
293+
}
292294
asOpts.clientOpts = {
293295
...asOpts.clientOpts,
294296
clientId,
@@ -654,8 +656,13 @@ export class OpenID4VCIClientV1_0_13 {
654656
}
655657
}
656658

657-
this.assertIssuerData();
658-
return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ?? false;
659+
return (
660+
this.clientId?.includes('ebsi') ||
661+
this._state.kid?.includes('did:ebsi:') ||
662+
this.getIssuer().includes('ebsi') ||
663+
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
664+
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
665+
);
659666
}
660667

661668
private assertIssuerData(): void {

packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ describe('ProofOfPossession Builder ', () => {
6262
it('should fail without supplied proof or callbacks and with kid without did', async function () {
6363
await expect(
6464
ProofOfPossessionBuilder.fromProof(undefined as never, OpenId4VCIVersion.VER_1_0_13)
65-
.withIssuer(IDENTIPROOF_ISSUER_URL)
66-
.withClientId('sphereon:wallet')
67-
.withKid(kid_withoutDid)
68-
.build(),
65+
.withIssuer(IDENTIPROOF_ISSUER_URL)
66+
.withClientId('sphereon:wallet')
67+
.withKid(kid_withoutDid)
68+
.build(),
6969
).rejects.toThrow(Error(PROOF_CANT_BE_CONSTRUCTED));
7070
});
7171

@@ -87,11 +87,11 @@ describe('ProofOfPossession Builder ', () => {
8787
callbacks: { signCallback: proofOfPossessionCallbackFunction },
8888
version: OpenId4VCIVersion.VER_1_0_08,
8989
})
90-
.withJwt(undefined as never)
91-
.withIssuer(IDENTIPROOF_ISSUER_URL)
92-
.withClientId('sphereon:wallet')
93-
.withKid(kid_withoutDid)
94-
.build(),
90+
.withJwt(undefined as never)
91+
.withIssuer(IDENTIPROOF_ISSUER_URL)
92+
.withClientId('sphereon:wallet')
93+
.withKid(kid_withoutDid)
94+
.build(),
9595
).toThrow(Error(NO_JWT_PROVIDED));
9696
});
9797

@@ -118,10 +118,10 @@ describe('ProofOfPossession Builder ', () => {
118118
},
119119
version: OpenId4VCIVersion.VER_1_0_08,
120120
})
121-
.withIssuer(IDENTIPROOF_ISSUER_URL)
122-
.withKid(kid_withoutDid)
123-
.withClientId('sphereon:wallet')
124-
.build();
121+
.withIssuer(IDENTIPROOF_ISSUER_URL)
122+
.withKid(kid_withoutDid)
123+
.withClientId('sphereon:wallet')
124+
.build();
125125
expect(proof).toBeDefined();
126126
});
127127

@@ -152,10 +152,10 @@ describe('ProofOfPossession Builder ', () => {
152152
callbacks: { signCallback: proofOfPossessionCallbackFunction },
153153
version: OpenId4VCIVersion.VER_1_0_08,
154154
})
155-
.withIssuer(IDENTIPROOF_ISSUER_URL)
156-
.withClientId('sphereon:wallet')
157-
.withKid(kid_withoutDid)
158-
.build(),
155+
.withIssuer(IDENTIPROOF_ISSUER_URL)
156+
.withClientId('sphereon:wallet')
157+
.withKid(kid_withoutDid)
158+
.build(),
159159
).rejects.toThrow(Error(JWS_NOT_VALID));
160160
});
161161

@@ -186,10 +186,10 @@ describe('ProofOfPossession Builder ', () => {
186186
callbacks: { signCallback: proofOfPossessionCallbackFunction },
187187
version: OpenId4VCIVersion.VER_1_0_08,
188188
})
189-
.withIssuer(IDENTIPROOF_ISSUER_URL)
190-
.withClientId('sphereon:wallet')
191-
.withKid(kid_withoutDid)
192-
.build(),
189+
.withIssuer(IDENTIPROOF_ISSUER_URL)
190+
.withClientId('sphereon:wallet')
191+
.withKid(kid_withoutDid)
192+
.build(),
193193
).rejects.toThrow(Error(JWS_NOT_VALID));
194194
});
195195
});

packages/client/lib/__tests__/SdJwt.spec.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -215,37 +215,37 @@ describe('sd-jwt vc', () => {
215215
const offered = supported['SdJwtCredentialId'] as CredentialSupportedSdJwtVc;
216216

217217
nock(issuerMetadata.token_endpoint as string)
218-
.post('/')
219-
.reply(200, async (_, body: string) => {
220-
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
221-
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
222-
credentialOfferSessions: vcIssuer.credentialOfferSessions,
223-
accessTokenIssuer: 'https://issuer.example.com',
224-
cNonces: vcIssuer.cNonces,
225-
cNonce: 'a-c-nonce',
226-
accessTokenSignerCallback: async () => 'ey.val.ue',
227-
tokenExpiresIn: 500,
218+
.post('/')
219+
.reply(200, async (_, body: string) => {
220+
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
221+
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
222+
credentialOfferSessions: vcIssuer.credentialOfferSessions,
223+
accessTokenIssuer: 'https://issuer.example.com',
224+
cNonces: vcIssuer.cNonces,
225+
cNonce: 'a-c-nonce',
226+
accessTokenSignerCallback: async () => 'ey.val.ue',
227+
tokenExpiresIn: 500,
228+
});
228229
});
229-
});
230230

231231
await client.acquireAccessToken({ pin: '123' });
232232
nock(issuerMetadata.credential_endpoint as string)
233-
.post('/')
234-
.reply(200, async (_, body) =>
235-
vcIssuer.issueCredential({
236-
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
237-
credential: {
238-
vct: 'Hello',
239-
iss: 'example.com',
240-
iat: 123,
241-
// Defines what can be disclosed (optional)
242-
__disclosureFrame: {
243-
name: true,
233+
.post('/')
234+
.reply(200, async (_, body) =>
235+
vcIssuer.issueCredential({
236+
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
237+
credential: {
238+
vct: 'Hello',
239+
iss: 'example.com',
240+
iat: 123,
241+
// Defines what can be disclosed (optional)
242+
__disclosureFrame: {
243+
name: true,
244+
},
244245
},
245-
},
246-
newCNonce: 'new-c-nonce',
247-
}),
248-
);
246+
newCNonce: 'new-c-nonce',
247+
}),
248+
);
249249

250250
const credentials = await client.acquireCredentials({
251251
credentialIdentifier: offered.vct,

packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,4 @@ describe('VcIssuer builder should', () => {
208208
credentialOffer: { credential_offer: { credentials: ['test_credential'], credential_issuer: 'test_issuer' } },
209209
})
210210
})
211-
212211
})

0 commit comments

Comments
 (0)