name: did-credential-lifecycle
description: Verifiable credential lifecycle management for agent identity. Issue, verify, revoke credentials, DID document key rotation, credential schema validation, and W3C VC data model compliance. Sources: microsoft/did-sdk-js, W3C VC spec.
origin: yana-ai — synthesized from microsoft/did-sdk-js (MIT), W3C Verifiable Credentials Data Model 1.1
license: Apache-2.0
version: 1.0.0
compatibility: yana-ai >= 1.3.49
/did-credential-lifecycle
When to Use
- Full lifecycle management: issue → present → verify → revoke agent credentials
- Key rotation: update DID document when signing key is compromised
- Credential schema validation: ensure agent credentials conform to expected structure
- Multi-issuer trust: accept credentials from a set of trusted issuers only
Do NOT use for
- Simple JWT token auth (use [[jwt-jws-jwe-patterns]])
- Single-agent environments without identity governance requirements
W3C Verifiable Credential structure
interface VerifiableCredential {
'@context': string[]
id: string
type: string[]
issuer: string | { id: string }
issuanceDate: string
expirationDate?: string
credentialSubject: Record<string, unknown>
credentialStatus?: {
id: string
type: string
}
proof: {
type: string
created: string
verificationMethod: string
proofPurpose: string
jws: string
}
}
Issue credential
function issueCredential(params: {
issuerDid: string
issuerPriv: string
subjectDid: string
claims: Record<string, unknown>
ttlHours: number
}): VerifiableCredential {
const { issuerDid, issuerPriv, subjectDid, claims, ttlHours } = params
const now = new Date()
const expiry = new Date(now.getTime() + ttlHours * 3600 * 1000)
const credential = {
'@context': ['https://www.w3.org/2018/credentials/v1'],
id: `urn:yamtam:vc:${Date.now()}`,
type: ['VerifiableCredential'],
issuer: issuerDid,
issuanceDate: now.toISOString(),
expirationDate: expiry.toISOString(),
credentialSubject: { id: subjectDid, ...claims },
}
const jws = signCommand(issuerPriv, JSON.stringify(credential))
return { ...credential, proof: {
type: 'EcdsaSecp256k1Signature2019',
created: now.toISOString(),
verificationMethod: `${issuerDid}#key-1`,
proofPurpose: 'assertionMethod',
jws,
}}
}
Verify credential
async function verifyCredential(
vc: VerifiableCredential,
trustedIssuers: string[]
): Promise<{ valid: boolean; reason?: string }> {
const issuer = typeof vc.issuer === 'string' ? vc.issuer : vc.issuer.id
if (!trustedIssuers.includes(issuer)) {
return { valid: false, reason: `untrusted issuer: ${issuer}` }
}
if (vc.expirationDate && new Date(vc.expirationDate) < new Date()) {
return { valid: false, reason: 'credential expired' }
}
const doc = await resolveAgent(issuer)
const pubKey = doc?.verificationMethod?.[0]?.publicKeyHex
if (!pubKey) return { valid: false, reason: 'cannot resolve issuer key' }
payload = { ...vc }
(payload any).
valid = (pubKey, .(payload), vc..)
valid ? { : } : { : , : }
}
Key rotation
function rotateKey(did: string, oldPriv: string, newPubHex: string): object {
const updateDoc = {
'@context': 'https://www.w3.org/ns/did/v1',
id: did,
verificationMethod: [{
id: `${did}#key-2`,
type: 'EcdsaSecp256k1VerificationKey2019',
controller: did,
publicKeyHex: newPubHex,
}],
authentication: [`${did}#key-2`],
}
const sig = signCommand(oldPriv, JSON.stringify(updateDoc))
return { ...updateDoc, updateProof: { jws: sig, signingKey: `${did}#key-1` } }
}
Anti-Fake-Pass Checklist
❌ No expiry on credentials → valid indefinitely, cannot force re-authentication
❌ Trusting any issuer → accept credentials from attacker-controlled DID
❌ Proof signature verified but issuer DID not checked → forged issuer field
❌ Key rotation without revocation list → old key signatures still accepted
❌ Credential status not checked → revoked credentials accepted as valid
❌ '@context' not validated → missing context means non-standard claims