Skip to content

Commit

Permalink
Support both OID4VCI and RFC 8414 well-known config URLs.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Aug 22, 2024
1 parent 32f87ca commit 4becbe6
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 14 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @digitalbazaar/oid4-client Changelog

## 3.6.0 - 2024-08-dd

### Added
- Add support for issuer configuration URLs that do not match RFC 8414,
but instead match the OID4VCI spec, i.e., `<issuer>/.well-known/...` will
be accepted and not just `<issuer origin>/.well-known/.../<issuer path>`.

## 3.5.0 - 2024-08-08

### Added
Expand Down
18 changes: 9 additions & 9 deletions lib/OID4Client.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*!
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
*/
import {discoverIssuer, generateDIDProofJWT} from './util.js';
import {generateDIDProofJWT, robustDiscoverIssuer} from './util.js';
import {httpClient} from '@digitalbazaar/http-client';

const GRANT_TYPES = new Map([
Expand Down Expand Up @@ -159,7 +159,7 @@ export class OID4Client {
nonce,
// the entity identified by the DID is issuing this JWT
iss: did,
// audience MUST be the target issuer per the OID4VC spec
// audience MUST be the target issuer per the OID4VCI spec
aud
});

Expand Down Expand Up @@ -227,6 +227,7 @@ export class OID4Client {
}
const {
'pre-authorized_code': preAuthorizedCode,
// FIXME: update to `tx_code` terminology
user_pin_required: userPinRequired
} = grant;
if(!preAuthorizedCode) {
Expand All @@ -238,11 +239,9 @@ export class OID4Client {

try {
// discover issuer info
const issuerConfigUrl =
`${parsedIssuer.origin}/.well-known/openid-credential-issuer` +
parsedIssuer.pathname;
const {issuerConfig, metadata} = await discoverIssuer(
{issuerConfigUrl, agent});
const {issuerConfig, metadata} = await robustDiscoverIssuer({
issuer: credential_issuer, agent
});

/* First get access token from AS (Authorization Server), e.g.:
Expand Down Expand Up @@ -306,8 +305,9 @@ export class OID4Client {
}

// create client w/access token
return new OID4Client(
{accessToken, agent, issuerConfig, metadata, offer});
return new OID4Client({
accessToken, agent, issuerConfig, metadata, offer
});
} catch(cause) {
const error = new Error('Could not create OID4 client.', {cause});
error.name = 'OperationError';
Expand Down
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*!
* Copyright (c) 2022-2023 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
*/
export * as oid4vp from './oid4vp.js';
export {
discoverIssuer,
generateDIDProofJWT,
parseCredentialOfferUrl,
robustDiscoverIssuer,
signJWT
} from './util.js';
export {OID4Client} from './OID4Client.js';
51 changes: 47 additions & 4 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2022-2023 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved.
*/
import * as base64url from 'base64url-universal';
import {httpClient} from '@digitalbazaar/http-client';
Expand Down Expand Up @@ -46,6 +46,14 @@ export async function discoverIssuer({issuerConfigUrl, agent} = {}) {
throw error;
}

// ensure `credential_issuer` matches `issuer`, if present
const {credential_issuer} = issuerMetaData;
if(credential_issuer !== undefined && credential_issuer !== issuer) {
const error = new Error('"credential_issuer" must match "issuer".');
error.name = 'DataError';
throw error;
}

/* Validate `issuer` value against `issuerConfigUrl` (per RFC 8414):
The `origin` and `path` element must be parsed from `issuer` and checked
Expand All @@ -64,9 +72,19 @@ export async function discoverIssuer({issuerConfigUrl, agent} = {}) {
expectedConfigUrl += pathname;
}
if(issuerConfigUrl !== expectedConfigUrl) {
const error = new Error('"issuer" does not match configuration URL.');
error.name = 'DataError';
throw error;
// alternatively, against RFC 8414, but according to OID4VCI, make sure
// the issuer config URL matches:
// <origin><path>/.well-known/<any-path-segment>
expectedConfigUrl = origin;
if(pathname !== '/') {
expectedConfigUrl += pathname;
}
expectedConfigUrl += `/.well-known/${anyPathSegment}`;
if(issuerConfigUrl !== expectedConfigUrl) {
const error = new Error('"issuer" does not match configuration URL.');
error.name = 'DataError';
throw error;
}
}

// fetch AS meta data
Expand Down Expand Up @@ -171,6 +189,31 @@ export function parseCredentialOfferUrl({url} = {}) {
return JSON.parse(searchParams.get('credential_offer'));
}

export async function robustDiscoverIssuer({issuer, agent} = {}) {
// try issuer config URLs based on OID4VCI (first) and RFC 8414 (second)
const parsedIssuer = new URL(issuer);
const {origin} = parsedIssuer;
const path = parsedIssuer.pathname === '/' ? '' : parsedIssuer.pathname;

const issuerConfigUrls = [
// OID4VCI
`${origin}${path}/.well-known/openid-credential-issuer`,
// RFC 8414
`${origin}/.well-known/openid-credential-issuer${path}`
];

let error;
for(const issuerConfigUrl of issuerConfigUrls) {
try {
const config = await discoverIssuer({issuerConfigUrl, agent});
return config;
} catch(e) {
error = e;
}
}
throw error;
}

export async function signJWT({payload, protectedHeader, signer} = {}) {
// encode payload and protected header
const b64Payload = base64url.encode(JSON.stringify(payload));
Expand Down

0 comments on commit 4becbe6

Please sign in to comment.