@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
341 lines (340 loc) • 11.4 kB
JavaScript
"use strict";
/**
* Local verification utilities for offline testing
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyProofLocally = verifyProofLocally;
exports.verifyDIDDocumentLocally = verifyDIDDocumentLocally;
exports.createMockProof = createMockProof;
const crypto_1 = require("crypto");
const json_canonicalize_1 = require("json-canonicalize");
const test_1 = require("@kya-os/contracts/test");
const test_environment_1 = require("./test-environment");
const mock_identity_provider_1 = require("./mock-identity-provider");
/**
* Canonicalize data using JCS (JSON Canonicalization Scheme) RFC 8785
*/
function canonicalizeJSON(data) {
return (0, json_canonicalize_1.canonicalize)(data);
}
/**
* Generate SHA-256 hash with prefix
*/
function generateHash(data) {
return `sha256:${(0, crypto_1.createHash)("sha256").update(data).digest("hex")}`;
}
/**
* Verify Ed25519 signature locally (mock implementation for testing)
*/
function verifySignature(data, signature, publicKey) {
(0, test_environment_1.ensureTestMode)();
try {
// Mock signature verification for testing
// In production, this would use proper Ed25519 verification
const expectedSignature = (0, crypto_1.createHash)("sha256")
.update(data + publicKey)
.digest("base64");
const valid = signature === expectedSignature;
return {
valid,
error: valid ? undefined : "Signature verification failed",
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error
? error.message
: "Unknown signature verification error",
};
}
}
/**
* Verify proof structure and content
*/
function verifyProofStructure(proof) {
(0, test_environment_1.ensureTestMode)();
const errors = [];
// Check required fields
const requiredFields = ["jws", "meta"];
const requiredMetaFields = [
"did",
"kid",
"ts",
"nonce",
"audience",
"sessionId",
"requestHash",
"responseHash",
];
let structure = true;
if (!proof || typeof proof !== "object") {
structure = false;
errors.push("Proof must be an object");
}
else {
for (const field of requiredFields) {
if (!(field in proof)) {
structure = false;
errors.push(`Missing required field: ${field}`);
}
}
if (proof.meta && typeof proof.meta === "object") {
for (const field of requiredMetaFields) {
if (!(field in proof.meta)) {
structure = false;
errors.push(`Missing required meta field: ${field}`);
}
}
}
else {
structure = false;
errors.push("Proof meta must be an object");
}
}
// Check timestamp validity (within reasonable bounds)
let timestamps = true;
if (proof?.meta?.ts) {
const now = Date.now() / 1000;
const proofTime = proof.meta.ts;
const skew = Math.abs(now - proofTime);
if (skew > 300) {
// 5 minutes for testing
timestamps = false;
errors.push(`Timestamp too far from current time: ${skew}s`);
}
}
else {
timestamps = false;
errors.push("Missing or invalid timestamp");
}
// Check hash format
let hashes = true;
if (proof?.meta?.requestHash &&
!proof.meta.requestHash.startsWith("sha256:")) {
hashes = false;
errors.push("Request hash must start with 'sha256:'");
}
if (proof?.meta?.responseHash &&
!proof.meta.responseHash.startsWith("sha256:")) {
hashes = false;
errors.push("Response hash must start with 'sha256:'");
}
const valid = structure && timestamps && hashes;
return {
valid,
structure,
timestamps,
hashes,
error: errors.length > 0 ? errors.join("; ") : undefined,
};
}
/**
* Verify session validity
*/
function verifySession(sessionId, timestamp) {
(0, test_environment_1.ensureTestMode)();
if (!sessionId || typeof sessionId !== "string") {
return {
valid: false,
expired: false,
error: "Invalid session ID",
};
}
// For testing, assume sessions are valid for 30 minutes
const now = Date.now() / 1000;
const sessionAge = now - timestamp;
const expired = sessionAge > 1800; // 30 minutes
return {
valid: !expired,
expired,
error: expired ? "Session expired" : undefined,
};
}
/**
* Perform local verification of a proof without KTA calls
*/
async function verifyProofLocally(proof, request, response) {
(0, test_environment_1.ensureTestMode)();
try {
const errors = [];
const warnings = [];
// Verify proof structure
const proofCheck = verifyProofStructure(proof);
if (!proofCheck.valid && proofCheck.error) {
errors.push(proofCheck.error);
}
let signatureValid = false;
let did;
let kid;
if (proofCheck.structure && proof?.meta) {
did = proof.meta.did;
kid = proof.meta.kid;
// Get mock identity for verification
const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)();
const identity = mockProvider.getIdentity("agent1") ||
mockProvider.getIdentity("agent2") ||
mockProvider.getIdentity("verifier1");
if (identity && identity.did === did && identity.kid === kid) {
// Verify signature using mock implementation
const canonicalMeta = canonicalizeJSON(proof.meta);
const signatureCheck = verifySignature(canonicalMeta, proof.jws, identity.publicKey);
signatureValid = signatureCheck.valid;
if (!signatureValid && signatureCheck.error) {
errors.push(signatureCheck.error);
}
}
else {
errors.push(`No matching identity found for DID: ${did}, KeyID: ${kid}`);
}
}
// Verify session
const sessionCheck = verifySession(proof?.meta?.sessionId, proof?.meta?.ts);
if (!sessionCheck.valid && sessionCheck.error) {
errors.push(sessionCheck.error);
}
if (sessionCheck.expired) {
warnings.push("Session is expired");
}
// Verify hashes if request/response provided
if (request && proof?.meta?.requestHash) {
const expectedRequestHash = generateHash(canonicalizeJSON(request));
if (expectedRequestHash !== proof.meta.requestHash) {
errors.push("Request hash mismatch");
}
}
if (response && proof?.meta?.responseHash) {
const expectedResponseHash = generateHash(canonicalizeJSON(response));
if (expectedResponseHash !== proof.meta.responseHash) {
errors.push("Response hash mismatch");
}
}
const result = {
valid: errors.length === 0,
did,
kid,
signature: {
valid: signatureValid,
algorithm: "EdDSA",
error: signatureValid ? undefined : "Signature verification failed",
},
proof: proofCheck,
session: sessionCheck,
errors,
warnings,
};
return test_1.LocalVerificationResultSchema.parse(result);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown verification error";
return test_1.LocalVerificationResultSchema.parse({
valid: false,
signature: {
valid: false,
algorithm: "EdDSA",
error: "Verification failed",
},
proof: {
valid: false,
structure: false,
timestamps: false,
hashes: false,
error: errorMessage,
},
session: {
valid: false,
expired: false,
error: "Session verification failed",
},
errors: [errorMessage],
warnings: [],
});
}
}
/**
* Verify DID document locally (mock implementation)
*/
async function verifyDIDDocumentLocally(did) {
(0, test_environment_1.ensureTestMode)();
try {
const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)();
const identities = mockProvider.getAllIdentities();
// Find identity with matching DID
const identity = Object.values(identities).find((id) => id.did === did);
if (!identity) {
return {
valid: false,
error: `No identity found for DID: ${did}`,
};
}
// Generate mock DID document
const document = {
"@context": ["https://www.w3.org/ns/did/v1"],
id: did,
verificationMethod: [
{
id: `${did}#${identity.kid}`,
type: "Ed25519VerificationKey2020",
controller: did,
publicKeyMultibase: `z${identity.publicKey}`,
},
],
authentication: [`${did}#${identity.kid}`],
assertionMethod: [`${did}#${identity.kid}`],
};
return {
valid: true,
document,
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error
? error.message
: "Unknown DID verification error",
};
}
}
/**
* Create a mock proof for testing
*/
function createMockProof(options) {
(0, test_environment_1.ensureTestMode)();
const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)();
const identity = mockProvider.getIdentity("agent1");
if (!identity) {
throw new Error(`${test_1.TEST_ERROR_CODES.LOCAL_VERIFICATION_FAILED}: No test identity available`);
}
const now = Math.floor(Date.now() / 1000);
const did = options.did || identity.did;
const kid = options.kid || identity.kid;
const sessionId = options.sessionId || "sess_test_mock";
const nonce = options.nonce || "mock_nonce";
const audience = options.audience || "test.example.com";
const requestHash = options.request
? generateHash(canonicalizeJSON(options.request))
: "sha256:mock_request_hash";
const responseHash = options.response
? generateHash(canonicalizeJSON(options.response))
: "sha256:mock_response_hash";
const meta = {
did,
kid: kid,
ts: now,
nonce,
audience,
sessionId,
requestHash,
responseHash,
};
// Generate mock JWS signature
const canonicalMeta = canonicalizeJSON(meta);
const jws = (0, crypto_1.createHash)("sha256")
.update(canonicalMeta + identity.publicKey)
.digest("base64");
return {
jws,
meta,
};
}