diff --git a/package.json b/package.json index e34b81a5f..2c8dc20da 100644 --- a/package.json +++ b/package.json @@ -111,9 +111,9 @@ "@veramo/url-handler": "4.2.0", "@sphereon/ssi-types": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", - "@sphereon/oid4vci-common": "0.12.1-next.4", - "@sphereon/oid4vci-client": "0.12.1-next.4", - "@sphereon/oid4vci-issuer": "0.12.1-next.4", + "@sphereon/oid4vci-common": "0.12.1-next.5", + "@sphereon/oid4vci-client": "0.12.1-next.5", + "@sphereon/oid4vci-issuer": "0.12.1-next.5", "@noble/hashes": "1.2.0", "did-jwt": "6.11.6", "did-jwt-vc": "3.1.3", diff --git a/packages/contact-manager-rest-api/package.json b/packages/contact-manager-rest-api/package.json index 45a8dac6e..f69d43c9d 100644 --- a/packages/contact-manager-rest-api/package.json +++ b/packages/contact-manager-rest-api/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@sphereon/ssi-express-support": "workspace:*", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.contact-manager": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", diff --git a/packages/data-store/package.json b/packages/data-store/package.json index 5680bf809..1abf2f54d 100644 --- a/packages/data-store/package.json +++ b/packages/data-store/package.json @@ -17,6 +17,7 @@ "@sphereon/pex": "^3.3.3", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-types": "workspace:*", + "@sphereon/ssi-sdk-ext.did-utils": "^0.21.0", "@veramo/core": "4.2.0", "@veramo/utils": "4.2.0", "blakejs": "^1.1.1", diff --git a/packages/data-store/src/types/contact/contact.ts b/packages/data-store/src/types/contact/contact.ts index b8bd8aa28..d98e59a28 100644 --- a/packages/data-store/src/types/contact/contact.ts +++ b/packages/data-store/src/types/contact/contact.ts @@ -1,3 +1,4 @@ +import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { IIdentifier } from '@veramo/core' export type MetadataTypes = string | number | Date | boolean | undefined @@ -126,7 +127,7 @@ export type PartialOpenIdConfig = Partial export type DidAuthConfig = { id: string - identifier: IIdentifier + idOpts: IIdentifierOpts stateId: string ownerId?: string tenantId?: string diff --git a/packages/data-store/src/utils/contact/MappingUtils.ts b/packages/data-store/src/utils/contact/MappingUtils.ts index 456480989..6d2b82491 100644 --- a/packages/data-store/src/utils/contact/MappingUtils.ts +++ b/packages/data-store/src/utils/contact/MappingUtils.ts @@ -166,7 +166,7 @@ export const correlationIdentifierFrom = (identifier: CorrelationIdentifierEntit export const didAuthConfigEntityFrom = (config: NonPersistedDidAuthConfig): DidAuthConfigEntity => { const didAuthConfig: DidAuthConfigEntity = new DidAuthConfigEntity() - didAuthConfig.identifier = config.identifier.did + didAuthConfig.identifier = typeof config.idOpts.identifier === 'string' ? config.idOpts.identifier : config.idOpts.identifier.did didAuthConfig.redirectUrl = config.redirectUrl didAuthConfig.sessionId = config.sessionId didAuthConfig.ownerId = config.ownerId @@ -479,7 +479,7 @@ export const openIdConfigFrom = (config: OpenIdConfigEntity): OpenIdConfig => { export const didAuthConfigFrom = (config: DidAuthConfigEntity): DidAuthConfig => { return { id: config.id, - identifier: { did: config.identifier, provider: '', keys: [], services: [] }, + idOpts: { identifier: config.identifier }, stateId: '', // FIXME redirectUrl: config.redirectUrl, sessionId: config.sessionId, diff --git a/packages/ebsi-authorization-client/__tests__/attestation.test.ts b/packages/ebsi-authorization-client/__tests__/attestation.test.ts index 8b7f611d1..1ccea59b8 100644 --- a/packages/ebsi-authorization-client/__tests__/attestation.test.ts +++ b/packages/ebsi-authorization-client/__tests__/attestation.test.ts @@ -1,38 +1,47 @@ import {CreateRequestObjectMode, getIssuerName} from '@sphereon/oid4vci-common' import {toJwk} from '@sphereon/ssi-sdk-ext.key-utils' import {createObjects, getConfig} from '@sphereon/ssi-sdk.agent-config' -import {IContactManager} from "@sphereon/ssi-sdk.contact-manager"; +import {IContactManager} from '@sphereon/ssi-sdk.contact-manager' import { - ConnectionType, - CorrelationIdentifierType, - CredentialRole, - IdentityOrigin, - NonPersistedParty, - Party, - PartyOrigin, - PartyTypeType -} from "@sphereon/ssi-sdk.data-store"; + ConnectionType, + CorrelationIdentifierType, + CredentialRole, + IdentityOrigin, + NonPersistedParty, + Party, + PartyOrigin, + PartyTypeType, +} from '@sphereon/ssi-sdk.data-store' import { - IOID4VCIHolder, - OID4VCIMachineEvents, - OID4VCIMachineInterpreter, - OID4VCIMachineState, - OID4VCIMachineStates, - OID4VCIStateListenerWithCallbacks, - SupportedDidMethodEnum + IOID4VCIHolder, + OID4VCICallbackStateListener, + OID4VCIMachine, + OID4VCIMachineEvents, + OID4VCIMachineInterpreter, + OID4VCIMachineState, + OID4VCIMachineStates, + SupportedDidMethodEnum, } from '@sphereon/ssi-sdk.oid4vci-holder' import {IPresentationExchange} from '@sphereon/ssi-sdk.presentation-exchange' -import {IDidAuthSiopOpAuthenticator} from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' +import { + IDidAuthSiopOpAuthenticator, + OID4VPCallbackStateListener, + Siopv2MachineInterpreter, + Siopv2MachineState, + Siopv2MachineStates, +} from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' +import {Siopv2OID4VPLinkHandler} from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth/dist/link-handler' import {IDIDManager, IIdentifier, IKeyManager, IResolver, MinimalImportableKey, TAgent} from '@veramo/core' // @ts-ignore import cors from 'cors' +import fetch from 'cross-fetch' + // @ts-ignore import express, {Express} from 'express' import {Server} from 'http' - import {DataSource} from 'typeorm' -import {ebsiCreateAttestationAuthRequestURL} from '../src/functions' +import {AttestationAuthRequestUrlResult, ebsiCreateAttestationAuthRequestURL} from '../src/functions' let dbConnection: Promise let agent: TAgent @@ -40,290 +49,341 @@ let app: Express | undefined let server: Server | undefined const port = 3333 const secp256k1PrivateKey: MinimalImportableKey = { - privateKeyHex: '6e491660cf923f7d9ce4a03401444b361817df9e76b926b55e21ffe7144d2ee6', - kms: 'local', - type: 'Secp256k1', - meta: { - purposes: ['capabilityInvocation'], - }, + privateKeyHex: '6e491660cf923f7d9ce4a03401444b361817df9e76b926b55e21ffe7144d2ee6', + kms: 'local', + type: 'Secp256k1', + meta: { + purposes: ['capabilityInvocation'], + }, } -const secp256k1Jwk = toJwk(secp256k1PrivateKey.privateKeyHex, 'Secp256k1', {isPrivateKey: true}) +const secp256k1Jwk = toJwk(secp256k1PrivateKey.privateKeyHex, 'Secp256k1', { isPrivateKey: true }) const secp256r1PrivateKey: MinimalImportableKey = { - privateKeyHex: 'f0710a0bb80c28a14ae62831bfe7f90a6937d006295fad6115e5539e7e314ee4', - kms: 'local', - type: 'Secp256r1', - meta: { - purposes: ['assertionMethod', 'authentication'], - }, + privateKeyHex: 'f0710a0bb80c28a14ae62831bfe7f90a6937d006295fad6115e5539e7e314ee4', + kms: 'local', + type: 'Secp256r1', + meta: { + purposes: ['assertionMethod', 'authentication'], + }, } -const secp256r1Jwk = toJwk(secp256r1PrivateKey.privateKeyHex, 'Secp256r1', {isPrivateKey: true}) - +const secp256r1Jwk = toJwk(secp256r1PrivateKey.privateKeyHex, 'Secp256r1', { isPrivateKey: true }) +let vpLinkHandler: Siopv2OID4VPLinkHandler +let authReqResult : AttestationAuthRequestUrlResult +let oid4vciMachine: OID4VCIMachine const MOCK_BASE_URL = 'https://ebsi-sphereon.ngrok.dev' // `http://localhost:${port}` jest.setTimeout(600000) const setup = async (): Promise => { - const config = await getConfig('packages/ebsi-authorization-client/agent.yml') - const {localAgent, db} = await createObjects(config, {localAgent: '/agent', db: '/dbConnection'}) - agent = localAgent as TAgent - dbConnection = db - - app = express() - app.use( - cors({ - origin: ['*'], - }), - ) - app.use(express.json()) - - app.get('/', async (req: express.Request, res: express.Response) => { - res.send({hello: 'world'}) - }) - - app.get('/.well-known/jwks', async (req: express.Request, res: express.Response) => { - res.send({keys: [{...secp256k1Jwk}, {...secp256r1Jwk}]}) - }) - - console.log(`########## $${MOCK_BASE_URL}`) - - server = app.listen(port, () => { - console.log(`Mock server is listening to port ${port}`) - }) - - return true + const config = await getConfig('packages/ebsi-authorization-client/agent.yml') + const { localAgent, db } = await createObjects(config, { localAgent: '/agent', db: '/dbConnection' }) + agent = localAgent as TAgent< + IKeyManager & IDIDManager & IDidAuthSiopOpAuthenticator & IPresentationExchange & IOID4VCIHolder & IResolver & IContactManager + > + dbConnection = db + + app = express() + app.use( + cors({ + origin: ['*'], + }), + ) + app.use(express.json()) + + app.get('/', async (req: express.Request, res: express.Response) => { + res.send({ hello: 'world' }) + }) + + app.get('/.well-known/jwks', async (req: express.Request, res: express.Response) => { + res.send({ keys: [{ ...secp256k1Jwk }, { ...secp256r1Jwk }] }) + }) + + console.log(`########## $${MOCK_BASE_URL}`) + + server = app.listen(port, () => { + console.log(`Mock server is listening to port ${port}`) + }) + + return true } const tearDown = async (): Promise => { - if (server) { - server.closeAllConnections() - } - - if (dbConnection && false) { - ;(await dbConnection)?.close() - } - return true + if (server) { + server.closeAllConnections() + } + + if (dbConnection && false) { + ;(await dbConnection)?.close() + } + return true } describe('attestation client should', () => { - let identifier: IIdentifier - beforeAll(async (): Promise => { - await setup() - identifier = await agent.didManagerCreate({ - provider: 'did:ebsi', - options: {secp256k1Key: secp256k1PrivateKey, secp256r1Key: secp256r1PrivateKey}, - }) - console.log(identifier.did) - }) + let identifier: IIdentifier - afterAll(async (): Promise => { - await tearDown() + beforeAll(async (): Promise => { + await setup() + identifier = await agent.didManagerCreate({ + provider: 'did:ebsi', + options: { secp256k1Key: secp256k1PrivateKey, secp256r1Key: secp256r1PrivateKey }, }) + console.log(identifier.did) + }) + + afterAll(async (): Promise => { + await tearDown() + }) + + it('get attestation to onboard', async () => { + const clientId = MOCK_BASE_URL + authReqResult = await ebsiCreateAttestationAuthRequestURL( + { + credentialType: 'VerifiableAuthorisationToOnboard', + formats: ['jwt_vc_json'], + redirectUri: `${MOCK_BASE_URL}`, + clientId, + idOpts: { identifier }, + credentialIssuer: 'http://192.168.2.90:3000/conformance/v3/issuer-mock', + //credentialIssuer: 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock', + requestObjectOpts: { + iss: clientId, + requestObjectMode: CreateRequestObjectMode.REQUEST_OBJECT, + jwksUri: `${MOCK_BASE_URL}/.well-known/jwks`, + }, + }, + { agent }, + ) - it('get attestation to onboard', async () => { - const clientId = MOCK_BASE_URL - const authReqResult = await ebsiCreateAttestationAuthRequestURL( + console.log(authReqResult) + + const addContact = async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => { + const { serverMetadata, hasContactConsent, contactAlias } = state.context + + if (!serverMetadata) { + return Promise.reject(Error('Missing serverMetadata in context')) + } + + const issuerUrl: URL = new URL(serverMetadata.issuer) + const correlationId: string = `${issuerUrl.protocol}//${issuerUrl.hostname}` + let issuerName: string = getIssuerName(correlationId, serverMetadata.credentialIssuerMetadata) + + const party: NonPersistedParty = { + contact: { + displayName: issuerName, + legalName: issuerName, + }, + // FIXME maybe its nicer if we can also just use the id only + // TODO using the predefined party type from the contact migrations here + // TODO this is not used as the screen itself adds one, look at the params of the screen, this is not being passed in + partyType: { + id: '3875c12e-fdaa-4ef6-a340-c936e054b627', + origin: PartyOrigin.EXTERNAL, + type: PartyTypeType.ORGANIZATION, + name: 'Sphereon_default_type', + tenantId: '95e09cfc-c974-4174-86aa-7bf1d5251fb4', + }, + uri: correlationId, + identities: [ + { + alias: correlationId, + roles: [CredentialRole.ISSUER], + origin: IdentityOrigin.EXTERNAL, + identifier: { + type: CorrelationIdentifierType.URL, + correlationId: issuerUrl.hostname, + }, + // TODO WAL-476 add support for correct connection + connection: { + type: ConnectionType.OPENID_CONNECT, + config: { + clientId: '138d7bf8-c930-4c6e-b928-97d3a4928b01', + clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', + scopes: ['auth'], + issuer: 'https://example.com/app-test', + redirectUrl: 'app:/callback', + dangerouslyAllowInsecureHttpRequests: true, + clientAuthMethod: 'post' as const, + }, + }, + }, + ], + } + + const onCreate = async ({ + party, + issuerUrl, + issuerName, + correlationId, + }: { + party: NonPersistedParty + issuerUrl: string + issuerName: string + correlationId: string + }): Promise => { + const displayName = party.contact.displayName ?? issuerName + const contacts: Array = await agent.cmGetContacts({ + filter: [ { - credentialType: 'VerifiableAuthorisationToOnboard', - formats: ["jwt_vc_json"], - redirectUri: `${MOCK_BASE_URL}`, - clientId, - idOpts: {identifier}, - credentialIssuer: 'http://192.168.2.90:3000/conformance/v3/issuer-mock', - //credentialIssuer: 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock', - requestObjectOpts: { - iss: clientId, - requestObjectMode: CreateRequestObjectMode.REQUEST_OBJECT, - jwksUri: `${MOCK_BASE_URL}/.well-known/jwks`, - }, + contact: { + // Searching on legalName as displayName is not unique, and we only support organizations for now + legalName: displayName, + }, + }, + ], + }) + if (contacts.length === 0 || !contacts[0]?.contact) { + const contact = await agent.cmAddContact({ + ...party, + displayName, + legalName: displayName, + contactType: { + type: PartyTypeType.ORGANIZATION, + name: displayName, + origin: PartyOrigin.EXTERNAL, + tenantId: party.tenantId ?? '1', }, - {agent}, - ) - - console.log(authReqResult) - - const addContact = async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => { - const {serverMetadata, hasContactConsent, contactAlias} = state.context; - - if (!serverMetadata) { - return Promise.reject(Error('Missing serverMetadata in context')); - } - - const issuerUrl: URL = new URL(serverMetadata.issuer); - const correlationId: string = `${issuerUrl.protocol}//${issuerUrl.hostname}`; - let issuerName: string = getIssuerName(correlationId, serverMetadata.credentialIssuerMetadata); - - const party: NonPersistedParty = { - contact: { - displayName: issuerName, - legalName: issuerName, - }, - // FIXME maybe its nicer if we can also just use the id only - // TODO using the predefined party type from the contact migrations here - // TODO this is not used as the screen itself adds one, look at the params of the screen, this is not being passed in - partyType: { - id: '3875c12e-fdaa-4ef6-a340-c936e054b627', - origin: PartyOrigin.EXTERNAL, - type: PartyTypeType.ORGANIZATION, - name: 'Sphereon_default_type', - tenantId: '95e09cfc-c974-4174-86aa-7bf1d5251fb4', - }, - uri: correlationId, - identities: [ - { - alias: correlationId, - roles: [CredentialRole.ISSUER], - origin: IdentityOrigin.EXTERNAL, - identifier: { - type: CorrelationIdentifierType.URL, - correlationId: issuerUrl.hostname, - }, - // TODO WAL-476 add support for correct connection - connection: { - type: ConnectionType.OPENID_CONNECT, - config: { - clientId: '138d7bf8-c930-4c6e-b928-97d3a4928b01', - clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', - scopes: ['auth'], - issuer: 'https://example.com/app-test', - redirectUrl: 'app:/callback', - dangerouslyAllowInsecureHttpRequests: true, - clientAuthMethod: 'post' as const, - }, - }, - }, - ], - }; - - const onCreate = async ({party, issuerUrl, issuerName, correlationId}: { - party: NonPersistedParty, - issuerUrl: string, - issuerName: string, - correlationId: string - }): Promise => { - - - const displayName = party.contact.displayName ?? issuerName - const contacts: Array = await agent.cmGetContacts({ - filter: [ - { - contact: { - // Searching on legalName as displayName is not unique, and we only support organizations for now - legalName: displayName, - }, - }, - ], - }); - if (contacts.length === 0 || !contacts[0]?.contact) { - const contact = await agent.cmAddContact({ - ...party, displayName, legalName: displayName, contactType: { - type: PartyTypeType.ORGANIZATION, - name: displayName, - origin: PartyOrigin.EXTERNAL, - tenantId: party.tenantId ?? '1', - } - }); - oid4vciMachine.send({ - type: OID4VCIMachineEvents.CREATE_CONTACT, - data: contact, - }); - } - } - - - const onConsentChange = async (hasConsent: boolean): Promise => { - oid4vciMachine.send({ - type: OID4VCIMachineEvents.SET_CONTACT_CONSENT, - data: hasConsent, - }); - }; - - const onAliasChange = async (alias: string): Promise => { - oid4vciMachine.send({ - type: OID4VCIMachineEvents.SET_CONTACT_ALIAS, - data: alias, - }); - }; - - if (!issuerName) { - issuerName = `EBSI unknown (${issuerUrl})` - } else if (issuerName.startsWith('http')) { - issuerName = `EBSI ${issuerName.replace(/https?:\/\//, '')}`; - } - if (!contactAlias) { - return await onAliasChange(issuerName) - } - issuerName = contactAlias - if (!hasContactConsent) { - return await onConsentChange(true) - } - await onCreate({party, issuerName, issuerUrl: issuerUrl.toString(), correlationId}) + }) + oid4vciMachine.send({ + type: OID4VCIMachineEvents.CREATE_CONTACT, + data: contact, + }) } + } - const callbacks = new Map Promise> - callbacks.set(OID4VCIMachineStates.addContact, addContact) - callbacks.set(OID4VCIMachineStates.selectCredentials, selectCredentials) - callbacks.set(OID4VCIMachineStates.initiateAuthorizationRequest, authorizationCodeUrl) - - const oid4vciMachine = await agent.oid4vciHolderGetMachineInterpreter({ - ...authReqResult, - issuanceOpt: { - identifier, - didMethod: SupportedDidMethodEnum.DID_EBSI, - }, - didMethodPreferences: [SupportedDidMethodEnum.DID_EBSI, SupportedDidMethodEnum.DID_KEY], - stateNavigationListener: OID4VCIStateListenerWithCallbacks(callbacks), + const onConsentChange = async (hasConsent: boolean): Promise => { + oid4vciMachine.send({ + type: OID4VCIMachineEvents.SET_CONTACT_CONSENT, + data: hasConsent, }) + } - const interpreter = oid4vciMachine.interpreter - interpreter.start() + const onAliasChange = async (alias: string): Promise => { + oid4vciMachine.send({ + type: OID4VCIMachineEvents.SET_CONTACT_ALIAS, + data: alias, + }) + } + + if (!issuerName) { + issuerName = `EBSI unknown (${issuerUrl})` + } else if (issuerName.startsWith('http')) { + issuerName = `EBSI ${issuerName.replace(/https?:\/\//, '')}` + } + if (!contactAlias) { + return await onAliasChange(issuerName) + } + issuerName = contactAlias + if (!hasContactConsent) { + return await onConsentChange(true) + } + await onCreate({ party, issuerName, issuerUrl: issuerUrl.toString(), correlationId }) + } - await new Promise((resolve) => setTimeout(resolve, 30000)) + const vciStateCallbacks = new Map< + OID4VCIMachineStates, + (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => Promise + >() + vciStateCallbacks.set(OID4VCIMachineStates.addContact, addContact) + vciStateCallbacks.set(OID4VCIMachineStates.selectCredentials, selectCredentials) + vciStateCallbacks.set(OID4VCIMachineStates.initiateAuthorizationRequest, authorizationCodeUrl) + + const vpStateCallbacks = new Map Promise>() + vpStateCallbacks.set(Siopv2MachineStates.done, siopDone) + vpLinkHandler = new Siopv2OID4VPLinkHandler({ + protocols: ['openid:'], + // @ts-ignore + context: { agent }, + noStateMachinePersistence: true, + stateNavigationListener: OID4VPCallbackStateListener(vpStateCallbacks), + }) - console.log(JSON.stringify(interpreter.getSnapshot().value, null, 2)) + oid4vciMachine = await agent.oid4vciHolderGetMachineInterpreter({ + ...authReqResult, + issuanceOpt: { + identifier, + didMethod: SupportedDidMethodEnum.DID_EBSI, + }, + didMethodPreferences: [SupportedDidMethodEnum.DID_EBSI, SupportedDidMethodEnum.DID_KEY], + stateNavigationListener: OID4VCICallbackStateListener(vciStateCallbacks), }) -}) + /*const oid4vpMachine = await agent.siopGetMachineInterpreter({ + stateNavigationListener: OID4VPCallbackStateListener(vpStateCallbacks), + }) +*/ + const interpreter = oid4vciMachine.interpreter + interpreter.start() -const selectCredentials = async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => { - const {contact, credentialToSelectFrom, selectedCredentials} = state.context; + await new Promise((resolve) => setTimeout(resolve, 30000)) - if (selectedCredentials && selectedCredentials.length > 0) { - console.log(`selected: ${selectedCredentials.join(', ')}`) - oid4vciMachine.send({ - type: OID4VCIMachineEvents.NEXT - }); - return - } else if (!contact) { - return Promise.reject(Error('Missing contact in context')); - } + console.log(JSON.stringify(interpreter.getSnapshot().value, null, 2)) + }) +}) - const onSelectType = async (selectedCredentials: Array): Promise => { - console.log(`Selected credentials: ${selectedCredentials.join(', ')}`) - oid4vciMachine.send({ - type: OID4VCIMachineEvents.SET_SELECTED_CREDENTIALS, - data: selectedCredentials, - }); - }; +const selectCredentials = async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => { + const { contact, credentialToSelectFrom, selectedCredentials } = state.context + + if (selectedCredentials && selectedCredentials.length > 0) { + console.log(`selected: ${selectedCredentials.join(', ')}`) + oid4vciMachine.send({ + type: OID4VCIMachineEvents.NEXT, + }) + return + } else if (!contact) { + return Promise.reject(Error('Missing contact in context')) + } + + const onSelectType = async (selectedCredentials: Array): Promise => { + console.log(`Selected credentials: ${selectedCredentials.join(', ')}`) + oid4vciMachine.send({ + type: OID4VCIMachineEvents.SET_SELECTED_CREDENTIALS, + data: selectedCredentials, + }) + } - await onSelectType(credentialToSelectFrom.map(sel => sel.credentialId)) + await onSelectType(credentialToSelectFrom.map((sel) => sel.credentialId)) } const authorizationCodeUrl = async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState) => { - const url = state.context.authorizationCodeURL; - console.log('navigateAuthorizationCodeURL: ', url); - if (!url) { - return Promise.reject(Error('Missing authorization URL in context')); + const url = state.context.authorizationCodeURL + console.log('navigateAuthorizationCodeURL: ', url) + if (!url) { + return Promise.reject(Error('Missing authorization URL in context')) + } + const onOpenAuthorizationUrl = async (url: string): Promise => { + console.log('onOpenAuthorizationUrl being invoked: ', url) + oid4vciMachine.send({ + type: OID4VCIMachineEvents.INVOKED_AUTHORIZATION_CODE_REQUEST, + data: url, + }) + const response = await fetch(url, { redirect: 'manual' }) + if (response.status < 301 || response.status > 302) { + throw Error(`When doing a headless auth, we expect to be redirected on getting the authz URL`) } - const onOpenAuthorizationUrl = async (url: string): Promise => { - console.log('onOpenAuthorizationUrl being invoked: ', url); - oid4vciMachine.send({ - type: OID4VCIMachineEvents.INVOKED_AUTHORIZATION_CODE_REQUEST, - data: url, - }); - const response = await fetch(url) - console.log(`onOpenAuthorizationUrl after openUrl: ${url}`); - console.log(JSON.stringify(response, null, 2)) - }; - await onOpenAuthorizationUrl(url) + const openidUri = response.headers.get('location') + if (!openidUri || !openidUri.startsWith('openid://')) { + throw Error( + `Expected a openid:// URI to be returned from EBSI in headless mode. Returned: ${openidUri}, ${JSON.stringify(await response.text())}`, + ) + } + + console.log(`onOpenAuthorizationUrl after openUrl: ${url}`) + const kid = authReqResult.authKey.meta?.jwkThumbprint ? `${authReqResult.identifier.did}#${authReqResult.authKey.meta.jwkThumbprint}` : authReqResult.authKey.kid + await vpLinkHandler.handle(openidUri, {idOpts: {identifier: authReqResult.identifier, kid}}) + } + await onOpenAuthorizationUrl(url) +} + + +const siopDone = async (oid4vpMachine: Siopv2MachineInterpreter, state: Siopv2MachineState) => { + // console.log('SIOP result:') + // console.log(JSON.stringify(state.context, null , 2)) + if (!state.context.authorizationResponseData?.queryParams?.code) { + throw Error(`No code was returned from the authorization step`) + } + oid4vciMachine.interpreter.send({ + type: OID4VCIMachineEvents.PROVIDE_AUTHORIZATION_CODE_RESPONSE, + data: state.context.authorizationResponseData.url!, + }) + } diff --git a/packages/ebsi-authorization-client/package.json b/packages/ebsi-authorization-client/package.json index 7874415bd..edb200046 100644 --- a/packages/ebsi-authorization-client/package.json +++ b/packages/ebsi-authorization-client/package.json @@ -21,12 +21,12 @@ "@sphereon/ssi-sdk.presentation-exchange": "workspace:*", "@sphereon/ssi-sdk.oid4vci-holder": "workspace:*", "@sphereon/ssi-sdk.siopv2-oid4vp-op-auth": "workspace:*", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.contact-manager": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-types": "workspace:*", - "@sphereon/ssi-sdk-ext.did-provider-ebsi": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-ebsi": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.8", "@veramo/core": "4.2.0", "@veramo/utils": "4.2.0", "cross-fetch": "^3.1.8", @@ -35,11 +35,11 @@ "uuidv4": "^6.2.13" }, "devDependencies": { - "@sphereon/oid4vci-client": "0.12.0", - "@sphereon/oid4vci-common": "0.12.0", + "@sphereon/oid4vci-client": "0.12.1-next.5", + "@sphereon/oid4vci-common": "0.12.1-next.5", "@sphereon/ssi-sdk.agent-config": "workspace:*", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@sphereon/ssi-sdk.data-store": "workspace:*", "@transmute/json-web-signature": "0.7.0-unstable.81", "@types/express": "^4.17.21", diff --git a/packages/ebsi-authorization-client/src/functions/Attestation.ts b/packages/ebsi-authorization-client/src/functions/Attestation.ts index 9b9fdb3f8..408325f4c 100644 --- a/packages/ebsi-authorization-client/src/functions/Attestation.ts +++ b/packages/ebsi-authorization-client/src/functions/Attestation.ts @@ -1,4 +1,4 @@ -import {OpenID4VCIClient} from '@sphereon/oid4vci-client' +import { OpenID4VCIClient } from '@sphereon/oid4vci-client' import { AuthorizationDetails, AuthzFlowType, @@ -9,19 +9,14 @@ import { ProofOfPossessionCallbacks, RequestObjectOpts, } from '@sphereon/oid4vci-common' -import {getIdentifier, IIdentifierOpts} from '@sphereon/ssi-sdk-ext.did-utils' -import {calculateJwkThumbprintForKey} from '@sphereon/ssi-sdk-ext.key-utils' -import { - getAuthenticationKey, IssuanceOpts, - PrepareStartArgs, - signCallback, - SupportedDidMethodEnum -} from '@sphereon/ssi-sdk.oid4vci-holder' -import {IIdentifier} from '@veramo/core' -import {_ExtendedIKey} from '@veramo/utils' -import {IRequiredContext} from '../types/IEBSIAuthorizationClient' +import { getIdentifier, IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' +import { calculateJwkThumbprintForKey } from '@sphereon/ssi-sdk-ext.key-utils' +import { getAuthenticationKey, IssuanceOpts, PrepareStartArgs, signCallback, SupportedDidMethodEnum } from '@sphereon/ssi-sdk.oid4vci-holder' +import { IIdentifier } from '@veramo/core' +import { _ExtendedIKey } from '@veramo/utils' +import { IRequiredContext } from '../types/IEBSIAuthorizationClient' -export interface AttestationAuthRequestUrlResult extends Omit, 'issuanceOpt'> { +export interface AttestationAuthRequestUrlResult extends Omit, 'issuanceOpt'> { issuanceOpt?: IssuanceOpts authorizationCodeURL: string identifier: IIdentifier @@ -100,7 +95,7 @@ export const ebsiCreateAttestationAuthRequestURL = async ( }) const signCallbacks: ProofOfPossessionCallbacks = requestObjectOpts.signCallbacks ?? { - signCallback: signCallback(vciClient, idOpts, context), + signCallback: await signCallback(vciClient, idOpts, context), } const authorizationRequestOpts = { redirectUri, diff --git a/packages/oid4vci-holder/package.json b/packages/oid4vci-holder/package.json index 02b6bb495..8051e897e 100644 --- a/packages/oid4vci-holder/package.json +++ b/packages/oid4vci-holder/package.json @@ -14,10 +14,10 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-client": "0.12.0", - "@sphereon/oid4vci-common": "0.12.0", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/oid4vci-client": "0.12.1-next.5", + "@sphereon/oid4vci-common": "0.12.1-next.5", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.contact-manager": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 4c72667e5..64a8a9713 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -1,88 +1,81 @@ -import {CredentialOfferClient, MetadataClient, OpenID4VCIClient} from '@sphereon/oid4vci-client' +import { CredentialOfferClient, MetadataClient, OpenID4VCIClient } from '@sphereon/oid4vci-client' import { - AuthorizationRequestOpts, - CredentialOfferRequestWithBaseUrl, - DefaultURISchemes, - EndpointMetadataResult, - getTypesFromAuthorizationDetails, - getTypesFromCredentialOffer, - getTypesFromObject, - Jwt, - NotificationRequest, - ProofOfPossessionCallbacks, + AuthorizationRequestOpts, + CredentialOfferRequestWithBaseUrl, + DefaultURISchemes, + EndpointMetadataResult, + getTypesFromAuthorizationDetails, + getTypesFromCredentialOffer, + getTypesFromObject, + Jwt, + NotificationRequest, + ProofOfPossessionCallbacks, } from '@sphereon/oid4vci-common' -import {IIdentifierOpts} from '@sphereon/ssi-sdk-ext.did-utils' +import {getIdentifier, getKey, IIdentifierOpts} from '@sphereon/ssi-sdk-ext.did-utils' import { - CorrelationIdentifierType, - CredentialRole, - IBasicCredentialLocaleBranding, - Identity, - IdentityOrigin, - NonPersistedIdentity, - Party, + CorrelationIdentifierType, + CredentialRole, + IBasicCredentialLocaleBranding, + Identity, + IdentityOrigin, + NonPersistedIdentity, + Party, } from '@sphereon/ssi-sdk.data-store' import { - CredentialMapper, - IVerifiableCredential, - JwtDecodedVerifiableCredential, - Loggers, - OriginalVerifiableCredential, - parseDid, - SdJwtDecodedVerifiableCredentialPayload, + CredentialMapper, + IVerifiableCredential, + JwtDecodedVerifiableCredential, + Loggers, + OriginalVerifiableCredential, + parseDid, + SdJwtDecodedVerifiableCredentialPayload, } from '@sphereon/ssi-types' +import { CredentialPayload, DIDDocument, IAgentPlugin, ProofFormat, VerifiableCredential, W3CVerifiableCredential } from '@veramo/core' +import { asArray, computeEntryHash } from '@veramo/utils' +import { decodeJWT, JWTHeader } from 'did-jwt' +import { v4 as uuidv4 } from 'uuid' +import { OID4VCIMachine } from '../machine/oid4vciMachine' import { - CredentialPayload, - DIDDocument, - IAgentPlugin, - ProofFormat, - VerifiableCredential, - W3CVerifiableCredential -} from '@veramo/core' -import {asArray, computeEntryHash} from '@veramo/utils' -import {decodeJWT, JWTHeader} from 'did-jwt' -import {v4 as uuidv4} from 'uuid' -import {OID4VCIMachine} from '../machine/oid4vciMachine' -import { - AddContactIdentityArgs, - AssertValidCredentialsArgs, - createCredentialsToSelectFromArgs, - CredentialToAccept, - CredentialToSelectFromResult, - GetContactArgs, - GetCredentialArgs, - GetCredentialsArgs, - GetIssuerMetadataArgs, - IOID4VCIHolder, - IRequiredSignAgentContext, - IssuanceOpts, - MappedCredentialToAccept, - OID4VCIHolderEvent, - OID4VCIHolderOptions, - OID4VCIMachine as OID4VCIMachineId, - OID4VCIMachineInstanceOpts, - OnContactIdentityCreatedArgs, - OnCredentialStoredArgs, - OnIdentifierCreatedArgs, - PrepareStartArgs, - RequestType, - RequiredContext, - SendNotificationArgs, - SignatureAlgorithmEnum, - StartResult, - StoreCredentialBrandingArgs, - StoreCredentialsArgs, - SupportedDidMethodEnum, + AddContactIdentityArgs, + AssertValidCredentialsArgs, + createCredentialsToSelectFromArgs, + CredentialToAccept, + CredentialToSelectFromResult, + GetContactArgs, + GetCredentialArgs, + GetCredentialsArgs, + GetIssuerMetadataArgs, + IOID4VCIHolder, + IRequiredSignAgentContext, + IssuanceOpts, + MappedCredentialToAccept, + OID4VCIHolderEvent, + OID4VCIHolderOptions, + OID4VCIMachine as OID4VCIMachineId, + OID4VCIMachineInstanceOpts, + OnContactIdentityCreatedArgs, + OnCredentialStoredArgs, + OnIdentifierCreatedArgs, + PrepareStartArgs, + RequestType, + RequiredContext, + SendNotificationArgs, + SignatureAlgorithmEnum, + StartResult, + StoreCredentialBrandingArgs, + StoreCredentialsArgs, + SupportedDidMethodEnum, } from '../types/IOID4VCIHolder' import { - getCredentialBranding, - getCredentialConfigsSupportedMerged, - getIdentifier, - getIssuanceOpts, - mapCredentialToAccept, - selectCredentialLocaleBranding, - signatureAlgorithmFromKey, - signJWT, - verifyCredentialToAccept, + getCredentialBranding, + getCredentialConfigsSupportedMerged, + getIdentifierOpts, + getIssuanceOpts, + mapCredentialToAccept, + selectCredentialLocaleBranding, + signatureAlgorithmFromKey, + signJWT, + verifyCredentialToAccept, } from './OID4VCIHolderService' /** @@ -91,729 +84,736 @@ import { // Exposing the methods here for any REST implementation export const oid4vciHolderContextMethods: Array = [ - 'cmGetContacts', - 'cmGetContact', - 'cmAddContact', - 'cmAddIdentity', - 'ibCredentialLocaleBrandingFrom', - 'ibAddCredentialBranding', - 'dataStoreSaveVerifiableCredential', - 'didManagerFind', - 'didManagerGet', - 'keyManagerSign', - 'verifyCredential', + 'cmGetContacts', + 'cmGetContact', + 'cmAddContact', + 'cmAddIdentity', + 'ibCredentialLocaleBrandingFrom', + 'ibAddCredentialBranding', + 'dataStoreSaveVerifiableCredential', + 'didManagerFind', + 'didManagerGet', + 'keyManagerSign', + 'verifyCredential', ] const logger = Loggers.DEFAULT.get('sphereon:oid4vci:holder') export function signCallback(client: OpenID4VCIClient, idOpts: IIdentifierOpts, context: IRequiredSignAgentContext) { - return (jwt: Jwt, kid?: string) => { - let iss = jwt.payload.iss - if (!kid) { - kid = jwt.header.kid - } - if (!kid) { - kid = idOpts.kid - } - if (kid) { - // sync back to id opts - idOpts.kid = kid - } + return async (jwt: Jwt, kid?: string) => { + let iss = jwt.payload.iss - if (!jwt.payload.client_id?.startsWith('http') && client.isEBSI()) { - iss = jwt.header.kid?.split('#')[0] - } else if (!iss) { - iss = jwt.header.kid?.split('#')[0] - } - if (!iss) { - return Promise.reject(Error(`No issuer could be determined from the JWT ${JSON.stringify(jwt)}`)) - } - const header = {...jwt.header, ...(kid && {kid})} as Partial - const payload = {...jwt.payload, ...(iss && {iss})} - return signJWT({ - idOpts, - header, - payload, - options: {issuer: iss, expiresIn: jwt.payload.exp, canonicalize: false}, - context, - }) + if (!kid) { + kid = jwt.header.kid + } + if (!kid) { + kid = idOpts.kid + } + + if (kid) { + // sync back to id opts + idOpts.kid = kid + } + + const identifier = await getIdentifier(idOpts, context) + const key = await getKey(identifier, undefined, context, kid) + if (key?.meta?.jwkThumbprint && kid === key.publicKeyHex) { + kid = key.meta.jwkThumbprint + } + + const httpsClientId = jwt.payload.client_id?.startsWith('http') + if (!httpsClientId && client.isEBSI()) { + iss = identifier.did /*kid?.split('#')[0]*/ + } else if (!iss) { + iss = identifier.did /* kid?.split('#')[0]*/ + } + if (!iss) { + return Promise.reject(Error(`No issuer could be determined from the JWT ${JSON.stringify(jwt)}`)) } + const header = { ...jwt.header, ...(kid && { kid: httpsClientId ? kid : `${identifier.did}#${kid}` }) } as Partial + const payload = { ...jwt.payload, ...(iss && { iss }) } + return signJWT({ + idOpts, + header, + payload, + options: { issuer: iss, expiresIn: jwt.payload.exp, canonicalize: false }, + context, + }) + } } export class OID4VCIHolder implements IAgentPlugin { - readonly eventTypes: Array = [ - OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED, - OID4VCIHolderEvent.CREDENTIAL_STORED, - OID4VCIHolderEvent.IDENTIFIER_CREATED, - ] - - readonly methods: IOID4VCIHolder = { - oid4vciHolderStart: this.oid4vciHolderStart.bind(this), - oid4vciHolderGetIssuerMetadata: this.oid4vciHolderGetIssuerMetadata.bind(this), - oid4vciHolderGetMachineInterpreter: this.oid4vciHolderGetMachineInterpreter.bind(this), - oid4vciHolderCreateCredentialsToSelectFrom: this.oid4vciHoldercreateCredentialsToSelectFrom.bind(this), - oid4vciHolderGetContact: this.oid4vciHolderGetContact.bind(this), - oid4vciHolderGetCredentials: this.oid4vciHolderGetCredentials.bind(this), - oid4vciHolderGetCredential: this.oid4vciHolderGetCredential.bind(this), - oid4vciHolderAddContactIdentity: this.oid4vciHolderAddContactIdentity.bind(this), - oid4vciHolderAssertValidCredentials: this.oid4vciHolderAssertValidCredentials.bind(this), - oid4vciHolderStoreCredentialBranding: this.oid4vciHolderStoreCredentialBranding.bind(this), - oid4vciHolderStoreCredentials: this.oid4vciHolderStoreCredentials.bind(this), - oid4vciHolderSendNotification: this.oid4vciHolderSendNotification.bind(this), - } - - private readonly vcFormatPreferences: Array = ['jwt_vc_json', 'jwt_vc', 'ldp_vc'] - private readonly jsonldCryptographicSuitePreferences: Array = [ - 'Ed25519Signature2018', - 'EcdsaSecp256k1Signature2019', - 'Ed25519Signature2020', - 'JsonWebSignature2020', - // "JcsEd25519Signature2020" - ] - private readonly didMethodPreferences: Array = [ - SupportedDidMethodEnum.DID_KEY, - SupportedDidMethodEnum.DID_JWK, - SupportedDidMethodEnum.DID_EBSI, - SupportedDidMethodEnum.DID_ION, - ] - private readonly jwtCryptographicSuitePreferences: Array = [ - SignatureAlgorithmEnum.ES256, - SignatureAlgorithmEnum.ES256K, - SignatureAlgorithmEnum.EdDSA, - ] - private static readonly DEFAULT_MOBILE_REDIRECT_URI = `${DefaultURISchemes.CREDENTIAL_OFFER}://` - private readonly defaultAuthorizationRequestOpts: AuthorizationRequestOpts = {redirectUri: OID4VCIHolder.DEFAULT_MOBILE_REDIRECT_URI} - private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise - private readonly onCredentialStored?: (args: OnCredentialStoredArgs) => Promise - private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise - - constructor(options?: OID4VCIHolderOptions) { - const { - onContactIdentityCreated, - onCredentialStored, - onIdentifierCreated, - vcFormatPreferences, - jsonldCryptographicSuitePreferences, - didMethodPreferences, - jwtCryptographicSuitePreferences, - defaultAuthorizationRequestOptions, - } = options ?? {} - - if (vcFormatPreferences !== undefined && vcFormatPreferences.length > 0) { - this.vcFormatPreferences = vcFormatPreferences - } - if (jsonldCryptographicSuitePreferences !== undefined && jsonldCryptographicSuitePreferences.length > 0) { - this.jsonldCryptographicSuitePreferences = jsonldCryptographicSuitePreferences - } - if (didMethodPreferences !== undefined && didMethodPreferences.length > 0) { - this.didMethodPreferences = didMethodPreferences - } - if (jwtCryptographicSuitePreferences !== undefined && jwtCryptographicSuitePreferences.length > 0) { - this.jwtCryptographicSuitePreferences = jwtCryptographicSuitePreferences - } - if (defaultAuthorizationRequestOptions) { - this.defaultAuthorizationRequestOpts = defaultAuthorizationRequestOptions - } - this.onContactIdentityCreated = onContactIdentityCreated - this.onCredentialStored = onCredentialStored - this.onIdentifierCreated = onIdentifierCreated - } - - public async onEvent(event: any, context: RequiredContext): Promise { - switch (event.type) { - case OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED: - this.onContactIdentityCreated?.(event.data) - break - case OID4VCIHolderEvent.CREDENTIAL_STORED: - this.onCredentialStored?.(event.data) - break - case OID4VCIHolderEvent.IDENTIFIER_CREATED: - this.onIdentifierCreated?.(event.data) - break - default: - return Promise.reject(Error(`Event type ${event.type} not supported`)) - } + readonly eventTypes: Array = [ + OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED, + OID4VCIHolderEvent.CREDENTIAL_STORED, + OID4VCIHolderEvent.IDENTIFIER_CREATED, + ] + + readonly methods: IOID4VCIHolder = { + oid4vciHolderStart: this.oid4vciHolderStart.bind(this), + oid4vciHolderGetIssuerMetadata: this.oid4vciHolderGetIssuerMetadata.bind(this), + oid4vciHolderGetMachineInterpreter: this.oid4vciHolderGetMachineInterpreter.bind(this), + oid4vciHolderCreateCredentialsToSelectFrom: this.oid4vciHoldercreateCredentialsToSelectFrom.bind(this), + oid4vciHolderGetContact: this.oid4vciHolderGetContact.bind(this), + oid4vciHolderGetCredentials: this.oid4vciHolderGetCredentials.bind(this), + oid4vciHolderGetCredential: this.oid4vciHolderGetCredential.bind(this), + oid4vciHolderAddContactIdentity: this.oid4vciHolderAddContactIdentity.bind(this), + oid4vciHolderAssertValidCredentials: this.oid4vciHolderAssertValidCredentials.bind(this), + oid4vciHolderStoreCredentialBranding: this.oid4vciHolderStoreCredentialBranding.bind(this), + oid4vciHolderStoreCredentials: this.oid4vciHolderStoreCredentials.bind(this), + oid4vciHolderSendNotification: this.oid4vciHolderSendNotification.bind(this), + } + + private readonly vcFormatPreferences: Array = ['jwt_vc_json', 'jwt_vc', 'ldp_vc'] + private readonly jsonldCryptographicSuitePreferences: Array = [ + 'Ed25519Signature2018', + 'EcdsaSecp256k1Signature2019', + 'Ed25519Signature2020', + 'JsonWebSignature2020', + // "JcsEd25519Signature2020" + ] + private readonly didMethodPreferences: Array = [ + SupportedDidMethodEnum.DID_KEY, + SupportedDidMethodEnum.DID_JWK, + SupportedDidMethodEnum.DID_EBSI, + SupportedDidMethodEnum.DID_ION, + ] + private readonly jwtCryptographicSuitePreferences: Array = [ + SignatureAlgorithmEnum.ES256, + SignatureAlgorithmEnum.ES256K, + SignatureAlgorithmEnum.EdDSA, + ] + private static readonly DEFAULT_MOBILE_REDIRECT_URI = `${DefaultURISchemes.CREDENTIAL_OFFER}://` + private readonly defaultAuthorizationRequestOpts: AuthorizationRequestOpts = { redirectUri: OID4VCIHolder.DEFAULT_MOBILE_REDIRECT_URI } + private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise + private readonly onCredentialStored?: (args: OnCredentialStoredArgs) => Promise + private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise + + constructor(options?: OID4VCIHolderOptions) { + const { + onContactIdentityCreated, + onCredentialStored, + onIdentifierCreated, + vcFormatPreferences, + jsonldCryptographicSuitePreferences, + didMethodPreferences, + jwtCryptographicSuitePreferences, + defaultAuthorizationRequestOptions, + } = options ?? {} + + if (vcFormatPreferences !== undefined && vcFormatPreferences.length > 0) { + this.vcFormatPreferences = vcFormatPreferences + } + if (jsonldCryptographicSuitePreferences !== undefined && jsonldCryptographicSuitePreferences.length > 0) { + this.jsonldCryptographicSuitePreferences = jsonldCryptographicSuitePreferences + } + if (didMethodPreferences !== undefined && didMethodPreferences.length > 0) { + this.didMethodPreferences = didMethodPreferences + } + if (jwtCryptographicSuitePreferences !== undefined && jwtCryptographicSuitePreferences.length > 0) { + this.jwtCryptographicSuitePreferences = jwtCryptographicSuitePreferences } + if (defaultAuthorizationRequestOptions) { + this.defaultAuthorizationRequestOpts = defaultAuthorizationRequestOptions + } + this.onContactIdentityCreated = onContactIdentityCreated + this.onCredentialStored = onCredentialStored + this.onIdentifierCreated = onIdentifierCreated + } + + public async onEvent(event: any, context: RequiredContext): Promise { + switch (event.type) { + case OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED: + this.onContactIdentityCreated?.(event.data) + break + case OID4VCIHolderEvent.CREDENTIAL_STORED: + this.onCredentialStored?.(event.data) + break + case OID4VCIHolderEvent.IDENTIFIER_CREATED: + this.onIdentifierCreated?.(event.data) + break + default: + return Promise.reject(Error(`Event type ${event.type} not supported`)) + } + } - /** - * FIXME: This method can only be used locally. Creating the interpreter should be local to where the agent is running - */ + /** + * FIXME: This method can only be used locally. Creating the interpreter should be local to where the agent is running + */ private async oid4vciHolderGetMachineInterpreter(opts: OID4VCIMachineInstanceOpts, context: RequiredContext): Promise { const authorizationRequestOpts = { ...this.defaultAuthorizationRequestOpts, ...opts.authorizationRequestOpts } - const services = { - start: (args: PrepareStartArgs) => - this.oid4vciHolderStart( - { - ...args, - authorizationRequestOpts, - }, - context, - ), - createCredentialsToSelectFrom: (args: createCredentialsToSelectFromArgs) => this.oid4vciHoldercreateCredentialsToSelectFrom(args, context), - getContact: (args: GetContactArgs) => this.oid4vciHolderGetContact(args, context), - getCredentials: (args: GetCredentialsArgs) => this.oid4vciHolderGetCredentials(args, context), - addContactIdentity: (args: AddContactIdentityArgs) => this.oid4vciHolderAddContactIdentity(args, context), - assertValidCredentials: (args: AssertValidCredentialsArgs) => this.oid4vciHolderAssertValidCredentials(args, context), - storeCredentialBranding: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context), - storeCredentials: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context), - sendNotification: (args: SendNotificationArgs) => this.oid4vciHolderSendNotification(args, context), - } + const services = { + start: (args: PrepareStartArgs) => + this.oid4vciHolderStart( + { + ...args, + authorizationRequestOpts, + }, + context, + ), + createCredentialsToSelectFrom: (args: createCredentialsToSelectFromArgs) => this.oid4vciHoldercreateCredentialsToSelectFrom(args, context), + getContact: (args: GetContactArgs) => this.oid4vciHolderGetContact(args, context), + getCredentials: (args: GetCredentialsArgs) => this.oid4vciHolderGetCredentials(args, context), + addContactIdentity: (args: AddContactIdentityArgs) => this.oid4vciHolderAddContactIdentity(args, context), + assertValidCredentials: (args: AssertValidCredentialsArgs) => this.oid4vciHolderAssertValidCredentials(args, context), + storeCredentialBranding: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context), + storeCredentials: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context), + sendNotification: (args: SendNotificationArgs) => this.oid4vciHolderSendNotification(args, context), + } - const oid4vciMachineInstanceArgs: OID4VCIMachineInstanceOpts = { + const oid4vciMachineInstanceArgs: OID4VCIMachineInstanceOpts = { ...opts, - authorizationRequestOpts, - services: { - ...services, + authorizationRequestOpts, + services: { + ...services, ...opts.services, - }, - } - - const {interpreter} = await OID4VCIMachine.newInstance(oid4vciMachineInstanceArgs, context) - - return { - interpreter, - } + }, } - /** - * This method is run before the machine starts! So there is no concept of the state machine context or states yet - * - * The result of this method can be directly passed into the start method of the state machine - * @param args - * @param context - * @private - */ - private async oid4vciHolderStart(args: PrepareStartArgs, context: RequiredContext): Promise { - const {requestData} = args - if (!requestData) { - throw Error(`Cannot start the OID4VCI holder flow without request data being provided`) - } - const {uri = undefined} = requestData - if (!uri) { - return Promise.reject(Error('Missing request URI in context')) - } + const { interpreter } = await OID4VCIMachine.newInstance(oid4vciMachineInstanceArgs, context) - const authorizationRequestOpts = {...this.defaultAuthorizationRequestOpts, ...args.authorizationRequestOpts} satisfies AuthorizationRequestOpts - // We filter the details first against our vcformat prefs - authorizationRequestOpts.authorizationDetails = authorizationRequestOpts?.authorizationDetails - ? asArray(authorizationRequestOpts.authorizationDetails).filter( - (detail) => typeof detail === 'string' || this.vcFormatPreferences.includes(detail.format), - ) - : undefined - - if (!authorizationRequestOpts.redirectUri) { - authorizationRequestOpts.redirectUri = OID4VCIHolder.DEFAULT_MOBILE_REDIRECT_URI - } - if (authorizationRequestOpts.redirectUri.startsWith('http') && !authorizationRequestOpts.clientId) { - // At least set a default for a web based wallet. - // TODO: We really need (dynamic) client registration support - authorizationRequestOpts.clientId = authorizationRequestOpts.redirectUri - } + return { + interpreter, + } + } + + /** + * This method is run before the machine starts! So there is no concept of the state machine context or states yet + * + * The result of this method can be directly passed into the start method of the state machine + * @param args + * @param context + * @private + */ + private async oid4vciHolderStart(args: PrepareStartArgs, context: RequiredContext): Promise { + const { requestData } = args + if (!requestData) { + throw Error(`Cannot start the OID4VCI holder flow without request data being provided`) + } + const { uri = undefined } = requestData + if (!uri) { + return Promise.reject(Error('Missing request URI in context')) + } - let oid4vciClient: OpenID4VCIClient - let types: string[][] | undefined = undefined - let offer: CredentialOfferRequestWithBaseUrl | undefined - if (requestData.existingClientState) { - oid4vciClient = await OpenID4VCIClient.fromState({state: requestData.existingClientState}) - offer = oid4vciClient.credentialOffer - } else { - offer = requestData.credentialOffer - if ( - uri.startsWith(RequestType.OPENID_INITIATE_ISSUANCE) || - uri.startsWith(RequestType.OPENID_CREDENTIAL_OFFER) || - uri.match(/https?:\/\/.*credential_offer(_uri)=?.*/) - ) { - if (!offer) { - // Let's make sure to convert the URI to offer, as it matches the regexes. Normally this should already have happened at this point though - offer = await CredentialOfferClient.fromURI(uri) - } - } else { - if (!!offer) { - logger.warning(`Non default URI used for credential offer: ${uri}`) - } - } - - if (!offer) { - // else no offer, meaning we have an issuer URL - logger.log(`Issuer url received (no credential offer): ${uri}`) - oid4vciClient = await OpenID4VCIClient.fromCredentialIssuer({ - credentialIssuer: uri, - authorizationRequest: authorizationRequestOpts, - clientId: authorizationRequestOpts.clientId, - createAuthorizationRequestURL: requestData.createAuthorizationRequestURL ?? true, - }) - } else { - logger.log(`Credential offer received: ${uri}`) - oid4vciClient = await OpenID4VCIClient.fromURI({ - uri, - authorizationRequest: authorizationRequestOpts, - clientId: authorizationRequestOpts.clientId, - createAuthorizationRequestURL: requestData.createAuthorizationRequestURL ?? true, - }) - } - } + const authorizationRequestOpts = { ...this.defaultAuthorizationRequestOpts, ...args.authorizationRequestOpts } satisfies AuthorizationRequestOpts + // We filter the details first against our vcformat prefs + authorizationRequestOpts.authorizationDetails = authorizationRequestOpts?.authorizationDetails + ? asArray(authorizationRequestOpts.authorizationDetails).filter( + (detail) => typeof detail === 'string' || this.vcFormatPreferences.includes(detail.format), + ) + : undefined - if (offer) { - types = getTypesFromCredentialOffer(offer.original_credential_offer) - } else { - types = asArray(authorizationRequestOpts.authorizationDetails) - .map((authReqOpts) => getTypesFromAuthorizationDetails(authReqOpts) ?? []) - .filter((inner) => inner.length > 0) - } + if (!authorizationRequestOpts.redirectUri) { + authorizationRequestOpts.redirectUri = OID4VCIHolder.DEFAULT_MOBILE_REDIRECT_URI + } + if (authorizationRequestOpts.redirectUri.startsWith('http') && !authorizationRequestOpts.clientId) { + // At least set a default for a web based wallet. + // TODO: We really need (dynamic) client registration support + authorizationRequestOpts.clientId = authorizationRequestOpts.redirectUri + } - const serverMetadata = await oid4vciClient.retrieveServerMetadata() - const credentialsSupported = await getCredentialConfigsSupportedMerged({ - client: oid4vciClient, - vcFormatPreferences: this.vcFormatPreferences, - types, + let oid4vciClient: OpenID4VCIClient + let types: string[][] | undefined = undefined + let offer: CredentialOfferRequestWithBaseUrl | undefined + if (requestData.existingClientState) { + oid4vciClient = await OpenID4VCIClient.fromState({ state: requestData.existingClientState }) + offer = oid4vciClient.credentialOffer + } else { + offer = requestData.credentialOffer + if ( + uri.startsWith(RequestType.OPENID_INITIATE_ISSUANCE) || + uri.startsWith(RequestType.OPENID_CREDENTIAL_OFFER) || + uri.match(/https?:\/\/.*credential_offer(_uri)=?.*/) + ) { + if (!offer) { + // Let's make sure to convert the URI to offer, as it matches the regexes. Normally this should already have happened at this point though + offer = await CredentialOfferClient.fromURI(uri) + } + } else { + if (!!offer) { + logger.warning(`Non default URI used for credential offer: ${uri}`) + } + } + + if (!offer) { + // else no offer, meaning we have an issuer URL + logger.log(`Issuer url received (no credential offer): ${uri}`) + oid4vciClient = await OpenID4VCIClient.fromCredentialIssuer({ + credentialIssuer: uri, + authorizationRequest: authorizationRequestOpts, + clientId: authorizationRequestOpts.clientId, + createAuthorizationRequestURL: requestData.createAuthorizationRequestURL ?? true, }) - const credentialBranding = await getCredentialBranding({credentialsSupported, context}) - const authorizationCodeURL = oid4vciClient.authorizationURL - if (authorizationCodeURL) { - logger.log(`authorization code URL ${authorizationCodeURL}`) - } - const oid4vciClientState = JSON.parse(await oid4vciClient.exportState()) + } else { + logger.log(`Credential offer received: ${uri}`) + oid4vciClient = await OpenID4VCIClient.fromURI({ + uri, + authorizationRequest: authorizationRequestOpts, + clientId: authorizationRequestOpts.clientId, + createAuthorizationRequestURL: requestData.createAuthorizationRequestURL ?? true, + }) + } + } - return { - authorizationCodeURL, - credentialBranding, - credentialsSupported, - serverMetadata, - oid4vciClientState, - } + if (offer) { + types = getTypesFromCredentialOffer(offer.original_credential_offer) + } else { + types = asArray(authorizationRequestOpts.authorizationDetails) + .map((authReqOpts) => getTypesFromAuthorizationDetails(authReqOpts) ?? []) + .filter((inner) => inner.length > 0) } - private async oid4vciHoldercreateCredentialsToSelectFrom( - args: createCredentialsToSelectFromArgs, - context: RequiredContext, - ): Promise> { - const {credentialBranding, locale, selectedCredentials /*, openID4VCIClientState*/, credentialsSupported} = args + const serverMetadata = await oid4vciClient.retrieveServerMetadata() + const credentialsSupported = await getCredentialConfigsSupportedMerged({ + client: oid4vciClient, + vcFormatPreferences: this.vcFormatPreferences, + types, + }) + const credentialBranding = await getCredentialBranding({ credentialsSupported, context }) + const authorizationCodeURL = oid4vciClient.authorizationURL + if (authorizationCodeURL) { + logger.log(`authorization code URL ${authorizationCodeURL}`) + } + const oid4vciClientState = JSON.parse(await oid4vciClient.exportState()) + + return { + authorizationCodeURL, + credentialBranding, + credentialsSupported, + serverMetadata, + oid4vciClientState, + } + } - // const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState! }) // TODO see if we need the check openID4VCIClientState defined - /*const credentialsSupported = await getCredentialConfigsSupportedBySingleTypeOrId({ + private async oid4vciHoldercreateCredentialsToSelectFrom( + args: createCredentialsToSelectFromArgs, + context: RequiredContext, + ): Promise> { + const { credentialBranding, locale, selectedCredentials /*, openID4VCIClientState*/, credentialsSupported } = args + + // const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState! }) // TODO see if we need the check openID4VCIClientState defined + /*const credentialsSupported = await getCredentialConfigsSupportedBySingleTypeOrId({ client, vcFormatPreferences: this.vcFormatPreferences, })*/ - logger.info(`Credentials supported ${Object.keys(credentialsSupported).join(', ')}`) - - const credentialSelection: Array = await Promise.all( - Object.entries(credentialsSupported).map( - async ([id, credentialConfigSupported]): Promise => { - if (credentialConfigSupported.format === 'vc+sd-jwt') { - return Promise.reject(Error('SD-JWT not supported yet')) - } - - // FIXME this allows for duplicate VerifiableCredential, which the user has no idea which ones those are and we also have a branding map with unique keys, so some branding will not match - // const defaultCredentialType = 'VerifiableCredential' - - const credentialTypes = getTypesFromObject(credentialConfigSupported) - // const credentialType = id /*?? credentialTypes?.find((type) => type !== defaultCredentialType) ?? defaultCredentialType*/ - const localeBranding = !credentialBranding - ? undefined - : credentialBranding?.[id] ?? - Object.entries(credentialBranding) - .find(([type, _brandings]) => { - credentialTypes && type in credentialTypes - }) - ?.map(([type, supported]) => supported) - const credentialAlias = ( - await selectCredentialLocaleBranding({ - locale, - localeBranding, - }) - )?.alias - - return { - id: uuidv4(), - credentialId: id, - credentialTypes: credentialTypes ?? asArray(id), - credentialAlias: credentialAlias ?? id, - isSelected: false, - } - }, - ), - ) + logger.info(`Credentials supported ${Object.keys(credentialsSupported).join(', ')}`) + + const credentialSelection: Array = await Promise.all( + Object.entries(credentialsSupported).map(async ([id, credentialConfigSupported]): Promise => { + if (credentialConfigSupported.format === 'vc+sd-jwt') { + return Promise.reject(Error('SD-JWT not supported yet')) + } + + // FIXME this allows for duplicate VerifiableCredential, which the user has no idea which ones those are and we also have a branding map with unique keys, so some branding will not match + // const defaultCredentialType = 'VerifiableCredential' + + const credentialTypes = getTypesFromObject(credentialConfigSupported) + // const credentialType = id /*?? credentialTypes?.find((type) => type !== defaultCredentialType) ?? defaultCredentialType*/ + const localeBranding = !credentialBranding + ? undefined + : credentialBranding?.[id] ?? + Object.entries(credentialBranding) + .find(([type, _brandings]) => { + credentialTypes && type in credentialTypes + }) + ?.map(([type, supported]) => supported) + const credentialAlias = ( + await selectCredentialLocaleBranding({ + locale, + localeBranding, + }) + )?.alias - // TODO find better place to do this, would be nice if the machine does this? - if (credentialSelection.length >= 1) { - credentialSelection.map(sel => selectedCredentials.push(sel.credentialId)) - } - logger.log(`Credential selection ${JSON.stringify(credentialSelection)}`) - - return credentialSelection + return { + id: uuidv4(), + credentialId: id, + credentialTypes: credentialTypes ?? asArray(id), + credentialAlias: credentialAlias ?? id, + isSelected: false, + } + }), + ) + + // TODO find better place to do this, would be nice if the machine does this? + if (credentialSelection.length >= 1) { + credentialSelection.map((sel) => selectedCredentials.push(sel.credentialId)) } + logger.log(`Credential selection ${JSON.stringify(credentialSelection)}`) - private async oid4vciHolderGetContact(args: GetContactArgs, context: RequiredContext): Promise { - const {serverMetadata} = args + return credentialSelection + } - if (serverMetadata === undefined) { - return Promise.reject(Error('Missing serverMetadata in context')) - } + private async oid4vciHolderGetContact(args: GetContactArgs, context: RequiredContext): Promise { + const { serverMetadata } = args - const correlationId: string = new URL(serverMetadata.issuer).hostname - const party = context.agent - .cmGetContacts({ - filter: [ - { - identities: { - identifier: { - correlationId, - }, - }, - }, - ], - }) - .then((contacts: Array): Party | undefined => (contacts.length === 1 ? contacts[0] : undefined)) - logger.log(`Party involved: `, party) - return party - } - - private async oid4vciHolderGetCredentials(args: GetCredentialsArgs, context: RequiredContext): Promise> { - const {verificationCode, openID4VCIClientState, didMethodPreferences = this.didMethodPreferences, issuanceOpt} = args - - if (!openID4VCIClientState) { - return Promise.reject(Error('Missing openID4VCI client state in context')) - } + if (serverMetadata === undefined) { + return Promise.reject(Error('Missing serverMetadata in context')) + } - const client = await OpenID4VCIClient.fromState({state: openID4VCIClientState}) - const credentialsSupported = await getCredentialConfigsSupportedMerged({ - client, - vcFormatPreferences: this.vcFormatPreferences, - configurationIds: args.selectedCredentials - }) - const serverMetadata = await client.retrieveServerMetadata() - const issuanceOpts = await getIssuanceOpts({ + const correlationId: string = new URL(serverMetadata.issuer).hostname + const party = context.agent + .cmGetContacts({ + filter: [ + { + identities: { + identifier: { + correlationId, + }, + }, + }, + ], + }) + .then((contacts: Array): Party | undefined => (contacts.length === 1 ? contacts[0] : undefined)) + logger.log(`Party involved: `, party) + return party + } + + private async oid4vciHolderGetCredentials(args: GetCredentialsArgs, context: RequiredContext): Promise> { + const { verificationCode, openID4VCIClientState, didMethodPreferences = this.didMethodPreferences, issuanceOpt, accessTokenOpts } = args + + if (!openID4VCIClientState) { + return Promise.reject(Error('Missing openID4VCI client state in context')) + } + + const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState }) + const credentialsSupported = await getCredentialConfigsSupportedMerged({ + client, + vcFormatPreferences: this.vcFormatPreferences, + configurationIds: args.selectedCredentials, + }) + const serverMetadata = await client.retrieveServerMetadata() + const issuanceOpts = await getIssuanceOpts({ + client, + credentialsSupported, + serverMetadata, + context, + didMethodPreferences: Array.isArray(didMethodPreferences) && didMethodPreferences.length > 0 ? didMethodPreferences : this.didMethodPreferences, + jwtCryptographicSuitePreferences: this.jwtCryptographicSuitePreferences, + jsonldCryptographicSuitePreferences: this.jsonldCryptographicSuitePreferences, + ...(issuanceOpt && { forceIssuanceOpt: issuanceOpt }), + }) + + const getCredentials = issuanceOpts.map( + async (issuanceOpt: IssuanceOpts): Promise => + await this.oid4vciHolderGetCredential( + { + issuanceOpt, + pin: verificationCode, client, - credentialsSupported, - serverMetadata, - context, - didMethodPreferences: (Array.isArray(didMethodPreferences) && didMethodPreferences.length > 0) ? didMethodPreferences : this.didMethodPreferences, - jwtCryptographicSuitePreferences: this.jwtCryptographicSuitePreferences, - jsonldCryptographicSuitePreferences: this.jsonldCryptographicSuitePreferences, - ...(issuanceOpt && {forceIssuanceOpt: issuanceOpt}) - }) + accessTokenOpts + }, + context, + ), + ) - const getCredentials = issuanceOpts.map( - async (issuanceOpt: IssuanceOpts): Promise => - await this.oid4vciHolderGetCredential( - { - issuanceOpt, - pin: verificationCode, - client, - }, - context, - ), - ) + const allCredentials = await Promise.all(getCredentials) + logger.log(`Credentials received`, allCredentials) + + return allCredentials + } - const allCredentials = await Promise.all(getCredentials) - logger.log(`Credentials received`, allCredentials) + private async oid4vciHolderGetCredential(args: GetCredentialArgs, context: RequiredContext): Promise { + const { issuanceOpt, pin, client, accessTokenOpts } = args - return allCredentials + if (!issuanceOpt) { + return Promise.reject(Error(`Cannot get credential issuance options`)) } + const idOpts = await getIdentifierOpts({ issuanceOpt, context }) + const { key, kid } = idOpts + const alg: SignatureAlgorithmEnum = await signatureAlgorithmFromKey({ key }) - private async oid4vciHolderGetCredential(args: GetCredentialArgs, context: RequiredContext): Promise { - const {issuanceOpt, pin, client} = args + const callbacks: ProofOfPossessionCallbacks = { + signCallback: await signCallback(client, idOpts, context), + } - if (!issuanceOpt) { - return Promise.reject(Error(`Cannot get credential issuance options`)) - } - const idOpts = await getIdentifier({issuanceOpt, context}) - const {key, kid} = idOpts - const alg: SignatureAlgorithmEnum = await signatureAlgorithmFromKey({key}) + try { + // We need to make sure we have acquired the access token + if (!client.clientId) { + client.clientId = issuanceOpt.identifier.did + } + await client.acquireAccessToken({ + clientId: client.clientId, + pin, + authorizationResponse: JSON.parse(await client.exportState()).authorizationCodeResponse, + additionalRequestParams: accessTokenOpts?.additionalRequestParams + }) + + // FIXME: This type mapping is wrong. It should use credential_identifier in case the access token response has authorization details + const types = getTypesFromObject(issuanceOpt) + const credentialTypes = issuanceOpt.credentialConfigurationId ?? issuanceOpt.id ?? types + if (!credentialTypes || (Array.isArray(credentialTypes) && credentialTypes.length === 0)) { + return Promise.reject(Error('cannot determine credential id to request')) + } + const credentialResponse = await client.acquireCredentials({ + credentialTypes, + proofCallbacks: callbacks, + format: issuanceOpt.format, + // TODO: We need to update the machine and add notifications support for actual deferred credentials instead of just waiting/retrying + deferredCredentialAwait: true, + kid, + alg, + jti: uuidv4(), + }) + + const credential = { + id: issuanceOpt.credentialConfigurationId ?? issuanceOpt.id, + types: types ?? asArray(credentialTypes), + issuanceOpt, + credentialResponse, + } satisfies CredentialToAccept + return mapCredentialToAccept({ credential }) + } catch (error) { + return Promise.reject(error) + } + } - const callbacks: ProofOfPossessionCallbacks = { - signCallback: signCallback(client, idOpts, context), - } + private async oid4vciHolderAddContactIdentity(args: AddContactIdentityArgs, context: RequiredContext): Promise { + const { credentialsToAccept, contact } = args - try { - // We need to make sure we have acquired the access token - if (!client.clientId) { - client.clientId = issuanceOpt.identifier.did - } - await client.acquireAccessToken({ - clientId: client.clientId, - pin, - authorizationResponse: JSON.parse(await client.exportState()).authorizationCodeResponse, - }) - - // FIXME: This type mapping is wrong. It should use credential_identifier in case the access token response has authorization details - const types = getTypesFromObject(issuanceOpt) - const credentialTypes = issuanceOpt.credentialConfigurationId ?? issuanceOpt.id ?? types - if (!credentialTypes || (Array.isArray(credentialTypes) && credentialTypes.length === 0)) { - return Promise.reject(Error('cannot determine credential id to request')) - } - const credentialResponse = await client.acquireCredentials({ - credentialTypes, - proofCallbacks: callbacks, - format: issuanceOpt.format, - // TODO: We need to update the machine and add notifications support for actual deferred credentials instead of just waiting/retrying - deferredCredentialAwait: true, - kid, - alg, - jti: uuidv4(), - }) - - const credential = { - id: issuanceOpt.credentialConfigurationId ?? issuanceOpt.id, - types: types ?? asArray(credentialTypes), - issuanceOpt, - credentialResponse, - } satisfies CredentialToAccept - return mapCredentialToAccept({credential}) - } catch (error) { - return Promise.reject(error) - } + if (!contact) { + return Promise.reject(Error('Missing contact in context')) } - private async oid4vciHolderAddContactIdentity(args: AddContactIdentityArgs, context: RequiredContext): Promise { - const {credentialsToAccept, contact} = args + if (credentialsToAccept === undefined || credentialsToAccept.length === 0) { + return Promise.reject(Error('Missing credential offers in context')) + } - if (!contact) { - return Promise.reject(Error('Missing contact in context')) - } + const correlationId: string = credentialsToAccept[0].correlationId + const identity: NonPersistedIdentity = { + alias: correlationId, + origin: IdentityOrigin.EXTERNAL, + roles: [CredentialRole.ISSUER], + identifier: { + type: CorrelationIdentifierType.DID, + correlationId, + }, + } - if (credentialsToAccept === undefined || credentialsToAccept.length === 0) { - return Promise.reject(Error('Missing credential offers in context')) - } + await context.agent.emit(OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED, { + contactId: contact.id, + identity, + }) + logger.log(`Contact added ${contact.id}`) - const correlationId: string = credentialsToAccept[0].correlationId - const identity: NonPersistedIdentity = { - alias: correlationId, - origin: IdentityOrigin.EXTERNAL, - roles: [CredentialRole.ISSUER], - identifier: { - type: CorrelationIdentifierType.DID, - correlationId, - }, - } + return context.agent.cmAddIdentity({ contactId: contact.id, identity }) + } - await context.agent.emit(OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED, { - contactId: contact.id, - identity, - }) - logger.log(`Contact added ${contact.id}`) + private async oid4vciHolderAssertValidCredentials(args: AssertValidCredentialsArgs, context: RequiredContext): Promise { + const { credentialsToAccept } = args - return context.agent.cmAddIdentity({contactId: contact.id, identity}) + await Promise.all( + credentialsToAccept.map( + async (mappedCredential: MappedCredentialToAccept): Promise => + verifyCredentialToAccept({ + mappedCredential, + context, + }), + ), + ) + } + + private async oid4vciHolderStoreCredentialBranding(args: StoreCredentialBrandingArgs, context: RequiredContext): Promise { + const { credentialBranding, serverMetadata, selectedCredentials, credentialsToAccept } = args + + if (serverMetadata === undefined) { + return Promise.reject(Error('Missing serverMetadata in context')) + } else if (selectedCredentials.length === 0) { + logger.warning(`No credentials selected for issuer: ${serverMetadata.issuer}`) + return } - private async oid4vciHolderAssertValidCredentials(args: AssertValidCredentialsArgs, context: RequiredContext): Promise { - const {credentialsToAccept} = args - - await Promise.all( - credentialsToAccept.map( - async (mappedCredential: MappedCredentialToAccept): Promise => - verifyCredentialToAccept({ - mappedCredential, - context, - }), - ), + let counter = 0 + for (const credentialId of selectedCredentials) { + const localeBranding: Array | undefined = credentialBranding?.[credentialId] + if (localeBranding && localeBranding.length > 0) { + const credential = credentialsToAccept.find( + (credAccept) => + credAccept.credential.id === credentialId ?? JSON.stringify(credAccept.types) === credentialId ?? credentialsToAccept[counter], + )! + counter++ + await context.agent.ibAddCredentialBranding({ + vcHash: computeEntryHash(credential.rawVerifiableCredential), + issuerCorrelationId: new URL(serverMetadata.issuer).hostname, + localeBranding, + }) + logger.log( + `Credential branding for issuer ${serverMetadata.issuer} and type ${credentialId} stored with locales ${localeBranding.map((b) => b.locale).join(',')}`, ) + } else { + logger.warning(`No credential branding found for issuer: ${serverMetadata.issuer} and type ${credentialId}`) + } } - - private async oid4vciHolderStoreCredentialBranding(args: StoreCredentialBrandingArgs, context: RequiredContext): Promise { - const {credentialBranding, serverMetadata, selectedCredentials, credentialsToAccept} = args - - if (serverMetadata === undefined) { - return Promise.reject(Error('Missing serverMetadata in context')) - } else if (selectedCredentials.length === 0) { - logger.warning(`No credentials selected for issuer: ${serverMetadata.issuer}`) - return - } - - - let counter = 0; - for (const credentialId of selectedCredentials) { - const localeBranding: Array | undefined = credentialBranding?.[credentialId] - if (localeBranding && localeBranding.length > 0) { - const credential = credentialsToAccept.find(credAccept => credAccept.credential.id === credentialId ?? JSON.stringify(credAccept.types) === credentialId ?? credentialsToAccept[counter])! - counter++ - await context.agent.ibAddCredentialBranding({ - vcHash: computeEntryHash(credential.rawVerifiableCredential), - issuerCorrelationId: new URL(serverMetadata.issuer).hostname, - localeBranding, - }) - logger.log(`Credential branding for issuer ${serverMetadata.issuer} and type ${credentialId} stored with locales ${localeBranding.map((b) => b.locale).join(',')}`) - } else { - logger.warning(`No credential branding found for issuer: ${serverMetadata.issuer} and type ${credentialId}`) - } - } + } + + private async oid4vciHolderStoreCredentials(args: StoreCredentialsArgs, context: RequiredContext): Promise { + function trimmed(input?: string) { + const trim = input?.trim() + if (trim === '') { + return undefined + } + return trim } - private async oid4vciHolderStoreCredentials(args: StoreCredentialsArgs, context: RequiredContext): Promise { - function trimmed(input?: string) { - const trim = input?.trim() - if (trim === '') { - return undefined - } - return trim - } - - const { - credentialsToAccept, - openID4VCIClientState, - credentialsSupported, - serverMetadata, - selectedCredentials - } = args + const { credentialsToAccept, openID4VCIClientState, credentialsSupported, serverMetadata, selectedCredentials } = args - const credentialToAccept = credentialsToAccept[0] - - if (selectedCredentials && selectedCredentials.length > 1) { - logger.error(`More than 1 credential selected ${selectedCredentials.join(', ')}, but current service only stores 1 credential!`) - } + const credentialToAccept = credentialsToAccept[0] - let persist = true - const verifiableCredential = credentialToAccept.uniformVerifiableCredential as VerifiableCredential - - const notificationId = credentialToAccept.credential.credentialResponse.notification_id - const subjectIssuance = credentialToAccept.credential_subject_issuance - const notificationEndpoint = serverMetadata?.credentialIssuerMetadata?.notification_endpoint - let holderCredential: - | IVerifiableCredential - | JwtDecodedVerifiableCredential - | SdJwtDecodedVerifiableCredentialPayload - | W3CVerifiableCredential - | undefined = undefined - if (!notificationEndpoint) { - logger.log(`Notifications not supported by issuer ${serverMetadata?.issuer}. Will not provide a notification`) - } else if (notificationEndpoint && !notificationId) { - logger.warning( - `Notification endpoint available in issuer metadata with value ${notificationEndpoint}, but no ${notificationId} provided. Will not send a notification to issuer ${serverMetadata?.issuer}`, - ) - } else if (notificationEndpoint && notificationId) { - logger.log(`Notification id ${notificationId} found, will send back a notification to ${notificationEndpoint}`) - let event = 'credential_accepted' - if (Array.isArray(subjectIssuance?.notification_events_supported)) { - event = subjectIssuance.notification_events_supported.includes('credential_accepted_holder_signed') - ? 'credential_accepted_holder_signed' - : 'credential_deleted_holder_signed' - logger.log(`Subject issuance/signing will be used, with event`, event) - const issuerVC = credentialToAccept.credential.credentialResponse.credential as OriginalVerifiableCredential - const wrappedIssuerVC = CredentialMapper.toWrappedVerifiableCredential(issuerVC) - console.log(`Wrapped VC: ${wrappedIssuerVC.type}, ${wrappedIssuerVC.format}`) - // We will use the subject of the VCI Issuer (the holder, as the issuer of the new credential, so the below is not a mistake!) - let issuer = - trimmed(wrappedIssuerVC.decoded.sub) ?? - trimmed(wrappedIssuerVC.decoded.credentialSubject.id) ?? - trimmed(verifiableCredential.credentialSubject.id) - - if (!issuer && openID4VCIClientState?.kid?.startsWith('did:')) { - issuer = parseDid(openID4VCIClientState?.kid).did - } - if (!issuer && openID4VCIClientState?.jwk?.kid?.startsWith('did:')) { - issuer = parseDid(openID4VCIClientState!.jwk!.kid!).did - } - if (!issuer && openID4VCIClientState?.clientId) { - issuer = trimmed(openID4VCIClientState.clientId) - } - if (!issuer && openID4VCIClientState?.accessTokenResponse) { - const decodedJwt = decodeJWT(openID4VCIClientState.accessTokenResponse.access_token) - issuer = decodedJwt.payload.sub - } - if (!issuer && credentialToAccept.credential.issuanceOpt.identifier) { - issuer = credentialToAccept.credential.issuanceOpt.identifier.did - } - - if (!issuer) { - throw Error(`We could not determine the issuer, which means we cannot sign the credential`) - } - logger.log(`Issuer for self-issued credential will be: ${issuer}`) - - const holderCredentialToSign = wrappedIssuerVC.decoded - let proofFormat: ProofFormat = 'lds' - if (wrappedIssuerVC.format.includes('jwt')) { - holderCredentialToSign.iss = issuer - proofFormat = 'jwt' - } - if ('issuer' in holderCredentialToSign || !('iss' in holderCredentialToSign)) { - holderCredentialToSign.issuer = issuer - } - if ('sub' in holderCredentialToSign) { - holderCredentialToSign.sub = issuer - } - if ('credentialSubject' in holderCredentialToSign && !Array.isArray(holderCredentialToSign.credentialSubject)) { - holderCredentialToSign.credentialSubject.id = issuer - } - if ('vc' in holderCredentialToSign) { - if (holderCredentialToSign.vc.credentialSubject) { - holderCredentialToSign.vc.credentialSubject.id = issuer - } - holderCredentialToSign.vc.issuer = issuer - delete holderCredentialToSign.vc.proof - delete holderCredentialToSign.vc.issuanceDate - } - delete holderCredentialToSign.proof - delete holderCredentialToSign.issuanceDate - delete holderCredentialToSign.iat - - logger.log(`Subject issuance/signing will sign credential of type ${proofFormat}:`, holderCredentialToSign) - const issuedVC = await context.agent.createVerifiableCredential({ - credential: holderCredentialToSign as CredentialPayload, - fetchRemoteContexts: true, - save: false, - proofFormat, - }) - if (!issuedVC) { - throw Error(`Could not issue holder credential from the wallet`) - } - logger.log(`Holder ${issuedVC.issuer} issued new credential with id ${issuedVC.id}`, issuedVC) - holderCredential = CredentialMapper.storedCredentialToOriginalFormat(issuedVC as IVerifiableCredential) - persist = event === 'credential_accepted_holder_signed' - } - - const notificationRequest: NotificationRequest = { - notification_id: notificationId, - ...(holderCredential && {credential: holderCredential}), - event, - } - - await this.oid4vciHolderSendNotification( - { - openID4VCIClientState, - stored: persist, - credentialsToAccept, - credentialsSupported, - notificationRequest, - serverMetadata, - }, - context, - ) - } - const persistCredential = holderCredential ? CredentialMapper.storedCredentialToOriginalFormat(holderCredential) : verifiableCredential - if (!persist && holderCredential) { - logger.log(`Will not persist credential, since we are signing as a holder and the issuer asked not to persist`) - } else { - logger.log(`Persisting credential`, persistCredential) - // @ts-ignore - const vcHash = await context.agent.dataStoreSaveVerifiableCredential({verifiableCredential: persistCredential}) - await context.agent.emit(OID4VCIHolderEvent.CREDENTIAL_STORED, { - vcHash, - credential: persistCredential, - }) - } + if (selectedCredentials && selectedCredentials.length > 1) { + logger.error(`More than 1 credential selected ${selectedCredentials.join(', ')}, but current service only stores 1 credential!`) } - private async oid4vciHolderSendNotification(args: SendNotificationArgs, context: RequiredContext): Promise { - const {serverMetadata, notificationRequest, openID4VCIClientState} = args - const notificationEndpoint = serverMetadata?.credentialIssuerMetadata?.notification_endpoint - if (!notificationEndpoint) { - return - } else if (!openID4VCIClientState) { - return Promise.reject(Error('Missing openID4VCI client state in context')) - } else if (!notificationRequest) { - return Promise.reject(Error('Missing notification request')) - } + let persist = true + const verifiableCredential = credentialToAccept.uniformVerifiableCredential as VerifiableCredential + + const notificationId = credentialToAccept.credential.credentialResponse.notification_id + const subjectIssuance = credentialToAccept.credential_subject_issuance + const notificationEndpoint = serverMetadata?.credentialIssuerMetadata?.notification_endpoint + let holderCredential: + | IVerifiableCredential + | JwtDecodedVerifiableCredential + | SdJwtDecodedVerifiableCredentialPayload + | W3CVerifiableCredential + | undefined = undefined + if (!notificationEndpoint) { + logger.log(`Notifications not supported by issuer ${serverMetadata?.issuer}. Will not provide a notification`) + } else if (notificationEndpoint && !notificationId) { + logger.warning( + `Notification endpoint available in issuer metadata with value ${notificationEndpoint}, but no ${notificationId} provided. Will not send a notification to issuer ${serverMetadata?.issuer}`, + ) + } else if (notificationEndpoint && notificationId) { + logger.log(`Notification id ${notificationId} found, will send back a notification to ${notificationEndpoint}`) + let event = 'credential_accepted' + if (Array.isArray(subjectIssuance?.notification_events_supported)) { + event = subjectIssuance.notification_events_supported.includes('credential_accepted_holder_signed') + ? 'credential_accepted_holder_signed' + : 'credential_deleted_holder_signed' + logger.log(`Subject issuance/signing will be used, with event`, event) + const issuerVC = credentialToAccept.credential.credentialResponse.credential as OriginalVerifiableCredential + const wrappedIssuerVC = CredentialMapper.toWrappedVerifiableCredential(issuerVC) + console.log(`Wrapped VC: ${wrappedIssuerVC.type}, ${wrappedIssuerVC.format}`) + // We will use the subject of the VCI Issuer (the holder, as the issuer of the new credential, so the below is not a mistake!) + let issuer = + trimmed(wrappedIssuerVC.decoded.sub) ?? + trimmed(wrappedIssuerVC.decoded.credentialSubject.id) ?? + trimmed(verifiableCredential.credentialSubject.id) + + if (!issuer && openID4VCIClientState?.kid?.startsWith('did:')) { + issuer = parseDid(openID4VCIClientState?.kid).did + } + if (!issuer && openID4VCIClientState?.jwk?.kid?.startsWith('did:')) { + issuer = parseDid(openID4VCIClientState!.jwk!.kid!).did + } + if (!issuer && openID4VCIClientState?.clientId) { + issuer = trimmed(openID4VCIClientState.clientId) + } + if (!issuer && openID4VCIClientState?.accessTokenResponse) { + const decodedJwt = decodeJWT(openID4VCIClientState.accessTokenResponse.access_token) + issuer = decodedJwt.payload.sub + } + if (!issuer && credentialToAccept.credential.issuanceOpt.identifier) { + issuer = credentialToAccept.credential.issuanceOpt.identifier.did + } + + if (!issuer) { + throw Error(`We could not determine the issuer, which means we cannot sign the credential`) + } + logger.log(`Issuer for self-issued credential will be: ${issuer}`) + + const holderCredentialToSign = wrappedIssuerVC.decoded + let proofFormat: ProofFormat = 'lds' + if (wrappedIssuerVC.format.includes('jwt')) { + holderCredentialToSign.iss = issuer + proofFormat = 'jwt' + } + if ('issuer' in holderCredentialToSign || !('iss' in holderCredentialToSign)) { + holderCredentialToSign.issuer = issuer + } + if ('sub' in holderCredentialToSign) { + holderCredentialToSign.sub = issuer + } + if ('credentialSubject' in holderCredentialToSign && !Array.isArray(holderCredentialToSign.credentialSubject)) { + holderCredentialToSign.credentialSubject.id = issuer + } + if ('vc' in holderCredentialToSign) { + if (holderCredentialToSign.vc.credentialSubject) { + holderCredentialToSign.vc.credentialSubject.id = issuer + } + holderCredentialToSign.vc.issuer = issuer + delete holderCredentialToSign.vc.proof + delete holderCredentialToSign.vc.issuanceDate + } + delete holderCredentialToSign.proof + delete holderCredentialToSign.issuanceDate + delete holderCredentialToSign.iat + + logger.log(`Subject issuance/signing will sign credential of type ${proofFormat}:`, holderCredentialToSign) + const issuedVC = await context.agent.createVerifiableCredential({ + credential: holderCredentialToSign as CredentialPayload, + fetchRemoteContexts: true, + save: false, + proofFormat, + }) + if (!issuedVC) { + throw Error(`Could not issue holder credential from the wallet`) + } + logger.log(`Holder ${issuedVC.issuer} issued new credential with id ${issuedVC.id}`, issuedVC) + holderCredential = CredentialMapper.storedCredentialToOriginalFormat(issuedVC as IVerifiableCredential) + persist = event === 'credential_accepted_holder_signed' + } + + const notificationRequest: NotificationRequest = { + notification_id: notificationId, + ...(holderCredential && { credential: holderCredential }), + event, + } + + await this.oid4vciHolderSendNotification( + { + openID4VCIClientState, + stored: persist, + credentialsToAccept, + credentialsSupported, + notificationRequest, + serverMetadata, + }, + context, + ) + } + const persistCredential = holderCredential ? CredentialMapper.storedCredentialToOriginalFormat(holderCredential) : verifiableCredential + if (!persist && holderCredential) { + logger.log(`Will not persist credential, since we are signing as a holder and the issuer asked not to persist`) + } else { + logger.log(`Persisting credential`, persistCredential) + // @ts-ignore + const vcHash = await context.agent.dataStoreSaveVerifiableCredential({ verifiableCredential: persistCredential }) + await context.agent.emit(OID4VCIHolderEvent.CREDENTIAL_STORED, { + vcHash, + credential: persistCredential, + }) + } + } + + private async oid4vciHolderSendNotification(args: SendNotificationArgs, context: RequiredContext): Promise { + const { serverMetadata, notificationRequest, openID4VCIClientState } = args + const notificationEndpoint = serverMetadata?.credentialIssuerMetadata?.notification_endpoint + if (!notificationEndpoint) { + return + } else if (!openID4VCIClientState) { + return Promise.reject(Error('Missing openID4VCI client state in context')) + } else if (!notificationRequest) { + return Promise.reject(Error('Missing notification request')) + } - logger.log(`Will send notification to ${notificationEndpoint}`, notificationRequest) + logger.log(`Will send notification to ${notificationEndpoint}`, notificationRequest) - const client = await OpenID4VCIClient.fromState({state: openID4VCIClientState}) - await client.sendNotification({notificationEndpoint}, notificationRequest, openID4VCIClientState?.accessTokenResponse?.access_token) - logger.log(`Notification to ${notificationEndpoint} has been dispatched`) - } + const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState }) + await client.sendNotification({ notificationEndpoint }, notificationRequest, openID4VCIClientState?.accessTokenResponse?.access_token) + logger.log(`Notification to ${notificationEndpoint} has been dispatched`) + } - private async oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise { - const {issuer, errorOnNotFound = true} = args - return MetadataClient.retrieveAllMetadata(issuer, {errorOnNotFound}) - } + private async oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise { + const { issuer, errorOnNotFound = true } = args + return MetadataClient.retrieveAllMetadata(issuer, { errorOnNotFound }) + } } diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts index 907062e57..a0a780dbd 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts @@ -232,7 +232,7 @@ export const getDefaultIssuanceOpts = async (args: GetDefaultIssuanceOptsArgs): didMethod: opts.client.isEBSI() ? SupportedDidMethodEnum.DID_KEY : SupportedDidMethodEnum.DID_JWK, keyType: 'Secp256r1', } as IssuanceOpts - const identifierOpts = await getIdentifier({ issuanceOpt, context }) + const identifierOpts = await getIdentifierOpts({ issuanceOpt, context }) return { ...issuanceOpt, @@ -240,7 +240,7 @@ export const getDefaultIssuanceOpts = async (args: GetDefaultIssuanceOptsArgs): } } -export const getIdentifier = async (args: GetIdentifierArgs): Promise => { +export const getIdentifierOpts = async (args: GetIdentifierArgs): Promise => { const { issuanceOpt, context } = args const identifier = @@ -382,12 +382,16 @@ export const getCredentialConfigsSupportedBySingleTypeOrId = async ( const format = supported.format const type: string = getTypesFromObject(supported)?.join() ?? '' const id = `${type}:${format}` - return id; + return id } if (configurationId) { const allSupported = client.getCredentialsSupported(false) - return Object.fromEntries(Object.entries(allSupported).filter(([id, supported]) => id === configurationId || supported.id === configurationId || createIdFromTypes(supported) === configurationId)) + return Object.fromEntries( + Object.entries(allSupported).filter( + ([id, supported]) => id === configurationId || supported.id === configurationId || createIdFromTypes(supported) === configurationId, + ), + ) } if (!types && !client.credentialOffer) { @@ -419,8 +423,6 @@ export const getCredentialConfigsSupportedBySingleTypeOrId = async ( }) let allSupported: Record - - if (!Array.isArray(offerSupported)) { allSupported = offerSupported } else { @@ -430,7 +432,7 @@ export const getCredentialConfigsSupportedBySingleTypeOrId = async ( allSupported[supported.id] = supported return } - const id = createIdFromTypes(supported); + const id = createIdFromTypes(supported) allSupported[id] = supported }) } @@ -500,14 +502,16 @@ export const getIssuanceOpts = async (args: GetIssuanceOptsArgs): Promise Promise >) => { - const callback = async (oid4vciMachine: OID4VCIMachineInterpreter, - state: OID4VCIMachineState): Promise => { - if (state._event.type === 'internal') { - logger.debug('oid4vciStateNavigationListener: internal event'); - // Make sure we do not navigate when triggered by an internal event. We need to stay on current screen - // Make sure we do not navigate when state has not changed - return; - } - logger.info(`state listener for state: ${JSON.stringify(state.value)}`) - - if (!callbacks || callbacks.size === 0) { - logger.debug(`no callbacks registered for state: ${JSON.stringify(state.value)}`) - return - } +export const OID4VCICallbackStateListener = ( + callbacks?: Map Promise>, +) => { + return async (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState): Promise => { + if (state._event.type === 'internal') { + logger.debug('oid4vciCallbackStateListener: internal event') + // Make sure we do not navigate when triggered by an internal event. We need to stay on current screen + // Make sure we do not navigate when state has not changed + return + } + logger.info(`state listener for state: ${JSON.stringify(state.value)}`) - callbacks.forEach((callback, key: OID4VCIMachineStates) => { - if (state.matches(key)) { - logger.log(`state callback found for state: ${JSON.stringify(state.value)}, will execute callback`) - callback(oid4vciMachine, state) - .then(() => logger.log(`state callback executed for state: ${JSON.stringify(state.value)}`)) - .catch((error) => logger.log(`state callback failed for state: ${JSON.stringify(state.value)}, error: ${JSON.stringify(error?.message)}, ${state.event}`)) - } - }) + if (!callbacks || callbacks.size === 0) { + logger.debug(`no callbacks registered for state: ${JSON.stringify(state.value)}`) + return } - return callback + + callbacks.forEach((callback, key: OID4VCIMachineStates) => { + if (state.matches(key)) { + logger.log(`state callback found for state: ${JSON.stringify(state.value)}, will execute callback`) + callback(oid4vciMachine, state) + .then(() => logger.log(`state callback executed for state: ${JSON.stringify(state.value)}`)) + .catch((error) => + logger.log(`state callback failed for state: ${JSON.stringify(state.value)}, error: ${JSON.stringify(error?.message)}, ${state.event}`), + ) + } + }) + } } diff --git a/packages/oid4vci-holder/src/machine/oid4vciMachine.ts b/packages/oid4vci-holder/src/machine/oid4vciMachine.ts index 896131bf5..f48da4ed6 100644 --- a/packages/oid4vci-holder/src/machine/oid4vciMachine.ts +++ b/packages/oid4vci-holder/src/machine/oid4vciMachine.ts @@ -82,15 +82,19 @@ const oid4vciRequireAuthorizationGuard = (ctx: OID4VCIMachineContext, _event: OI throw Error('Missing openID4VCI client state in context') } - if ( - !openID4VCIClientState.credentialOffer?.supportedFlows ?? - (openID4VCIClientState.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint ? [AuthzFlowType.AUTHORIZATION_CODE_FLOW] : []) - ) { - return false - } else if (!openID4VCIClientState.authorizationURL) { + if (!openID4VCIClientState.authorizationURL) { + return !ctx.openID4VCIClientState?.authorizationCodeResponse + } else if (openID4VCIClientState.authorizationRequestOpts || !openID4VCIClientState.credentialOffer) { + // We have authz options or there is not credential offer to begin with. + // We require authz as long as we do not have the authz code response + return !ctx.openID4VCIClientState?.authorizationCodeResponse + } else if (openID4VCIClientState.credentialOffer?.supportedFlows?.includes(AuthzFlowType.AUTHORIZATION_CODE_FLOW)) { + return !ctx.openID4VCIClientState?.authorizationCodeResponse + } else if (openID4VCIClientState.credentialOffer?.supportedFlows?.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) { return false + } else if (openID4VCIClientState.endpointMetadata?.credentialIssuerMetadata?.authorization_endpoint) { + return !ctx.openID4VCIClientState?.authorizationCodeResponse } - return !ctx.openID4VCIClientState?.authorizationCodeResponse } @@ -590,9 +594,9 @@ export class OID4VCIMachine { if (typeof opts?.stateNavigationListener === 'function') { interpreter.onTransition((snapshot: OID4VCIMachineState): void => { if (opts?.stateNavigationListener === undefined) { - throw new Error('stateNavigationListener is required when no custom navigation hook is used') - } - opts.stateNavigationListener(interpreter, snapshot) + throw new Error('stateNavigationListener is required when no custom navigation hook is used') + } + opts.stateNavigationListener(interpreter, snapshot) }) } } diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index 3868d32c3..e6fe9919b 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -1,4 +1,4 @@ -import {OpenID4VCIClient, OpenID4VCIClientState} from '@sphereon/oid4vci-client' +import { OpenID4VCIClient, OpenID4VCIClientState } from '@sphereon/oid4vci-client' import { AuthorizationRequestOpts, AuthorizationResponse, @@ -10,11 +10,11 @@ import { ExperimentalSubjectIssuance, NotificationRequest, } from '@sphereon/oid4vci-common' -import {IIdentifierOpts} from '@sphereon/ssi-sdk-ext.did-utils' -import {IContactManager} from '@sphereon/ssi-sdk.contact-manager' -import {IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding, Identity, Party} from '@sphereon/ssi-sdk.data-store' -import {IIssuanceBranding} from '@sphereon/ssi-sdk.issuance-branding' -import {IVerifiableCredential, WrappedVerifiableCredential, WrappedVerifiablePresentation} from '@sphereon/ssi-types' +import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' +import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' +import { IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding, Identity, Party } from '@sphereon/ssi-sdk.data-store' +import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding' +import { IVerifiableCredential, WrappedVerifiableCredential, WrappedVerifiablePresentation } from '@sphereon/ssi-types' import { IAgentContext, ICredentialIssuer, @@ -28,18 +28,10 @@ import { TKeyType, VerifiableCredential, } from '@veramo/core' -import {IDataStore, IDataStoreORM} from '@veramo/data-store' -import {_ExtendedIKey} from '@veramo/utils' -import {JWTHeader, JWTPayload} from 'did-jwt' -import { - BaseActionObject, - Interpreter, - ResolveTypegenMeta, - ServiceMap, - State, - StateMachine, - TypegenDisabled -} from 'xstate' +import { IDataStore, IDataStoreORM } from '@veramo/data-store' +import { _ExtendedIKey } from '@veramo/utils' +import { JWTHeader, JWTPayload } from 'did-jwt' +import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, TypegenDisabled } from 'xstate' export interface IOID4VCIHolder extends IPluginMethodMap { oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise @@ -48,7 +40,10 @@ export interface IOID4VCIHolder extends IPluginMethodMap { oid4vciHolderStart(args: PrepareStartArgs, context: RequiredContext): Promise - oid4vciHolderCreateCredentialsToSelectFrom(args: createCredentialsToSelectFromArgs, context: RequiredContext): Promise> + oid4vciHolderCreateCredentialsToSelectFrom( + args: createCredentialsToSelectFromArgs, + context: RequiredContext, + ): Promise> oid4vciHolderGetContact(args: GetContactArgs, context: RequiredContext): Promise @@ -109,7 +104,10 @@ export type createCredentialsToSelectFromArgs = Pick< 'credentialsSupported' | 'credentialBranding' | 'selectedCredentials' | 'locale' | 'openID4VCIClientState' > export type GetContactArgs = Pick -export type GetCredentialsArgs = Pick +export type GetCredentialsArgs = Pick< + OID4VCIMachineContext, + 'verificationCode' | 'openID4VCIClientState' | 'selectedCredentials' | 'didMethodPreferences' | 'issuanceOpt' | 'accessTokenOpts' +> export type AddContactIdentityArgs = Pick export type AssertValidCredentialsArgs = Pick export type StoreCredentialBrandingArgs = Pick< @@ -161,6 +159,7 @@ export type MappedCredentialToAccept = ExperimentalSubjectIssuance & { export type OID4VCIMachineContext = { authorizationRequestOpts?: AuthorizationRequestOpts + accessTokenOpts?: AccessTokenOpts didMethodPreferences?: Array issuanceOpt?: IssuanceOpts requestData?: RequestData // TODO WAL-673 fix type as this is not always a qr code (deeplink) @@ -561,6 +560,11 @@ export type GetCredentialArgs = { pin?: string issuanceOpt: IssuanceOpts client: OpenID4VCIClient + accessTokenOpts?: AccessTokenOpts +} + +export type AccessTokenOpts = { + additionalRequestParams?: Record } export enum SignatureAlgorithmEnum { diff --git a/packages/oid4vci-issuer-rest-api/package.json b/packages/oid4vci-issuer-rest-api/package.json index be7f9f96e..43e3fe936 100644 --- a/packages/oid4vci-issuer-rest-api/package.json +++ b/packages/oid4vci-issuer-rest-api/package.json @@ -11,9 +11,9 @@ "start:dev": "ts-node __tests__/RestAPI.ts" }, "dependencies": { - "@sphereon/oid4vci-common": "0.12.0", - "@sphereon/oid4vci-issuer": "0.12.0", - "@sphereon/oid4vci-issuer-server": "0.12.0", + "@sphereon/oid4vci-common": "0.12.1-next.5", + "@sphereon/oid4vci-issuer": "0.12.1-next.5", + "@sphereon/oid4vci-issuer-server": "0.12.1-next.5", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", "@sphereon/ssi-sdk.oid4vci-issuer": "workspace:*", @@ -35,10 +35,10 @@ "@sphereon/did-uni-client": "^0.6.3", "@sphereon/pex": "3.3.3", "@sphereon/pex-models": "^2.2.4", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-handler-ld-local": "workspace:*", "@types/body-parser": "^1.19.2", diff --git a/packages/oid4vci-issuer-rest-client/package.json b/packages/oid4vci-issuer-rest-client/package.json index db6e348c2..7af1fa80f 100644 --- a/packages/oid4vci-issuer-rest-client/package.json +++ b/packages/oid4vci-issuer-rest-client/package.json @@ -16,7 +16,7 @@ "generate-plugin-schema": "ts-node ../../packages/dev/bin/sphereon.js dev generate-plugin-schema" }, "dependencies": { - "@sphereon/oid4vci-common": "0.12.0", + "@sphereon/oid4vci-common": "0.12.1-next.5", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", "cross-fetch": "^3.1.8" diff --git a/packages/oid4vci-issuer-store/package.json b/packages/oid4vci-issuer-store/package.json index 69b41c6b5..bbd1a2c59 100644 --- a/packages/oid4vci-issuer-store/package.json +++ b/packages/oid4vci-issuer-store/package.json @@ -14,8 +14,8 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "0.12.0", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/oid4vci-common": "0.12.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", "@veramo/core": "4.2.0", "@veramo/credential-w3c": "4.2.0", diff --git a/packages/oid4vci-issuer/package.json b/packages/oid4vci-issuer/package.json index 9a6876d45..272bc947f 100644 --- a/packages/oid4vci-issuer/package.json +++ b/packages/oid4vci-issuer/package.json @@ -14,9 +14,9 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "0.12.0", - "@sphereon/oid4vci-issuer": "0.12.0", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/oid4vci-common": "0.12.1-next.5", + "@sphereon/oid4vci-issuer": "0.12.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", "@sphereon/ssi-sdk.oid4vci-issuer-store": "workspace:*", diff --git a/packages/presentation-exchange/package.json b/packages/presentation-exchange/package.json index cf56380ff..f3f45723a 100644 --- a/packages/presentation-exchange/package.json +++ b/packages/presentation-exchange/package.json @@ -16,7 +16,7 @@ "dependencies": { "@sphereon/pex": "^3.3.3", "@sphereon/pex-models": "^2.2.4", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0" diff --git a/packages/sd-jwt/package.json b/packages/sd-jwt/package.json index 73a51f402..573641492 100644 --- a/packages/sd-jwt/package.json +++ b/packages/sd-jwt/package.json @@ -17,7 +17,7 @@ "dependencies": { "@sd-jwt/core": "^0.6.1", "@sd-jwt/sd-jwt-vc": "^0.6.1", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@veramo/utils": "4.2.0", "debug": "^4.3.5" }, @@ -25,10 +25,10 @@ "@sd-jwt/decode": "^0.6.1", "@sd-jwt/types": "^0.6.1", "@sd-jwt/utils": "^0.6.1", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@types/node": "18.15.3", "@veramo/core": "4.2.0", "@veramo/data-store": "4.2.0", diff --git a/packages/siopv2-oid4vp-op-auth/package.json b/packages/siopv2-oid4vp-op-auth/package.json index 7aeabad34..9c0c9d82b 100644 --- a/packages/siopv2-oid4vp-op-auth/package.json +++ b/packages/siopv2-oid4vp-op-auth/package.json @@ -17,8 +17,8 @@ "@sphereon/did-auth-siop": "0.6.4", "@sphereon/pex": "^3.3.3", "@sphereon/pex-models": "2.2.4", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.contact-manager": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", @@ -53,6 +53,7 @@ "files": [ "dist/**/*", "src/**/*", + "src/localization/translations/*", "README.md", "plugin.schema.json", "LICENSE" diff --git a/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts b/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts index c4a9330dd..e1bdaa794 100644 --- a/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts +++ b/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts @@ -1,7 +1,25 @@ -import { IOpSessionArgs, LOGGER_NAMESPACE, RequiredContext, schema } from '../index' -import { IAgentPlugin } from '@veramo/core' -import { OpSession } from '../session' -import { v4 as uuidv4 } from 'uuid' +import { + decodeUriAsJson, + PresentationSignCallback, + SupportedVersion, + VerifiedAuthorizationRequest +} from '@sphereon/did-auth-siop' +import { + ConnectionType, + CorrelationIdentifierType, + CredentialRole, + Identity, + IdentityOrigin, + NonPersistedIdentity, + Party, +} from '@sphereon/ssi-sdk.data-store' +import {Loggers, LogMethod, W3CVerifiableCredential} from '@sphereon/ssi-types' +import {IAgentPlugin} from '@veramo/core' +import {v4 as uuidv4} from 'uuid' +import {IOpSessionArgs, LOGGER_NAMESPACE, RequiredContext, schema, Siopv2AuthorizationResponseData} from '../index' +import {Siopv2Machine} from '../machine/Siopv2Machine' +import {siopSendAuthorizationResponse, translateCorrelationIdToName} from '../services/Siopv2MachineService' +import {OpSession} from '../session' import { IDidAuthSiopOpAuthenticator, IGetSiopSessionArgs, @@ -10,12 +28,13 @@ import { IRemoveSiopSessionArgs, IRequiredContext, } from '../types/IDidAuthSiopOpAuthenticator' -import { PresentationSignCallback, SupportedVersion, VerifiedAuthorizationRequest } from '@sphereon/did-auth-siop' +import {Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts} from '../types/machine' import { AddIdentityArgs, CreateConfigArgs, CreateConfigResult, + DidAuthSiopOpAuthenticatorOptions, GetSiopRequestArgs, OnContactIdentityCreatedArgs, OnIdentifierCreatedArgs, @@ -23,21 +42,7 @@ import { SendResponseArgs, Siopv2AuthorizationRequestData, Siopv2HolderEvent, - DidAuthSiopOpAuthenticatorOptions, } from '../types/siop-service' -import { Siopv2Machine } from '../machine/Siopv2Machine' -import { Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts } from '../types/machine' -import { Loggers, LogMethod, W3CVerifiableCredential } from '@sphereon/ssi-types' -import { - ConnectionType, - CorrelationIdentifierType, - CredentialRole, - Identity, - IdentityOrigin, - NonPersistedIdentity, - Party, -} from '@sphereon/ssi-sdk.data-store' -import { siopSendAuthorizationResponse, translateCorrelationIdToName } from '../services/Siopv2MachineService' const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, { methods: [LogMethod.CONSOLE, LogMethod.DEBUG_PKG] }).get(LOGGER_NAMESPACE) @@ -143,6 +148,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { } const siopv2MachineOpts: Siopv2MachineInstanceOpts = { + ...opts, url, stateNavigationListener, services: { @@ -184,11 +190,11 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { const session: OpSession = await agent .siopGetOPSession({ sessionId }) - .catch(async () => await agent.siopRegisterSession({ requestJwtOrUri: redirectUrl, sessionId })) + .catch(async () => await agent.siopRegisterOPSession({ requestJwtOrUri: redirectUrl, sessionId })) logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`) const verifiedAuthorizationRequest = await session.getAuthorizationRequest() - logger.debug('Request: ' + JSON.stringify(verifiedAuthorizationRequest, null, 2)) + logger.trace('Request: ' + JSON.stringify(verifiedAuthorizationRequest, null, 2)) const name = verifiedAuthorizationRequest.registrationMetadataPayload?.client_name const url = verifiedAuthorizationRequest.responseURI ?? @@ -230,7 +236,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { } return agent - .getContacts({ + .cmGetContacts({ filter: [ { identities: { @@ -283,7 +289,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { } } - private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise { + private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise { const { didAuthConfig, authorizationRequestData, selectedCredentials } = args if (didAuthConfig === undefined) { @@ -294,10 +300,11 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { return Promise.reject(Error('Missing authorization request data in context')) } - return await siopSendAuthorizationResponse( + const response = await siopSendAuthorizationResponse( ConnectionType.SIOPv2_OpenID4VP, { sessionId: didAuthConfig.sessionId, + ...(args.idOpts && { idOpts: args.idOpts }), ...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition: [ { @@ -309,5 +316,11 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { }, context, ) + + return { + body: await response.json(), + url: response.url, + queryParams: decodeUriAsJson(response.url) + } } } diff --git a/packages/siopv2-oid4vp-op-auth/src/index.ts b/packages/siopv2-oid4vp-op-auth/src/index.ts index 73ac65746..00f8f07d0 100644 --- a/packages/siopv2-oid4vp-op-auth/src/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/index.ts @@ -4,5 +4,7 @@ const schema = require('../plugin.schema.json') export { schema } export { DidAuthSiopOpAuthenticator } from './agent/DidAuthSiopOpAuthenticator' +export { Siopv2Machine } from './machine/Siopv2Machine' +export * from './machine/CallbackStateListener' export * from './session' export * from './types' diff --git a/packages/siopv2-oid4vp-op-auth/src/link-handler/index.ts b/packages/siopv2-oid4vp-op-auth/src/link-handler/index.ts index 181079599..949c9944e 100644 --- a/packages/siopv2-oid4vp-op-auth/src/link-handler/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/link-handler/index.ts @@ -1,3 +1,4 @@ +import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { LinkHandlerAdapter } from '@sphereon/ssi-sdk.core' import { IMachineStatePersistence, interpreterStartOrResume } from '@sphereon/ssi-sdk.xstate-machine-persistence' import { IAgentContext } from '@veramo/core' @@ -11,26 +12,30 @@ export class Siopv2OID4VPLinkHandler extends LinkHandlerAdapter { private readonly stateNavigationListener: | ((oid4vciMachine: Siopv2MachineInterpreter, state: Siopv2MachineState, navigation?: any) => Promise) | undefined - private noStateMachinePersistence: boolean + private readonly noStateMachinePersistence: boolean + private readonly idOpts?: IIdentifierOpts constructor( args: Pick & { protocols?: Array context: IAgentContext noStateMachinePersistence?: boolean + idOpts?: IIdentifierOpts }, ) { super({ ...args, id: 'Siopv2' }) this.context = args.context this.noStateMachinePersistence = args.noStateMachinePersistence === true this.stateNavigationListener = args.stateNavigationListener + this.idOpts = args.idOpts } - async handle(url: string | URL): Promise { + async handle(url: string | URL, opts?: {idOpts?: IIdentifierOpts}): Promise { logger.debug(`handling SIOP link: ${url}`) const siopv2Machine = await this.context.agent.siopGetMachineInterpreter({ url, + idOpts: opts?.idOpts ?? this.idOpts, stateNavigationListener: this.stateNavigationListener, }) siopv2Machine.interpreter.start() diff --git a/packages/siopv2-oid4vp-op-auth/src/machine/CallbackStateListener.ts b/packages/siopv2-oid4vp-op-auth/src/machine/CallbackStateListener.ts new file mode 100644 index 000000000..9c1a8d044 --- /dev/null +++ b/packages/siopv2-oid4vp-op-auth/src/machine/CallbackStateListener.ts @@ -0,0 +1,36 @@ +import { Loggers, LogLevel, LogMethod } from '@sphereon/ssi-types' +import { Siopv2MachineInterpreter, Siopv2MachineState, Siopv2MachineStates } from '../types' + +const logger = Loggers.DEFAULT.options('sphereon:oid4vp:holder', { defaultLogLevel: LogLevel.DEBUG, methods: [LogMethod.CONSOLE] }).get( + 'sphereon:oid4vp:holder', +) + +export const OID4VPCallbackStateListener = ( + callbacks?: Map Promise>, +) => { + return async (oid4vciMachine: Siopv2MachineInterpreter, state: Siopv2MachineState): Promise => { + if (state._event.type === 'internal') { + logger.debug('oid4vpCallbackStateListener: internal event') + // Make sure we do not navigate when triggered by an internal event. We need to stay on current screen + // Make sure we do not navigate when state has not changed + return + } + logger.info(`state listener for state: ${JSON.stringify(state.value)}`) + + if (!callbacks || callbacks.size === 0) { + logger.debug(`no callbacks registered for state: ${JSON.stringify(state.value)}`) + return + } + + callbacks.forEach((callback, key: Siopv2MachineStates) => { + if (state.matches(key)) { + logger.log(`state callback found for state: ${JSON.stringify(state.value)}, will execute callback`) + callback(oid4vciMachine, state) + .then(() => logger.log(`state callback executed for state: ${JSON.stringify(state.value)}`)) + .catch((error) => + logger.log(`state callback failed for state: ${JSON.stringify(state.value)}, error: ${JSON.stringify(error?.message)}, ${state.event}`), + ) + } + }) + } +} diff --git a/packages/siopv2-oid4vp-op-auth/src/machine/Siopv2Machine.ts b/packages/siopv2-oid4vp-op-auth/src/machine/Siopv2Machine.ts index 45c0baa19..9a9a59498 100644 --- a/packages/siopv2-oid4vp-op-auth/src/machine/Siopv2Machine.ts +++ b/packages/siopv2-oid4vp-op-auth/src/machine/Siopv2Machine.ts @@ -21,7 +21,7 @@ import { Siopv2MachineStates, Siopv2StateMachine, } from '../types/machine' -import { Siopv2AuthorizationRequestData } from '../types' +import {Siopv2AuthorizationRequestData, Siopv2AuthorizationResponseData} from '../types' const Siopv2HasNoContactGuard = (_ctx: Siopv2MachineContext, _event: Siopv2MachineEventTypes): boolean => { const { contact } = _ctx @@ -80,12 +80,13 @@ const Siopv2IsSiopWithOID4VPGuard = (_ctx: Siopv2MachineContext, _event: Siopv2M } const createSiopv2Machine = (opts: CreateSiopv2MachineOpts): Siopv2StateMachine => { - const { url } = opts + const { url, idOpts } = opts const initialContext: Siopv2MachineContext = { url: new URL(url).toString(), hasContactConsent: true, contactAlias: '', selectedCredentials: [], + idOpts, } return createMachine({ @@ -284,6 +285,9 @@ const createSiopv2Machine = (opts: CreateSiopv2MachineOpts): Siopv2StateMachine src: Siopv2MachineServices.sendResponse, onDone: { target: Siopv2MachineStates.done, + actions: assign({ + authorizationResponseData: (_ctx: Siopv2MachineContext, _event: DoneInvokeEvent) => _event.data, + }), }, onError: { target: Siopv2MachineStates.handleError, diff --git a/packages/siopv2-oid4vp-op-auth/src/services/IdentifierService.ts b/packages/siopv2-oid4vp-op-auth/src/services/IdentifierService.ts index 0c0172307..f8472cbeb 100644 --- a/packages/siopv2-oid4vp-op-auth/src/services/IdentifierService.ts +++ b/packages/siopv2-oid4vp-op-auth/src/services/IdentifierService.ts @@ -7,7 +7,7 @@ import { GetIdentifierArgs, GetOrCreatePrimaryIdentifierArgs, IdentifierAliasEnum, - IdentifierOpts, + IdentifierWithKey, KeyManagementSystemEnum, SupportedDidMethodEnum, } from '../types/identifier' @@ -15,7 +15,7 @@ import { IDIDManager, IIdentifier, IKey, IResolver, TAgent } from '@veramo/core' import { getFirstKeyWithRelation } from '@sphereon/ssi-sdk-ext.did-utils' import { Siopv2HolderEvent } from '../types' -export const getIdentifier = async (args: GetIdentifierArgs): Promise => { +export const getIdentifierWithKey = async (args: GetIdentifierArgs): Promise => { const { keyOpts, context } = args const identifier = diff --git a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts index b25431cbb..2488f939f 100644 --- a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts +++ b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts @@ -1,12 +1,17 @@ -import { ConnectionType } from '@sphereon/ssi-sdk.data-store' -import { IIdentifier } from '@veramo/core' -import { getIdentifier, getOrCreatePrimaryIdentifier } from './IdentifierService' -import { SupportedDidMethodEnum } from '../types/identifier' -import { CredentialMapper, Loggers, LogMethod, PresentationSubmission } from '@sphereon/ssi-types' -import { SupportedVersion } from '@sphereon/did-auth-siop' -import { getKey } from '@sphereon/ssi-sdk-ext.did-utils' -import { LOGGER_NAMESPACE, RequiredContext, VerifiableCredentialsWithDefinition, VerifiablePresentationWithDefinition } from '../types' -import { OID4VP, OpSession } from '../session' +import {SupportedVersion} from '@sphereon/did-auth-siop' +import {getIdentifier, getKey, IIdentifierOpts} from '@sphereon/ssi-sdk-ext.did-utils' +import {ConnectionType} from '@sphereon/ssi-sdk.data-store' +import {CredentialMapper, Loggers, LogMethod, PresentationSubmission} from '@sphereon/ssi-types' +import {IIdentifier} from '@veramo/core' +import {OID4VP, OpSession} from '../session' +import { + LOGGER_NAMESPACE, + RequiredContext, + SupportedDidMethodEnum, + VerifiableCredentialsWithDefinition, + VerifiablePresentationWithDefinition +} from '../types' +import {getOrCreatePrimaryIdentifier} from './IdentifierService' const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, { methods: [LogMethod.CONSOLE, LogMethod.DEBUG_PKG] }).get(LOGGER_NAMESPACE) @@ -15,16 +20,18 @@ export const siopSendAuthorizationResponse = async ( args: { sessionId: string verifiableCredentialsWithDefinition?: VerifiableCredentialsWithDefinition[] + idOpts?: IIdentifierOpts }, context: RequiredContext, ) => { const { agent } = context + let { idOpts } = args if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) { return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`)) } const session: OpSession = await agent.siopGetOPSession({ sessionId: args.sessionId }) - let identifiers: Array = await session.getSupportedIdentifiers() + let identifiers: Array = idOpts ? [await getIdentifier(idOpts, context)] : await session.getSupportedIdentifiers() if (!identifiers || identifiers.length === 0) { throw Error(`No DID methods found in agent that are supported by the relying party`) } @@ -98,20 +105,22 @@ export const siopSendAuthorizationResponse = async ( throw Error(`Only one verifiable presentation supported for now. Got ${presentationsAndDefs.length}`) } - const identifierOpts = presentationsAndDefs[0].identifierOpts - const getIdentifierResponse = await getIdentifier({ + idOpts = presentationsAndDefs[0].identifierOpts + identifier = await getIdentifier(idOpts, context) + /*key = await getKey(identifier, 'authentication', context, idOpts.kid) + const getIdentifierResponse = await getIdentifierWithKey({ context, keyOpts: { - identifier: identifierOpts.identifier as IIdentifier, // FIXME BEFORE PR - cast? + identifier, kid: identifierOpts.kid, - didMethod: SupportedDidMethodEnum.DID_JWK, // FIXME BEFORE PR - where does this hav to come from? - keyType: 'Secp256r1', // FIXME BEFORE PR - where does this hav to come from? + didMethod: parseDid(identifier.did).method as SupportedDidMethodEnum, + keyType: key.type }, - }) - identifier = getIdentifierResponse.identifier + })*/ presentationSubmission = presentationsAndDefs[0].presentationSubmission } - const kid: string = (await getKey(identifier, 'authentication', session.context)).kid + const key = await getKey(identifier, 'authentication', session.context, idOpts?.kid) + const kid: string = idOpts?.kid?.startsWith('did:') ? idOpts?.kid : `${identifier.did}#${key?.meta?.jwkThumbprint ? key.meta.jwkThumbprint : key.kid}` logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentation, null, 2)) logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2)) diff --git a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts index aa650ef26..b6df107a5 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts @@ -39,7 +39,7 @@ import { RequiredContext, RetrieveContactArgs, SendResponseArgs, - Siopv2AuthorizationRequestData, + Siopv2AuthorizationRequestData, Siopv2AuthorizationResponseData, } from './siop-service' export const LOGGER_NAMESPACE = 'sphereon:siopv2-oid4vp:op-auth' @@ -56,7 +56,7 @@ export interface IDidAuthSiopOpAuthenticator extends IPluginMethodMap { siopGetSiopRequest(args: GetSiopRequestArgs, context: RequiredContext): Promise siopRetrieveContact(args: RetrieveContactArgs, context: RequiredContext): Promise siopAddIdentity(args: AddIdentityArgs, context: RequiredContext): Promise - siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise + siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise } export interface IOpSessionArgs { @@ -64,6 +64,7 @@ export interface IOpSessionArgs { requestJwtOrUri: string | URI providedPresentationDefinitions?: Array + idOpts?: IIdentifierOpts // identifier: IIdentifier context: IRequiredContext op?: IOPOptions diff --git a/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts index 9119296c3..1635d6518 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/identifier/index.ts @@ -55,7 +55,7 @@ export type GetIdentifierArgs = { context: RequiredContext } -export type IdentifierOpts = { +export type IdentifierWithKey = { identifier: IIdentifier key: _ExtendedIKey kid: string diff --git a/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts index af482c2e5..c40dc3f9f 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts @@ -1,16 +1,17 @@ -import { IIdentifier } from '@veramo/core' -import { DidAuthConfig, Party } from '@sphereon/ssi-sdk.data-store' import { VerifiedAuthorizationRequest } from '@sphereon/did-auth-siop' +import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' +import { DidAuthConfig, Party } from '@sphereon/ssi-sdk.data-store' import { OriginalVerifiableCredential } from '@sphereon/ssi-types' -import { ErrorDetails } from '../error' import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, TypegenDisabled } from 'xstate' -import { Siopv2AuthorizationRequestData } from '../siop-service' +import { ErrorDetails } from '../error' +import {Siopv2AuthorizationRequestData, Siopv2AuthorizationResponseData} from '../siop-service' export type Siopv2MachineContext = { url: string - identifier?: IIdentifier + idOpts?: IIdentifierOpts didAuthConfig?: Omit authorizationRequestData?: Siopv2AuthorizationRequestData + authorizationResponseData?: Siopv2AuthorizationResponseData verifiedAuthorizationRequest?: VerifiedAuthorizationRequest contact?: Party hasContactConsent: boolean @@ -71,6 +72,7 @@ export type Siopv2StateMachine = StateMachine< export type CreateSiopv2MachineOpts = { url: string | URL + idOpts?: IIdentifierOpts machineId?: string } diff --git a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts index b2c7e812f..b3d2a2c03 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts @@ -1,3 +1,4 @@ +import { IIdentifierOpts } from '@sphereon/ssi-sdk-ext.did-utils' import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core' import { PresentationDefinitionWithLocation, RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop' @@ -12,15 +13,16 @@ export type DidAuthSiopOpAuthenticatorOptions = { export type GetMachineArgs = { url: string | URL + idOpts?: IIdentifierOpts stateNavigationListener?: (siopv2Machine: Siopv2MachineInterpreter, state: Siopv2MachineState, navigation?: any) => Promise } export type CreateConfigArgs = Pick -export type CreateConfigResult = Omit +export type CreateConfigResult = Omit export type GetSiopRequestArgs = Pick export type RetrieveContactArgs = Pick export type AddIdentityArgs = Pick -export type SendResponseArgs = Pick +export type SendResponseArgs = Pick export enum Siopv2HolderEvent { CONTACT_IDENTITY_CREATED = 'contact_identity_created', // TODO BEFORE PR: same events as the oid4vci holder module? @@ -32,6 +34,13 @@ export enum SupportedLanguage { DUTCH = 'nl', } +export type Siopv2AuthorizationResponseData = { + body?: string + url?: string + queryParams?: Record +} + + export type Siopv2AuthorizationRequestData = { correlationId: string registrationMetadataPayload: RPRegistrationMetadataPayload @@ -51,4 +60,4 @@ export type OnIdentifierCreatedArgs = { identifier: IIdentifier } -export type RequiredContext = IAgentContext +export type RequiredContext = IAgentContext diff --git a/packages/siopv2-oid4vp-op-auth/tsconfig.json b/packages/siopv2-oid4vp-op-auth/tsconfig.json index 4c1d2172c..178ce51e5 100644 --- a/packages/siopv2-oid4vp-op-auth/tsconfig.json +++ b/packages/siopv2-oid4vp-op-auth/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig-base.json", "compilerOptions": { + "resolveJsonModule": true, "rootDir": "src", "outDir": "dist", "declarationDir": "dist" diff --git a/packages/siopv2-oid4vp-rp-auth/package.json b/packages/siopv2-oid4vp-rp-auth/package.json index 9b1081c28..9f3abfa23 100644 --- a/packages/siopv2-oid4vp-rp-auth/package.json +++ b/packages/siopv2-oid4vp-rp-auth/package.json @@ -16,7 +16,7 @@ "dependencies": { "@sphereon/did-auth-siop": "0.6.4", "@sphereon/pex": "^3.3.3", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", "@sphereon/ssi-sdk.pd-manager": "workspace:*", diff --git a/packages/siopv2-oid4vp-rp-rest-api/package.json b/packages/siopv2-oid4vp-rp-rest-api/package.json index 083889d9c..4412ed177 100644 --- a/packages/siopv2-oid4vp-rp-rest-api/package.json +++ b/packages/siopv2-oid4vp-rp-rest-api/package.json @@ -36,7 +36,7 @@ "@sphereon/did-uni-client": "^0.6.3", "@sphereon/pex": "^3.3.3", "@sphereon/pex-models": "^2.2.4", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-handler-ld-local": "workspace:*", "@types/body-parser": "^1.19.2", diff --git a/packages/uni-resolver-registrar-api/package.json b/packages/uni-resolver-registrar-api/package.json index a817d3f85..37174a3e0 100644 --- a/packages/uni-resolver-registrar-api/package.json +++ b/packages/uni-resolver-registrar-api/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@sphereon/ssi-express-support": "workspace:*", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", @@ -31,8 +31,8 @@ }, "devDependencies": { "@sphereon/did-uni-client": "^0.6.3", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-handler-ld-local": "workspace:*", "@types/body-parser": "^1.19.2", diff --git a/packages/vc-handler-ld-local/package.json b/packages/vc-handler-ld-local/package.json index 9bbc8345e..4884ac06f 100644 --- a/packages/vc-handler-ld-local/package.json +++ b/packages/vc-handler-ld-local/package.json @@ -24,8 +24,8 @@ "@digitalcredentials/x25519-key-agreement-2020-context": "^1.0.0", "@noble/hashes": "^1.2.0", "@sphereon/isomorphic-webcrypto": "^2.4.1-unstable.0", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", @@ -57,10 +57,10 @@ }, "devDependencies": { "@sphereon/did-uni-client": "^0.6.3", - "@sphereon/ssi-sdk-ext.did-provider-key": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-provider-lto": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-key": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-provider-lto": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@transmute/lds-ecdsa-secp256k1-recovery2020": "^0.0.7", "@types/nock": "^11.1.0", diff --git a/packages/vc-status-list-issuer-drivers/package.json b/packages/vc-status-list-issuer-drivers/package.json index 7af0af08d..911e2f652 100644 --- a/packages/vc-status-list-issuer-drivers/package.json +++ b/packages/vc-status-list-issuer-drivers/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@sphereon/ssi-express-support": "workspace:*", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", diff --git a/packages/vc-status-list-issuer-rest-api/package.json b/packages/vc-status-list-issuer-rest-api/package.json index 61a50dbdb..7339d0a11 100644 --- a/packages/vc-status-list-issuer-rest-api/package.json +++ b/packages/vc-status-list-issuer-rest-api/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@sphereon/ssi-express-support": "workspace:*", - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-status-list": "workspace:*", @@ -30,8 +30,8 @@ }, "devDependencies": { "@sphereon/did-uni-client": "^0.6.3", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-handler-ld-local": "workspace:*", diff --git a/packages/vc-status-list/package.json b/packages/vc-status-list/package.json index 1c4ecde1b..e8f5c164b 100644 --- a/packages/vc-status-list/package.json +++ b/packages/vc-status-list/package.json @@ -10,7 +10,7 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-utils": "0.21.1-next.8", "@sphereon/ssi-types": "workspace:*", "@sphereon/vc-status-list": "7.0.0-next.0", "@veramo/core": "4.2.0", diff --git a/packages/w3c-vc-api/package.json b/packages/w3c-vc-api/package.json index 49fbfe1a0..cba284fa0 100644 --- a/packages/w3c-vc-api/package.json +++ b/packages/w3c-vc-api/package.json @@ -32,10 +32,10 @@ }, "devDependencies": { "@sphereon/did-uni-client": "^0.6.3", - "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.did-provider-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@sphereon/ssi-sdk.data-store": "workspace:*", "@sphereon/ssi-sdk.vc-handler-ld-local": "workspace:*", diff --git a/packages/web3-provider-headless/package.json b/packages/web3-provider-headless/package.json index c55f58e85..702d944d9 100644 --- a/packages/web3-provider-headless/package.json +++ b/packages/web3-provider-headless/package.json @@ -40,8 +40,8 @@ "web3-validator": "^2.0.0" }, "devDependencies": { - "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.5", - "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.5", + "@sphereon/ssi-sdk-ext.key-manager": "0.21.1-next.8", + "@sphereon/ssi-sdk-ext.kms-local": "0.21.1-next.8", "@types/body-parser": "^1.19.2", "@types/cors": "^2.8.13", "@types/dotenv-flow": "^3.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78371decb..a82ccb0bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,9 @@ overrides: '@veramo/url-handler': 4.2.0 '@sphereon/ssi-types': workspace:* '@sphereon/ssi-sdk.core': workspace:* - '@sphereon/oid4vci-common': 0.12.1-next.4 - '@sphereon/oid4vci-client': 0.12.1-next.4 - '@sphereon/oid4vci-issuer': 0.12.1-next.4 + '@sphereon/oid4vci-common': 0.12.1-next.5 + '@sphereon/oid4vci-client': 0.12.1-next.5 + '@sphereon/oid4vci-issuer': 0.12.1-next.5 '@noble/hashes': 1.2.0 did-jwt: 6.11.6 did-jwt-vc: 3.1.3 @@ -228,11 +228,11 @@ importers: specifier: workspace:* version: link:../ssi-express-support '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.contact-manager': specifier: workspace:* version: link:../contact-manager @@ -366,6 +366,9 @@ importers: '@sphereon/pex': specifier: ^3.3.3 version: 3.3.3 + '@sphereon/ssi-sdk-ext.did-utils': + specifier: ^0.21.0 + version: 0.21.0(msrcrypto@1.5.8) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -468,14 +471,14 @@ importers: specifier: ^2.2.4 version: 2.2.4 '@sphereon/ssi-sdk-ext.did-provider-ebsi': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.contact-manager': specifier: workspace:* version: link:../contact-manager @@ -514,17 +517,17 @@ importers: version: 6.2.13 devDependencies: '@sphereon/oid4vci-client': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config @@ -798,17 +801,17 @@ importers: packages/oid4vci-holder: dependencies: '@sphereon/oid4vci-client': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-sdk-ext.did-resolver-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5 + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.contact-manager': specifier: workspace:* version: link:../contact-manager @@ -877,14 +880,14 @@ importers: packages/oid4vci-issuer: dependencies: '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/oid4vci-issuer': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -935,14 +938,14 @@ importers: packages/oid4vci-issuer-rest-api: dependencies: '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/oid4vci-issuer': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/oid4vci-issuer-server': - specifier: 0.12.0 - version: 0.12.0 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -1002,17 +1005,17 @@ importers: specifier: ^2.2.4 version: 2.2.4 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -1095,8 +1098,8 @@ importers: packages/oid4vci-issuer-rest-client: dependencies: '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-types': specifier: workspace:* version: link:../ssi-types @@ -1132,11 +1135,11 @@ importers: packages/oid4vci-issuer-store: dependencies: '@sphereon/oid4vci-common': - specifier: 0.12.1-next.4 - version: 0.12.1-next.4 + specifier: 0.12.1-next.5 + version: 0.12.1-next.5 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.kv-store-temp': specifier: workspace:* version: link:../kv-store @@ -1351,8 +1354,8 @@ importers: specifier: ^2.2.4 version: 2.2.4 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -1480,8 +1483,8 @@ importers: specifier: ^0.6.1 version: 0.6.1 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@veramo/utils': specifier: 4.2.0 version: 4.2.0 @@ -1499,17 +1502,17 @@ importers: specifier: ^0.6.1 version: 0.6.1 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.did-resolver-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5 + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@types/node': specifier: 18.15.3 version: 18.15.3 @@ -1568,18 +1571,30 @@ importers: '@sphereon/pex-models': specifier: 2.2.4 version: 2.2.4 + '@sphereon/ssi-sdk-ext.did-resolver-jwk': + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk.contact-manager': + specifier: workspace:* + version: link:../contact-manager '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core + '@sphereon/ssi-sdk.data-store': + specifier: workspace:* + version: link:../data-store '@sphereon/ssi-sdk.pd-manager': specifier: workspace:* version: link:../pd-manager '@sphereon/ssi-sdk.presentation-exchange': specifier: workspace:* version: link:../presentation-exchange + '@sphereon/ssi-sdk.xstate-machine-persistence': + specifier: workspace:* + version: link:../xstate-persistence '@sphereon/ssi-types': specifier: workspace:* version: link:../ssi-types @@ -1598,9 +1613,18 @@ importers: did-jwt-vc: specifier: 3.1.3 version: 3.1.3 + i18n-js: + specifier: ^3.8.0 + version: 3.9.2 + lodash.memoize: + specifier: ^4.1.2 + version: 4.1.2 uuid: specifier: ^9.0.1 version: 9.0.1 + xstate: + specifier: ^4.38.3 + version: 4.38.3 devDependencies: '@sphereon/did-uni-client': specifier: ^0.6.3 @@ -1608,6 +1632,12 @@ importers: '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config + '@types/i18n-js': + specifier: ^3.8.0 + version: 3.8.9 + '@types/lodash.memoize': + specifier: ^4.1.7 + version: 4.1.9 '@types/uuid': specifier: ^9.0.1 version: 9.0.8 @@ -1642,8 +1672,8 @@ importers: specifier: ^3.3.3 version: 3.3.3 '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -1773,8 +1803,8 @@ importers: specifier: ^2.2.4 version: 2.2.4 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -2068,14 +2098,14 @@ importers: specifier: workspace:* version: link:../ssi-express-support '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -2120,11 +2150,11 @@ importers: specifier: ^0.6.3 version: 0.6.3 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.did-resolver-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5 + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk.data-store': specifier: workspace:* version: link:../data-store @@ -2252,11 +2282,11 @@ importers: specifier: ^2.4.1-unstable.0 version: 2.4.1-unstable.0(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.key-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config @@ -2349,17 +2379,17 @@ importers: specifier: ^0.6.3 version: 0.6.3 '@sphereon/ssi-sdk-ext.did-provider-key': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.did-provider-lto': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(typescript@5.4.2) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(typescript@5.4.2) '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@transmute/lds-ecdsa-secp256k1-recovery2020': specifier: ^0.0.7 version: 0.0.7 @@ -2433,8 +2463,8 @@ importers: packages/vc-status-list: dependencies: '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-types': specifier: workspace:* version: link:../ssi-types @@ -2479,8 +2509,8 @@ importers: specifier: workspace:* version: link:../ssi-express-support '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config @@ -2528,8 +2558,8 @@ importers: specifier: workspace:* version: link:../ssi-express-support '@sphereon/ssi-sdk-ext.did-utils': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -2574,11 +2604,11 @@ importers: specifier: ^0.6.3 version: 0.6.3 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.did-resolver-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5 + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config @@ -2716,17 +2746,17 @@ importers: specifier: ^0.6.3 version: 0.6.3 '@sphereon/ssi-sdk-ext.did-provider-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8) '@sphereon/ssi-sdk-ext.did-resolver-jwk': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5 + specifier: 0.21.1-next.8 + version: 0.21.1-next.8 '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.agent-config': specifier: workspace:* version: link:../agent-config @@ -2962,11 +2992,11 @@ importers: version: 2.0.6 devDependencies: '@sphereon/ssi-sdk-ext.key-manager': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk-ext.kms-local': - specifier: 0.21.1-next.5 - version: 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + specifier: 0.21.1-next.8 + version: 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@types/body-parser': specifier: ^1.19.2 version: 1.19.5 @@ -7300,11 +7330,11 @@ packages: - typescript dev: true - /@sphereon/oid4vci-client@0.12.1-next.4: - resolution: {integrity: sha512-zxvlmNko/Q/aJHKneJKytrp2iPx+6jpfyyjDz9KUcXb7hDZDlCNfhhZqZQtVJwtSFbUSx3YGx6ylwve55hw+sg==} + /@sphereon/oid4vci-client@0.12.1-next.5: + resolution: {integrity: sha512-TjW9h1h1NBkdeY514G6jrlzfd2Im03EV+GPd8HWpPGZNTx/7bk0HWiRBLYKTvZ1fzDo/xzCdqFT/Bj2MSGEK/Q==} engines: {node: '>=18'} dependencies: - '@sphereon/oid4vci-common': 0.12.1-next.4 + '@sphereon/oid4vci-common': 0.12.1-next.5 '@sphereon/ssi-types': link:packages/ssi-types cross-fetch: 3.1.8 debug: 4.3.5 @@ -7312,8 +7342,8 @@ packages: - encoding - supports-color - /@sphereon/oid4vci-common@0.12.1-next.4: - resolution: {integrity: sha512-CXPFc2ecSxUL5NQo3IvRd7bRaGPkdMx7C7fiIdBk1xyhyoWoAabYDmyCaadLBExNkgnshjFV90NUoRK+qonfTw==} + /@sphereon/oid4vci-common@0.12.1-next.5: + resolution: {integrity: sha512-fj9u5G4h8LPeA57cqhBjHkpZlYPePctZXQe1HKBM7TH0aeCu7SK/V3dfE+ba86tn3xytG1ojKRcUpGPdsock0w==} engines: {node: '>=18'} dependencies: '@sphereon/ssi-types': link:packages/ssi-types @@ -7324,12 +7354,12 @@ packages: transitivePeerDependencies: - encoding - /@sphereon/oid4vci-issuer-server@0.12.0: - resolution: {integrity: sha512-+eHkJ/6zWyluJ1W8r6StmoRGY3xZjDBzEAplBRZf1OtK7MpHZWhkThBGqQGN6Av+vf2qZIyEj7YCyz8PkkvbIA==} + /@sphereon/oid4vci-issuer-server@0.12.1-next.5: + resolution: {integrity: sha512-CQBBk31CTrA+nTsWjzhM9gv5GPFeGl5nnuiIJ6+c+CukN0o2nATBKPNt9ZTY+Iilw+q5LVf0phm8+kXji9wfKA==} engines: {node: '>=18'} dependencies: - '@sphereon/oid4vci-common': 0.12.1-next.4 - '@sphereon/oid4vci-issuer': 0.12.1-next.4 + '@sphereon/oid4vci-common': 0.12.1-next.5 + '@sphereon/oid4vci-issuer': 0.12.1-next.5 '@sphereon/ssi-express-support': 0.25.1-unstable.87 '@sphereon/ssi-types': link:packages/ssi-types body-parser: 1.20.2 @@ -7349,8 +7379,8 @@ packages: - supports-color dev: false - /@sphereon/oid4vci-issuer@0.12.1-next.4: - resolution: {integrity: sha512-5qiYJvAhwSsj4OHC2O8XqZNHDkXifJFKAx99/cUG4+ug/hWUnsA3RqpAuqKVmko4CwtbVuZGZ57b16q4TIukwQ==} + /@sphereon/oid4vci-issuer@0.12.1-next.5: + resolution: {integrity: sha512-xbUeN07aDFr08ULxFwj7eGdcn2ON83zYFopd7scrAHe5tAqhDI3b3zl0Ja8UjHkLzP3uanh9BHG0F30Y3Eu89A==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -7358,7 +7388,7 @@ packages: awesome-qr: optional: true dependencies: - '@sphereon/oid4vci-common': 0.12.1-next.4 + '@sphereon/oid4vci-common': 0.12.1-next.5 '@sphereon/ssi-types': link:packages/ssi-types uuid: 9.0.1 transitivePeerDependencies: @@ -7425,12 +7455,12 @@ packages: - supports-color dev: false - /@sphereon/ssi-sdk-ext.did-provider-ebsi@0.21.1-next.5(msrcrypto@1.5.8): - resolution: {integrity: sha512-ndhMcEL9hNREfc4aEXZztCMJgnAt5TlItP4Ac8lkUG9Zgq7EqskiYvS4rns/6wu3z/iiuHV+cqr1Cl3JTx+nFg==} + /@sphereon/ssi-sdk-ext.did-provider-ebsi@0.21.1-next.8(msrcrypto@1.5.8): + resolution: {integrity: sha512-+VhtJW5DTP0n/blIuwf6ZGmnHOGJD9yKLlhQ0sB+DgdIeusc72Q6SbfS8vsZC2g8UXddcfgRZful8xy5lhHyyQ==} dependencies: '@ethersproject/random': 5.7.0 - '@sphereon/ssi-sdk-ext.did-resolver-ebsi': 0.21.1-next.5 - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.did-resolver-ebsi': 0.21.1-unstable.8 + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) '@veramo/did-manager': 4.2.0 '@veramo/did-provider-key': 4.2.0 @@ -7452,12 +7482,12 @@ packages: - utf-8-validate dev: false - /@sphereon/ssi-sdk-ext.did-provider-jwk@0.21.1-next.5(msrcrypto@1.5.8): - resolution: {integrity: sha512-3oAHkJgGGPXB4HxeRJ9Q2WKAMcqf+5Ir3rEnIlkst/jWe3/bCZfljR9iv511Q6L8Jd+Zz7gNLilE292Scb/5gA==} + /@sphereon/ssi-sdk-ext.did-provider-jwk@0.21.1-next.8(msrcrypto@1.5.8): + resolution: {integrity: sha512-JrfTumgV65+877HAa78lUmYnr9N46wxidCF1Tv/OBqW4b0c4H9Ho33Rr4XftO+/9sqy8aYOaJLvyMb+Z98aKZA==} dependencies: '@ethersproject/random': 5.7.0 - '@sphereon/ssi-sdk-ext.did-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.did-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-types': link:packages/ssi-types '@stablelib/ed25519': 1.0.3 '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) @@ -7476,11 +7506,11 @@ packages: - supports-color dev: true - /@sphereon/ssi-sdk-ext.did-provider-key@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-uUHDwFYK7jC457UhOcjZ/TAKKjgiewOZ4GkDQZdorrFea9w5DtYjg1O1GQ/xBKe4e6XFxZ/N/4dtGaYE05cCgA==} + /@sphereon/ssi-sdk-ext.did-provider-key@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-8epN60uvel+vO4EIqY8+3ZfWtY8UZOA4YfIlQStnyV+SesTLWPfYbaAR9AoO8j8CDF9c7I7hW/odmX+TvBGTrA==} dependencies: - '@sphereon/ssi-sdk-ext.did-resolver-key': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.did-resolver-key': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@transmute/did-key-bls12381': 0.3.0-unstable.10 '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) '@veramo/did-manager': 4.2.0 @@ -7500,8 +7530,8 @@ packages: - supports-color dev: true - /@sphereon/ssi-sdk-ext.did-provider-lto@0.21.1-next.5(typescript@5.4.2): - resolution: {integrity: sha512-YV0uLALzhd7VHZCZi7q0KSrsvgR5RAKodnTTFFYwCGQBHoQkp/EdPfG553fnWixgayxd46mwgEfZjqQPFRvv9g==} + /@sphereon/ssi-sdk-ext.did-provider-lto@0.21.1-next.8(typescript@5.4.2): + resolution: {integrity: sha512-Jb8Vso9U44jUHuJuHBJjQnGE9P2wGP8bHSyBPPFATW/8CFP4k+prPQ1stl+gUDRS1X3ZMdLuHT083C39NwtBAA==} dependencies: '@lto-network/lto-crypto': 1.1.1 '@lto-network/lto-transactions': 1.2.12(debug@4.3.5)(typescript@5.4.2) @@ -7518,8 +7548,8 @@ packages: - typescript dev: true - /@sphereon/ssi-sdk-ext.did-resolver-ebsi@0.21.1-next.5: - resolution: {integrity: sha512-bHg3Lk5xoJNqFPgibltxfGuFZxiEUL2l55ze2mK7oYJGfYlt0B79ElWTM4Ox+Qc29uHCj8X7CfJAcOWoKpUUGg==} + /@sphereon/ssi-sdk-ext.did-resolver-ebsi@0.21.1-unstable.8: + resolution: {integrity: sha512-A9YIzCZ4qRtBLJWoHVPJ1oefnNnl/0v4wJxKps0VXTSVcmLUTFWAD3u/BVx5WYIRmzOhlid9ezK4l2MdxlSj/A==} dependencies: cross-fetch: 3.1.8 did-resolver: 4.1.0 @@ -7528,8 +7558,8 @@ packages: - encoding dev: false - /@sphereon/ssi-sdk-ext.did-resolver-jwk@0.21.1-next.5: - resolution: {integrity: sha512-C+yplFZevYZeDMq12Cjjh2BHKx5sFcv1fmNJwT3f8NjMynK4Jun5bG/mfW3tCbkNYXXGKMIty0HYBQhKmp244A==} + /@sphereon/ssi-sdk-ext.did-resolver-jwk@0.21.1-next.8: + resolution: {integrity: sha512-/eDnPopjJLkZ9PhbtK61MKribgS9Z2SUD6kwkLKAJiaX6hJeZEiHZPhnwwehxis+nioWo1ohsD5mT8nFh+nLRQ==} dependencies: '@sphereon/ssi-types': link:packages/ssi-types base64url: 3.0.1 @@ -7539,10 +7569,10 @@ packages: transitivePeerDependencies: - supports-color - /@sphereon/ssi-sdk-ext.did-resolver-key@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-kzDIKe2jgb1hZb3XCxomzaVO0JCTTjfE04gBuny/6DrFSb6iffIdSir8dyeSfkr+pdkhWI81KFNUxmv1rfo/uQ==} + /@sphereon/ssi-sdk-ext.did-resolver-key@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-xUH64St4k61y/32ThWK996IyXP0Sa60Jw+wx5vc609ILG2NdMm0oOC2l8WJHAZa8VSsg2JlveOUO1L35bUC9IA==} dependencies: - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@stablelib/ed25519': 1.0.3 bigint-mod-arith: 3.3.1 did-resolver: 4.1.0 @@ -7562,12 +7592,36 @@ packages: - supports-color dev: true - /@sphereon/ssi-sdk-ext.did-utils@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-g/Sp/oaYjsH1cP0aQMuyDiyI9xvrNmYMjOR9PljYAiq/If/PQ9gldnp2M/iN/vSaEdV0G6yFI+bq8HWcyo77rQ==} + /@sphereon/ssi-sdk-ext.did-utils@0.21.0(msrcrypto@1.5.8): + resolution: {integrity: sha512-q0QEgzORSS7aqPyamf7euZi1WjsmnUowa7fMk8x00bvcSg9bCtGZ2KT7uWkP7jN+Nz+aezr6GWEahb+enp/yHw==} + dependencies: + '@ethersproject/transactions': 5.7.0 + '@sphereon/did-uni-client': 0.6.3 + '@sphereon/ssi-sdk-ext.key-utils': 0.21.0(msrcrypto@1.5.8) + '@sphereon/ssi-sdk.core': link:packages/ssi-sdk-core + '@stablelib/ed25519': 1.0.3 + '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) + '@veramo/utils': 4.2.0 + did-jwt: 6.11.6(patch_hash=afqywxnnjnsy6hwgax66dyyiey) + did-resolver: 4.1.0 + elliptic: 6.5.4 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - encoding + - expo + - expo-crypto + - msrcrypt + - msrcrypto + - react-native-securerandom + - supports-color + dev: false + + /@sphereon/ssi-sdk-ext.did-utils@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-fy1UROL1/na79wpuIWTXkX99lJBaW2wYW4H0v5fURcVc0mElpRO8qdyHTqyz01H92D83s7tfsGr52tvmATdXeA==} dependencies: '@ethersproject/transactions': 5.7.0 '@sphereon/did-uni-client': 0.6.3 - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@sphereon/ssi-sdk.core': link:packages/ssi-sdk-core '@stablelib/ed25519': 1.0.3 '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) @@ -7585,10 +7639,10 @@ packages: - react-native-securerandom - supports-color - /@sphereon/ssi-sdk-ext.key-manager@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-MK31DY/kXpPuoheDoA5rPugTjFZWxEuMEwBuoduLk1/4xNU/wxA/Ly1/5XK12FHJoGpQC0iDBHe1OmRp1VZPiA==} + /@sphereon/ssi-sdk-ext.key-manager@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-BBaA8DH+omuV/op9laGdKvUWVM/MxtDkSbQG8/WY/8dcuwBryDV5tbF/xjeFGcFXE1kiRJKf7/mLP9b6Q9EYCg==} dependencies: - '@sphereon/ssi-sdk-ext.kms-local': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.kms-local': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) '@veramo/key-manager': 4.2.0 transitivePeerDependencies: @@ -7601,8 +7655,35 @@ packages: - react-native-securerandom - supports-color - /@sphereon/ssi-sdk-ext.key-utils@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-V0KXzz5OipJiZipoXBu66TmjFpXdKqS582T+VH/Sv92ehyrgFkiA1wqlyVin9zzqzML/aV3H4liQT956g0w9RA==} + /@sphereon/ssi-sdk-ext.key-utils@0.21.0(msrcrypto@1.5.8): + resolution: {integrity: sha512-erXqSUxWXFTr8yat5cAo5TxxnzGNDKzx8A5sR2LHxoZfOywBKj7UaqMJ/pKgFwT0agjeMHa57Dq48OQblUV9Tw==} + dependencies: + '@ethersproject/random': 5.7.0 + '@sphereon/isomorphic-webcrypto': 2.4.1-unstable.0(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@stablelib/ed25519': 1.0.3 + '@stablelib/sha256': 1.0.1 + '@stablelib/sha512': 1.0.1 + '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) + base64url: 3.0.1 + debug: 4.3.5 + did-resolver: 4.1.0 + elliptic: 6.5.4 + lodash.isplainobject: 4.0.6 + multiformats: 9.9.0 + uint8arrays: 3.1.1 + varint: 6.0.0 + web-encoding: 1.1.5 + transitivePeerDependencies: + - expo + - expo-crypto + - msrcrypt + - msrcrypto + - react-native-securerandom + - supports-color + dev: false + + /@sphereon/ssi-sdk-ext.key-utils@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-8Ejp4GdjI/UmJX6SZuhM/mB/UdpQolRVJ+TtJrpOSmour74H4OZDPvDHn1EScYurej7vGuNQ/5mEvbnBdmiC0g==} dependencies: '@ethersproject/random': 5.7.0 '@sphereon/isomorphic-webcrypto': 2.4.1-unstable.0(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) @@ -7627,8 +7708,8 @@ packages: - react-native-securerandom - supports-color - /@sphereon/ssi-sdk-ext.kms-local@0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): - resolution: {integrity: sha512-ajKmID67jx/X+fdjkSYYqqvGgBMIXPukfmHznz4vLuD1n6A39s/QwRnxU04Ddqv8BpBkXccgIkXi9VE2f3unpw==} + /@sphereon/ssi-sdk-ext.kms-local@0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1): + resolution: {integrity: sha512-6Ij5nuKKNnirN/XUsa+Q5NSZfGQ6NgVOva7taMj3FbmVQzTGbDGCjfLImw/HWUIMixV8nbUOj7hs/Z0u/orDvQ==} peerDependencies: '@mattrglobal/bbs-signatures': ^1.3.1 peerDependenciesMeta: @@ -7636,8 +7717,8 @@ packages: optional: true dependencies: '@sphereon/isomorphic-webcrypto': 2.4.1-unstable.0(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) - '@sphereon/ssi-sdk-ext.did-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) - '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.5(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.did-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) + '@sphereon/ssi-sdk-ext.key-utils': 0.21.1-next.8(msrcrypto@1.5.8)(react-native-securerandom@1.0.1) '@trust/keyto': 2.0.0-alpha1 '@veramo/core': 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau) '@veramo/key-manager': 4.2.0