Skip to content

Commit

Permalink
Merge pull request #255 from Sphereon-Opensource/feature/SPRIND-84
Browse files Browse the repository at this point in the history
feature/SPRIND-84
  • Loading branch information
sanderPostma authored Nov 4, 2024
2 parents a39cf01 + 3c9ab66 commit e02b30b
Show file tree
Hide file tree
Showing 30 changed files with 1,872 additions and 1,005 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ test/*.js
/packages/pd-manager/plugin.schema.json
/packages/event-logger/plugin.schema.json
/packages/credential-validation/plugin.schema.json
/packages/oidf-client/plugin.schema.json

**/.env.energyshr
**/.env.local
Expand Down
63 changes: 59 additions & 4 deletions packages/data-store/src/__tests__/contact.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { DataSources } from '@sphereon/ssi-sdk.agent-config'
import { DataSource } from 'typeorm'
import { DataStoreContactEntities, DataStoreMigrations, IdentityOrigin, MetadataItem, MetadataTypes, PartyOrigin } from '../index'
import {
ConnectionType,
DataStoreContactEntities,
DataStoreMigrations,
IdentityOrigin,
MetadataItem,
MetadataTypes,
PartyOrigin
} from '../index'
import { ContactStore } from '../contact/ContactStore'
import {
CorrelationIdentifierType,
Expand Down Expand Up @@ -367,6 +375,27 @@ describe('Contact store tests', (): void => {
correlationId: 'example_did3',
},
},
{
alias: 'test_alias4',
origin: IdentityOrigin.EXTERNAL,
roles: [CredentialRole.FEDERATION_TRUST_ANCHOR],
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: <const>'post',
},
},
identifier: {
type: CorrelationIdentifierType.URL,
correlationId: 'example_url4',
},
},
],
electronicAddresses: [
{
Expand All @@ -391,7 +420,7 @@ describe('Contact store tests', (): void => {
}
const result: Array<Party> = await contactStore.getParties(args)

expect(result[0].identities.length).toEqual(3)
expect(result[0].identities.length).toEqual(4)
expect(result[0].electronicAddresses.length).toEqual(1)
})

Expand Down Expand Up @@ -1208,15 +1237,41 @@ describe('Contact store tests', (): void => {
correlationId: 'example_did3',
},
},
{
alias: 'test_alias4',
origin: IdentityOrigin.EXTERNAL,
roles: [CredentialRole.FEDERATION_TRUST_ANCHOR],
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: <const>'post',
},
},
identifier: {
type: CorrelationIdentifierType.URL,
correlationId: 'example_url4',
},
},
],
}

const savedParty: Party = await contactStore.addParty(party)
const result: Party = await contactStore.getParty({ partyId: savedParty.id })

expect(result.roles).toBeDefined()
expect(result.roles.length).toEqual(3)
expect(result.roles).toEqual([CredentialRole.VERIFIER, CredentialRole.ISSUER, CredentialRole.HOLDER])
expect(result.roles.length).toEqual(4)
expect(result.roles).toEqual([
CredentialRole.VERIFIER,
CredentialRole.ISSUER,
CredentialRole.HOLDER,
CredentialRole.FEDERATION_TRUST_ANCHOR
])
})

