"use strict";
/**
 * ASI-07 (OWASP Agentic Top 10 2026) — Inter-Agent Communication signing.
 *
 * Microsoft AGT parity: per-message authenticity for agent↔agent messages.
 * A sending agent signs an {@link AgentMessageEnvelope} over the canonical
 * JSON of its authenticated fields ({agentId, nonce, payload, timestamp})
 * with its Ed25519 private key. A receiving agent verifies the signature,
 * rejects replays (nonce already seen), and rejects messages outside a
 * bounded clock-skew window BEFORE acting on the message.
 *
 * Why per-message (not just handshake): handshake-only identity binding lets
 * a compromised relay or man-in-the-middle inject/forge messages mid-session.
 * Signing every message closes the inter-agent injection class (ASI-07).
 *
 * Locked Architectural Invariants honored:
 *   - I-2 (advisory, never authoritative) / I-3 (graceful degradation):
 *     {@link verifyAgentMessage} NEVER throws. Every failure mode returns a
 *     structured {valid:false, status} result. A malformed or hostile message
 *     can never crash the agent's execution path — the receiving org's policy
 *     decides what to do with a non-`valid` result (WARN / BLOCK / drop).
 *   - I-10 (push-receive only): signing/verification are pure in-process
 *     crypto. No network call, no outbound dependency, no platform contact.
 *
 * Crypto: Node built-in `crypto` Ed25519 (`crypto.sign(null, ...)` /
 * `crypto.verify(null, ...)`), mirroring the established
 * `packages/compliance-agent-runtime/src/signing.ts` passport-signing
 * primitive. Self-contained here (no cross-package import) so the orchestrator
 * adapters in `src/orchestrator-adapters/*` can depend on it without reaching
 * into another workspace package.
 *
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.canonicalJsonStringify = canonicalJsonStringify;
exports.generateAgentMessageKeyPair = generateAgentMessageKeyPair;
exports.signAgentMessage = signAgentMessage;
exports.verifyAgentMessage = verifyAgentMessage;
const crypto = __importStar(require("node:crypto"));
// ---------------------------------------------------------------------------
// Canonicalization (deterministic, key-order independent)
// ---------------------------------------------------------------------------
/**
 * Canonical JSON encoding: object keys sorted lexicographically at every
 * nesting level, no insignificant whitespace, UTF-8. Same value → same bytes
 * regardless of source key order. Mirrors the compliance-agent-runtime
 * passport signer's canonical form (internal-interop subset, not RFC 8785).
 */
function canonicalJsonStringify(value) {
    if (value === null || typeof value !== 'object') {
        return JSON.stringify(value) ?? 'null';
    }
    if (Array.isArray(value)) {
        return '[' + value.map((v) => canonicalJsonStringify(v)).join(',') + ']';
    }
    const obj = value;
    const keys = Object.keys(obj).sort();
    const parts = keys.map((k) => JSON.stringify(k) + ':' + canonicalJsonStringify(obj[k]));
    return '{' + parts.join(',') + '}';
}
/**
 * The exact authenticated sub-object the signature covers.
 *
 * `algorithm` is included in the signed body (Shield ASI-07 audit F-4,
 * 2026-05-29): without it, if a second algorithm were ever added with
 * different canonicalization, an attacker could swap the envelope's
 * `algorithm` field while keeping a signature made under another scheme
 * (algorithm-substitution). Binding the algorithm into the signature closes
 * that forward-looking gap before any production signatures exist.
 */
function signedBody(agentId, nonce, payload, timestamp) {
    return { agentId, algorithm: 'ed25519/canonical-json', nonce, payload, timestamp };
}
// ---------------------------------------------------------------------------
// Key generation
// ---------------------------------------------------------------------------
/** Generate a fresh Ed25519 keypair for inter-agent message signing. */
function generateAgentMessageKeyPair() {
    const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
    return {
        privateKeyPem: privateKey.export({ format: 'pem', type: 'pkcs8' }).toString(),
        publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
    };
}
// ---------------------------------------------------------------------------
// Sign
// ---------------------------------------------------------------------------
/**
 * Sign an inter-agent message. Returns a fully-formed envelope ready to send.
 * A fresh random nonce + current timestamp are generated unless overridden.
 */
