Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,8 @@ export class OpenId4VcController extends ConsumptionBaseController {

return { status: serverResponse.status, message: serverResponse.body };
}

public async createDefaultPresentation(credential: VerifiableCredential): Promise<string> {
return await this.holder.createDefaultPresentation(credential);
}
}
36 changes: 35 additions & 1 deletion packages/consumption/src/modules/openid4vc/local/Holder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { BaseRecord, ClaimFormat, DidJwk, DidKey, InjectionSymbols, JwkDidCreateOptions, KeyDidCreateOptions, Kms, MdocRecord, SdJwtVcRecord, X509Module } from "@credo-ts/core";
import {
BaseRecord,
ClaimFormat,
DidJwk,
DidKey,
InjectionSymbols,
JwkDidCreateOptions,
KeyDidCreateOptions,
Kms,
MdocRecord,
SdJwtVcApi,
SdJwtVcRecord,
X509Module
} from "@credo-ts/core";
import { OpenId4VciCredentialResponse, OpenId4VcModule, type OpenId4VciResolvedCredentialOffer, type OpenId4VpResolvedAuthorizationRequest } from "@credo-ts/openid4vc";
import { VerifiableCredential } from "@nmshd/content";
import { AccountController } from "@nmshd/transport";
import { AttributesController, OwnIdentityAttribute } from "../../attributes";
import { BaseAgent } from "./BaseAgent";
Expand Down Expand Up @@ -190,6 +204,26 @@ export class Holder extends BaseAgent<ReturnType<typeof getOpenIdHolderModules>>
return submissionResult.serverResponse;
}

public async createDefaultPresentation(credential: VerifiableCredential): Promise<string> {
if (credential.type !== ClaimFormat.SdJwtDc) throw new Error("Creating a default presentation is only supported for dc+sd-jwt credentials.");
if (!credential.defaultPresentationConfig) throw new Error("Default presentation not configured.");

const sdJwtVcApi = this.agent.dependencyManager.resolve(SdJwtVcApi);
const presentation = await sdJwtVcApi.present({
sdJwtVc: sdJwtVcApi.fromCompact(credential.value as string),
presentationFrame: credential.defaultPresentationConfig.presentationFrame,
verifierMetadata: credential.defaultPresentationConfig.keyBinding
? {
audience: "defaultPresentationAudience",
issuedAt: Date.now() / 1000,
nonce: "defaultPresentationNonce"
}
: undefined
Comment on lines +214 to +221
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to save those settings in the attribute? Or is is possible to pass them in the UseCase? IMO the used type in the credo lib heavily blows up the generated types - and I don't really see how we set this from e.g. the app.

Additionally: audience and nonce should not default to nonsense values, instead this should also be passed to this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The end goal is that the holder shows a QR code with the presentation that is not prompted like in the OID4VP flow, which allows offline presentation and is more in line with showing a digital ticket. So the configuration has to be stored somewhere and the attribute is the simple choice.
audience and nonce should under normal circumstances be set by the verifier, but in this scenario that's not possible and I don't want to put the effort in to build something without them. So while it would be possible to move them to the use case, I don't see them changed to other values.
If the generated types are problematic, I have no problem with simplifying them - it's easy enough to explain what is correct there without the type

@fatschi @tnotheis Do you agree with this plan?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still not understand WHO stores that information. Also, how is the verifier transfer the information to the app, when the app is just doing a button press.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no information transfer from the verifier, an example scenario is that you display a QR code with the Heidelbergpass, it is scanned at e. g. the zoo without any further prompting from the verifier. So the holder has to store the information and should get it from the issuer during the issuance, although we haven't decided yet how this happens

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't those changes useless until this decision was done? Also, we could do those changes with a hardcoded config for now and then implement the config storage when we determined who stores what where ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have a short Teams call where we discuss this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should, yeah.

});

return presentation;
}

public async exit(): Promise<void> {
await this.shutdown();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IPresentationFrame } from "@credo-ts/core";
import { serialize, type, validate } from "@js-soft/ts-serval";
import { AbstractAttributeValue, AbstractAttributeValueJSON, IAbstractAttributeValue } from "../AbstractAttributeValue";
import { RenderHints, RenderHintsEditType, RenderHintsTechnicalType, ValueHints } from "../hints";
Expand All @@ -8,16 +9,18 @@ export interface VerifiableCredentialJSON extends AbstractAttributeValueJSON {
value: string | Record<string, any>;
type: string;
displayInformation?: Record<string, any>[];
defaultPresentationConfig?: { presentationFrame: IPresentationFrame; keyBinding?: boolean };
}

export interface IVerifiableCredential extends IAbstractAttributeValue {
value: string | Record<string, any>;
type: string;
displayInformation?: Record<string, any>[];
defaultPresentationConfig?: { presentationFrame: IPresentationFrame; keyBinding?: boolean };
}

@type("VerifiableCredential")
export class VerifiableCredential extends AbstractAttributeValue {
export class VerifiableCredential extends AbstractAttributeValue implements IVerifiableCredential {
@serialize({ any: true })
@validate({ customValidator: validateValue })
public value: string | Record<string, any>;
Expand All @@ -30,6 +33,10 @@ export class VerifiableCredential extends AbstractAttributeValue {
@validate({ nullable: true, max: PROPRIETARY_ATTRIBUTE_MAX_DESCRIPTION_LENGTH })
public displayInformation?: Record<string, any>[];

@serialize({ any: true })
@validate({ nullable: true })
public defaultPresentationConfig?: { presentationFrame: IPresentationFrame; keyBinding?: boolean };

public static get valueHints(): ValueHints {
return ValueHints.from({});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
AcceptAuthorizationRequestRequest,
AcceptAuthorizationRequestResponse,
AcceptAuthorizationRequestUseCase,
CreateDefaultPresentationRequest,
CreateDefaultPresentationResponse,
CreateDefaultPresentationUseCase,
RequestCredentialsRequest,
RequestCredentialsResponse,
RequestCredentialsUseCase,
Expand All @@ -24,7 +27,8 @@ export class OpenId4VcFacade {
@Inject private readonly requestCredentialsUseCase: RequestCredentialsUseCase,
@Inject private readonly storeCredentialsUseCase: StoreCredentialsUseCase,
@Inject private readonly resolveAuthorizationRequestUseCase: ResolveAuthorizationRequestUseCase,
@Inject private readonly acceptAuthorizationRequestUseCase: AcceptAuthorizationRequestUseCase
@Inject private readonly acceptAuthorizationRequestUseCase: AcceptAuthorizationRequestUseCase,
@Inject private readonly createDefaultPresentationUseCase: CreateDefaultPresentationUseCase
) {}

public async resolveCredentialOffer(request: ResolveCredentialOfferRequest): Promise<Result<ResolveCredentialOfferResponse>> {
Expand All @@ -46,4 +50,8 @@ export class OpenId4VcFacade {
public async acceptAuthorizationRequest(request: AcceptAuthorizationRequestRequest): Promise<Result<AcceptAuthorizationRequestResponse>> {
return await this.acceptAuthorizationRequestUseCase.execute(request);
}

public async createDefaultPresentation(request: CreateDefaultPresentationRequest): Promise<Result<CreateDefaultPresentationResponse>> {
return await this.createDefaultPresentationUseCase.execute(request);
}
}
Loading
Loading