/** * A2-prep — PRODUCTION native-tunnel CA wiring from ON-DISK P-256 material. Generates throwaway * P-256 CAs (key + cert PEM) in a tmp dir — standing in for the VPS `gen-device-ca.sh` output — then * proves `buildFileBackedNativeCas` binds each CA's signer to its on-disk key and its anchor to its * on-disk cert, that a leaf issued by the wired signer CHAINS to that on-disk cert, that a WIRED * control-plane `/enroll` mints an frp-client leaf whose dNSName SAN = `.` from the real * on-disk CA, and that partial/missing/malformed material FAILS CLOSED (INV9). */ import 'reflect-metadata' import { describe, test, expect, beforeEach, afterEach } from 'vitest' import * as x509 from '@peculiar/x509' import { webcrypto, generateKeyPairSync } from 'node:crypto' import { mkdtempSync, writeFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { buildNativeCas, buildFileBackedNativeCas } from '../src/boot/native-ca.js' import { inProcessP256CaSigner } from '../src/boot/ca-wiring.js' import { assembleCertificate } from '../src/ca/x509-assembler.js' import { createFrpClientLeafSigner } from '../src/ca/frpclient-issue.js' import { createHostRegistry } from '../src/registry/hosts.js' import { createMemoryStores } from '../src/store/memory.js' import { fingerprint } from '../src/ca/fingerprint.js' import { buildCsrEc } from '../src/ca/csr-ec.js' import { createPairingIssuer } from '../src/pairing/issue.js' import { buildControlPlane } from '../src/main.js' import { loadEnv, type ControlPlaneEnv, type NativeCaEnvConfig } from '../src/env.js' import { bytesToBase64 } from '../src/util/bytes.js' import type { Stores } from '../src/store/ports.js' x509.cryptoProvider.set(webcrypto) const ACCOUNT_A = '11111111-1111-4111-8111-111111111111' const ZONE = 'native.example.test' const DAY_MS = 24 * 60 * 60 * 1000 let dir: string let env: ControlPlaneEnv beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'cp-nativeca-')) env = loadEnv({ PG_URL: 'postgres://u:p@localhost:5432/cp', REDIS_URL: 'redis://localhost:6379', CAPABILITY_SIGN_PUBKEY_B64: bytesToBase64(new Uint8Array(32).fill(7)), CA_INTERMEDIATE_KMS_KEY_REF: 'kms://key/intermediate', CA_INTERMEDIATE_CERT_PATH: join(dir, 'int.cert.pem'), NODE_MTLS_TRUST_BUNDLE_PATH: join(dir, 'node-ca.pem'), RELAY_TRUST_DOMAIN: ZONE, BASE_DOMAIN: 'term.example.com', }) }) afterEach(() => { rmSync(dir, { recursive: true, force: true }) }) /** Write a self-signed throwaway P-256 CA (key + cert PEM) to disk — mirrors the VPS gen-device-ca output. */ async function writeThrowawayP256Ca(cn: string): Promise<{ keyPath: string; certPath: string; caDer: Uint8Array }> { const caKeys = generateKeyPairSync('ec', { namedCurve: 'P-256' }) const keyPem = caKeys.privateKey.export({ format: 'pem', type: 'pkcs8' }) as string const caSpki = new Uint8Array(caKeys.publicKey.export({ format: 'der', type: 'spki' })) const signer = inProcessP256CaSigner(caKeys.privateKey) const now = Date.now() const caDer = await assembleCertificate({ subjectPublicKey: caSpki, subject: `CN=${cn}`, issuer: `CN=${cn}`, serialNumber: Uint8Array.from([0x01]), notBefore: new Date(now - DAY_MS), notAfter: new Date(now + 365 * DAY_MS), extensions: [ new x509.BasicConstraintsExtension(true, undefined, true), new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true), ], signer, sigAlg: 'ecdsa-p256', }) const certPem = new x509.X509Certificate(caDer).toString('pem') const keyPath = join(dir, `${cn}.key.pem`) const certPath = join(dir, `${cn}.cert.pem`) writeFileSync(keyPath, keyPem) writeFileSync(certPath, certPem) return { keyPath, certPath, caDer } } async function writeNativeCaConfig(): Promise<{ config: NativeCaEnvConfig; frpCaDer: Uint8Array; deviceCaDer: Uint8Array }> { const frp = await writeThrowawayP256Ca('frp-client-CA') const dev = await writeThrowawayP256Ca('device-CA') return { config: { frpClientCaCertPath: frp.certPath, frpClientCaKeyPath: frp.keyPath, deviceCaCertPath: dev.certPath, deviceCaKeyPath: dev.keyPath, dnsZone: ZONE, }, frpCaDer: frp.caDer, deviceCaDer: dev.caDer, } } function b64(bytes: Uint8Array): string { return Buffer.from(bytes).toString('base64') } function sanNames(der: Uint8Array): ReturnType { return new x509.X509Certificate(der).getExtension(x509.SubjectAlternativeNameExtension)!.names.toJSON() } describe('buildFileBackedNativeCas — anchors + signer from on-disk P-256 material', () => { test('each CA anchor DER == its on-disk cert DER; issuer names parse', async () => { const { config, frpCaDer, deviceCaDer } = await writeNativeCaConfig() const cas = await buildFileBackedNativeCas(env, config) expect(b64(cas.frpClientCa.caCertDer)).toBe(b64(frpCaDer)) expect(b64(cas.deviceCa.caCertDer)).toBe(b64(deviceCaDer)) expect(cas.frpClientCa.anchorsDer).toEqual([cas.frpClientCa.caCertDer]) expect(cas.deviceCa.anchorsDer).toEqual([cas.deviceCa.caCertDer]) expect(cas.frpClientCa.issuerName.toString()).toContain('frp-client-CA') expect(cas.deviceCa.issuerName.toString()).toContain('device-CA') }) test('a leaf minted by the wired frp-client signer CHAINS to the on-disk CA cert', async () => { const { config, frpCaDer } = await writeNativeCaConfig() const cas = await buildFileBackedNativeCas(env, config) // Bind a P-256 host so the signer will issue for it. const stores = createMemoryStores() const hosts = createHostRegistry({ hosts: stores.hosts }) const { der: csr, keys } = await buildCsrEc('CN=alice') const spki = new Uint8Array(await webcrypto.subtle.exportKey('spki', keys.publicKey)) const host = await hosts.bindHost({ accountId: ACCOUNT_A, subdomain: 'alice', agentPubkey: spki, enrollFpr: fingerprint(spki) }) const signer = createFrpClientLeafSigner({ hosts: stores.hosts, signer: cas.frpClientCa.signer, issuerName: cas.frpClientCa.issuerName, caChainDer: cas.frpClientCa.anchorsDer, trustDomain: ZONE, dnsZone: ZONE, }) const { cert } = await signer.signHostLeaf(host.hostId, spki, csr) // The leaf verifies against the ON-DISK CA cert's public key (real chain) … const onDiskCa = new x509.X509Certificate(frpCaDer) expect(await new x509.X509Certificate(cert).verify({ publicKey: onDiskCa.publicKey, signatureOnly: true })).toBe(true) // … and carries the enforcement dNSName SAN under the configured zone. expect(sanNames(cert)).toContainEqual({ type: 'dns', value: `alice.${ZONE}` }) }) }) describe('WIRED control-plane /enroll from on-disk native CAs', () => { async function ownedApp(): Promise<{ app: Awaited>['app']; stores: Stores; frpCaDer: Uint8Array }> { const { config, frpCaDer } = await writeNativeCaConfig() const nativeCas = await buildFileBackedNativeCas(env, config) const stores = createMemoryStores() const built = await buildControlPlane(env, { stores, production: true, nativeCas, nativeDnsZone: ZONE }) await built.app.ready() return { app: built.app, stores, frpCaDer } } test('POST /enroll (native EC arm) → 201 frp-client leaf that chains to the on-disk CA + dNSName SAN .', async () => { const { app, stores, frpCaDer } = await ownedApp() const issuer = createPairingIssuer({ pairing: stores.pairing, pairingTtlSec: 3600 }) const { code } = await issuer.issuePairingCode(ACCOUNT_A) const { der: csr, keys } = await buildCsrEc('CN=web-terminal-host') const spki = new Uint8Array(await webcrypto.subtle.exportKey('spki', keys.publicKey)) const res = await app.inject({ method: 'POST', url: '/enroll', payload: { code, machineId: 'MB-1', agentPubkey: b64(spki), csr: b64(csr) }, }) expect(res.statusCode).toBe(201) const body = JSON.parse(res.body) expect(typeof body.subdomain).toBe('string') expect(body.hostContentSecret).toBeNull() const leafDer = new Uint8Array(Buffer.from(body.cert, 'base64')) // chains to the REAL on-disk frp-client-CA … const onDiskCa = new x509.X509Certificate(frpCaDer) expect(await new x509.X509Certificate(leafDer).verify({ publicKey: onDiskCa.publicKey, signatureOnly: true })).toBe(true) // … the caChain the server returned IS that on-disk anchor … expect(b64(new Uint8Array(Buffer.from(body.caChain[0], 'base64')))).toBe(b64(frpCaDer)) // … and the leaf carries the server-assigned dNSName SAN under the env-configured zone. expect(sanNames(leafDer)).toContainEqual({ type: 'dns', value: `${body.subdomain}.${ZONE}` }) }) }) describe('native-tunnel CA fail-closed (INV9)', () => { test('buildNativeCas production without material → refuses to boot', async () => { await expect(buildNativeCas(env, { production: true })).rejects.toThrow(/unresolvable in production/) }) test('buildFileBackedNativeCas with an unreadable cert path → refuses to boot', async () => { const { config } = await writeNativeCaConfig() const broken: NativeCaEnvConfig = { ...config, frpClientCaCertPath: join(dir, 'missing.cert.pem') } await expect(buildFileBackedNativeCas(env, broken)).rejects.toThrow(/certificate file is unreadable/) }) test('buildFileBackedNativeCas with a NON-P256 key file → refuses to boot (fail-closed)', async () => { const { config } = await writeNativeCaConfig() const edPath = join(dir, 'ed.key.pem') writeFileSync(edPath, generateKeyPairSync('ed25519').privateKey.export({ format: 'pem', type: 'pkcs8' }) as string) const broken: NativeCaEnvConfig = { ...config, frpClientCaKeyPath: edPath } await expect(buildFileBackedNativeCas(env, broken)).rejects.toThrow(/P-256|could not be resolved/) }) test('buildFileBackedNativeCas with a malformed cert file → refuses to boot', async () => { const { config } = await writeNativeCaConfig() const badCert = join(dir, 'bad.cert.pem') writeFileSync(badCert, 'not a certificate') const broken: NativeCaEnvConfig = { ...config, deviceCaCertPath: badCert } await expect(buildFileBackedNativeCas(env, broken)).rejects.toThrow(/parseable X.509 certificate/) }) })