function signAgentMessage(input) {
    const nonce = input.nonce ?? crypto.randomBytes(16).toString('base64url');
    const timestamp = input.timestamp ?? new Date().toISOString();
    const canonical = canonicalJsonStringify(signedBody(input.agentId, nonce, input.payload, timestamp));
    const privateKey = crypto.createPrivateKey({
        key: input.privateKeyPem,
        format: 'pem',
        type: 'pkcs8',
    });
    const signature = crypto
        .sign(null, Buffer.from(canonical, 'utf-8'), privateKey)
        .toString('base64url');
    return {
        payload: input.payload,
        agentId: input.agentId,
        timestamp,
        nonce,
        signature,
        algorithm: 'ed25519/canonical-json',
    };
}
// ---------------------------------------------------------------------------
// Verify (NEVER throws — Invariant I-2/I-3)
// ---------------------------------------------------------------------------
const DEFAULT_MAX_SKEW_MS = 5 * 60 * 1000;
function isNonEmptyString(v) {
    return typeof v === 'string' && v.length > 0;
}
/**
 * Verify a signed inter-agent message. NEVER throws — returns a structured
 * result for every input, including hostile/garbage envelopes.
 *
 * Order of checks (cheap structural rejection first; nonce burned LAST so a
 * forged message can't consume a legitimate future nonce):
 *   1. structural well-formedness + algorithm  → `malformed`
 *   2. clock-skew window                        → `expired`
 *   3. sender public key resolvable             → `unknown-sender`
 *   4. Ed25519 signature over canonical body    → `bad-signature`
 *   5. replay (nonce already seen)              → `replay`
 *   6. all pass → record nonce, return `valid`
 */
function verifyAgentMessage(envelope, opts) {
    const now = opts.now ?? Date.now();
    const maxSkew = opts.maxClockSkewMs ?? DEFAULT_MAX_SKEW_MS;
    // 1. Structural well-formedness.
    const env = envelope;
    if (env === null ||
        typeof env !== 'object' ||
        !isNonEmptyString(env.agentId) ||
        !isNonEmptyString(env.nonce) ||
        !isNonEmptyString(env.timestamp) ||
        !isNonEmptyString(env.signature) ||
        env.algorithm !== 'ed25519/canonical-json' ||
        !('payload' in env)) {
        return {
            valid: false,
            status: 'malformed',
            agentId: typeof env?.agentId === 'string' ? env.agentId : null,
            reason: 'Envelope is missing required fields or uses an unsupported algorithm.',
        };
    }
    const agentId = env.agentId;
    const nonce = env.nonce;
    const timestamp = env.timestamp;
    const signature = env.signature;
    const payload = env.payload;
    // 2. Clock-skew window.
    const ts = Date.parse(timestamp);
    if (Number.isNaN(ts)) {
        return { valid: false, status: 'malformed', agentId, reason: 'Timestamp is not a valid ISO-8601 date.' };
    }
    if (Math.abs(now - ts) > maxSkew) {
        return {
            valid: false,
            status: 'expired',
            agentId,
            reason: `Message timestamp is outside the ${maxSkew}ms skew window (|now-ts|=${Math.abs(now - ts)}ms).`,
        };
    }
    // 3. Resolve sender public key.
    let publicKeyPem;
    try {
        publicKeyPem = opts.resolvePublicKey(agentId);
    }
    catch {
        publicKeyPem = null; // resolver must not be able to crash the path
    }
    if (!isNonEmptyString(publicKeyPem)) {
        return { valid: false, status: 'unknown-sender', agentId, reason: `No public key on file for sender "${agentId}".` };
    }
    // 4. Verify Ed25519 signature over the canonical authenticated body.
    let sigOk = false;
    try {
        const canonical = canonicalJsonStringify(signedBody(agentId, nonce, payload, timestamp));
        const publicKey = crypto.createPublicKey({ key: publicKeyPem, format: 'pem', type: 'spki' });
        const signatureBytes = Buffer.from(signature, 'base64url');
        sigOk = crypto.verify(null, Buffer.from(canonical, 'utf-8'), publicKey, signatureBytes);
    }
    catch {
        sigOk = false; // bad PEM / bad base64url / verify error → not valid, never throw
    }
    if (!sigOk) {
        return { valid: false, status: 'bad-signature', agentId, reason: 'Ed25519 signature does not match the message body.' };
    }
    // 5. Replay check (signature already proven good — safe to consult nonce).
    if (opts.nonceSeen && opts.nonceSeen(nonce)) {
        return { valid: false, status: 'replay', agentId, reason: `Nonce "${nonce}" has already been consumed (replay).` };
    }
    // 6. Valid — burn the nonce LAST so only good messages consume it.
    if (opts.recordNonce) {
        try {
            opts.recordNonce(nonce);
        }
        catch {
            /* recording failure must not crash the agent path; signature already verified */
        }
    }
    return { valid: true, status: 'valid', agentId, reason: 'Signature valid, in-window, sender known, not a replay.' };
}
//# sourceMappingURL=agent-message-envelope.js.map