import { describe, test, expect } from 'vitest' import { createMemoryStores } from '../src/store/memory.js' import { createHostRegistry } from '../src/registry/hosts.js' import { createLeafRenewer } from '../src/ca/rotate.js' import { LeafSignError } from '../src/ca/sign.js' import { buildCaSigner, inProcessCaSigner, type KmsResolver } from '../src/boot/ca-wiring.js' import { buildCsr } from '../src/ca/csr.js' import { fingerprint } from '../src/ca/fingerprint.js' import { generateEd25519 } from '../src/util/crypto.js' import { loadEnv } from '../src/env.js' import { bytesToBase64 } from '../src/util/bytes.js' import { randomBytes } from 'node:crypto' const ACCOUNT = '11111111-1111-4111-8111-111111111111' async function boundHost() { const stores = createMemoryStores() const hosts = createHostRegistry({ hosts: stores.hosts }) const { publicKeyRaw, privateKey } = generateEd25519() const host = await hosts.bindHost({ accountId: ACCOUNT, subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) }) const renewer = createLeafRenewer({ hosts: stores.hosts, signer: inProcessCaSigner(), caChainDer: [] }) return { stores, hosts, host, publicKeyRaw, privateKey, renewer } } describe('T15 renewHostLeaf (INV14)', () => { test('active host + valid CSR → fresh short-TTL leaf (same subject pubkey)', async () => { const { host, publicKeyRaw, privateKey, renewer } = await boundHost() const { cert } = await renewer.renewHostLeaf(host.hostId, buildCsr(privateKey, publicKeyRaw)) const certJson = JSON.parse(Buffer.from(cert).toString()) const tbs = JSON.parse(Buffer.from(certJson.tbs, 'base64').toString()) expect(tbs.subjectSpki).toBe(Buffer.from(publicKeyRaw).toString('base64')) expect(tbs.renewed).toBe(true) }) test('CSR proof-of-possession fails → reject even for an active host', async () => { const { host, publicKeyRaw, renewer } = await boundHost() const forged = new Uint8Array(96) forged.set(publicKeyRaw, 0) forged.set(randomBytes(64), 32) // garbage signature await expect(renewer.renewHostLeaf(host.hostId, forged)).rejects.toBeInstanceOf(LeafSignError) }) test('revoked host cannot renew (INV12+INV14 loop)', async () => { const { hosts, host, publicKeyRaw, privateKey, renewer } = await boundHost() await hosts.setHostStatus(host.hostId, 'revoked') await expect(renewer.renewHostLeaf(host.hostId, buildCsr(privateKey, publicKeyRaw))).rejects.toBeInstanceOf(LeafSignError) }) }) describe('T1/T15 buildCaSigner startup key-policy check (INV9, §3.1)', () => { const env = () => loadEnv({ PG_URL: 'postgres://u:p@localhost:5432/cp', REDIS_URL: 'redis://localhost:6379', CAPABILITY_SIGN_PUBKEY_B64: bytesToBase64(new Uint8Array(32).fill(1)), CA_INTERMEDIATE_KMS_KEY_REF: 'kms://key/intermediate', CA_INTERMEDIATE_CERT_PATH: '/etc/cp/int.pem', NODE_MTLS_TRUST_BUNDLE_PATH: '/etc/cp/node-ca.pem', BASE_DOMAIN: 'term.example.com', }) test('fails fast when the KMS key ref cannot be resolved', async () => { const resolver: KmsResolver = { async resolve() { throw new Error('no such key') } } await expect(buildCaSigner(env(), resolver)).rejects.toThrow(/could not be resolved/) }) test('fails fast when the key policy is broader than the service principal', async () => { const resolver: KmsResolver = { async resolve() { return { signer: inProcessCaSigner(), policyRestrictedToServicePrincipal: false } }, } await expect(buildCaSigner(env(), resolver)).rejects.toThrow(/policy is broader/) }) test('succeeds when policy is restricted', async () => { const resolver: KmsResolver = { async resolve() { return { signer: inProcessCaSigner(), policyRestrictedToServicePrincipal: true } }, } await expect(buildCaSigner(env(), resolver)).resolves.toBeDefined() }) })