anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
313 lines • 14.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceProvider = void 0;
const jose_1 = require("jose");
const did_1 = require("../core/did");
const selective_disclosure_1 = require("../zkp/selective-disclosure");
const revocation_service_1 = require("../revocation/revocation-service");
const storage_1 = require("../storage");
const session_manager_1 = require("./session-manager");
const verification_errors_1 = require("./verification-errors");
const batch_operations_1 = require("./batch-operations");
const presentation_request_1 = require("./presentation-request");
class ServiceProvider {
constructor(name, trustedIssuers = [], options = {}) {
this.name = name;
this.trustedIssuers = new Set(trustedIssuers);
this.checkRevocation = options.checkRevocation ?? true;
this.storageProvider = options.storageProvider || storage_1.StorageFactory.getDefaultProvider();
this.sessionManager = new session_manager_1.SessionManager(options.sessionManager);
this.batchOperations = new batch_operations_1.BatchOperations(options.batchOperations);
// For presentation requests, we need a DID - in practice this would be the service provider's DID
this.presentationRequest = new presentation_request_1.PresentationRequest(`did:key:${name}`);
}
// Backward compatibility constructor
static create(name, trustedIssuers = [], checkRevocation = true, storageProvider) {
return new ServiceProvider(name, trustedIssuers, {
checkRevocation,
storageProvider
});
}
async verifyPresentation(presentation) {
const errors = [];
const timestamp = new Date();
try {
// 1. Verify the presentation signature
if (!presentation.proof?.jws) {
return {
valid: false,
errors: [verification_errors_1.VerificationError.missingProof('presentation')],
timestamp
};
}
// Extract holder DID from proof
const holderDID = presentation.proof.verificationMethod.split('#')[0];
// Verify presentation JWT
const isPresentationValid = await this.verifyJWT(presentation.proof.jws, holderDID);
if (!isPresentationValid.valid) {
return {
valid: false,
errors: [verification_errors_1.VerificationError.invalidPresentationSignature(isPresentationValid.error)],
timestamp
};
}
// 2. Verify each credential in the presentation
const verifiedCredentials = [];
for (const credential of presentation.verifiableCredential) {
// Check if this is a selectively disclosed credential
const isSelectivelyDisclosed = credential.type.includes('SelectivelyDisclosedCredential');
if (isSelectivelyDisclosed) {
const sdCredential = credential;
// Verify the original issuer's signature
const credResult = await this.verifyCredential(sdCredential);
if (!credResult.valid) {
errors.push(verification_errors_1.VerificationError.invalidSignature(sdCredential.id, credResult.error));
continue;
}
// Verify the selective disclosure proof
const holderKey = did_1.DIDService.getPublicKeyFromDID(holderDID);
const sdValid = await selective_disclosure_1.SelectiveDisclosure.verifySelectiveDisclosure(sdCredential, holderKey);
if (!sdValid) {
errors.push(verification_errors_1.VerificationError.invalidDisclosureProof(sdCredential.id));
continue;
}
// Check if issuer is trusted
if (!this.trustedIssuers.has(sdCredential.issuer)) {
errors.push(verification_errors_1.VerificationError.untrustedIssuer(sdCredential.issuer, sdCredential.id));
continue;
}
// Check revocation status
if (this.checkRevocation) {
const isRevoked = await this.checkCredentialRevocation(sdCredential.id, sdCredential.issuer);
if (isRevoked) {
errors.push(verification_errors_1.VerificationError.revokedCredential(sdCredential.id, sdCredential.issuer));
continue;
}
}
// Extract disclosed attributes
const { id, ...attributes } = sdCredential.credentialSubject;
verifiedCredentials.push({
id: sdCredential.id,
issuer: sdCredential.issuer,
type: sdCredential.type,
attributes,
selectivelyDisclosed: true,
disclosedAttributes: sdCredential.disclosureProof?.disclosedAttributes
});
}
else {
// Regular credential verification
const credResult = await this.verifyCredential(credential);
if (!credResult.valid) {
errors.push(verification_errors_1.VerificationError.invalidSignature(credential.id, credResult.error));
continue;
}
// Check if issuer is trusted
if (!this.trustedIssuers.has(credential.issuer)) {
errors.push(verification_errors_1.VerificationError.untrustedIssuer(credential.issuer, credential.id));
continue;
}
// Check revocation status
if (this.checkRevocation) {
const isRevoked = await this.checkCredentialRevocation(credential.id, credential.issuer);
if (isRevoked) {
errors.push(verification_errors_1.VerificationError.revokedCredential(credential.id, credential.issuer));
continue;
}
}
// Extract relevant attributes
const { id, ...attributes } = credential.credentialSubject;
verifiedCredentials.push({
id: credential.id,
issuer: credential.issuer,
type: credential.type,
attributes
});
}
}
if (verifiedCredentials.length === 0 && errors.length > 0) {
return {
valid: false,
errors,
timestamp
};
}
return {
valid: true,
holder: holderDID,
credentials: verifiedCredentials,
errors: errors.length > 0 ? errors : undefined,
timestamp
};
}
catch (error) {
const verificationError = error instanceof Error
? new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.NETWORK_ERROR, `Verification error: ${error.message}`)
: new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.NETWORK_ERROR, 'Unknown verification error');
return {
valid: false,
errors: [verificationError],
timestamp
};
}
}
async verifyCredential(credential) {
try {
if (!credential.proof?.jws) {
return { valid: false, error: 'Credential missing proof' };
}
const result = await this.verifyJWT(credential.proof.jws, credential.issuer);
return result;
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async verifyJWT(jwt, issuerDID) {
try {
// Extract public key from DID
const publicKey = did_1.DIDService.getPublicKeyFromDID(issuerDID);
// Convert to JWK for jose
const publicKeyJwk = {
kty: 'OKP',
crv: 'Ed25519',
x: Buffer.from(publicKey).toString('base64url')
};
const key = await (0, jose_1.importJWK)(publicKeyJwk, 'EdDSA');
// Verify JWT
await (0, jose_1.jwtVerify)(jwt, key, {
algorithms: ['EdDSA']
});
return { valid: true };
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Invalid signature'
};
}
}
addTrustedIssuer(issuerDID) {
this.trustedIssuers.add(issuerDID);
}
removeTrustedIssuer(issuerDID) {
this.trustedIssuers.delete(issuerDID);
}
getTrustedIssuers() {
return Array.from(this.trustedIssuers);
}
getName() {
return this.name;
}
setRevocationCheck(enabled) {
this.checkRevocation = enabled;
}
async checkCredentialRevocation(credentialId, issuerDID) {
try {
// First check storage provider
const isRevoked = await this.storageProvider.checkRevocation(issuerDID, credentialId);
if (isRevoked) {
return true;
}
// Also check the mock registry for backward compatibility
const revocationList = await revocation_service_1.RevocationService.fetchRevocationListByIssuer(issuerDID);
if (!revocationList) {
// No revocation list published - credential is not revoked
return false;
}
// Verify the revocation list signature
const issuerPublicKey = did_1.DIDService.getPublicKeyFromDID(issuerDID);
const isValid = await revocation_service_1.RevocationService.verifyRevocationList(revocationList, issuerPublicKey);
if (!isValid) {
// Invalid revocation list - treat as not revoked but log warning
console.warn(`Invalid revocation list signature from issuer ${issuerDID}`);
return false;
}
// Check if the credential ID is in the revocation list
return revocationList.revokedCredentials.includes(credentialId);
}
catch (error) {
// Error checking revocation - treat as not revoked
console.error(`Error checking revocation for credential ${credentialId}:`, error);
return false;
}
}
setStorageProvider(provider) {
this.storageProvider = provider;
}
// Session Management Methods
async createSession(verificationResult, metadata) {
return this.sessionManager.createSession(verificationResult, metadata);
}
async validateSession(sessionId) {
return this.sessionManager.validateSession(sessionId);
}
async setSessionExpiry(sessionId, duration) {
return this.sessionManager.setSessionExpiry(sessionId, duration);
}
getSession(sessionId) {
return this.sessionManager.getSession(sessionId);
}
getAllSessions() {
return this.sessionManager.getAllSessions();
}
getSessionsByHolder(holderDID) {
return this.sessionManager.getSessionsByHolder(holderDID);
}
removeSession(sessionId) {
this.sessionManager.removeSession(sessionId);
}
clearAllSessions() {
this.sessionManager.clearAllSessions();
}
// Enhanced verification with automatic session creation
async verifyPresentationWithSession(presentation, createSession = true, sessionMetadata) {
const verificationResult = await this.verifyPresentation(presentation);
if (createSession && verificationResult.valid) {
const session = await this.createSession(verificationResult, sessionMetadata);
return { verification: verificationResult, session };
}
return { verification: verificationResult };
}
// Handle credential revocation by invalidating related sessions
async handleCredentialRevocation(credentialId) {
await this.sessionManager.revokeSessions(credentialId);
}
// Cleanup method
destroy() {
this.sessionManager.destroy();
}
// Batch Operations
async batchVerifyPresentations(presentations) {
return this.batchOperations.batchVerifyPresentations(presentations, (presentation) => this.verifyPresentation(presentation));
}
async batchCheckRevocations(credentialIds) {
return this.batchOperations.batchCheckRevocations(credentialIds, (credentialId) => this.checkCredentialRevocation(credentialId, 'unknown'));
}
async batchVerifyWithRevocationCheck(presentations) {
return this.batchOperations.batchVerifyWithRevocationCheck(presentations, (presentation) => this.verifyPresentation(presentation), (credentialId) => this.checkCredentialRevocation(credentialId, 'unknown'));
}
// Presentation Request Protocol
async createPresentationRequest(options) {
return this.presentationRequest.createRequest(options);
}
async createSimplePresentationRequest(credentialTypes, purpose, requiredAttributes = [], optionalAttributes = []) {
return this.presentationRequest.createSimpleRequest(credentialTypes, purpose, requiredAttributes, optionalAttributes);
}
async validatePresentationAgainstRequest(presentation, request) {
return this.presentationRequest.validateAgainstRequest(presentation, request);
}
// Enhanced verification that includes request validation
async verifyPresentationWithRequest(presentation, request) {
const [verification, requestValidation] = await Promise.all([
this.verifyPresentation(presentation),
this.validatePresentationAgainstRequest(presentation, request)
]);
return { verification, requestValidation };
}
}
exports.ServiceProvider = ServiceProvider;
//# sourceMappingURL=service-provider.js.map