@kya-os/mcp-i 0.1.0 → 1.2.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 +406 -71
- package/dist/149.js +1 -0
- package/dist/189.js +1 -0
- package/dist/261.js +1 -0
- package/dist/28.js +1 -0
- package/dist/295.js +1 -0
- package/dist/460.js +1 -0
- package/dist/570.js +1 -0
- package/dist/634.js +1 -0
- package/dist/647.js +1 -0
- package/dist/67.js +1 -0
- package/dist/739.js +1 -0
- package/dist/742.js +1 -0
- package/dist/904.js +1 -0
- package/dist/938.js +1 -0
- package/dist/auth/api-key.d.ts +16 -0
- package/dist/auth/api-key.js +82 -0
- package/dist/auth/jwt.d.ts +43 -0
- package/dist/auth/jwt.js +51 -0
- package/dist/auth/oauth/factory.d.ts +12 -0
- package/dist/auth/oauth/factory.js +36 -0
- package/dist/auth/oauth/index.d.ts +5 -0
- package/dist/auth/oauth/index.js +27 -0
- package/dist/auth/oauth/providers/proxy-provider.d.ts +13 -0
- package/dist/auth/oauth/providers/proxy-provider.js +159 -0
- package/dist/auth/oauth/router.d.ts +4 -0
- package/dist/auth/oauth/router.js +294 -0
- package/dist/auth/oauth/storage/memory-storage.d.ts +12 -0
- package/dist/auth/oauth/storage/memory-storage.js +40 -0
- package/dist/auth/oauth/types.d.ts +112 -0
- package/dist/auth/oauth/types.js +2 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/concurrency.test.d.ts +5 -0
- package/dist/cache/__tests__/concurrency.test.js +300 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.js +132 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.js +133 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.js +252 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.js +95 -0
- package/dist/cache/cloudflare-kv-nonce-cache.d.ts +14 -0
- package/dist/cache/cloudflare-kv-nonce-cache.js +93 -0
- package/dist/cache/dynamodb-nonce-cache.d.ts +15 -0
- package/dist/cache/dynamodb-nonce-cache.js +92 -0
- package/dist/cache/index.d.ts +16 -0
- package/dist/cache/index.js +32 -0
- package/dist/cache/memory-nonce-cache.d.ts +44 -0
- package/dist/cache/memory-nonce-cache.js +105 -0
- package/dist/cache/nonce-cache-factory.d.ts +20 -0
- package/dist/cache/nonce-cache-factory.js +208 -0
- package/dist/cache/redis-nonce-cache.d.ts +14 -0
- package/dist/cache/redis-nonce-cache.js +53 -0
- package/dist/compiler/compiler-context.d.ts +23 -0
- package/dist/compiler/compiler-context.js +24 -0
- package/dist/compiler/config/constants.d.ts +41 -0
- package/dist/compiler/config/constants.js +45 -0
- package/dist/compiler/config/index.d.ts +252 -0
- package/dist/compiler/config/index.js +15 -0
- package/dist/compiler/config/injection.d.ts +26 -0
- package/dist/compiler/config/injection.js +58 -0
- package/dist/compiler/config/schemas/experimental/index.d.ts +91 -0
- package/dist/compiler/config/schemas/experimental/index.js +16 -0
- package/dist/compiler/config/schemas/experimental/oauth.d.ts +74 -0
- package/dist/compiler/config/schemas/experimental/oauth.js +25 -0
- package/dist/compiler/config/schemas/index.d.ts +6 -0
- package/dist/compiler/config/schemas/index.js +17 -0
- package/dist/compiler/config/schemas/paths.d.ts +9 -0
- package/dist/compiler/config/schemas/paths.js +12 -0
- package/dist/compiler/config/schemas/transport/http.d.ts +82 -0
- package/dist/compiler/config/schemas/transport/http.js +33 -0
- package/dist/compiler/config/schemas/transport/stdio.d.ts +9 -0
- package/dist/compiler/config/schemas/transport/stdio.js +15 -0
- package/dist/compiler/config/schemas/webpack.d.ts +3 -0
- package/dist/compiler/config/schemas/webpack.js +15 -0
- package/dist/compiler/config/types.d.ts +1 -0
- package/dist/compiler/config/types.js +2 -0
- package/dist/compiler/config/utils.d.ts +20 -0
- package/dist/compiler/config/utils.js +36 -0
- package/dist/compiler/generate-env-code.d.ts +1 -0
- package/dist/compiler/generate-env-code.js +8 -0
- package/dist/compiler/generate-import-code.d.ts +1 -0
- package/dist/compiler/generate-import-code.js +24 -0
- package/dist/compiler/get-webpack-config/get-entries.d.ts +3 -0
- package/dist/compiler/get-webpack-config/get-entries.js +29 -0
- package/dist/compiler/get-webpack-config/get-externals.d.ts +7 -0
- package/dist/compiler/get-webpack-config/get-externals.js +88 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.d.ts +8 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.js +25 -0
- package/dist/compiler/get-webpack-config/index.d.ts +4 -0
- package/dist/compiler/get-webpack-config/index.js +101 -0
- package/dist/compiler/get-webpack-config/plugins.d.ts +8 -0
- package/dist/compiler/get-webpack-config/plugins.js +132 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.d.ts +9 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.js +40 -0
- package/dist/compiler/index.d.ts +6 -0
- package/dist/compiler/index.js +194 -0
- package/dist/compiler/on-first-build.d.ts +3 -0
- package/dist/compiler/on-first-build.js +58 -0
- package/dist/compiler/parse-xmcp-config.d.ts +9 -0
- package/dist/compiler/parse-xmcp-config.js +155 -0
- package/dist/compiler/start-http-server.d.ts +1 -0
- package/dist/compiler/start-http-server.js +34 -0
- package/dist/index.d.ts +12 -54
- package/dist/index.js +22 -190
- package/dist/index.js.LICENSE.txt +49 -0
- package/dist/runtime/__tests__/audit.test.d.ts +4 -0
- package/dist/runtime/__tests__/audit.test.js +328 -0
- package/dist/runtime/__tests__/identity.test.d.ts +4 -0
- package/dist/runtime/__tests__/identity.test.js +164 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.d.ts +4 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.js +372 -0
- package/dist/runtime/__tests__/proof.test.d.ts +4 -0
- package/dist/runtime/__tests__/proof.test.js +302 -0
- package/dist/runtime/__tests__/session.test.d.ts +4 -0
- package/dist/runtime/__tests__/session.test.js +254 -0
- package/dist/runtime/__tests__/well-known.test.d.ts +4 -0
- package/dist/runtime/__tests__/well-known.test.js +312 -0
- package/dist/runtime/adapter-express.js +2 -0
- package/dist/runtime/adapter-express.js.LICENSE.txt +252 -0
- package/dist/runtime/adapter-nextjs.js +2 -0
- package/dist/runtime/adapter-nextjs.js.LICENSE.txt +53 -0
- package/dist/runtime/adapters/express/index.d.ts +2 -0
- package/dist/runtime/adapters/express/index.js +48 -0
- package/dist/runtime/adapters/nextjs/index.d.ts +8 -0
- package/dist/runtime/adapters/nextjs/index.js +18 -0
- package/dist/runtime/audit.d.ts +93 -0
- package/dist/runtime/audit.js +212 -0
- package/dist/runtime/debug.d.ts +118 -0
- package/dist/runtime/debug.js +612 -0
- package/dist/runtime/delegation-hooks.d.ts +85 -0
- package/dist/runtime/delegation-hooks.js +116 -0
- package/dist/runtime/demo.d.ts +71 -0
- package/dist/runtime/demo.js +135 -0
- package/dist/runtime/headers.d.ts +1 -0
- package/dist/runtime/headers.js +9 -0
- package/dist/runtime/http.js +2 -0
- package/dist/runtime/http.js.LICENSE.txt +252 -0
- package/dist/runtime/identity.d.ts +105 -0
- package/dist/runtime/identity.js +232 -0
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.js +56 -0
- package/dist/runtime/mcpi-runtime.d.ts +164 -0
- package/dist/runtime/mcpi-runtime.js +352 -0
- package/dist/runtime/proof.d.ts +87 -0
- package/dist/runtime/proof.js +223 -0
- package/dist/runtime/session.d.ts +88 -0
- package/dist/runtime/session.js +216 -0
- package/dist/runtime/stdio.js +2 -0
- package/dist/runtime/stdio.js.LICENSE.txt +1 -0
- package/dist/runtime/templates/home.d.ts +2 -0
- package/dist/runtime/templates/home.js +50 -0
- package/dist/runtime/transports/http/base-streamable-http.d.ts +25 -0
- package/dist/runtime/transports/http/base-streamable-http.js +16 -0
- package/dist/runtime/transports/http/http-context.d.ts +9 -0
- package/dist/runtime/transports/http/http-context.js +8 -0
- package/dist/runtime/transports/http/index.d.ts +1 -0
- package/dist/runtime/transports/http/index.js +55 -0
- package/dist/runtime/transports/http/setup-cors.d.ts +4 -0
- package/dist/runtime/transports/http/setup-cors.js +24 -0
- package/dist/runtime/transports/http/stateless-streamable-http.d.ts +39 -0
- package/dist/runtime/transports/http/stateless-streamable-http.js +331 -0
- package/dist/runtime/transports/stdio/index.d.ts +1 -0
- package/dist/runtime/transports/stdio/index.js +51 -0
- package/dist/runtime/utils/server.d.ts +42 -0
- package/dist/runtime/utils/server.js +39 -0
- package/dist/runtime/utils/tools.d.ts +8 -0
- package/dist/runtime/utils/tools.js +115 -0
- package/dist/runtime/verifier-middleware.d.ts +76 -0
- package/dist/runtime/verifier-middleware.js +322 -0
- package/dist/runtime/well-known.d.ts +151 -0
- package/dist/runtime/well-known.js +258 -0
- package/dist/storage/config.d.ts +28 -0
- package/dist/storage/config.js +79 -0
- package/dist/storage/delegation.d.ts +59 -0
- package/dist/storage/delegation.js +130 -0
- package/dist/storage/merkle-verifier.d.ts +84 -0
- package/dist/storage/merkle-verifier.js +261 -0
- package/dist/test/__tests__/nonce-cache-integration.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache-integration.test.js +116 -0
- package/dist/test/__tests__/nonce-cache.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache.test.js +122 -0
- package/dist/test/__tests__/runtime-integration.test.d.ts +4 -0
- package/dist/test/__tests__/runtime-integration.test.js +192 -0
- package/dist/test/__tests__/test-infrastructure.test.d.ts +4 -0
- package/dist/test/__tests__/test-infrastructure.test.js +178 -0
- package/dist/test/deterministic-keys.d.ts +31 -0
- package/dist/test/deterministic-keys.js +108 -0
- package/dist/test/examples/test-usage-example.d.ts +140 -0
- package/dist/test/examples/test-usage-example.js +175 -0
- package/dist/test/index.d.ts +11 -0
- package/dist/test/index.js +27 -0
- package/dist/test/local-verification.d.ts +28 -0
- package/dist/test/local-verification.js +342 -0
- package/dist/test/mock-identity-provider.d.ts +96 -0
- package/dist/test/mock-identity-provider.js +243 -0
- package/dist/test/runtime-integration.d.ts +63 -0
- package/dist/test/runtime-integration.js +140 -0
- package/dist/test/test-environment.d.ts +26 -0
- package/dist/test/test-environment.js +50 -0
- package/dist/types/declarations.d.ts +1 -0
- package/dist/types/declarations.js +6 -0
- package/dist/types/middleware.d.ts +2 -0
- package/dist/types/middleware.js +2 -0
- package/dist/types/tool.d.ts +80 -0
- package/dist/types/tool.js +2 -0
- package/dist/utils/cli-icons.d.ts +3 -0
- package/dist/utils/cli-icons.js +7 -0
- package/dist/utils/constants.d.ts +6 -0
- package/dist/utils/constants.js +13 -0
- package/dist/utils/context.d.ts +33 -0
- package/dist/utils/context.js +58 -0
- package/dist/utils/file-watcher.d.ts +19 -0
- package/dist/utils/file-watcher.js +49 -0
- package/dist/utils/fs-utils.d.ts +2 -0
- package/dist/utils/fs-utils.js +22 -0
- package/dist/utils/path-validation.d.ts +3 -0
- package/dist/utils/path-validation.js +56 -0
- package/dist/utils/spawn-process.d.ts +9 -0
- package/dist/utils/spawn-process.js +50 -0
- package/dist/utils/subscribable.d.ts +12 -0
- package/dist/utils/subscribable.js +44 -0
- package/package.json +99 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detached Proof Generation for XMCP-I Runtime
|
|
4
|
+
*
|
|
5
|
+
* Handles JCS canonicalization, SHA-256 digest generation, and Ed25519 detached JWS signing
|
|
6
|
+
* according to requirements 5.1, 5.2, 5.3, 5.6.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ProofGenerator = void 0;
|
|
10
|
+
exports.createProofResponse = createProofResponse;
|
|
11
|
+
exports.extractCanonicalData = extractCanonicalData;
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
const jose_1 = require("jose");
|
|
14
|
+
/**
|
|
15
|
+
* Proof generator class
|
|
16
|
+
*/
|
|
17
|
+
class ProofGenerator {
|
|
18
|
+
identity;
|
|
19
|
+
constructor(identity) {
|
|
20
|
+
this.identity = identity;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate detached proof for tool request/response
|
|
24
|
+
* Requirements: 5.1, 5.2, 5.3, 5.6
|
|
25
|
+
*/
|
|
26
|
+
async generateProof(request, response, session, options = {}) {
|
|
27
|
+
// Generate canonical hashes
|
|
28
|
+
const hashes = this.generateCanonicalHashes(request, response);
|
|
29
|
+
// Create proof metadata
|
|
30
|
+
const meta = {
|
|
31
|
+
did: this.identity.did,
|
|
32
|
+
kid: this.identity.keyId,
|
|
33
|
+
ts: Math.floor(Date.now() / 1000),
|
|
34
|
+
nonce: session.nonce,
|
|
35
|
+
audience: session.audience,
|
|
36
|
+
sessionId: session.sessionId,
|
|
37
|
+
requestHash: hashes.requestHash,
|
|
38
|
+
responseHash: hashes.responseHash,
|
|
39
|
+
...options, // Include scopeId and delegationRef if provided
|
|
40
|
+
};
|
|
41
|
+
// Generate detached JWS
|
|
42
|
+
const jws = await this.generateDetachedJWS(meta);
|
|
43
|
+
return {
|
|
44
|
+
jws,
|
|
45
|
+
meta,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate canonical hashes for request and response
|
|
50
|
+
* Requirement: 5.1
|
|
51
|
+
*/
|
|
52
|
+
generateCanonicalHashes(request, response) {
|
|
53
|
+
// Canonicalize request (exclude transport metadata, include method and params)
|
|
54
|
+
const canonicalRequest = {
|
|
55
|
+
method: request.method,
|
|
56
|
+
...(request.params && { params: request.params }),
|
|
57
|
+
};
|
|
58
|
+
// Canonicalize response (only the data part, exclude meta)
|
|
59
|
+
const canonicalResponse = response.data;
|
|
60
|
+
// Generate SHA-256 hashes with JCS canonicalization
|
|
61
|
+
const requestHash = this.generateSHA256Hash(canonicalRequest);
|
|
62
|
+
const responseHash = this.generateSHA256Hash(canonicalResponse);
|
|
63
|
+
return {
|
|
64
|
+
requestHash,
|
|
65
|
+
responseHash,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate SHA-256 hash with JCS canonicalization
|
|
70
|
+
* Requirement: 5.2
|
|
71
|
+
*/
|
|
72
|
+
generateSHA256Hash(data) {
|
|
73
|
+
// JCS (JSON Canonicalization Scheme) canonicalization
|
|
74
|
+
const canonicalJson = this.canonicalizeJSON(data);
|
|
75
|
+
// Generate SHA-256 hash
|
|
76
|
+
const hash = (0, crypto_1.createHash)("sha256")
|
|
77
|
+
.update(canonicalJson, "utf8")
|
|
78
|
+
.digest("hex");
|
|
79
|
+
return `sha256:${hash}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* JCS canonicalization implementation
|
|
83
|
+
* This is a simplified implementation - in production, use a proper JCS library
|
|
84
|
+
*/
|
|
85
|
+
canonicalizeJSON(obj) {
|
|
86
|
+
if (obj === null)
|
|
87
|
+
return "null";
|
|
88
|
+
if (typeof obj === "boolean")
|
|
89
|
+
return obj.toString();
|
|
90
|
+
if (typeof obj === "number") {
|
|
91
|
+
// Handle special number cases
|
|
92
|
+
if (Number.isNaN(obj))
|
|
93
|
+
return "null";
|
|
94
|
+
if (!Number.isFinite(obj))
|
|
95
|
+
return "null";
|
|
96
|
+
return obj.toString();
|
|
97
|
+
}
|
|
98
|
+
if (typeof obj === "string")
|
|
99
|
+
return JSON.stringify(obj);
|
|
100
|
+
if (Array.isArray(obj)) {
|
|
101
|
+
const items = obj.map((item) => this.canonicalizeJSON(item));
|
|
102
|
+
return `[${items.join(",")}]`;
|
|
103
|
+
}
|
|
104
|
+
if (typeof obj === "object") {
|
|
105
|
+
// Sort keys for canonical ordering
|
|
106
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
107
|
+
const pairs = sortedKeys.map((key) => {
|
|
108
|
+
const value = this.canonicalizeJSON(obj[key]);
|
|
109
|
+
return `${JSON.stringify(key)}:${value}`;
|
|
110
|
+
});
|
|
111
|
+
return `{${pairs.join(",")}}`;
|
|
112
|
+
}
|
|
113
|
+
// Fallback for other types
|
|
114
|
+
return JSON.stringify(obj);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generate Ed25519 detached JWS (compact format)
|
|
118
|
+
* Requirement: 5.3
|
|
119
|
+
*/
|
|
120
|
+
async generateDetachedJWS(meta) {
|
|
121
|
+
try {
|
|
122
|
+
// Import the private key
|
|
123
|
+
const privateKeyPem = this.formatPrivateKeyAsPEM(this.identity.privateKey);
|
|
124
|
+
const privateKey = await (0, jose_1.importPKCS8)(privateKeyPem, "EdDSA");
|
|
125
|
+
// Create JWT with proof metadata as payload
|
|
126
|
+
const jwt = await new jose_1.SignJWT(meta)
|
|
127
|
+
.setProtectedHeader({
|
|
128
|
+
alg: "EdDSA",
|
|
129
|
+
kid: this.identity.keyId,
|
|
130
|
+
})
|
|
131
|
+
.sign(privateKey);
|
|
132
|
+
// For detached JWS, we remove the payload part (middle section)
|
|
133
|
+
const [header, , signature] = jwt.split(".");
|
|
134
|
+
return `${header}..${signature}`;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new Error(`Failed to generate detached JWS: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Format base64 private key as PEM for JOSE library
|
|
142
|
+
*/
|
|
143
|
+
formatPrivateKeyAsPEM(base64PrivateKey) {
|
|
144
|
+
// For Ed25519, we need to format as PKCS#8 PEM
|
|
145
|
+
// This is a simplified implementation - in production, use proper key formatting
|
|
146
|
+
const keyData = Buffer.from(base64PrivateKey, "base64");
|
|
147
|
+
// Ed25519 PKCS#8 header and footer
|
|
148
|
+
const header = "-----BEGIN PRIVATE KEY-----\n";
|
|
149
|
+
const footer = "\n-----END PRIVATE KEY-----";
|
|
150
|
+
// For Ed25519, we need to wrap the raw key in PKCS#8 structure
|
|
151
|
+
// This is a simplified approach - in production, use proper ASN.1 encoding
|
|
152
|
+
const pkcs8Header = Buffer.from([
|
|
153
|
+
0x30,
|
|
154
|
+
0x2e, // SEQUENCE, length 46
|
|
155
|
+
0x02,
|
|
156
|
+
0x01,
|
|
157
|
+
0x00, // INTEGER version 0
|
|
158
|
+
0x30,
|
|
159
|
+
0x05, // SEQUENCE, length 5
|
|
160
|
+
0x06,
|
|
161
|
+
0x03,
|
|
162
|
+
0x2b,
|
|
163
|
+
0x65,
|
|
164
|
+
0x70, // OID for Ed25519
|
|
165
|
+
0x04,
|
|
166
|
+
0x22, // OCTET STRING, length 34
|
|
167
|
+
0x04,
|
|
168
|
+
0x20, // OCTET STRING, length 32 (the actual key)
|
|
169
|
+
]);
|
|
170
|
+
const fullKey = Buffer.concat([pkcs8Header, keyData.subarray(0, 32)]);
|
|
171
|
+
const base64Key = fullKey.toString("base64");
|
|
172
|
+
// Format as PEM with line breaks every 64 characters
|
|
173
|
+
const formattedKey = base64Key.match(/.{1,64}/g)?.join("\n") || base64Key;
|
|
174
|
+
return header + formattedKey + footer;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Verify a detached proof (for testing/validation)
|
|
178
|
+
*/
|
|
179
|
+
async verifyProof(proof, request, response) {
|
|
180
|
+
try {
|
|
181
|
+
// Regenerate hashes
|
|
182
|
+
const expectedHashes = this.generateCanonicalHashes(request, response);
|
|
183
|
+
// Check if hashes match
|
|
184
|
+
if (proof.meta.requestHash !== expectedHashes.requestHash ||
|
|
185
|
+
proof.meta.responseHash !== expectedHashes.responseHash) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
// TODO: Verify JWS signature (requires public key)
|
|
189
|
+
// For now, just validate the structure
|
|
190
|
+
const jwsParts = proof.jws.split(".");
|
|
191
|
+
return jwsParts.length === 3 && jwsParts[1] === ""; // Detached format
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.ProofGenerator = ProofGenerator;
|
|
199
|
+
/**
|
|
200
|
+
* Utility functions
|
|
201
|
+
*/
|
|
202
|
+
/**
|
|
203
|
+
* Create a tool response with proof
|
|
204
|
+
*/
|
|
205
|
+
async function createProofResponse(request, data, identity, session, options = {}) {
|
|
206
|
+
const response = { data };
|
|
207
|
+
const proofGenerator = new ProofGenerator(identity);
|
|
208
|
+
const proof = await proofGenerator.generateProof(request, response, session, options);
|
|
209
|
+
response.meta = { proof };
|
|
210
|
+
return response;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Extract canonical data for hashing (utility for testing)
|
|
214
|
+
*/
|
|
215
|
+
function extractCanonicalData(request, response) {
|
|
216
|
+
return {
|
|
217
|
+
request: {
|
|
218
|
+
method: request.method,
|
|
219
|
+
...(request.params && { params: request.params }),
|
|
220
|
+
},
|
|
221
|
+
response: response.data,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handshake and Session Management for XMCP-I Runtime
|
|
3
|
+
*
|
|
4
|
+
* Handles handshake enforcement, session management, and nonce validation
|
|
5
|
+
* according to requirements 4.5-4.9 and 19.1-19.2.
|
|
6
|
+
*/
|
|
7
|
+
import { HandshakeRequest, SessionContext, NonceCache } from "@kya-os/contracts/handshake";
|
|
8
|
+
/**
|
|
9
|
+
* Session management configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionConfig {
|
|
12
|
+
timestampSkewSeconds?: number;
|
|
13
|
+
sessionTtlMinutes?: number;
|
|
14
|
+
absoluteSessionLifetime?: number;
|
|
15
|
+
nonceCache?: NonceCache;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handshake validation result
|
|
19
|
+
*/
|
|
20
|
+
export interface HandshakeResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
session?: SessionContext;
|
|
23
|
+
error?: {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
remediation?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Session manager class
|
|
31
|
+
*/
|
|
32
|
+
export declare class SessionManager {
|
|
33
|
+
private config;
|
|
34
|
+
private sessions;
|
|
35
|
+
constructor(config?: SessionConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Validate handshake and create or retrieve session
|
|
38
|
+
* Requirements: 4.5, 4.6, 4.7, 4.8, 4.9
|
|
39
|
+
*/
|
|
40
|
+
validateHandshake(request: HandshakeRequest): Promise<HandshakeResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Get session by ID and update last activity
|
|
43
|
+
*/
|
|
44
|
+
getSession(sessionId: string): Promise<SessionContext | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Generate a unique session ID
|
|
47
|
+
*/
|
|
48
|
+
private generateSessionId;
|
|
49
|
+
/**
|
|
50
|
+
* Generate a cryptographically secure nonce
|
|
51
|
+
*/
|
|
52
|
+
static generateNonce(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Cleanup expired sessions and nonces
|
|
55
|
+
*/
|
|
56
|
+
cleanup(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Get session statistics
|
|
59
|
+
*/
|
|
60
|
+
getStats(): {
|
|
61
|
+
activeSessions: number;
|
|
62
|
+
config: {
|
|
63
|
+
timestampSkewSeconds: number;
|
|
64
|
+
sessionTtlMinutes: number;
|
|
65
|
+
absoluteSessionLifetime?: number;
|
|
66
|
+
cacheType: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Clear all sessions (useful for testing)
|
|
71
|
+
*/
|
|
72
|
+
clearSessions(): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Default session manager instance
|
|
76
|
+
*/
|
|
77
|
+
export declare const defaultSessionManager: SessionManager;
|
|
78
|
+
/**
|
|
79
|
+
* Utility functions
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* Create a handshake request
|
|
83
|
+
*/
|
|
84
|
+
export declare function createHandshakeRequest(audience: string): HandshakeRequest;
|
|
85
|
+
/**
|
|
86
|
+
* Validate handshake request format
|
|
87
|
+
*/
|
|
88
|
+
export declare function validateHandshakeFormat(request: any): request is HandshakeRequest;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Handshake and Session Management for XMCP-I Runtime
|
|
4
|
+
*
|
|
5
|
+
* Handles handshake enforcement, session management, and nonce validation
|
|
6
|
+
* according to requirements 4.5-4.9 and 19.1-19.2.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.defaultSessionManager = exports.SessionManager = void 0;
|
|
10
|
+
exports.createHandshakeRequest = createHandshakeRequest;
|
|
11
|
+
exports.validateHandshakeFormat = validateHandshakeFormat;
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
const memory_nonce_cache_1 = require("../cache/memory-nonce-cache");
|
|
14
|
+
/**
|
|
15
|
+
* Session manager class
|
|
16
|
+
*/
|
|
17
|
+
class SessionManager {
|
|
18
|
+
config;
|
|
19
|
+
sessions = new Map();
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
timestampSkewSeconds: parseInt(process.env.XMCP_I_TS_SKEW_SEC || "120"),
|
|
23
|
+
sessionTtlMinutes: parseInt(process.env.XMCP_I_SESSION_TTL_MIN || "30"),
|
|
24
|
+
// absoluteSessionLifetime: undefined, // Disabled by default (omit to use undefined)
|
|
25
|
+
nonceCache: new memory_nonce_cache_1.MemoryNonceCache(),
|
|
26
|
+
...config,
|
|
27
|
+
};
|
|
28
|
+
// Warn about multi-instance deployments with memory cache
|
|
29
|
+
if (this.config.nonceCache instanceof memory_nonce_cache_1.MemoryNonceCache) {
|
|
30
|
+
console.warn("Warning: Using MemoryNonceCache - not suitable for multi-instance deployments. " +
|
|
31
|
+
"Consider using Redis, DynamoDB, or Cloudflare KV for production.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate handshake and create or retrieve session
|
|
36
|
+
* Requirements: 4.5, 4.6, 4.7, 4.8, 4.9
|
|
37
|
+
*/
|
|
38
|
+
async validateHandshake(request) {
|
|
39
|
+
try {
|
|
40
|
+
// Validate timestamp (±120s clock skew by default)
|
|
41
|
+
const now = Math.floor(Date.now() / 1000);
|
|
42
|
+
const timeDiff = Math.abs(now - request.timestamp);
|
|
43
|
+
if (timeDiff > this.config.timestampSkewSeconds) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: {
|
|
47
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
48
|
+
message: `Timestamp outside acceptable range (±${this.config.timestampSkewSeconds}s)`,
|
|
49
|
+
remediation: `Check NTP sync on client and server. Current server time: ${now}, received: ${request.timestamp}, diff: ${timeDiff}s. Adjust XMCP_I_TS_SKEW_SEC if needed.`,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Validate nonce (must be unique within session window)
|
|
54
|
+
const nonceExists = await this.config.nonceCache.has(request.nonce);
|
|
55
|
+
if (nonceExists) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: {
|
|
59
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
60
|
+
message: "Nonce already used (replay attack prevention)",
|
|
61
|
+
remediation: "Generate a new unique nonce for each request",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Add nonce to cache with TTL >= session TTL
|
|
66
|
+
const nonceTtlSeconds = this.config.sessionTtlMinutes * 60 + 60; // Session TTL + 1 minute buffer
|
|
67
|
+
await this.config.nonceCache.add(request.nonce, nonceTtlSeconds);
|
|
68
|
+
// Generate session ID
|
|
69
|
+
const sessionId = this.generateSessionId();
|
|
70
|
+
// Create session context
|
|
71
|
+
const session = {
|
|
72
|
+
sessionId,
|
|
73
|
+
audience: request.audience,
|
|
74
|
+
nonce: request.nonce,
|
|
75
|
+
timestamp: request.timestamp,
|
|
76
|
+
createdAt: now,
|
|
77
|
+
lastActivity: now,
|
|
78
|
+
ttlMinutes: this.config.sessionTtlMinutes,
|
|
79
|
+
};
|
|
80
|
+
// Store session
|
|
81
|
+
this.sessions.set(sessionId, session);
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
session,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: {
|
|
91
|
+
code: "XMCP_I_EHANDSHAKE",
|
|
92
|
+
message: `Handshake validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get session by ID and update last activity
|
|
99
|
+
*/
|
|
100
|
+
async getSession(sessionId) {
|
|
101
|
+
const session = this.sessions.get(sessionId);
|
|
102
|
+
if (!session) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const now = Math.floor(Date.now() / 1000);
|
|
106
|
+
// Check if session has expired (idle timeout)
|
|
107
|
+
const idleTimeSeconds = now - session.lastActivity;
|
|
108
|
+
const maxIdleSeconds = session.ttlMinutes * 60;
|
|
109
|
+
if (idleTimeSeconds > maxIdleSeconds) {
|
|
110
|
+
this.sessions.delete(sessionId);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
// Check absolute session lifetime if configured
|
|
114
|
+
if (this.config.absoluteSessionLifetime) {
|
|
115
|
+
const sessionAgeSeconds = now - session.createdAt;
|
|
116
|
+
const maxAgeSeconds = this.config.absoluteSessionLifetime * 60;
|
|
117
|
+
if (sessionAgeSeconds > maxAgeSeconds) {
|
|
118
|
+
this.sessions.delete(sessionId);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Update last activity
|
|
123
|
+
session.lastActivity = now;
|
|
124
|
+
this.sessions.set(sessionId, session);
|
|
125
|
+
return session;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Generate a unique session ID
|
|
129
|
+
*/
|
|
130
|
+
generateSessionId() {
|
|
131
|
+
const timestamp = Date.now().toString(36);
|
|
132
|
+
const random = (0, crypto_1.randomBytes)(8).toString("hex");
|
|
133
|
+
return `sess_${timestamp}_${random}`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Generate a cryptographically secure nonce
|
|
137
|
+
*/
|
|
138
|
+
static generateNonce() {
|
|
139
|
+
return (0, crypto_1.randomBytes)(16).toString("base64url"); // 128-bit nonce
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Cleanup expired sessions and nonces
|
|
143
|
+
*/
|
|
144
|
+
async cleanup() {
|
|
145
|
+
const now = Math.floor(Date.now() / 1000);
|
|
146
|
+
// Clean up expired sessions
|
|
147
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
148
|
+
const idleTimeSeconds = now - session.lastActivity;
|
|
149
|
+
const maxIdleSeconds = session.ttlMinutes * 60;
|
|
150
|
+
let expired = idleTimeSeconds > maxIdleSeconds;
|
|
151
|
+
// Check absolute lifetime
|
|
152
|
+
if (!expired && this.config.absoluteSessionLifetime) {
|
|
153
|
+
const sessionAgeSeconds = now - session.createdAt;
|
|
154
|
+
const maxAgeSeconds = this.config.absoluteSessionLifetime * 60;
|
|
155
|
+
expired = sessionAgeSeconds > maxAgeSeconds;
|
|
156
|
+
}
|
|
157
|
+
if (expired) {
|
|
158
|
+
this.sessions.delete(sessionId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Clean up expired nonces
|
|
162
|
+
await this.config.nonceCache.cleanup();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get session statistics
|
|
166
|
+
*/
|
|
167
|
+
getStats() {
|
|
168
|
+
return {
|
|
169
|
+
activeSessions: this.sessions.size,
|
|
170
|
+
config: {
|
|
171
|
+
timestampSkewSeconds: this.config.timestampSkewSeconds,
|
|
172
|
+
sessionTtlMinutes: this.config.sessionTtlMinutes,
|
|
173
|
+
absoluteSessionLifetime: this.config.absoluteSessionLifetime,
|
|
174
|
+
cacheType: this.config.nonceCache.constructor.name,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Clear all sessions (useful for testing)
|
|
180
|
+
*/
|
|
181
|
+
clearSessions() {
|
|
182
|
+
this.sessions.clear();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.SessionManager = SessionManager;
|
|
186
|
+
/**
|
|
187
|
+
* Default session manager instance
|
|
188
|
+
*/
|
|
189
|
+
exports.defaultSessionManager = new SessionManager();
|
|
190
|
+
/**
|
|
191
|
+
* Utility functions
|
|
192
|
+
*/
|
|
193
|
+
/**
|
|
194
|
+
* Create a handshake request
|
|
195
|
+
*/
|
|
196
|
+
function createHandshakeRequest(audience) {
|
|
197
|
+
return {
|
|
198
|
+
nonce: SessionManager.generateNonce(),
|
|
199
|
+
audience,
|
|
200
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Validate handshake request format
|
|
205
|
+
*/
|
|
206
|
+
function validateHandshakeFormat(request) {
|
|
207
|
+
return (typeof request === "object" &&
|
|
208
|
+
request !== null &&
|
|
209
|
+
typeof request.nonce === "string" &&
|
|
210
|
+
request.nonce.length > 0 &&
|
|
211
|
+
typeof request.audience === "string" &&
|
|
212
|
+
request.audience.length > 0 &&
|
|
213
|
+
typeof request.timestamp === "number" &&
|
|
214
|
+
request.timestamp > 0 &&
|
|
215
|
+
Number.isInteger(request.timestamp));
|
|
216
|
+
}
|