@fedmcp/core
Version:
Federal Model Context Protocol - TypeScript implementation
99 lines • 3.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Verifier = void 0;
const crypto_1 = require("crypto");
const signer_1 = require("./signer");
class Verifier {
constructor() {
this.publicKeys = new Map();
}
async addPublicKey(keyId, publicKey) {
this.publicKeys.set(keyId, publicKey);
}
async addPublicKeyJWK(jwk) {
const keyId = jwk.kid || await this.generateKeyIdFromJWK(jwk);
const publicKey = await crypto_1.subtle.importKey('jwk', jwk, {
name: 'ECDSA',
namedCurve: 'P-256'
}, true, ['verify']);
this.publicKeys.set(keyId, publicKey);
}
async verify(jws) {
// Split JWS into parts
const parts = jws.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWS format');
}
const [encodedHeader, encodedPayload, encodedSignature] = parts;
// Decode header
const headerJson = new TextDecoder().decode((0, signer_1.base64urlDecode)(encodedHeader));
const header = JSON.parse(headerJson);
const keyId = header.kid;
if (!keyId) {
throw new Error('No key ID in JWS header');
}
const publicKey = this.publicKeys.get(keyId);
if (!publicKey) {
throw new Error(`Unknown key ID: ${keyId}`);
}
// Verify signature
const signingInput = `${encodedHeader}.${encodedPayload}`;
const signature = (0, signer_1.base64urlDecode)(encodedSignature);
const isValid = await crypto_1.subtle.verify({
name: 'ECDSA',
hash: 'SHA-256'
}, publicKey, signature, new TextEncoder().encode(signingInput));
if (!isValid) {
throw new Error('Invalid signature');
}
// Decode and validate payload
const payloadJson = new TextDecoder().decode((0, signer_1.base64urlDecode)(encodedPayload));
const payload = JSON.parse(payloadJson);
// Extract artifact
if (!payload.artifact) {
throw new Error('No artifact in JWS payload');
}
const artifact = payload.artifact;
// Verify claims
if (artifact.id !== payload.sub) {
throw new Error("Artifact ID doesn't match subject claim");
}
if (artifact.workspaceId !== payload.iss) {
throw new Error("Workspace ID doesn't match issuer claim");
}
// Check expiration
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
throw new Error('Token has expired');
}
return artifact;
}
async verifyWithResult(jws) {
try {
const artifact = await this.verify(jws);
return {
valid: true,
claims: { artifact }
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async generateKeyIdFromJWK(jwk) {
// Generate key ID from JWK coordinates
const keyMaterial = `${jwk.x || ''}${jwk.y || ''}`;
const encoder = new TextEncoder();
const data = encoder.encode(keyMaterial);
const hash = await crypto_1.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hash))
.slice(0, 8)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
}
exports.Verifier = Verifier;
//# sourceMappingURL=verifier.js.map