import { describe, test, expect, vi } from 'vitest' import { createMemoryStores } from '../src/store/memory.js' import { createHostRegistry } from '../src/registry/hosts.js' import { createLeafSigner, LeafSignError } from '../src/ca/sign.js' import { inProcessCaSigner } from '../src/boot/ca-wiring.js' import { buildCsr, verifyCsrPoP } from '../src/ca/csr.js' import { fingerprint } from '../src/ca/fingerprint.js' import { generateEd25519, sealToX25519, openFromX25519, generateX25519 } from '../src/util/crypto.js' import { randomBytes } from 'node:crypto' function boundHost() { const stores = createMemoryStores() const hosts = createHostRegistry({ hosts: stores.hosts }) return { stores, hosts } } describe('T8 signHostLeaf — INV14 registry-gated + CSR PoP', () => { test('active registered host + valid CSR → cert signed under CA', async () => { const { stores, hosts } = boundHost() const { publicKeyRaw, privateKey } = generateEd25519() const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) }) const signer = createLeafSigner({ hosts: stores.hosts, signer: inProcessCaSigner(), caChainDer: [] }) const { cert } = await signer.signHostLeaf(host.hostId, publicKeyRaw, buildCsr(privateKey, publicKeyRaw)) expect(cert.length).toBeGreaterThan(0) }) test('CSR proof-of-possession fails even for a registered active key → reject, KMS never called', async () => { const { stores, hosts } = boundHost() const { publicKeyRaw } = generateEd25519() const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) }) const spy = inProcessCaSigner() const signSpy = vi.spyOn(spy, 'sign') const signer = createLeafSigner({ hosts: stores.hosts, signer: spy, caChainDer: [] }) // forge a CSR: correct embedded pub but a garbage signature const forged = new Uint8Array(96) forged.set(publicKeyRaw, 0) forged.set(randomBytes(64), 32) expect(verifyCsrPoP(forged).ok).toBe(false) await expect(signer.signHostLeaf(host.hostId, publicKeyRaw, forged)).rejects.toBeInstanceOf(LeafSignError) expect(signSpy).not.toHaveBeenCalled() }) test('INV14 gate: unregistered pubkey → throws, KMS sign never invoked', async () => { const { stores } = boundHost() const { publicKeyRaw, privateKey } = generateEd25519() const spy = inProcessCaSigner() const signSpy = vi.spyOn(spy, 'sign') const signer = createLeafSigner({ hosts: stores.hosts, signer: spy, caChainDer: [] }) await expect(signer.signHostLeaf('00000000-0000-4000-8000-000000000000', publicKeyRaw, buildCsr(privateKey, publicKeyRaw))).rejects.toBeInstanceOf(LeafSignError) expect(signSpy).not.toHaveBeenCalled() }) test('revoked host cannot be signed (INV12+INV14)', async () => { const { stores, hosts } = boundHost() const { publicKeyRaw, privateKey } = generateEd25519() const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) }) await hosts.setHostStatus(host.hostId, 'revoked') const signer = createLeafSigner({ hosts: stores.hosts, signer: inProcessCaSigner(), caChainDer: [] }) await expect(signer.signHostLeaf(host.hostId, publicKeyRaw, buildCsr(privateKey, publicKeyRaw))).rejects.toBeInstanceOf(LeafSignError) }) }) describe('host content-secret wrap (FIX 3 security property)', () => { test('only the matching private key can unwrap; raw secret absent from blob', () => { const recipient = generateX25519() const secret = new Uint8Array(randomBytes(32)) const blob = sealToX25519(recipient.publicKeyRaw, secret) expect(Buffer.from(blob).includes(Buffer.from(secret))).toBe(false) // raw secret not in ciphertext expect(Buffer.from(openFromX25519(recipient.privateKey, blob)).equals(Buffer.from(secret))).toBe(true) const wrong = generateX25519() expect(() => openFromX25519(wrong.privateKey, blob)).toThrow() }) })