/** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
/**
 * GC-PERM-011 — Policy signing and provenance for the permissions runtime.
 *
 * Provides HMAC-SHA256-based signature creation and verification for policy
 * bundles. Used by the policy loader to validate bundle integrity before
 * the evaluator processes any rules.
 *
 * Signing key is supplied at runtime (environment or config). The signer
 * never stores keys in bundle payloads.
 */
import { createHmac, timingSafeEqual } from 'node:crypto';
// ── Canonical serialisation ───────────────────────────────────────────────────
/**
 * canonicalise — Produces a deterministic JSON string for the given value.
 *
 * Keys are sorted recursively so that the representation is stable
 * regardless of insertion order, ensuring consistent HMAC inputs.
 *
 * @param value — The value to serialise.
 */
export function canonicalise(value) {
    if (value === null || typeof value !== 'object') {
        return JSON.stringify(value);
    }
    if (Array.isArray(value)) {
        return '[' + value.map(canonicalise).join(',') + ']';
    }
    const obj = value;
    const keys = Object.keys(obj).sort();
    const pairs = keys.map((k) => `${JSON.stringify(k)}:${canonicalise(obj[k])}`);
    return '{' + pairs.join(',') + '}';
}
// ── Signing ───────────────────────────────────────────────────────────────────
/**
 * signBundle — Creates a signed policy bundle from a payload.
 *
 * Generates a hex-encoded HMAC-SHA256 signature over the canonical JSON
 * of the payload and embeds it in the returned `SignedPolicyBundle`.
 *
 * @param bundleId — Unique identifier for this bundle.
 * @param payload  — The policy payload to sign.
 * @param key      — Raw signing key (Buffer or hex string).
 * @param issuer   — Optional human-readable issuer label.
 */
export function signBundle(bundleId, payload, key, issuer) {
    const issuedAt = new Date().toISOString();
    const composite = { bundleId, issuedAt, issuer, payload };
    const canonical = canonicalise(composite);
    const keyBuf = typeof key === 'string' ? Buffer.from(key, 'hex') : key;
    const signature = createHmac('sha256', keyBuf)
        .update(canonical, 'utf8')
        .digest('hex');
    const bundle = {
        bundleId,
        issuedAt,
        payload,
        signature,
    };
    if (issuer !== undefined)
        bundle.issuer = issuer;
    return bundle;
}
// ── Verification ──────────────────────────────────────────────────────────────
/**
 * verifyBundle — Validates the HMAC-SHA256 signature of a policy bundle.
 *
 * Computes the expected HMAC over the canonical payload and performs
 * a constant-time comparison against the stored signature to prevent
 * timing side-channel attacks.
 *
 * Returns `{ ok: false, status: 'missing' }` when the bundle carries no
 * signature field. Callers decide whether to accept unsigned bundles.
 *
 * @param bundle — The bundle to verify.
 * @param key    — The verification key (Buffer or hex string).
 */
export function verifyBundle(bundle, key) {
    if (bundle.signature === undefined || bundle.signature === '') {
        return {
            ok: false,
            status: 'missing',
            message: `Bundle "${bundle.bundleId}" carries no signature.`,
        };
    }
    const composite = {
        bundleId: bundle.bundleId,
        issuedAt: bundle.issuedAt,
        issuer: bundle.issuer,
        payload: bundle.payload,
    };
    const canonical = canonicalise(composite);
    const keyBuf = typeof key === 'string' ? Buffer.from(key, 'hex') : key;
    let expected;
    try {
        expected = createHmac('sha256', keyBuf)
            .update(canonical, 'utf8')
            .digest();
    }
    catch {
        // Non-fatal: key is malformed; treat as invalid
        return {
            ok: false,
            status: 'invalid',
            message: `Bundle "${bundle.bundleId}": key error during signature computation.`,
        };
    }
    if (!/^[0-9a-f]+$/i.test(bundle.signature)) {
        return {
            ok: false,
            status: 'invalid',
            message: `Bundle "${bundle.bundleId}": signature is not valid hex.`,
        };
    }
    let actual;
    try {
        actual = Buffer.from(bundle.signature, 'hex');
    }
    catch {
        return {
            ok: false,
            status: 'invalid',
            message: `Bundle "${bundle.bundleId}": signature is not valid hex.`,
        };
    }
    if (expected.length !== actual.length) {
        return {
            ok: false,
            status: 'invalid',
            message: `Bundle "${bundle.bundleId}": signature length mismatch.`,
        };
    }
    const valid = timingSafeEqual(expected, actual);
    if (!valid) {
        return {
            ok: false,
            status: 'invalid',
            message: `Bundle "${bundle.bundleId}": signature mismatch — payload may be tampered.`,
        };
    }
    return {
        ok: true,
        status: 'valid',
        message: `Bundle "${bundle.bundleId}": signature valid.`,
    };
}