it('should add relationship', async (): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class CreateDigitalCredential1708525189001 implements MigrationInterface
await queryRunner.query(`CREATE TYPE "digital_document_type" AS ENUM('VC', 'VP', 'C', 'P')`)
await queryRunner.query(`CREATE TYPE "digital_regulation_type" AS ENUM('PID', 'QEAA', 'EAA', 'NON_REGULATED')`)
await queryRunner.query(`CREATE TYPE "digital_credential_document_format" AS ENUM('JSON_LD', 'JWT', 'SD_JWT', 'MSO_MDOC')`)
await queryRunner.query(`CREATE TYPE "digital_credential_credential_role" AS ENUM('ISSUER', 'VERIFIER', 'HOLDER')`)
await queryRunner.query(`CREATE TYPE "digital_credential_credential_role" AS ENUM('ISSUER', 'VERIFIER', 'HOLDER', 'FEDERATION_TRUST_ANCHOR')`)
await queryRunner.query(`CREATE TYPE "digital_credential_correlation_type" AS ENUM('DID', 'KID', 'URL', 'X509_SAN')`)
await queryRunner.query(`CREATE TYPE "digital_credential_state_type" AS ENUM('REVOKED', 'VERIFIED', 'EXPIRED')`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class CreateDigitalCredential1708525189002 implements MigrationInterface
"document_type" varchar CHECK( "document_type" IN ('VC', 'VP', 'C', 'P') ) NOT NULL,
"regulation_type" varchar CHECK( "regulation_type" IN ('PID', 'QEAA', 'EAA', 'NON_REGULATED') ) NOT NULL DEFAULT 'NON_REGULATED',
"document_format" varchar CHECK( "document_format" IN ('JSON_LD', 'JWT', 'SD_JWT', 'MSO_MDOC') ) NOT NULL,
"credential_role" varchar CHECK( "credential_role" IN ('ISSUER', 'VERIFIER', 'HOLDER') ) NOT NULL,
"credential_role" varchar CHECK( "credential_role" IN ('ISSUER', 'VERIFIER', 'HOLDER', 'FEDERATION_TRUST_ANCHOR') ) NOT NULL,
"raw_document" text NOT NULL,
"uniform_document" text NOT NULL,
"credential_id" text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export enum CredentialRole {
ISSUER = 'ISSUER',
VERIFIER = 'VERIFIER',
HOLDER = 'HOLDER',
FEDERATION_TRUST_ANCHOR = 'FEDERATION_TRUST_ANCHOR'
}

export enum CredentialStateType {
Expand Down
1 change: 1 addition & 0 deletions packages/oid4vci-holder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@sphereon/ssi-sdk.data-store": "workspace:*",
"@sphereon/ssi-sdk.issuance-branding": "workspace:*",
"@sphereon/ssi-sdk.mdl-mdoc": "workspace:*",
"@sphereon/ssi-sdk.oidf-client": "workspace:*",
"@sphereon/ssi-sdk.sd-jwt": "workspace:*",
"@sphereon/ssi-sdk.xstate-machine-persistence": "workspace:*",
"@sphereon/ssi-types": "workspace:*",
Expand Down
65 changes: 51 additions & 14 deletions packages/oid4vci-holder/src/agent/OID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import {
GetCredentialsArgs,
GetIssuerMetadataArgs,
IOID4VCIHolder,
GetFederationTrustArgs,
IssuanceOpts,
MappedCredentialToAccept,
OID4VCIHolderEvent,
Expand All @@ -101,7 +102,7 @@ import {
StoreCredentialsArgs,
VerificationResult,
VerifyEBSICredentialIssuerArgs,
VerifyEBSICredentialIssuerResult,
VerifyEBSICredentialIssuerResult
} from '../types/IOID4VCIHolder'
import {
getBasicIssuerLocaleBranding,
Expand Down Expand Up @@ -254,8 +255,8 @@ export class OID4VCIHolder implements IAgentPlugin {
didMethodPreferences,
jwtCryptographicSuitePreferences,
defaultAuthorizationRequestOptions,
hasher,
} = options ?? {}
hasher
} = { ...options }

this.hasher = hasher
if (vcFormatPreferences !== undefined && vcFormatPreferences.length > 0) {
Expand Down Expand Up @@ -319,6 +320,7 @@ export class OID4VCIHolder implements IAgentPlugin {
storeCredentialBranding: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context),
storeCredentials: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context),
sendNotification: (args: SendNotificationArgs) => this.oid4vciHolderSendNotification(args, context),
getFederationTrust: (args: GetFederationTrustArgs) => this.getFederationTrust(args, context)
}

const oid4vciMachineInstanceArgs: OID4VCIMachineInstanceOpts = {
Expand Down Expand Up @@ -987,17 +989,6 @@ export class OID4VCIHolder implements IAgentPlugin {
}
}

private idFromW3cCredentialSubject(wrappedIssuerVC: WrappedW3CVerifiableCredential): string | undefined {
if (Array.isArray(wrappedIssuerVC.credential?.credentialSubject)) {
if (wrappedIssuerVC.credential?.credentialSubject.length > 0) {
return wrappedIssuerVC.credential?.credentialSubject[0].id
}
} else {
return wrappedIssuerVC.credential?.credentialSubject?.id
}
return undefined
}

private async oid4vciHolderSendNotification(args: SendNotificationArgs, context: RequiredContext): Promise<void> {
const { serverMetadata, notificationRequest, openID4VCIClientState } = args
const notificationEndpoint = serverMetadata?.credentialIssuerMetadata?.notification_endpoint
Expand All @@ -1016,6 +1007,41 @@ export class OID4VCIHolder implements IAgentPlugin {
logger.log(`Notification to ${notificationEndpoint} has been dispatched`)
}

private async getFederationTrust(args: GetFederationTrustArgs, context: RequiredContext): Promise<Array<string>> {
const { requestData, serverMetadata, trustAnchors } = args

if (trustAnchors.length === 0) {
return Promise.reject(Error('No trust anchors found'))
}

if (!requestData?.uri) {
return Promise.reject(Error('Missing request URI in context'))
}

if (!serverMetadata) {
return Promise.reject(Error('Missing serverMetadata in context'))
}

const url = new URL(requestData?.uri)
const params = new URLSearchParams(url.search)
const openidFederation = params.get('openid_federation')
const entityIdentifier = openidFederation ?? serverMetadata.issuer

const trustedAnchors = []
for (const trustAnchor of trustAnchors) {
const resolveResult = await context.agent.resolveTrustChain({
entityIdentifier,
trustAnchors: [trustAnchor]
})

if (Array.isArray(resolveResult) && resolveResult.length > 0) {
trustedAnchors.push(trustAnchor)
}
}

return trustedAnchors
}

private async oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise<EndpointMetadataResult> {
const { issuer, errorOnNotFound = true } = args
return MetadataClient.retrieveAllMetadata(issuer, { errorOnNotFound })
Expand Down Expand Up @@ -1047,4 +1073,15 @@ export class OID4VCIHolder implements IAgentPlugin {
}
return [CredentialCorrelationType.URL, issuer]
}

private idFromW3cCredentialSubject(wrappedIssuerVC: WrappedW3CVerifiableCredential): string | undefined {
if (Array.isArray(wrappedIssuerVC.credential?.credentialSubject)) {
if (wrappedIssuerVC.credential?.credentialSubject.length > 0) {
return wrappedIssuerVC.credential?.credentialSubject[0].id
}
} else {
return wrappedIssuerVC.credential?.credentialSubject?.id
}
return undefined
}
}
1 change: 0 additions & 1 deletion packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
} from '@sphereon/ssi-sdk-ext.identifier-resolution'
import { keyTypeFromCryptographicSuite } from '@sphereon/ssi-sdk-ext.key-utils'
import { IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding } from '@sphereon/ssi-sdk.data-store'

import {
CredentialMapper,
IVerifiableCredential,
Expand Down
5 changes: 4 additions & 1 deletion packages/oid4vci-holder/src/link-handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter {
private readonly noStateMachinePersistence: boolean
private readonly authorizationRequestOpts?: AuthorizationRequestOpts
private readonly clientOpts?: AuthorizationServerClientOpts
private readonly trustAnchors?: Array<string>

constructor(
args: Pick<GetMachineArgs, 'stateNavigationListener' | 'authorizationRequestOpts' | 'clientOpts'> & {
args: Pick<GetMachineArgs, 'stateNavigationListener' | 'authorizationRequestOpts' | 'clientOpts' | 'trustAnchors'> & {
priority?: number | DefaultLinkPriorities
protocols?: Array<string | RegExp>
noStateMachinePersistence?: boolean
Expand All @@ -31,6 +32,7 @@ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter {
this.context = args.context
this.noStateMachinePersistence = args.noStateMachinePersistence === true
this.stateNavigationListener = args.stateNavigationListener
this.trustAnchors = args.trustAnchors
}

async handle(
Expand All @@ -57,6 +59,7 @@ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter {
flowType: opts?.flowType,
uri,
},
trustAnchors: this.trustAnchors,
authorizationRequestOpts: { ...this.authorizationRequestOpts, ...opts?.authorizationRequestOpts },
...((clientOpts.clientId || clientOpts.clientAssertionType) && { clientOpts: clientOpts as AuthorizationServerClientOpts }),
stateNavigationListener: this.stateNavigationListener,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"oid4vci_machine_credential_selection_error_title": "Credential selection",
"oid4vci_machine_initiation_error_title": "Initiate OID4VCI provider",
"oid4vci_machine_credential_verification_failed_message": "The credential verification resulted in an error.",
"oid4vci_machine_credential_verification_schema_failed_message": "The credential schema verification resulted in an error."
"oid4vci_machine_credential_verification_schema_failed_message": "The credential schema verification resulted in an error.",
"oid4vci_machine_retrieve_federation_trust_error_title": "Retrieve federation trust"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"oid4vci_machine_retrieve_contact_error_title": "Ophalen contact",
"oid4vci_machine_credential_selection_error_title": "Credential selectie",
"oid4vci_machine_initiation_error_title": "Initiëren OID4VCI provider",
"oid4vci_machine_credential_verification_failed_message": "Verificatie van de credential leidde tot een fout."
"oid4vci_machine_credential_verification_failed_message": "Verificatie van de credential leidde tot een fout.",
"oid4vci_machine_retrieve_federation_trust_error_title": "Ophalen federatievertrouwen"
}
Loading

0 comments on commit e02b30b

Please sign in to comment.