@sphereon/gx-compliance-client
Version:
<!--suppress HtmlDeprecatedAttribute --> <h1 align="center"> <br> <a href="https://www.sphereon.com"><img src="https://sphereon.com/content/themes/sphereon/assets/img/logo.svg" alt="Sphereon" width="400"></a> <br>Gaia-X Compliance client (Typescript
337 lines (308 loc) • 12.8 kB
text/typescript
import { DIDDocument, IAgentPlugin, IIdentifier, IService, VerifiableCredential, VerifiablePresentation } from '@veramo/core'
import {
asDID,
AssertionProofPurpose,
CredentialValidationResult,
ExportFileResult,
extractSubjectDIDFromVCs,
GXRequiredContext,
IAcquireComplianceCredentialFromExistingParticipantArgs,
IGXComplianceClient,
IImportDIDArg,
IOnboardParticipantWithCredentialArgs,
IOnboardParticipantWithCredentialIdsArgs,
ISignInfo,
IVerifySelfDescribedCredential,
schema,
VerifiableCredentialResponse,
} from '../index'
import {
IAcquireComplianceCredentialArgs,
IAcquireComplianceCredentialFromUnsignedParticipantArgs,
IAddServiceOfferingArgs,
IAddServiceOfferingUnsignedArgs,
IGaiaxComplianceConfig,
IGaiaxOnboardingResult,
} from '../types'
import { ICredentialSubject } from '@sphereon/ssi-types'
import { DID } from './DID'
import { CredentialHandler } from './CredentialHandler'
import { extractApiTypeFromVC } from '../utils'
import { getApiVersionedUrl, postRequest } from '../utils'
import { extractSignInfo } from '../utils'
/**
* {@inheritDoc IGXComplianceClient}
*/
export class GXComplianceClient implements IAgentPlugin {
public readonly _config: IGaiaxComplianceConfig
private readonly credentialHandler: CredentialHandler = new CredentialHandler(this)
readonly schema = schema.IGaiaxComplianceClient
constructor(config: IGaiaxComplianceConfig) {
this._config = config
}
public client() {
return this
}
public config() {
return this.client()._config
}
readonly methods: IGXComplianceClient = {
submitComplianceCredential: this.submitComplianceCredential.bind(this),
acquireComplianceCredentialFromExistingParticipant: this.acquireComplianceCredentialFromExistingParticipant.bind(this),
acquireComplianceCredentialFromUnsignedParticipant: this.acquireComplianceCredentialFromUnsignedParticipant.bind(this),
submitServiceOffering: this.submitServiceOffering.bind(this),
createAndSubmitServiceOffering: this.createAndSubmitServiceOffering.bind(this),
createDIDFromX509: this.createDIDFromX509.bind(this),
exportDIDDocument: this.exportDIDDocument.bind(this),
exportDIDToPath: this.exportDIDToPath.bind(this),
issueVerifiableCredential: this.credentialHandler.issueVerifiableCredential.bind(this),
issueVerifiablePresentation: this.credentialHandler.issueVerifiablePresentation.bind(this),
checkVerifiableCredential: this.credentialHandler.checkVerifiableCredential.bind(this),
checkVerifiablePresentation: this.credentialHandler.checkVerifiablePresentation.bind(this),
onboardParticipantWithCredential: this.onboardParticipantWithCredential.bind(this),
onboardParticipantWithCredentialIds: this.onboardParticipantWithCredentialIds.bind(this),
verifySelfDescription: this.verifySelfDescription.bind(this),
}
/** {@inheritDoc IGXComplianceClient.submitComplianceCredential} */
private async submitComplianceCredential(args: IAcquireComplianceCredentialArgs, _context: GXRequiredContext): Promise<VerifiableCredential> {
console.log(JSON.stringify(args.selfDescriptionVP, null, 2))
try {
return (await postRequest(this.getApiVersionedUrl() + '/compliance', JSON.stringify(args.selfDescriptionVP))) as VerifiableCredential
} catch (e) {
throw new Error('Error on fetching complianceVC: ' + e)
}
}
/** {@inheritDoc IGXComplianceClient.acquireComplianceCredentialFromExistingParticipant} */
private async acquireComplianceCredentialFromExistingParticipant(
args: IAcquireComplianceCredentialFromExistingParticipantArgs,
context: GXRequiredContext
): Promise<VerifiableCredentialResponse> {
const selfDescribedVC = await context.agent.dataStoreGetVerifiableCredential({
hash: args.participantSDId,
})
const did = (selfDescribedVC.credentialSubject as ICredentialSubject)['id'] as string
const signInfo: ISignInfo = await extractSignInfo({ did, section: 'authentication' }, context)
const uniqueVP = await this.credentialHandler.issueVerifiablePresentation(
{
keyRef: signInfo.keyRef,
verifiableCredentials: [selfDescribedVC],
challenge: GXComplianceClient.getDateChallenge(),
domain: signInfo.participantDomain,
persist: true,
},
context
)
return this.acquireComplianceCredential(
{
verifiablePresentation: uniqueVP.verifiablePresentation,
},
context
)
}
/** {@inheritDoc IGXComplianceClient.acquireComplianceCredentialFromUnsignedParticipant} */
private async acquireComplianceCredentialFromUnsignedParticipant(
args: IAcquireComplianceCredentialFromUnsignedParticipantArgs,
context: GXRequiredContext
): Promise<VerifiableCredentialResponse> {
const signInfo: ISignInfo = await extractSignInfo(
{
did: args.credential!.credentialSubject!.id as string,
section: 'assertionMethod',
},
context
)
const selfDescription = await this.credentialHandler.issueVerifiableCredential(
{
credential: args.credential,
domain: signInfo.participantDomain,
keyRef: signInfo.keyRef,
persist: true,
},
context
)
console.log(selfDescription.hash)
const uniqueVP = await this.credentialHandler.issueVerifiablePresentation(
{
challenge: GXComplianceClient.getDateChallenge(),
keyRef: signInfo.keyRef,
verifiableCredentials: [selfDescription.verifiableCredential],
domain: signInfo.participantDomain,
},
context
)
const verifiableCredentialResponse = (await this.acquireComplianceCredential(
{
verifiablePresentation: uniqueVP.verifiablePresentation,
},
context
)) as VerifiableCredentialResponse
return verifiableCredentialResponse
}
/** {@inheritDoc IGXComplianceClient.createAndSubmitServiceOffering} */
private async createAndSubmitServiceOffering(args: IAddServiceOfferingUnsignedArgs, context: GXRequiredContext): Promise<IGaiaxOnboardingResult> {
//TODO: implement fetching compliance VC from data storage
if (!args.complianceId && !args.complianceVC) {
throw new Error('You should provide either complianceId or complete complianceVC')
}
const complianceIsPersisted = args.complianceId
const complianceCredential = complianceIsPersisted
? await context.agent.dataStoreGetVerifiableCredential({
hash: args.complianceId!,
})
: args.complianceVC!
const did = asDID(args.domain ?? extractSubjectDIDFromVCs([complianceCredential]))
const serviceOffering = await this.credentialHandler.issueVerifiableCredential(
{
domain: did,
keyRef: args.keyRef,
credential: args.serviceOfferingCredential,
persist: true,
},
context
)
const serviceOfferingVP = await this.credentialHandler.issueVerifiablePresentation(
{
challenge: args.challenge ? args.challenge : GXComplianceClient.getDateChallenge(),
keyRef: args.keyRef,
// purpose: args.purpose,
verifiableCredentials: [complianceCredential, serviceOffering.verifiableCredential],
domain: did,
persist: true,
},
context
)
return await this.submitServiceOffering(
{
serviceOfferingVP: serviceOfferingVP.verifiablePresentation,
},
context
)
}
/** {@inheritDoc IGXComplianceClient.submitServiceOffering} */
private async submitServiceOffering(args: IAddServiceOfferingArgs, _context: GXRequiredContext): Promise<IGaiaxOnboardingResult> {
try {
return (await postRequest(
this.getApiVersionedUrl() + '/service-offering/verify/raw',
JSON.stringify(args.serviceOfferingVP)
)) as IGaiaxOnboardingResult
} catch (e) {
throw new Error('Error on fetching complianceVC: ' + e)
}
}
/** {@inheritDoc IGXComplianceClient.createDIDFromX509} */
private async createDIDFromX509(args: IImportDIDArg, context: GXRequiredContext): Promise<IIdentifier> {
return DID.createDIDFromX509(
{
...args,
kms: args.kms ? args.kms : this._config.kmsName ? this._config.kmsName : 'local',
},
context
)
}
private async exportDIDDocument({ domain, services }: { domain: string; services?: IService[] }, context: GXRequiredContext): Promise<DIDDocument> {
return DID.exportDocument({ domain, services }, context)
}
private async exportDIDToPath(
{ domain, services, path }: { domain: string; path?: string; services?: IService[] },
context: GXRequiredContext
): Promise<ExportFileResult[]> {
return DID.exportToPath({ domain, path, services }, context)
}
/** {@inheritDoc IGXComplianceClient.verifyUnsignedSelfDescribedCredential} */
private async verifySelfDescription(args: IVerifySelfDescribedCredential, context: GXRequiredContext): Promise<CredentialValidationResult> {
if (!args.verifiableCredential && !args.id) {
throw new Error('You should provide either vc id or vc itself')
}
const vc = args.verifiableCredential
? args.verifiableCredential
: await context.agent.dataStoreGetVerifiableCredential({
hash: args.id as string,
})
const valid = context.agent.verifyCredentialLDLocal({
credential: vc,
purpose: new AssertionProofPurpose(),
fetchRemoteContexts: true,
})
if (!valid) {
throw Error(`Invalid verifiable credential supplied`)
}
let url = this.getApiVersionedUrl()
if ((vc.type as string[]).indexOf('ServiceOffering') != -1) {
url = url + '/service-offering/validate/vc'
} else if ((vc.type as string[]).indexOf('LegalPerson') != -1 || (vc.type as string[]).indexOf('NaturalPerson') != -1) {
url = url + '/participant/validate/vc'
}
try {
return (await postRequest(url, JSON.stringify(vc))) as CredentialValidationResult
} catch (e: any) {
throw new Error('Error on fetching complianceCredential: ' + e.message)
}
}
/**
* Below are the helper functions for this agent. These are for inner functionality of the agent
*/
private async acquireComplianceCredential(
args: { verifiablePresentation: VerifiablePresentation },
context: GXRequiredContext
): Promise<VerifiableCredentialResponse> {
const complianceCredential = await this.submitComplianceCredential(
{
selfDescriptionVP: args.verifiablePresentation,
},
context
)
const hash: string = await context.agent.dataStoreSaveVerifiableCredential({
verifiableCredential: complianceCredential,
})
return {
verifiableCredential: complianceCredential,
hash,
}
}
public static getDateChallenge(): string {
return new Date().toISOString().substring(0, 10)
}
private async onboardParticipantWithCredential(args: IOnboardParticipantWithCredentialArgs, context: GXRequiredContext) {
const onboardingVP = await this.credentialHandler.issueVerifiablePresentation(
{
keyRef: args.keyRef,
// purpose: args.purpose,
verifiableCredentials: [args.complianceVC, args.selfDescriptionVC],
challenge: args.challenge ? args.challenge : GXComplianceClient.getDateChallenge(),
domain: asDID(args.domain ?? extractSubjectDIDFromVCs([args.selfDescriptionVC])),
persist: true,
},
context
)
const apiType = extractApiTypeFromVC(args.selfDescriptionVC)
const URL = `${this.getApiVersionedUrl()}/${apiType}/verify/raw?store=true`
try {
return (await postRequest(URL, JSON.stringify(onboardingVP))) as VerifiableCredential
} catch (e) {
throw new Error('Error on onboarding a complianceVC: ' + e)
}
}
private async onboardParticipantWithCredentialIds(args: IOnboardParticipantWithCredentialIdsArgs, context: GXRequiredContext) {
const complianceCredential = await context.agent.dataStoreGetVerifiableCredential({
hash: args.complianceId,
})
const selfDescriptionVC = await context.agent.dataStoreGetVerifiableCredential({
hash: args.selfDescriptionId,
})
const did = (selfDescriptionVC.credentialSubject as ICredentialSubject)['id'] as string
const signInfo: ISignInfo = await extractSignInfo({ did, section: 'authentication' }, context)
return this.onboardParticipantWithCredential(
{
complianceVC: complianceCredential,
selfDescriptionVC: selfDescriptionVC,
keyRef: signInfo.keyRef,
domain: signInfo.participantDomain,
challenge: GXComplianceClient.getDateChallenge(),
},
context
)
}
private getApiVersionedUrl() {
return getApiVersionedUrl(this._config)
}
}