Skip to content

Commit

Permalink
chore: better EBSI detection. Hookup client assertion to EBSI as well
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed Jun 28, 2024
1 parent 03caf09 commit 2759750
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 60 deletions.
10 changes: 8 additions & 2 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ import { createAuthorizationRequestUrl } from './AuthorizationCodeClient';
import { createAuthorizationRequestUrlV1_0_11 } from './AuthorizationCodeClientV1_0_11';
import { CredentialOfferClient } from './CredentialOfferClient';
import { CredentialRequestOpts } from './CredentialRequestClient';
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
import { CredentialRequestClientBuilderV1_0_13 } from './CredentialRequestClientBuilderV1_0_13';
import { MetadataClient } from './MetadataClient';
import { OpenID4VCIClientStateV1_0_11 } from './OpenID4VCIClientV1_0_11';
import { OpenID4VCIClientStateV1_0_13 } from './OpenID4VCIClientV1_0_13';
Expand Down Expand Up @@ -298,7 +298,10 @@ export class OpenID4VCIClient {
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
: undefined);
if (clientId) {
if (this.isEBSI() || (clientId && kid)) {
if (!clientId) {
throw Error(`Client id expected for EBSI`);
}
asOpts.clientOpts = {
...asOpts.clientOpts,
clientId,
Expand Down Expand Up @@ -667,6 +670,9 @@ export class OpenID4VCIClient {
}
// this.assertIssuerData();
return (
this.clientId?.includes('ebsi') ||
this._state.kid?.includes('did:ebsi:') ||
this.getIssuer().includes('ebsi') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
);
Expand Down
19 changes: 14 additions & 5 deletions packages/client/lib/OpenID4VCIClientV1_0_11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,10 @@ export class OpenID4VCIClientV1_0_11 {
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
: undefined);
if (clientId) {
if (this.isEBSI() || (clientId && kid)) {
if (!clientId) {
throw Error(`Client id expected for EBSI`);
}
asOpts.clientOpts = {
...asOpts.clientOpts,
clientId,
Expand Down Expand Up @@ -602,8 +605,8 @@ export class OpenID4VCIClientV1_0_11 {
*/
public isEBSI() {
if (
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)['credentials'] &&
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11).credentials.find(
this.credentialOffer &&
(this.credentialOffer?.credential_offer as CredentialOfferPayloadV1_0_11)?.credentials?.find(
(cred) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand All @@ -612,8 +615,14 @@ export class OpenID4VCIClientV1_0_11 {
) {
return true;
}
this.assertIssuerData();
return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') === true;
// this.assertIssuerData();
return (
this.clientId?.includes('ebsi') ||
this._state.kid?.includes('did:ebsi:') ||
this.getIssuer().includes('ebsi') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
);
}

private assertIssuerData(): void {
Expand Down
17 changes: 12 additions & 5 deletions packages/client/lib/OpenID4VCIClientV1_0_13.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import { CredentialRequestOpts } from './CredentialRequestClient';
import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
import { MetadataClientV1_0_13 } from './MetadataClientV1_0_13';
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
import { generateMissingPKCEOpts } from './functions';
import { sendNotification } from './functions';
import { generateMissingPKCEOpts, sendNotification } from './functions';

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

Expand Down Expand Up @@ -288,7 +287,10 @@ export class OpenID4VCIClientV1_0_13 {
(kid && clientId && typeof asOpts.clientOpts?.signCallbacks === 'function'
? 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
: undefined);
if (clientId) {
if (this.isEBSI() || (clientId && kid)) {
if (!clientId) {
throw Error(`Client id expected for EBSI`);
}
asOpts.clientOpts = {
...asOpts.clientOpts,
clientId,
Expand Down Expand Up @@ -654,8 +656,13 @@ export class OpenID4VCIClientV1_0_13 {
}
}

this.assertIssuerData();
return this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ?? false;
return (
this.clientId?.includes('ebsi') ||
this._state.kid?.includes('did:ebsi:') ||
this.getIssuer().includes('ebsi') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_endpoint?.includes('ebsi.eu') ||
this.endpointMetadata.credentialIssuerMetadata?.authorization_server?.includes('ebsi.eu')
);
}

private assertIssuerData(): void {
Expand Down
42 changes: 21 additions & 21 deletions packages/client/lib/__tests__/ProofOfPossessionBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ describe('ProofOfPossession Builder ', () => {
it('should fail without supplied proof or callbacks and with kid without did', async function () {
await expect(
ProofOfPossessionBuilder.fromProof(undefined as never, OpenId4VCIVersion.VER_1_0_13)
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
).rejects.toThrow(Error(PROOF_CANT_BE_CONSTRUCTED));
});

Expand All @@ -87,11 +87,11 @@ describe('ProofOfPossession Builder ', () => {
callbacks: { signCallback: proofOfPossessionCallbackFunction },
version: OpenId4VCIVersion.VER_1_0_08,
})
.withJwt(undefined as never)
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
.withJwt(undefined as never)
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
).toThrow(Error(NO_JWT_PROVIDED));
});

Expand All @@ -118,10 +118,10 @@ describe('ProofOfPossession Builder ', () => {
},
version: OpenId4VCIVersion.VER_1_0_08,
})
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withKid(kid_withoutDid)
.withClientId('sphereon:wallet')
.build();
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withKid(kid_withoutDid)
.withClientId('sphereon:wallet')
.build();
expect(proof).toBeDefined();
});

Expand Down Expand Up @@ -152,10 +152,10 @@ describe('ProofOfPossession Builder ', () => {
callbacks: { signCallback: proofOfPossessionCallbackFunction },
version: OpenId4VCIVersion.VER_1_0_08,
})
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
).rejects.toThrow(Error(JWS_NOT_VALID));
});

Expand Down Expand Up @@ -186,10 +186,10 @@ describe('ProofOfPossession Builder ', () => {
callbacks: { signCallback: proofOfPossessionCallbackFunction },
version: OpenId4VCIVersion.VER_1_0_08,
})
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
.withIssuer(IDENTIPROOF_ISSUER_URL)
.withClientId('sphereon:wallet')
.withKid(kid_withoutDid)
.build(),
).rejects.toThrow(Error(JWS_NOT_VALID));
});
});
52 changes: 26 additions & 26 deletions packages/client/lib/__tests__/SdJwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,37 +215,37 @@ describe('sd-jwt vc', () => {
const offered = supported['SdJwtCredentialId'] as CredentialSupportedSdJwtVc;

nock(issuerMetadata.token_endpoint as string)
.post('/')
.reply(200, async (_, body: string) => {
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
credentialOfferSessions: vcIssuer.credentialOfferSessions,
accessTokenIssuer: 'https://issuer.example.com',
cNonces: vcIssuer.cNonces,
cNonce: 'a-c-nonce',
accessTokenSignerCallback: async () => 'ey.val.ue',
tokenExpiresIn: 500,
.post('/')
.reply(200, async (_, body: string) => {
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
credentialOfferSessions: vcIssuer.credentialOfferSessions,
accessTokenIssuer: 'https://issuer.example.com',
cNonces: vcIssuer.cNonces,
cNonce: 'a-c-nonce',
accessTokenSignerCallback: async () => 'ey.val.ue',
tokenExpiresIn: 500,
});
});
});

await client.acquireAccessToken({ pin: '123' });
nock(issuerMetadata.credential_endpoint as string)
.post('/')
.reply(200, async (_, body) =>
vcIssuer.issueCredential({
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
credential: {
vct: 'Hello',
iss: 'example.com',
iat: 123,
// Defines what can be disclosed (optional)
__disclosureFrame: {
name: true,
.post('/')
.reply(200, async (_, body) =>
vcIssuer.issueCredential({
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
credential: {
vct: 'Hello',
iss: 'example.com',
iat: 123,
// Defines what can be disclosed (optional)
__disclosureFrame: {
name: true,
},
},
},
newCNonce: 'new-c-nonce',
}),
);
newCNonce: 'new-c-nonce',
}),
);

const credentials = await client.acquireCredentials({
credentialIdentifier: offered.vct,
Expand Down
1 change: 0 additions & 1 deletion packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,4 @@ describe('VcIssuer builder should', () => {
credentialOffer: { credential_offer: { credentials: ['test_credential'], credential_issuer: 'test_issuer' } },
})
})

})

0 comments on commit 2759750

Please sign in to comment.