@mcp-i/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/dist/auth/handshake.d.ts +104 -0
- package/dist/auth/handshake.d.ts.map +1 -0
- package/dist/auth/handshake.js +230 -0
- package/dist/auth/handshake.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +31 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +7 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/delegation/audience-validator.d.ts +9 -0
- package/dist/delegation/audience-validator.d.ts.map +1 -0
- package/dist/delegation/audience-validator.js +17 -0
- package/dist/delegation/audience-validator.js.map +1 -0
- package/dist/delegation/bitstring.d.ts +37 -0
- package/dist/delegation/bitstring.d.ts.map +1 -0
- package/dist/delegation/bitstring.js +117 -0
- package/dist/delegation/bitstring.js.map +1 -0
- package/dist/delegation/cascading-revocation.d.ts +45 -0
- package/dist/delegation/cascading-revocation.d.ts.map +1 -0
- package/dist/delegation/cascading-revocation.js +148 -0
- package/dist/delegation/cascading-revocation.js.map +1 -0
- package/dist/delegation/delegation-graph.d.ts +49 -0
- package/dist/delegation/delegation-graph.d.ts.map +1 -0
- package/dist/delegation/delegation-graph.js +99 -0
- package/dist/delegation/delegation-graph.js.map +1 -0
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +154 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/did-web-resolver.d.ts +83 -0
- package/dist/delegation/did-web-resolver.d.ts.map +1 -0
- package/dist/delegation/did-web-resolver.js +218 -0
- package/dist/delegation/did-web-resolver.js.map +1 -0
- package/dist/delegation/index.d.ts +21 -0
- package/dist/delegation/index.d.ts.map +1 -0
- package/dist/delegation/index.js +21 -0
- package/dist/delegation/index.js.map +1 -0
- package/dist/delegation/outbound-headers.d.ts +81 -0
- package/dist/delegation/outbound-headers.d.ts.map +1 -0
- package/dist/delegation/outbound-headers.js +139 -0
- package/dist/delegation/outbound-headers.js.map +1 -0
- package/dist/delegation/outbound-proof.d.ts +43 -0
- package/dist/delegation/outbound-proof.d.ts.map +1 -0
- package/dist/delegation/outbound-proof.js +52 -0
- package/dist/delegation/outbound-proof.js.map +1 -0
- package/dist/delegation/statuslist-manager.d.ts +44 -0
- package/dist/delegation/statuslist-manager.d.ts.map +1 -0
- package/dist/delegation/statuslist-manager.js +126 -0
- package/dist/delegation/statuslist-manager.js.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.js +145 -0
- package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
- package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
- package/dist/delegation/utils.d.ts +49 -0
- package/dist/delegation/utils.d.ts.map +1 -0
- package/dist/delegation/utils.js +131 -0
- package/dist/delegation/utils.js.map +1 -0
- package/dist/delegation/vc-issuer.d.ts +56 -0
- package/dist/delegation/vc-issuer.d.ts.map +1 -0
- package/dist/delegation/vc-issuer.js +80 -0
- package/dist/delegation/vc-issuer.js.map +1 -0
- package/dist/delegation/vc-verifier.d.ts +112 -0
- package/dist/delegation/vc-verifier.d.ts.map +1 -0
- package/dist/delegation/vc-verifier.js +280 -0
- package/dist/delegation/vc-verifier.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +23 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +82 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/with-mcpi.d.ts +152 -0
- package/dist/middleware/with-mcpi.d.ts.map +1 -0
- package/dist/middleware/with-mcpi.js +472 -0
- package/dist/middleware/with-mcpi.js.map +1 -0
- package/dist/proof/errors.d.ts +49 -0
- package/dist/proof/errors.d.ts.map +1 -0
- package/dist/proof/errors.js +61 -0
- package/dist/proof/errors.js.map +1 -0
- package/dist/proof/generator.d.ts +65 -0
- package/dist/proof/generator.d.ts.map +1 -0
- package/dist/proof/generator.js +163 -0
- package/dist/proof/generator.js.map +1 -0
- package/dist/proof/index.d.ts +4 -0
- package/dist/proof/index.d.ts.map +1 -0
- package/dist/proof/index.js +4 -0
- package/dist/proof/index.js.map +1 -0
- package/dist/proof/verifier.d.ts +108 -0
- package/dist/proof/verifier.d.ts.map +1 -0
- package/dist/proof/verifier.js +299 -0
- package/dist/proof/verifier.js.map +1 -0
- package/dist/providers/base.d.ts +64 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +19 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/memory.d.ts +33 -0
- package/dist/providers/memory.d.ts.map +1 -0
- package/dist/providers/memory.js +102 -0
- package/dist/providers/memory.js.map +1 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +77 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +251 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/types/protocol.d.ts +320 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +229 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +104 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/base64.d.ts +13 -0
- package/dist/utils/base64.d.ts.map +1 -0
- package/dist/utils/base64.js +99 -0
- package/dist/utils/base64.js.map +1 -0
- package/dist/utils/crypto-service.d.ts +37 -0
- package/dist/utils/crypto-service.d.ts.map +1 -0
- package/dist/utils/crypto-service.js +153 -0
- package/dist/utils/crypto-service.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +156 -0
- package/dist/utils/did-helpers.d.ts.map +1 -0
- package/dist/utils/did-helpers.js +193 -0
- package/dist/utils/did-helpers.js.map +1 -0
- package/dist/utils/ed25519-constants.d.ts +18 -0
- package/dist/utils/ed25519-constants.d.ts.map +1 -0
- package/dist/utils/ed25519-constants.js +21 -0
- package/dist/utils/ed25519-constants.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +105 -0
- package/src/__tests__/integration/full-flow.test.ts +362 -0
- package/src/__tests__/providers/base.test.ts +173 -0
- package/src/__tests__/providers/memory.test.ts +332 -0
- package/src/__tests__/utils/mock-providers.ts +319 -0
- package/src/__tests__/utils/node-crypto-provider.ts +93 -0
- package/src/auth/handshake.ts +411 -0
- package/src/auth/index.ts +11 -0
- package/src/auth/types.ts +40 -0
- package/src/delegation/__tests__/audience-validator.test.ts +110 -0
- package/src/delegation/__tests__/bitstring.test.ts +346 -0
- package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
- package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
- package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
- package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
- package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
- package/src/delegation/__tests__/utils.test.ts +185 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
- package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
- package/src/delegation/audience-validator.ts +24 -0
- package/src/delegation/bitstring.ts +160 -0
- package/src/delegation/cascading-revocation.ts +224 -0
- package/src/delegation/delegation-graph.ts +143 -0
- package/src/delegation/did-key-resolver.ts +181 -0
- package/src/delegation/did-web-resolver.ts +270 -0
- package/src/delegation/index.ts +33 -0
- package/src/delegation/outbound-headers.ts +193 -0
- package/src/delegation/outbound-proof.ts +90 -0
- package/src/delegation/statuslist-manager.ts +219 -0
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
- package/src/delegation/storage/memory-graph-storage.ts +178 -0
- package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
- package/src/delegation/utils.ts +189 -0
- package/src/delegation/vc-issuer.ts +137 -0
- package/src/delegation/vc-verifier.ts +440 -0
- package/src/index.ts +264 -0
- package/src/logging/__tests__/logger.test.ts +366 -0
- package/src/logging/index.ts +6 -0
- package/src/logging/logger.ts +91 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
- package/src/middleware/index.ts +16 -0
- package/src/middleware/with-mcpi.ts +766 -0
- package/src/proof/__tests__/proof-generator.test.ts +483 -0
- package/src/proof/__tests__/verifier.test.ts +488 -0
- package/src/proof/errors.ts +75 -0
- package/src/proof/generator.ts +255 -0
- package/src/proof/index.ts +22 -0
- package/src/proof/verifier.ts +449 -0
- package/src/providers/base.ts +68 -0
- package/src/providers/index.ts +15 -0
- package/src/providers/memory.ts +130 -0
- package/src/session/__tests__/session-manager.test.ts +342 -0
- package/src/session/index.ts +7 -0
- package/src/session/manager.ts +332 -0
- package/src/types/protocol.ts +596 -0
- package/src/utils/__tests__/base58.test.ts +281 -0
- package/src/utils/__tests__/base64.test.ts +239 -0
- package/src/utils/__tests__/crypto-service.test.ts +530 -0
- package/src/utils/__tests__/did-helpers.test.ts +156 -0
- package/src/utils/base58.ts +115 -0
- package/src/utils/base64.ts +116 -0
- package/src/utils/crypto-service.ts +209 -0
- package/src/utils/did-helpers.ts +210 -0
- package/src/utils/ed25519-constants.ts +23 -0
- package/src/utils/index.ts +9 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Audience Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates if a delegation's audience matches the server DID.
|
|
5
|
+
* Supports both single server DID and multiple server DIDs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DelegationRecord } from '../types/protocol.js';
|
|
9
|
+
|
|
10
|
+
export function verifyDelegationAudience(
|
|
11
|
+
delegation: DelegationRecord,
|
|
12
|
+
serverDid: string
|
|
13
|
+
): boolean {
|
|
14
|
+
if (!delegation.constraints.audience) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const audience = delegation.constraints.audience;
|
|
19
|
+
if (typeof audience === 'string') {
|
|
20
|
+
return audience === serverDid;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return audience.includes(serverDid);
|
|
24
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitstring Utilities for StatusList2021
|
|
3
|
+
*
|
|
4
|
+
* Implements GZIP compression + base64url encoding for efficient status lists.
|
|
5
|
+
* Per W3C StatusList2021 spec, each bit represents credential status:
|
|
6
|
+
* - 0: Not revoked/suspended
|
|
7
|
+
* - 1: Revoked/suspended
|
|
8
|
+
*
|
|
9
|
+
* Related Spec: W3C StatusList2021
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface CompressionFunction {
|
|
13
|
+
compress(data: Uint8Array): Promise<Uint8Array>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DecompressionFunction {
|
|
17
|
+
decompress(data: Uint8Array): Promise<Uint8Array>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class BitstringManager {
|
|
21
|
+
private bits: Uint8Array;
|
|
22
|
+
private size: number;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
size: number,
|
|
26
|
+
private compressor: CompressionFunction,
|
|
27
|
+
private decompressor: DecompressionFunction
|
|
28
|
+
) {
|
|
29
|
+
this.size = size;
|
|
30
|
+
const byteCount = Math.ceil(size / 8);
|
|
31
|
+
this.bits = new Uint8Array(byteCount);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setBit(index: number, value: boolean): void {
|
|
35
|
+
if (index < 0 || index >= this.size) {
|
|
36
|
+
throw new Error(`Bit index ${index} out of range (0-${this.size - 1})`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const byteIndex = Math.floor(index / 8);
|
|
40
|
+
const bitIndex = index % 8;
|
|
41
|
+
|
|
42
|
+
if (value) {
|
|
43
|
+
this.bits[byteIndex]! |= 1 << bitIndex;
|
|
44
|
+
} else {
|
|
45
|
+
this.bits[byteIndex]! &= ~(1 << bitIndex);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getBit(index: number): boolean {
|
|
50
|
+
if (index < 0 || index >= this.size) {
|
|
51
|
+
throw new Error(`Bit index ${index} out of range (0-${this.size - 1})`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const byteIndex = Math.floor(index / 8);
|
|
55
|
+
const bitIndex = index % 8;
|
|
56
|
+
|
|
57
|
+
return (this.bits[byteIndex]! & (1 << bitIndex)) !== 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getSetBits(): number[] {
|
|
61
|
+
const setBits: number[] = [];
|
|
62
|
+
for (let i = 0; i < this.size; i++) {
|
|
63
|
+
if (this.getBit(i)) {
|
|
64
|
+
setBits.push(i);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return setBits;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async encode(): Promise<string> {
|
|
71
|
+
const compressed = await this.compressor.compress(this.bits);
|
|
72
|
+
return this.base64urlEncode(compressed);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static async decode(
|
|
76
|
+
encodedList: string,
|
|
77
|
+
compressor: CompressionFunction,
|
|
78
|
+
decompressor: DecompressionFunction
|
|
79
|
+
): Promise<BitstringManager> {
|
|
80
|
+
const compressed = BitstringManager.base64urlDecode(encodedList);
|
|
81
|
+
const decompressed = await decompressor.decompress(compressed);
|
|
82
|
+
|
|
83
|
+
const size = decompressed.length * 8;
|
|
84
|
+
const manager = new BitstringManager(size, compressor, decompressor);
|
|
85
|
+
manager.bits = decompressed;
|
|
86
|
+
return manager;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getRawBits(): Uint8Array {
|
|
90
|
+
return this.bits;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getSize(): number {
|
|
94
|
+
return this.size;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private base64urlEncode(data: Uint8Array): string {
|
|
98
|
+
const base64 = this.bytesToBase64(data);
|
|
99
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static base64urlDecode(encoded: string): Uint8Array {
|
|
103
|
+
let base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
104
|
+
while (base64.length % 4 !== 0) {
|
|
105
|
+
base64 += '=';
|
|
106
|
+
}
|
|
107
|
+
return BitstringManager.base64ToBytes(base64);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private bytesToBase64(bytes: Uint8Array): string {
|
|
111
|
+
const binary = Array.from(bytes)
|
|
112
|
+
.map((byte) => String.fromCharCode(byte))
|
|
113
|
+
.join('');
|
|
114
|
+
return btoa(binary);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static base64ToBytes(base64: string): Uint8Array {
|
|
118
|
+
let standardBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');
|
|
119
|
+
const paddingNeeded = (4 - (standardBase64.length % 4)) % 4;
|
|
120
|
+
standardBase64 += '='.repeat(paddingNeeded);
|
|
121
|
+
|
|
122
|
+
const binary = atob(standardBase64);
|
|
123
|
+
const bytes = new Uint8Array(binary.length);
|
|
124
|
+
for (let i = 0; i < binary.length; i++) {
|
|
125
|
+
bytes[i] = binary.charCodeAt(i);
|
|
126
|
+
}
|
|
127
|
+
return bytes;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static fromSetBits(
|
|
131
|
+
size: number,
|
|
132
|
+
setBits: number[],
|
|
133
|
+
compressor: CompressionFunction,
|
|
134
|
+
decompressor: DecompressionFunction
|
|
135
|
+
): BitstringManager {
|
|
136
|
+
const manager = new BitstringManager(size, compressor, decompressor);
|
|
137
|
+
for (const index of setBits) {
|
|
138
|
+
manager.setBit(index, true);
|
|
139
|
+
}
|
|
140
|
+
return manager;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function isIndexSet(
|
|
145
|
+
encodedList: string,
|
|
146
|
+
index: number,
|
|
147
|
+
decompressor: DecompressionFunction
|
|
148
|
+
): Promise<boolean> {
|
|
149
|
+
const compressed = (BitstringManager as unknown as { base64urlDecode: (s: string) => Uint8Array })['base64urlDecode'](encodedList);
|
|
150
|
+
const decompressed = await decompressor.decompress(compressed);
|
|
151
|
+
|
|
152
|
+
const byteIndex = Math.floor(index / 8);
|
|
153
|
+
const bitIndex = index % 8;
|
|
154
|
+
|
|
155
|
+
if (byteIndex >= decompressed.length) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (decompressed[byteIndex]! & (1 << bitIndex)) !== 0;
|
|
160
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cascading Revocation Manager
|
|
3
|
+
*
|
|
4
|
+
* Implements cascading revocation per Python POC design.
|
|
5
|
+
* When a parent delegation is revoked, all children are automatically revoked.
|
|
6
|
+
*
|
|
7
|
+
* Related Spec: MCP-I §4.4, Delegation Chains
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CredentialStatus } from '../types/protocol.js';
|
|
11
|
+
import { DelegationGraphManager, type DelegationNode } from './delegation-graph.js';
|
|
12
|
+
import { StatusList2021Manager } from './statuslist-manager.js';
|
|
13
|
+
|
|
14
|
+
export interface RevocationEvent {
|
|
15
|
+
delegationId: string;
|
|
16
|
+
isRoot: boolean;
|
|
17
|
+
parentId?: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type RevocationHook = (event: RevocationEvent) => Promise<void> | void;
|
|
23
|
+
|
|
24
|
+
export interface CascadingRevocationOptions {
|
|
25
|
+
reason?: string;
|
|
26
|
+
onRevoke?: RevocationHook;
|
|
27
|
+
maxDepth?: number;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class CascadingRevocationManager {
|
|
32
|
+
constructor(
|
|
33
|
+
private graph: DelegationGraphManager,
|
|
34
|
+
private statusList: StatusList2021Manager
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
async revokeDelegation(
|
|
38
|
+
delegationId: string,
|
|
39
|
+
options: CascadingRevocationOptions = {}
|
|
40
|
+
): Promise<RevocationEvent[]> {
|
|
41
|
+
const maxDepth = options.maxDepth || 100;
|
|
42
|
+
const events: RevocationEvent[] = [];
|
|
43
|
+
|
|
44
|
+
const targetNode = await this.graph.getNode(delegationId);
|
|
45
|
+
if (!targetNode) {
|
|
46
|
+
throw new Error(`Delegation not found: ${delegationId}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const depth = await this.graph.getDepth(delegationId);
|
|
50
|
+
if (depth > maxDepth) {
|
|
51
|
+
throw new Error(`Delegation depth ${depth} exceeds maximum ${maxDepth}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rootEvent = await this.revokeNode(
|
|
55
|
+
targetNode,
|
|
56
|
+
true,
|
|
57
|
+
options.reason,
|
|
58
|
+
options.dryRun
|
|
59
|
+
);
|
|
60
|
+
events.push(rootEvent);
|
|
61
|
+
|
|
62
|
+
if (options.onRevoke) {
|
|
63
|
+
await options.onRevoke(rootEvent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const descendants = await this.graph.getDescendants(delegationId);
|
|
67
|
+
|
|
68
|
+
for (const descendant of descendants) {
|
|
69
|
+
const event = await this.revokeNode(
|
|
70
|
+
descendant,
|
|
71
|
+
false,
|
|
72
|
+
`Cascaded from ${delegationId}`,
|
|
73
|
+
options.dryRun,
|
|
74
|
+
delegationId
|
|
75
|
+
);
|
|
76
|
+
events.push(event);
|
|
77
|
+
|
|
78
|
+
if (options.onRevoke) {
|
|
79
|
+
await options.onRevoke(event);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return events;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async revokeNode(
|
|
87
|
+
node: DelegationNode,
|
|
88
|
+
isRoot: boolean,
|
|
89
|
+
reason?: string,
|
|
90
|
+
dryRun?: boolean,
|
|
91
|
+
parentId?: string
|
|
92
|
+
): Promise<RevocationEvent> {
|
|
93
|
+
const event: RevocationEvent = {
|
|
94
|
+
delegationId: node.id,
|
|
95
|
+
isRoot,
|
|
96
|
+
parentId,
|
|
97
|
+
timestamp: Date.now(),
|
|
98
|
+
reason,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (dryRun) {
|
|
102
|
+
return event;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (node.credentialStatusId) {
|
|
106
|
+
const credentialStatus = this.parseCredentialStatus(node.credentialStatusId);
|
|
107
|
+
if (credentialStatus) {
|
|
108
|
+
await this.statusList.updateStatus(credentialStatus, true);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return event;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async restoreDelegation(delegationId: string): Promise<RevocationEvent> {
|
|
116
|
+
const node = await this.graph.getNode(delegationId);
|
|
117
|
+
if (!node) {
|
|
118
|
+
throw new Error(`Delegation not found: ${delegationId}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const event: RevocationEvent = {
|
|
122
|
+
delegationId: node.id,
|
|
123
|
+
isRoot: true,
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
reason: 'Restored',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (node.credentialStatusId) {
|
|
129
|
+
const credentialStatus = this.parseCredentialStatus(node.credentialStatusId);
|
|
130
|
+
if (credentialStatus) {
|
|
131
|
+
await this.statusList.updateStatus(credentialStatus, false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return event;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async isRevoked(delegationId: string): Promise<{
|
|
139
|
+
revoked: boolean;
|
|
140
|
+
reason?: string;
|
|
141
|
+
revokedAncestor?: string;
|
|
142
|
+
}> {
|
|
143
|
+
const chain = await this.graph.getChain(delegationId);
|
|
144
|
+
|
|
145
|
+
for (const node of chain.reverse()) {
|
|
146
|
+
if (node.credentialStatusId) {
|
|
147
|
+
const credentialStatus = this.parseCredentialStatus(node.credentialStatusId);
|
|
148
|
+
if (credentialStatus) {
|
|
149
|
+
const isRevoked = await this.statusList.checkStatus(credentialStatus);
|
|
150
|
+
if (isRevoked) {
|
|
151
|
+
return {
|
|
152
|
+
revoked: true,
|
|
153
|
+
reason: node.id === delegationId ? 'Directly revoked' : 'Ancestor revoked',
|
|
154
|
+
revokedAncestor: node.id === delegationId ? undefined : node.id,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { revoked: false };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async getRevokedInSubtree(rootId: string): Promise<string[]> {
|
|
165
|
+
const descendants = await this.graph.getDescendants(rootId);
|
|
166
|
+
const revoked: string[] = [];
|
|
167
|
+
|
|
168
|
+
const rootRevoked = await this.isRevoked(rootId);
|
|
169
|
+
if (rootRevoked.revoked) {
|
|
170
|
+
revoked.push(rootId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const node of descendants) {
|
|
174
|
+
const isRevoked = await this.isRevoked(node.id);
|
|
175
|
+
if (isRevoked.revoked) {
|
|
176
|
+
revoked.push(node.id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return revoked;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private parseCredentialStatus(credentialStatusId: string): CredentialStatus | null {
|
|
184
|
+
const match = credentialStatusId.match(/^(.+)#(\d+)$/);
|
|
185
|
+
if (!match) return null;
|
|
186
|
+
|
|
187
|
+
const [, statusListCredential, indexStr] = match;
|
|
188
|
+
const index = parseInt(indexStr!, 10);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
id: credentialStatusId,
|
|
192
|
+
type: 'StatusList2021Entry',
|
|
193
|
+
statusPurpose: 'revocation',
|
|
194
|
+
statusListIndex: index.toString(),
|
|
195
|
+
statusListCredential: statusListCredential!,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async validateDelegation(delegationId: string): Promise<{ valid: boolean; reason?: string }> {
|
|
200
|
+
const revokedCheck = await this.isRevoked(delegationId);
|
|
201
|
+
if (revokedCheck.revoked) {
|
|
202
|
+
return {
|
|
203
|
+
valid: false,
|
|
204
|
+
reason: revokedCheck.revokedAncestor
|
|
205
|
+
? `Ancestor ${revokedCheck.revokedAncestor} is revoked`
|
|
206
|
+
: 'Delegation is revoked',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const chainValidation = await this.graph.validateChain(delegationId);
|
|
211
|
+
if (!chainValidation.valid) {
|
|
212
|
+
return chainValidation;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { valid: true };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function createCascadingRevocationManager(
|
|
220
|
+
graph: DelegationGraphManager,
|
|
221
|
+
statusList: StatusList2021Manager
|
|
222
|
+
): CascadingRevocationManager {
|
|
223
|
+
return new CascadingRevocationManager(graph, statusList);
|
|
224
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Graph Manager
|
|
3
|
+
*
|
|
4
|
+
* Tracks parent-child relationships between delegation credentials.
|
|
5
|
+
* Critical for cascading revocation per Delegation-Revocation.md.
|
|
6
|
+
*
|
|
7
|
+
* Related Spec: MCP-I §4.4, Delegation Chains
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface DelegationNode {
|
|
11
|
+
id: string;
|
|
12
|
+
parentId: string | null;
|
|
13
|
+
children: string[];
|
|
14
|
+
issuerDid: string;
|
|
15
|
+
subjectDid: string;
|
|
16
|
+
credentialStatusId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DelegationGraphStorageProvider {
|
|
20
|
+
getNode(delegationId: string): Promise<DelegationNode | null>;
|
|
21
|
+
setNode(node: DelegationNode): Promise<void>;
|
|
22
|
+
getChildren(delegationId: string): Promise<DelegationNode[]>;
|
|
23
|
+
getChain(delegationId: string): Promise<DelegationNode[]>;
|
|
24
|
+
getDescendants(delegationId: string): Promise<DelegationNode[]>;
|
|
25
|
+
deleteNode(delegationId: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class DelegationGraphManager {
|
|
29
|
+
constructor(private storage: DelegationGraphStorageProvider) {}
|
|
30
|
+
|
|
31
|
+
async registerDelegation(params: {
|
|
32
|
+
id: string;
|
|
33
|
+
parentId: string | null;
|
|
34
|
+
issuerDid: string;
|
|
35
|
+
subjectDid: string;
|
|
36
|
+
credentialStatusId?: string;
|
|
37
|
+
}): Promise<DelegationNode> {
|
|
38
|
+
const node: DelegationNode = {
|
|
39
|
+
id: params.id,
|
|
40
|
+
parentId: params.parentId,
|
|
41
|
+
children: [],
|
|
42
|
+
issuerDid: params.issuerDid,
|
|
43
|
+
subjectDid: params.subjectDid,
|
|
44
|
+
credentialStatusId: params.credentialStatusId,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await this.storage.setNode(node);
|
|
48
|
+
|
|
49
|
+
if (params.parentId) {
|
|
50
|
+
await this.addChildToParent(params.parentId, params.id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return node;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async addChildToParent(parentId: string, childId: string): Promise<void> {
|
|
57
|
+
const parent = await this.storage.getNode(parentId);
|
|
58
|
+
if (!parent) {
|
|
59
|
+
throw new Error(`Parent delegation not found: ${parentId}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!parent.children.includes(childId)) {
|
|
63
|
+
parent.children.push(childId);
|
|
64
|
+
await this.storage.setNode(parent);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getNode(delegationId: string): Promise<DelegationNode | null> {
|
|
69
|
+
return this.storage.getNode(delegationId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getChildren(delegationId: string): Promise<DelegationNode[]> {
|
|
73
|
+
return this.storage.getChildren(delegationId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getDescendants(delegationId: string): Promise<DelegationNode[]> {
|
|
77
|
+
return this.storage.getDescendants(delegationId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getChain(delegationId: string): Promise<DelegationNode[]> {
|
|
81
|
+
return this.storage.getChain(delegationId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async isAncestor(ancestorId: string, descendantId: string): Promise<boolean> {
|
|
85
|
+
const chain = await this.getChain(descendantId);
|
|
86
|
+
return chain.some((node) => node.id === ancestorId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getDepth(delegationId: string): Promise<number> {
|
|
90
|
+
const chain = await this.getChain(delegationId);
|
|
91
|
+
return chain.length - 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async validateChain(delegationId: string): Promise<{ valid: boolean; reason?: string }> {
|
|
95
|
+
const chain = await this.getChain(delegationId);
|
|
96
|
+
|
|
97
|
+
if (chain.length === 0) {
|
|
98
|
+
return { valid: false, reason: 'Delegation not found' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (let i = 1; i < chain.length; i++) {
|
|
102
|
+
const parent = chain[i - 1]!;
|
|
103
|
+
const child = chain[i]!;
|
|
104
|
+
|
|
105
|
+
if (child.issuerDid !== parent.subjectDid) {
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
reason: `Invalid chain: ${child.id} issued by ${child.issuerDid} but parent ${parent.id} subject is ${parent.subjectDid}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (child.parentId !== parent.id) {
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
reason: `Invalid chain: ${child.id} parentId=${child.parentId} but actual parent is ${parent.id}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { valid: true };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async removeDelegation(delegationId: string): Promise<void> {
|
|
124
|
+
const node = await this.storage.getNode(delegationId);
|
|
125
|
+
if (!node) return;
|
|
126
|
+
|
|
127
|
+
if (node.parentId) {
|
|
128
|
+
const parent = await this.storage.getNode(node.parentId);
|
|
129
|
+
if (parent) {
|
|
130
|
+
parent.children = parent.children.filter((id) => id !== delegationId);
|
|
131
|
+
await this.storage.setNode(parent);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await this.storage.deleteNode(delegationId);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function createDelegationGraph(
|
|
140
|
+
storage: DelegationGraphStorageProvider
|
|
141
|
+
): DelegationGraphManager {
|
|
142
|
+
return new DelegationGraphManager(storage);
|
|
143
|
+
}
|