@mcp-i/core 1.1.0-canary.2 → 1.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/README.md +123 -333
- package/dist/auth/handshake.d.ts +19 -4
- package/dist/auth/handshake.d.ts.map +1 -1
- package/dist/auth/handshake.js +52 -15
- package/dist/auth/handshake.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts.map +1 -1
- package/dist/delegation/did-key-resolver.js +9 -6
- package/dist/delegation/did-key-resolver.js.map +1 -1
- package/dist/delegation/outbound-headers.d.ts +2 -4
- package/dist/delegation/outbound-headers.d.ts.map +1 -1
- package/dist/delegation/outbound-headers.js +2 -3
- package/dist/delegation/outbound-headers.js.map +1 -1
- package/dist/delegation/statuslist-manager.d.ts.map +1 -1
- package/dist/delegation/statuslist-manager.js +1 -1
- package/dist/delegation/statuslist-manager.js.map +1 -1
- package/dist/delegation/vc-verifier.d.ts.map +1 -1
- package/dist/delegation/vc-verifier.js +2 -2
- package/dist/delegation/vc-verifier.js.map +1 -1
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/mcpi-transport.d.ts +39 -0
- package/dist/middleware/mcpi-transport.d.ts.map +1 -0
- package/dist/middleware/mcpi-transport.js +121 -0
- package/dist/middleware/mcpi-transport.js.map +1 -0
- package/dist/middleware/with-mcpi-server.d.ts +25 -9
- package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
- package/dist/middleware/with-mcpi-server.js +62 -47
- package/dist/middleware/with-mcpi-server.js.map +1 -1
- package/dist/middleware/with-mcpi.d.ts +26 -5
- package/dist/middleware/with-mcpi.d.ts.map +1 -1
- package/dist/middleware/with-mcpi.js +108 -10
- package/dist/middleware/with-mcpi.js.map +1 -1
- package/dist/providers/memory.js +2 -2
- package/dist/providers/memory.js.map +1 -1
- package/dist/session/manager.d.ts +7 -1
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +20 -4
- package/dist/session/manager.js.map +1 -1
- package/dist/utils/crypto-service.d.ts.map +1 -1
- package/dist/utils/crypto-service.js +11 -10
- package/dist/utils/crypto-service.js.map +1 -1
- package/dist/utils/did-helpers.d.ts +12 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +18 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/errors.test.ts +56 -0
- package/src/__tests__/integration/full-flow.test.ts +1 -1
- package/src/__tests__/integration/mcp-enhance-server.test.ts +48 -5
- package/src/__tests__/integration/mcp-transport-context7.test.ts +19 -15
- package/src/__tests__/integration/mcp-transport.test.ts +13 -10
- package/src/__tests__/providers/base.test.ts +1 -1
- package/src/__tests__/providers/memory.test.ts +2 -2
- package/src/__tests__/utils/mock-providers.ts +2 -2
- package/src/auth/__tests__/handshake.test.ts +190 -0
- package/src/auth/handshake.ts +88 -21
- package/src/auth/index.ts +1 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +2 -2
- package/src/delegation/__tests__/outbound-headers.test.ts +16 -20
- package/src/delegation/__tests__/statuslist-manager.test.ts +120 -7
- package/src/delegation/__tests__/vc-verifier.test.ts +45 -3
- package/src/delegation/did-key-resolver.ts +11 -6
- package/src/delegation/outbound-headers.ts +1 -4
- package/src/delegation/statuslist-manager.ts +3 -1
- package/src/delegation/vc-verifier.ts +3 -2
- package/src/errors.ts +65 -0
- package/src/index.ts +10 -0
- package/src/middleware/__tests__/mcpi-transport.test.ts +150 -0
- package/src/middleware/__tests__/with-mcpi-server.test.ts +117 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +124 -6
- package/src/middleware/index.ts +6 -0
- package/src/middleware/mcpi-transport.ts +162 -0
- package/src/middleware/with-mcpi-server.ts +83 -92
- package/src/middleware/with-mcpi.ts +147 -11
- package/src/proof/__tests__/errors.test.ts +79 -0
- package/src/proof/__tests__/verifier.test.ts +5 -5
- package/src/providers/memory.ts +2 -2
- package/src/session/__tests__/session-manager.test.ts +3 -3
- package/src/session/manager.ts +28 -6
- package/src/utils/crypto-service.ts +11 -10
- package/src/utils/did-helpers.ts +19 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
PROOF_VERIFICATION_ERROR_CODES,
|
|
4
|
+
ProofVerificationError,
|
|
5
|
+
createProofVerificationError,
|
|
6
|
+
} from "../errors.js";
|
|
7
|
+
|
|
8
|
+
describe("PROOF_VERIFICATION_ERROR_CODES", () => {
|
|
9
|
+
it("should define all expected error code categories", () => {
|
|
10
|
+
const codes = PROOF_VERIFICATION_ERROR_CODES;
|
|
11
|
+
|
|
12
|
+
// Proof structure
|
|
13
|
+
expect(codes.INVALID_PROOF_STRUCTURE).toBeDefined();
|
|
14
|
+
expect(codes.MISSING_REQUIRED_FIELD).toBeDefined();
|
|
15
|
+
|
|
16
|
+
// Security
|
|
17
|
+
expect(codes.NONCE_REPLAY_DETECTED).toBeDefined();
|
|
18
|
+
expect(codes.TIMESTAMP_SKEW_EXCEEDED).toBeDefined();
|
|
19
|
+
|
|
20
|
+
// JWS
|
|
21
|
+
expect(codes.INVALID_JWS_SIGNATURE).toBeDefined();
|
|
22
|
+
expect(codes.INVALID_JWS_FORMAT).toBeDefined();
|
|
23
|
+
|
|
24
|
+
// JWK
|
|
25
|
+
expect(codes.INVALID_JWK_FORMAT).toBeDefined();
|
|
26
|
+
|
|
27
|
+
// DID
|
|
28
|
+
expect(codes.DID_RESOLUTION_FAILED).toBeDefined();
|
|
29
|
+
expect(codes.DID_DOCUMENT_NOT_FOUND).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("ProofVerificationError", () => {
|
|
34
|
+
it("should extend Error", () => {
|
|
35
|
+
const err = new ProofVerificationError(
|
|
36
|
+
PROOF_VERIFICATION_ERROR_CODES.NONCE_REPLAY_DETECTED,
|
|
37
|
+
"Nonce reused",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(err).toBeInstanceOf(Error);
|
|
41
|
+
expect(err.name).toBe("ProofVerificationError");
|
|
42
|
+
expect(err.message).toBe("Nonce reused");
|
|
43
|
+
expect(err.code).toBe("NONCE_REPLAY_DETECTED");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should include optional details", () => {
|
|
47
|
+
const err = new ProofVerificationError(
|
|
48
|
+
PROOF_VERIFICATION_ERROR_CODES.DID_DOCUMENT_NOT_FOUND,
|
|
49
|
+
"DID not found",
|
|
50
|
+
{ did: "did:key:z6MkTest" },
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(err.details).toEqual({ did: "did:key:z6MkTest" });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should have undefined details when not provided", () => {
|
|
57
|
+
const err = new ProofVerificationError(
|
|
58
|
+
PROOF_VERIFICATION_ERROR_CODES.INVALID_JWS_FORMAT,
|
|
59
|
+
"Bad format",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(err.details).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("createProofVerificationError", () => {
|
|
67
|
+
it("should create a ProofVerificationError instance", () => {
|
|
68
|
+
const err = createProofVerificationError(
|
|
69
|
+
PROOF_VERIFICATION_ERROR_CODES.TIMESTAMP_SKEW_EXCEEDED,
|
|
70
|
+
"Clock skew too large",
|
|
71
|
+
{ skew: 300 },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(err).toBeInstanceOf(ProofVerificationError);
|
|
75
|
+
expect(err.code).toBe("TIMESTAMP_SKEW_EXCEEDED");
|
|
76
|
+
expect(err.message).toBe("Clock skew too large");
|
|
77
|
+
expect(err.details).toEqual({ skew: 300 });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -35,7 +35,7 @@ describe('ProofVerifier Security', () => {
|
|
|
35
35
|
kty: 'OKP',
|
|
36
36
|
crv: 'Ed25519',
|
|
37
37
|
x: 'VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ',
|
|
38
|
-
kid: 'did:key:z123#
|
|
38
|
+
kid: 'did:key:z123#z123',
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
const createValidProof = (): DetachedProof => {
|
|
@@ -64,7 +64,7 @@ describe('ProofVerifier Security', () => {
|
|
|
64
64
|
jws,
|
|
65
65
|
meta: {
|
|
66
66
|
did: 'did:key:z123',
|
|
67
|
-
kid: 'did:key:z123#
|
|
67
|
+
kid: 'did:key:z123#z123',
|
|
68
68
|
ts: Math.floor(Date.now() / 1000),
|
|
69
69
|
nonce: 'nonce123',
|
|
70
70
|
audience: 'test-audience',
|
|
@@ -104,7 +104,7 @@ describe('ProofVerifier Security', () => {
|
|
|
104
104
|
mockFetchProvider = {
|
|
105
105
|
resolveDID: vi.fn().mockResolvedValue({
|
|
106
106
|
verificationMethod: [{
|
|
107
|
-
id: 'did:key:z123#
|
|
107
|
+
id: 'did:key:z123#z123',
|
|
108
108
|
publicKeyJwk: validJwk,
|
|
109
109
|
}],
|
|
110
110
|
}),
|
|
@@ -417,7 +417,7 @@ describe('ProofVerifier Security', () => {
|
|
|
417
417
|
|
|
418
418
|
describe('fetchPublicKeyFromDID', () => {
|
|
419
419
|
it('should fetch public key from DID document', async () => {
|
|
420
|
-
const jwk = await proofVerifier.fetchPublicKeyFromDID('did:key:z123', '
|
|
420
|
+
const jwk = await proofVerifier.fetchPublicKeyFromDID('did:key:z123', 'z123');
|
|
421
421
|
|
|
422
422
|
expect(jwk).toEqual(validJwk);
|
|
423
423
|
expect(mockFetchProvider.resolveDID).toHaveBeenCalledWith('did:key:z123');
|
|
@@ -462,7 +462,7 @@ describe('ProofVerifier Security', () => {
|
|
|
462
462
|
it('should throw ProofVerificationError if JWK is not Ed25519', async () => {
|
|
463
463
|
mockFetchProvider.resolveDID = vi.fn().mockResolvedValue({
|
|
464
464
|
verificationMethod: [{
|
|
465
|
-
id: 'did:key:z123#
|
|
465
|
+
id: 'did:key:z123#z123',
|
|
466
466
|
publicKeyJwk: {
|
|
467
467
|
kty: 'RSA',
|
|
468
468
|
crv: 'RS256',
|
package/src/providers/memory.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
IdentityProvider,
|
|
12
12
|
type AgentIdentity,
|
|
13
13
|
} from './base.js';
|
|
14
|
-
import { generateDidKeyFromBase64 } from '../utils/did-helpers.js';
|
|
14
|
+
import { generateDidKeyFromBase64, didKeyFragment } from '../utils/did-helpers.js';
|
|
15
15
|
|
|
16
16
|
export class MemoryStorageProvider extends StorageProvider {
|
|
17
17
|
private store: Map<string, string> = new Map();
|
|
@@ -116,7 +116,7 @@ export class MemoryIdentityProvider extends IdentityProvider {
|
|
|
116
116
|
|
|
117
117
|
return {
|
|
118
118
|
did,
|
|
119
|
-
kid: `${did}
|
|
119
|
+
kid: `${did}#${didKeyFragment(did)}`,
|
|
120
120
|
privateKey: keyPair.privateKey,
|
|
121
121
|
publicKey: keyPair.publicKey,
|
|
122
122
|
createdAt: new Date().toISOString(),
|
|
@@ -88,7 +88,7 @@ describe("SessionManager", () => {
|
|
|
88
88
|
const result = await manager.validateHandshake(request);
|
|
89
89
|
|
|
90
90
|
expect(result.success).toBe(false);
|
|
91
|
-
expect(result.error?.code).toBe("
|
|
91
|
+
expect(result.error?.code).toBe("handshake_failed");
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it("should accept request within timestamp skew", async () => {
|
|
@@ -106,7 +106,7 @@ describe("SessionManager", () => {
|
|
|
106
106
|
// Same request again (same nonce)
|
|
107
107
|
const second = await manager.validateHandshake(request);
|
|
108
108
|
expect(second.success).toBe(false);
|
|
109
|
-
expect(second.error?.code).toBe("
|
|
109
|
+
expect(second.error?.code).toBe("handshake_failed");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
it("should accept different nonces in succession", async () => {
|
|
@@ -261,7 +261,7 @@ describe("SessionManager", () => {
|
|
|
261
261
|
const result = await sm.validateHandshake(request);
|
|
262
262
|
|
|
263
263
|
expect(result.success).toBe(false);
|
|
264
|
-
expect(result.error?.code).toBe("
|
|
264
|
+
expect(result.error?.code).toBe("handshake_failed");
|
|
265
265
|
expect(result.error?.message).toContain("Audience mismatch");
|
|
266
266
|
});
|
|
267
267
|
|
package/src/session/manager.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Cloudflare Workers) to remain synchronous without platform-specific imports.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { MCPI_ERROR_CODES, type MCPIErrorCode } from "../errors.js";
|
|
12
13
|
import type {
|
|
13
14
|
HandshakeRequest,
|
|
14
15
|
SessionContext,
|
|
@@ -24,28 +25,33 @@ export interface SessionConfig {
|
|
|
24
25
|
absoluteSessionLifetime?: number;
|
|
25
26
|
nonceCache?: NonceCache;
|
|
26
27
|
serverDid?: string;
|
|
28
|
+
/** Maximum number of concurrent sessions. Oldest sessions are evicted when exceeded. Default: 10000 */
|
|
29
|
+
maxSessions?: number;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export interface HandshakeResult {
|
|
30
33
|
success: boolean;
|
|
31
34
|
session?: SessionContext;
|
|
32
35
|
error?: {
|
|
33
|
-
code:
|
|
36
|
+
code: MCPIErrorCode;
|
|
34
37
|
message: string;
|
|
35
38
|
remediation?: string;
|
|
36
39
|
};
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export class SessionManager {
|
|
40
|
-
private config: Required<Omit<SessionConfig, 'absoluteSessionLifetime' | 'serverDid'>> & {
|
|
43
|
+
private config: Required<Omit<SessionConfig, 'absoluteSessionLifetime' | 'serverDid' | 'maxSessions'>> & {
|
|
41
44
|
absoluteSessionLifetime?: number;
|
|
42
45
|
serverDid?: string;
|
|
43
46
|
};
|
|
44
47
|
private cryptoProvider: CryptoProvider;
|
|
45
48
|
private sessions = new Map<string, SessionContext>();
|
|
49
|
+
private sessionInsertionOrder: string[] = [];
|
|
50
|
+
private maxSessions: number;
|
|
46
51
|
|
|
47
52
|
constructor(cryptoProvider: CryptoProvider, config: SessionConfig = {}) {
|
|
48
53
|
this.cryptoProvider = cryptoProvider;
|
|
54
|
+
this.maxSessions = config.maxSessions ?? 10_000;
|
|
49
55
|
this.config = {
|
|
50
56
|
timestampSkewSeconds: config.timestampSkewSeconds ?? 120,
|
|
51
57
|
sessionTtlMinutes: config.sessionTtlMinutes ?? 30,
|
|
@@ -89,7 +95,7 @@ export class SessionManager {
|
|
|
89
95
|
return {
|
|
90
96
|
success: false,
|
|
91
97
|
error: {
|
|
92
|
-
code:
|
|
98
|
+
code: MCPI_ERROR_CODES.handshake_failed,
|
|
93
99
|
message: `Timestamp outside acceptable range (±${this.config.timestampSkewSeconds}s)`,
|
|
94
100
|
remediation: `Check NTP sync on client and server. Current server time: ${now}, received: ${request.timestamp}, diff: ${timeDiff}s. Adjust timestampSkewSeconds if needed.`,
|
|
95
101
|
},
|
|
@@ -101,7 +107,7 @@ export class SessionManager {
|
|
|
101
107
|
return {
|
|
102
108
|
success: false,
|
|
103
109
|
error: {
|
|
104
|
-
code:
|
|
110
|
+
code: MCPI_ERROR_CODES.handshake_failed,
|
|
105
111
|
message: `Audience mismatch: expected ${this.config.serverDid}, got ${request.audience}`,
|
|
106
112
|
},
|
|
107
113
|
};
|
|
@@ -115,7 +121,7 @@ export class SessionManager {
|
|
|
115
121
|
return {
|
|
116
122
|
success: false,
|
|
117
123
|
error: {
|
|
118
|
-
code:
|
|
124
|
+
code: MCPI_ERROR_CODES.handshake_failed,
|
|
119
125
|
message: 'Nonce already used (replay attack prevention)',
|
|
120
126
|
remediation: 'Generate a new unique nonce for each request',
|
|
121
127
|
},
|
|
@@ -146,14 +152,16 @@ export class SessionManager {
|
|
|
146
152
|
...(clientInfo && { clientInfo }),
|
|
147
153
|
};
|
|
148
154
|
|
|
155
|
+
this.evictIfNeeded();
|
|
149
156
|
this.sessions.set(sessionId, session);
|
|
157
|
+
this.sessionInsertionOrder.push(sessionId);
|
|
150
158
|
|
|
151
159
|
return { success: true, session };
|
|
152
160
|
} catch (error) {
|
|
153
161
|
return {
|
|
154
162
|
success: false,
|
|
155
163
|
error: {
|
|
156
|
-
code:
|
|
164
|
+
code: MCPI_ERROR_CODES.handshake_failed,
|
|
157
165
|
message: `Handshake validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
158
166
|
},
|
|
159
167
|
};
|
|
@@ -262,6 +270,15 @@ export class SessionManager {
|
|
|
262
270
|
.replace(/=/g, '');
|
|
263
271
|
}
|
|
264
272
|
|
|
273
|
+
private evictIfNeeded(): void {
|
|
274
|
+
while (this.sessions.size >= this.maxSessions && this.sessionInsertionOrder.length > 0) {
|
|
275
|
+
const oldest = this.sessionInsertionOrder.shift();
|
|
276
|
+
if (oldest) {
|
|
277
|
+
this.sessions.delete(oldest);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
265
282
|
async cleanup(): Promise<void> {
|
|
266
283
|
const now = Math.floor(Date.now() / 1000);
|
|
267
284
|
|
|
@@ -281,6 +298,10 @@ export class SessionManager {
|
|
|
281
298
|
}
|
|
282
299
|
}
|
|
283
300
|
|
|
301
|
+
this.sessionInsertionOrder = this.sessionInsertionOrder.filter(
|
|
302
|
+
id => this.sessions.has(id)
|
|
303
|
+
);
|
|
304
|
+
|
|
284
305
|
await this.config.nonceCache.cleanup();
|
|
285
306
|
}
|
|
286
307
|
|
|
@@ -306,6 +327,7 @@ export class SessionManager {
|
|
|
306
327
|
|
|
307
328
|
clearSessions(): void {
|
|
308
329
|
this.sessions.clear();
|
|
330
|
+
this.sessionInsertionOrder = [];
|
|
309
331
|
}
|
|
310
332
|
}
|
|
311
333
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { CryptoProvider } from '../providers/base.js';
|
|
9
|
+
import { logger } from '../logging/index.js';
|
|
9
10
|
import {
|
|
10
11
|
base64urlDecodeToString,
|
|
11
12
|
base64urlDecodeToBytes,
|
|
@@ -43,7 +44,7 @@ export class CryptoService {
|
|
|
43
44
|
const result = await this.cryptoProvider.verify(data, signature, publicKey);
|
|
44
45
|
return result === true;
|
|
45
46
|
} catch (error) {
|
|
46
|
-
|
|
47
|
+
logger.error('[CryptoService] Ed25519 verification error:', error);
|
|
47
48
|
return false;
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -101,12 +102,12 @@ export class CryptoService {
|
|
|
101
102
|
): Promise<boolean> {
|
|
102
103
|
try {
|
|
103
104
|
if (!this.isValidEd25519JWK(publicKeyJwk)) {
|
|
104
|
-
|
|
105
|
+
logger.error('[CryptoService] Invalid Ed25519 JWK format');
|
|
105
106
|
return false;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
if (options?.expectedKid && publicKeyJwk.kid !== options.expectedKid) {
|
|
109
|
-
|
|
110
|
+
logger.error('[CryptoService] Key ID mismatch');
|
|
110
111
|
return false;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -126,22 +127,22 @@ export class CryptoService {
|
|
|
126
127
|
const signatureBytes = base64urlDecodeToBytes(signatureB64);
|
|
127
128
|
parsed = { header, payload: undefined, signatureBytes, signingInput: '' };
|
|
128
129
|
} catch {
|
|
129
|
-
|
|
130
|
+
logger.error('[CryptoService] Invalid detached JWS format');
|
|
130
131
|
return false;
|
|
131
132
|
}
|
|
132
133
|
} else {
|
|
133
|
-
|
|
134
|
+
logger.error('[CryptoService] Invalid JWS format:', error);
|
|
134
135
|
return false;
|
|
135
136
|
}
|
|
136
137
|
} else {
|
|
137
|
-
|
|
138
|
+
logger.error('[CryptoService] Invalid JWS format:', error);
|
|
138
139
|
return false;
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
const expectedAlg = options?.alg || 'EdDSA';
|
|
143
144
|
if (parsed.header['alg'] !== expectedAlg) {
|
|
144
|
-
|
|
145
|
+
logger.error(
|
|
145
146
|
`[CryptoService] Unsupported algorithm: ${parsed.header['alg']}, expected ${expectedAlg}`
|
|
146
147
|
);
|
|
147
148
|
return false;
|
|
@@ -164,7 +165,7 @@ export class CryptoService {
|
|
|
164
165
|
signingInputBytes = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
165
166
|
} else {
|
|
166
167
|
if (!parsed.signingInput) {
|
|
167
|
-
|
|
168
|
+
logger.error('[CryptoService] Missing signing input for compact JWS');
|
|
168
169
|
return false;
|
|
169
170
|
}
|
|
170
171
|
signingInputBytes = new TextEncoder().encode(parsed.signingInput);
|
|
@@ -174,13 +175,13 @@ export class CryptoService {
|
|
|
174
175
|
try {
|
|
175
176
|
publicKeyBase64 = this.jwkToBase64PublicKey(publicKeyJwk);
|
|
176
177
|
} catch (error) {
|
|
177
|
-
|
|
178
|
+
logger.error('[CryptoService] Failed to extract public key:', error);
|
|
178
179
|
return false;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
return await this.verifyEd25519(signingInputBytes, parsed.signatureBytes, publicKeyBase64);
|
|
182
183
|
} catch (error) {
|
|
183
|
-
|
|
184
|
+
logger.error('[CryptoService] JWS verification error:', error);
|
|
184
185
|
return false;
|
|
185
186
|
}
|
|
186
187
|
}
|
package/src/utils/did-helpers.ts
CHANGED
|
@@ -208,3 +208,22 @@ export function generateDidKeyFromBase64(publicKeyBase64: string): string {
|
|
|
208
208
|
);
|
|
209
209
|
return generateDidKeyFromBytes(publicKeyBytes);
|
|
210
210
|
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the spec-compliant fragment identifier for a did:key DID.
|
|
214
|
+
*
|
|
215
|
+
* Per the did:key spec (W3C CCG), the fragment equals the multibase-encoded
|
|
216
|
+
* public key value (the DID-specific-id). For example:
|
|
217
|
+
* did:key:z6MkABC... → z6MkABC...
|
|
218
|
+
*
|
|
219
|
+
* @see https://w3c-ccg.github.io/did-key-spec/#document-creation-algorithm
|
|
220
|
+
* @param did - A did:key DID string
|
|
221
|
+
* @returns The fragment identifier (multibase value), or 'keys-1' as fallback for non-did:key
|
|
222
|
+
*/
|
|
223
|
+
export function didKeyFragment(did: string): string {
|
|
224
|
+
if (did.startsWith('did:key:')) {
|
|
225
|
+
return did.slice('did:key:'.length);
|
|
226
|
+
}
|
|
227
|
+
// Fallback for non-did:key methods
|
|
228
|
+
return 'keys-1';
|
|
229
|
+
}
|