Skip to content

Commit

Permalink
fix: Fix for when credential_configuration_ids is being used together…
Browse files Browse the repository at this point in the history
… with credentials_supported
  • Loading branch information
nklomp committed Aug 30, 2024
1 parent 47aebdc commit f696867
Show file tree
Hide file tree
Showing 24 changed files with 675 additions and 536 deletions.
2 changes: 1 addition & 1 deletion packages/callback-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@sphereon/oid4vci-client": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-types": "0.29.0",
"@sphereon/ssi-types": "0.29.1-unstable.208",
"jose": "^4.10.0"
},
"devDependencies": {
Expand Down
104 changes: 84 additions & 20 deletions packages/client/lib/CredentialRequestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereo
import {
acquireDeferredCredential,
CredentialRequestV1_0_13,
CredentialRequestWithoutProofV1_0_13,
CredentialResponse,
DPoPResponseParams,
getCredentialRequestForVersion,
Expand All @@ -17,7 +18,7 @@ import {
URL_NOT_VALID,
} from '@sphereon/oid4vci-common';
import { ExperimentalSubjectIssuance } from '@sphereon/oid4vci-common';
import { CredentialFormat } from '@sphereon/ssi-types';
import { CredentialFormat, DIDDocument } from '@sphereon/ssi-types';
import Debug from 'debug';

import { CredentialRequestClientBuilderV1_0_11 } from './CredentialRequestClientBuilderV1_0_11';
Expand All @@ -42,7 +43,16 @@ export interface CredentialRequestOpts {
subjectIssuance?: ExperimentalSubjectIssuance;
}

export async function buildProof<DIDDoc>(
export type CreateCredentialRequestOpts<DIDDoc = DIDDocument> = {
credentialIdentifier?: string;
credentialTypes?: string | string[];
context?: string[];
format?: CredentialFormat | OID4VCICredentialFormat;
subjectIssuance?: ExperimentalSubjectIssuance;
version: OpenId4VCIVersion;
};

export async function buildProof<DIDDoc = DIDDocument>(
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession,
opts: {
version: OpenId4VCIVersion;
Expand Down Expand Up @@ -85,7 +95,34 @@ export class CredentialRequestClient {
this._credentialRequestOpts = { ...builder };
}

public async acquireCredentialsUsingProof<DIDDoc>(opts: {
/**
* Typically you should not use this method, as it omits a proof from the request.
* There are certain issuers that in specific circumstances can do without this proof, because they have other means of user binding
* like using DPoP together with an authorization code flow. These are however rare, so you should be using the acquireCredentialsUsingProof normally
* @param opts
*/
public async acquireCredentialsWithoutProof<DIDDoc = DIDDocument>(opts: {
credentialIdentifier?: string;
credentialTypes?: string | string[];
context?: string[];
format?: CredentialFormat | OID4VCICredentialFormat;
subjectIssuance?: ExperimentalSubjectIssuance;
createDPoPOpts?: CreateDPoPClientOpts;
}): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
const { credentialIdentifier, credentialTypes, format, context, subjectIssuance } = opts;

const request = await this.createCredentialRequestWithoutProof<DIDDoc>({
credentialTypes,
context,
format,
version: this.version(),
credentialIdentifier,
subjectIssuance,
});
return await this.acquireCredentialsUsingRequestWithoutProof(request, opts.createDPoPOpts);
}

public async acquireCredentialsUsingProof<DIDDoc = DIDDocument>(opts: {
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
credentialIdentifier?: string;
credentialTypes?: string | string[];
Expand All @@ -96,7 +133,7 @@ export class CredentialRequestClient {
}): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
const { credentialIdentifier, credentialTypes, proofInput, format, context, subjectIssuance } = opts;

const request = await this.createCredentialRequest({
const request = await this.createCredentialRequest<DIDDoc>({
proofInput,
credentialTypes,
context,
Expand All @@ -108,9 +145,23 @@ export class CredentialRequestClient {
return await this.acquireCredentialsUsingRequest(request, opts.createDPoPOpts);
}

public async acquireCredentialsUsingRequestWithoutProof(
uniformRequest: UniformCredentialRequest,
createDPoPOpts?: CreateDPoPClientOpts,
): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
return await this.acquireCredentialsUsingRequestImpl(uniformRequest, createDPoPOpts);
}

public async acquireCredentialsUsingRequest(
uniformRequest: UniformCredentialRequest,
createDPoPOpts?: CreateDPoPClientOpts,
): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
return await this.acquireCredentialsUsingRequestImpl(uniformRequest, createDPoPOpts);
}

private async acquireCredentialsUsingRequestImpl(
uniformRequest: UniformCredentialRequest & { proof?: ProofOfPossession },
createDPoPOpts?: CreateDPoPClientOpts,
): Promise<OpenIDResponse<CredentialResponse, DPoPResponseParams> & { access_token: string }> {
if (this.version() < OpenId4VCIVersion.VER_1_0_13) {
throw new Error('Versions below v1.0.13 (draft 13) are not supported by the V13 credential request client.');
Expand Down Expand Up @@ -194,24 +245,37 @@ export class CredentialRequestClient {
});
}

public async createCredentialRequest<DIDDoc>(opts: {
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
credentialIdentifier?: string;
credentialTypes?: string | string[];
context?: string[];
format?: CredentialFormat | OID4VCICredentialFormat;
subjectIssuance?: ExperimentalSubjectIssuance;
version: OpenId4VCIVersion;
}): Promise<CredentialRequestV1_0_13> {
public async createCredentialRequestWithoutProof<DIDDoc = DIDDocument>(
opts: CreateCredentialRequestOpts<DIDDoc>,
): Promise<CredentialRequestWithoutProofV1_0_13> {
return await this.createCredentialRequestImpl(opts);
}

public async createCredentialRequest<DIDDoc = DIDDocument>(
opts: CreateCredentialRequestOpts<DIDDoc> & {
proofInput: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
},
): Promise<CredentialRequestV1_0_13> {
return await this.createCredentialRequestImpl(opts);
}

private async createCredentialRequestImpl<DIDDoc = DIDDocument>(
opts: CreateCredentialRequestOpts<DIDDoc> & {
proofInput?: ProofOfPossessionBuilder<DIDDoc> | ProofOfPossession;
},
): Promise<CredentialRequestV1_0_13> {
const { proofInput, credentialIdentifier: credential_identifier } = opts;
const proof = await buildProof(proofInput, opts);
let proof: ProofOfPossession | undefined = undefined;
if (proofInput) {
proof = await buildProof(proofInput, opts);
}
if (credential_identifier) {
if (opts.format || opts.credentialTypes || opts.context) {
throw Error(`You cannot mix credential_identifier with format, credential types and/or context`);
}
return {
credential_identifier,
proof,
...(proof && { proof }),
};
}
const formatSelection = opts.format ?? this.credentialRequestOpts.format;
Expand Down Expand Up @@ -239,7 +303,7 @@ export class CredentialRequestClient {
type: types,
},
format,
proof,
...(proof && { proof }),
...opts.subjectIssuance,
};
} else if (format === 'jwt_vc_json-ld' || format === 'ldp_vc') {
Expand All @@ -249,7 +313,7 @@ export class CredentialRequestClient {

return {
format,
proof,
...(proof && { proof }),
...opts.subjectIssuance,

credential_definition: {
Expand All @@ -263,7 +327,7 @@ export class CredentialRequestClient {
}
return {
format,
proof,
...(proof && { proof }),
vct: types[0],
...opts.subjectIssuance,
};
Expand All @@ -273,13 +337,13 @@ export class CredentialRequestClient {
}
return {
format,
proof,
...(proof && { proof }),
doctype: types[0],
...opts.subjectIssuance,
};
}

throw new Error(`Unsupported format: ${format}`);
throw new Error(`Unsupported credential format: ${format}`);
}

private version(): OpenId4VCIVersion {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/ProofOfPossessionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Typ,
} from '@sphereon/oid4vci-common';

export class ProofOfPossessionBuilder<DIDDoc> {
export class ProofOfPossessionBuilder<DIDDoc = never> {
private readonly proof?: ProofOfPossession;
private readonly callbacks?: ProofOfPossessionCallbacks<DIDDoc>;
private readonly version: OpenId4VCIVersion;
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dependencies": {
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "0.29.0",
"@sphereon/ssi-types": "0.29.1-unstable.208",
"cross-fetch": "^3.1.8",
"debug": "^4.3.5"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"references": [
{
"path": "../common"
},
{
"path": "../oid4vci-common"
}
]
}
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build:clean": "tsc --build --clean && tsc --build"
},
"dependencies": {
"@sphereon/ssi-types": "0.29.0",
"@sphereon/ssi-types": "0.29.1-unstable.208",
"jwt-decode": "^4.0.0",
"sha.js": "^2.4.11",
"uint8arrays": "3.1.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/did-auth-siop-adapter/lib/DidJwtAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export const verfiyDidJwtAdapter = async (
if (jwtVerifier.type === 'request-object' && (jwt.payload as JwtPayload & { client_id?: string }).client_id?.startsWith('did:')) {
const authorizationRequestPayload = jwt.payload as AuthorizationRequestPayload
if (options.verification?.checkLinkedDomain && options.verification.checkLinkedDomain != CheckLinkedDomain.NEVER) {
if(!authorizationRequestPayload.client_id) {
if (!authorizationRequestPayload.client_id) {
return Promise.reject(Error('missing client_id from AuthorizationRequestPayload'))
}
await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, options.verification)
} else if (!options.verification?.checkLinkedDomain && options.verification.wellknownDIDVerifyCallback) {
if(!authorizationRequestPayload.client_id) {
if (!authorizationRequestPayload.client_id) {
return Promise.reject(Error('missing client_id from AuthorizationRequestPayload'))
}
await validateLinkedDomainWithDid(authorizationRequestPayload.client_id, options.verification)
Expand Down
16 changes: 10 additions & 6 deletions packages/did-auth-siop-adapter/lib/did/DidJWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ export async function createDidJWT(

export async function signIDTokenPayload(payload: IDTokenPayload, signature: InternalSignature | ExternalSignature | SuppliedSignature) {
if (isInternalSignature(signature)) {
if(!signature.kid) {
if (!signature.kid) {
return Promise.reject(Error('missing kid from signature'))
}
return signDidJwtInternal(payload, payload.issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner)
} else if (isExternalSignature(signature)) {
return signDidJwtExternal(payload, signature.signatureUri, signature.authZToken, signature.alg, signature.kid)
} else if (isSuppliedSignature(signature)) {
if(!signature.kid) {
if (!signature.kid) {
return Promise.reject(Error('missing kid from signature'))
}
return signDidJwtSupplied(payload, payload.issuer, signature.signature, signature.alg, signature.kid)
Expand All @@ -114,14 +114,14 @@ export async function signRequestObjectPayload(payload: RequestObjectPayload, si
payload.sub = signature.did
}
if (isInternalSignature(signature)) {
if(!signature.kid) {
if (!signature.kid) {
return Promise.reject(Error('missing kid from signature'))
}
return signDidJwtInternal(payload, issuer, signature.hexPrivateKey, signature.alg, signature.kid, signature.customJwtSigner)
} else if (isExternalSignature(signature)) {
return signDidJwtExternal(payload, signature.signatureUri, signature.authZToken, signature.alg, signature.kid)
} else if (isSuppliedSignature(signature)) {
if(!signature.kid) {
if (!signature.kid) {
return Promise.reject(Error('missing kid from signature'))
}
return signDidJwtSupplied(payload, issuer, signature.signature, signature.alg, signature.kid)
Expand Down Expand Up @@ -171,7 +171,7 @@ async function signDidJwtExternal(
}

const response: SIOPResonse<SignatureResponse> = await post(signatureUri, JSON.stringify(body), { bearerToken: authZToken })
if(!response.successBody) {
if (!response.successBody) {
return Promise.reject(Error('the siop SignatureResponse does not have a successBody'))
}
return response.successBody.jws
Expand Down Expand Up @@ -257,7 +257,11 @@ export function getSubDidFromPayload(payload: JWTPayload, header?: JWTHeader): s
}

export function isIssSelfIssued(payload: JWTPayload): boolean {
return payload.iss && payload.iss.includes(ResponseIss.SELF_ISSUED_V1) || payload.iss.includes(ResponseIss.SELF_ISSUED_V2) || payload.iss === payload.sub
return (
(payload.iss && payload.iss.includes(ResponseIss.SELF_ISSUED_V1)) ||
payload.iss.includes(ResponseIss.SELF_ISSUED_V2) ||
payload.iss === payload.sub
)
}

export function getMethodFromDid(did: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function checkInvalidMessages(validationErrorMessages: string[]): { status: bool
return { status: true }
}

export async function validateLinkedDomainWithDid(did: string, verification: InternalVerification | ExternalVerification) : Promise<void>{
export async function validateLinkedDomainWithDid(did: string, verification: InternalVerification | ExternalVerification): Promise<void> {
const { checkLinkedDomain, resolveOpts, wellknownDIDVerifyCallback } = verification
if (checkLinkedDomain === CheckLinkedDomain.NEVER) {
return
Expand All @@ -68,7 +68,7 @@ export async function validateLinkedDomainWithDid(did: string, verification: Int
return
}
try {
if(!wellknownDIDVerifyCallback) {
if (!wellknownDIDVerifyCallback) {
return Promise.reject(Error('wellknownDIDVerifyCallback is required for checkWellKnownDid'))
}
const validationResult = await checkWellKnownDid({ didDocument, verifyCallback: wellknownDIDVerifyCallback })
Expand All @@ -79,7 +79,7 @@ export async function validateLinkedDomainWithDid(did: string, verification: Int
throw new Error(messageCondition.message ? messageCondition.message : validationErrorMessages[0])
}
}
} catch (err) {
} catch (err: any) {
const messageCondition: { status: boolean; message?: string } = checkInvalidMessages([err.message])
if (checkLinkedDomain === CheckLinkedDomain.ALWAYS || (checkLinkedDomain === CheckLinkedDomain.IF_PRESENT && !messageCondition.status)) {
throw new Error(err.message)
Expand Down
2 changes: 1 addition & 1 deletion packages/did-auth-siop-adapter/lib/types/SIOP.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface NoSignature {
export interface ExternalSignature {
signatureUri: string // url to call to generate a withSignature
did: string
authZToken?: string // Optional: bearer token to use to the call
authZToken: string // Optional: bearer token to use to the call
hexPublicKey?: string // Optional: hex encoded public key to compute JWK key, if not possible from DIDres Document

alg: SigningAlgo
Expand Down
4 changes: 2 additions & 2 deletions packages/issuer-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-express-support": "0.29.0",
"@sphereon/ssi-types": "0.29.0",
"@sphereon/ssi-express-support": "0.29.1-unstable.208",
"@sphereon/ssi-types": "0.29.1-unstable.208",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/issuer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "0.29.0",
"@sphereon/ssi-types": "0.29.1-unstable.208",
"uuid": "^9.0.0"
},
"peerDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/oid4vci-common/lib/types/CredentialIssuance.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export interface Jwt {
payload: JWTPayload;
}

export interface ProofOfPossessionCallbacks<DIDDoc> {
export interface ProofOfPossessionCallbacks<DIDDoc = never> {
signCallback: JWTSignerCallback;
verifyCallback?: JWTVerifyCallback<DIDDoc>;
}
Expand Down Expand Up @@ -170,9 +170,9 @@ export interface JWTPayload {
}

export type JWTSignerCallback = (jwt: Jwt, kid?: string) => Promise<string>;
export type JWTVerifyCallback<DIDDoc> = (args: { jwt: string; kid?: string }) => Promise<JwtVerifyResult<DIDDoc>>;
export type JWTVerifyCallback<DIDDoc = never> = (args: { jwt: string; kid?: string }) => Promise<JwtVerifyResult<DIDDoc>>;

export interface JwtVerifyResult<DIDDoc> {
export interface JwtVerifyResult<DIDDoc = never> {
jwt: Jwt;
kid?: string;
alg: string;
Expand Down
Loading

0 comments on commit f696867

Please sign in to comment.