/** * Automated credential verification workflow * Auto-verify on receipt, verification receipt issuance, chain tracking, revocation checking */ import { getEventBus, CredentialEvents } from '@the-order/events'; import { getVerifiableCredentialById, isCredentialRevoked, createVerifiableCredential, logCredentialAction, } from '@the-order/database'; import { KMSClient } from '@the-order/crypto'; import { DIDResolver } from '@the-order/auth'; import { getEnv } from '@the-order/shared'; import { randomUUID } from 'crypto'; export interface VerificationResult { valid: boolean; credentialId: string; verifiedAt: Date; verificationReceiptId?: string; errors?: string[]; warnings?: string[]; } /** * Initialize automated credential verification */ export async function initializeAutomatedVerification(kmsClient: KMSClient): Promise { const eventBus = getEventBus(); // Subscribe to credential received events await eventBus.subscribe('credential.received', async (data) => { const eventData = data as { credentialId: string; receivedBy: string; source?: string; }; try { const result = await verifyCredential(eventData.credentialId, kmsClient); // Publish verification event await eventBus.publish(CredentialEvents.VERIFIED, { credentialId: eventData.credentialId, valid: result.valid, verifiedAt: result.verifiedAt.toISOString(), verificationReceiptId: result.verificationReceiptId, errors: result.errors, warnings: result.warnings, }); // Issue verification receipt if valid if (result.valid && result.verificationReceiptId) { await eventBus.publish(CredentialEvents.ISSUED, { subjectDid: eventData.receivedBy, credentialType: ['VerifiableCredential', 'VerificationReceipt'], credentialId: result.verificationReceiptId, issuedAt: result.verifiedAt.toISOString(), }); } } catch (error) { console.error('Failed to verify credential:', error); } }); } /** * Verify a credential */ export async function verifyCredential( credentialId: string, kmsClient: KMSClient ): Promise { const errors: string[] = []; const warnings: string[] = []; // Get credential from database const credential = await getVerifiableCredentialById(credentialId); if (!credential) { return { valid: false, credentialId, verifiedAt: new Date(), errors: ['Credential not found'], }; } // Check if revoked const revoked = await isCredentialRevoked(credentialId); if (revoked) { return { valid: false, credentialId, verifiedAt: new Date(), errors: ['Credential has been revoked'], }; } // Check expiration if (credential.expiration_date && new Date() > credential.expiration_date) { return { valid: false, credentialId, verifiedAt: new Date(), errors: ['Credential has expired'], }; } // Verify proof/signature try { const proof = credential.proof as { type?: string; verificationMethod?: string; jws?: string; created?: string; }; if (!proof || !proof.jws) { errors.push('Credential missing proof'); } else { // Verify signature using issuer DID const resolver = new DIDResolver(); const credentialData = { id: credential.credential_id, type: credential.credential_type, issuer: credential.issuer_did, subject: credential.subject_did, credentialSubject: credential.credential_subject, issuanceDate: credential.issuance_date.toISOString(), expirationDate: credential.expiration_date?.toISOString(), }; const credentialJson = JSON.stringify(credentialData); const isValid = await resolver.verifySignature( credential.issuer_did, credentialJson, proof.jws ); if (!isValid) { errors.push('Credential signature verification failed'); } } } catch (error) { errors.push(`Signature verification error: ${error instanceof Error ? error.message : 'Unknown error'}`); } // Verify credential chain (if applicable) // This would check parent credentials, issuer credentials, etc. // For now, we'll just log a warning if chain verification is needed if (credential.credential_type.includes('ChainCredential')) { warnings.push('Credential chain verification not fully implemented'); } const valid = errors.length === 0; // Create verification receipt if valid let verificationReceiptId: string | undefined; if (valid) { try { verificationReceiptId = await createVerificationReceipt(credentialId, credential.issuer_did, kmsClient); } catch (error) { warnings.push(`Failed to create verification receipt: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Log verification action await logCredentialAction({ credential_id: credentialId, issuer_did: credential.issuer_did, subject_did: credential.subject_did, credential_type: credential.credential_type, action: 'verified', metadata: { valid, errors, warnings, verificationReceiptId, }, }); return { valid, credentialId, verifiedAt: new Date(), verificationReceiptId, errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined, }; } /** * Create verification receipt */ async function createVerificationReceipt( verifiedCredentialId: string, issuerDid: string, kmsClient: KMSClient ): Promise { const env = getEnv(); const receiptIssuerDid = env.VC_ISSUER_DID || (env.VC_ISSUER_DOMAIN ? `did:web:${env.VC_ISSUER_DOMAIN}` : undefined); if (!receiptIssuerDid) { throw new Error('VC_ISSUER_DID or VC_ISSUER_DOMAIN must be configured'); } const receiptId = randomUUID(); const issuanceDate = new Date(); const receiptData = { id: receiptId, type: ['VerifiableCredential', 'VerificationReceipt'], issuer: receiptIssuerDid, subject: issuerDid, credentialSubject: { verifiedCredentialId, verifiedAt: issuanceDate.toISOString(), verificationStatus: 'valid', }, issuanceDate: issuanceDate.toISOString(), }; const receiptJson = JSON.stringify(receiptData); const signature = await kmsClient.sign(Buffer.from(receiptJson)); const proof = { type: 'KmsSignature2024', created: issuanceDate.toISOString(), proofPurpose: 'assertionMethod', verificationMethod: `${receiptIssuerDid}#kms-key`, jws: signature.toString('base64'), }; await createVerifiableCredential({ credential_id: receiptId, issuer_did: receiptIssuerDid, subject_did: issuerDid, credential_type: receiptData.type, credential_subject: receiptData.credentialSubject, issuance_date: issuanceDate, expiration_date: undefined, proof, }); return receiptId; } /** * Verify credential chain */ export async function verifyCredentialChain(credentialId: string): Promise<{ valid: boolean; chain: Array<{ credentialId: string; valid: boolean }>; errors: string[]; }> { const chain: Array<{ credentialId: string; valid: boolean }> = []; const errors: string[] = []; // Get credential const credential = await getVerifiableCredentialById(credentialId); if (!credential) { return { valid: false, chain, errors: ['Credential not found'] }; } // Verify this credential (requires KMS client - this is a placeholder) // In production, this should be passed as a parameter // For now, we'll create a minimal verification const credentialVerification = await getVerifiableCredentialById(credentialId); const isValid = credentialVerification !== null && !credentialVerification.revoked; chain.push({ credentialId, valid: isValid }); const verification = { valid: isValid, credentialId, verifiedAt: new Date(), errors: isValid ? undefined : ['Credential not found or revoked'], }; if (!verification.valid && verification.errors) { errors.push(...verification.errors); } // In production, this would recursively verify parent credentials // For now, we'll just verify the immediate credential return { valid: chain.every((c) => c.valid), chain, errors, }; }