/** * A4 (FIX C-2) — P-256 device leaf issuance off the device-CA via `assembleCertificate`. Proves the * leaf re-parses + verifies against the device-CA public key; carries SAN dNSName `.terminal.yaojia.wang` * + the device SPIFFE URI (`.../device/`, parseable by relay-auth as kind 'device'); is * clientAuth / CA:false; and that the registry gate rejects uniformly (unknown / revoked / pubkey mismatch). */ import 'reflect-metadata' import { describe, test, expect } from 'vitest' import * as x509 from '@peculiar/x509' import { webcrypto } from 'node:crypto' import { parseSpiffeId } from 'relay-auth/src/agent/spiffe.js' import { buildCsrEc } from '../src/ca/csr-ec.js' import { inProcessP256CaSigner } from '../src/boot/ca-wiring.js' import { createDeviceLeafSigner, DeviceLeafSignError } from '../src/ca/device-issue.js' import { createMemoryDeviceStore, type DeviceRecord } from '../src/registry/devices.js' x509.cryptoProvider.set(webcrypto) const SAN_BASE = 'terminal.yaojia.wang' const TRUST_DOMAIN = 'example.com' const ACCOUNT_A = 'a1' async function spkiOf(pub: CryptoKey): Promise { return new Uint8Array(await webcrypto.subtle.exportKey('spki', pub)) } function record(overrides: Partial): DeviceRecord { const now = Date.now() return { deviceId: 'dev-1', accountId: ACCOUNT_A, subdomainScope: 'alice', ecPubkeySpki: new Uint8Array([1, 2, 3]), serial: '0102030405060708090a0b0c0d0e0f10', status: 'active', notAfter: new Date(now + 24 * 60 * 60 * 1000).toISOString(), attestationLevel: 'none', createdAt: new Date(now).toISOString(), revokedAt: null, ...overrides, } } async function fixture() { const ca = inProcessP256CaSigner() const store = createMemoryDeviceStore() const signer = createDeviceLeafSigner({ signer: ca, issuer: 'CN=device-CA', caChainDer: [ca.publicKeyRaw], sanBaseDomain: SAN_BASE, trustDomain: TRUST_DOMAIN, devices: store, }) const { der: csr, keys } = await buildCsrEc('CN=web-terminal-device') const embeddedSpki = await spkiOf(keys.publicKey) return { ca, store, signer, csr, embeddedSpki } } async function importP256Public(spkiDer: Uint8Array): Promise { return webcrypto.subtle.importKey('spki', new Uint8Array(spkiDer), { name: 'ECDSA', namedCurve: 'P-256' }, true, [ 'verify', ]) } describe('A4 device-issue — real P-256 device leaf', () => { test('leaf re-parses and verifies against the device-CA public key', async () => { const { ca, store, signer, csr, embeddedSpki } = await fixture() await store.insert(record({ deviceId: 'dev-1', ecPubkeySpki: embeddedSpki })) const { cert } = await signer.signDeviceLeaf('dev-1', csr) const leaf = new x509.X509Certificate(cert) expect(() => new x509.X509Certificate(cert)).not.toThrow() const caPub = await importP256Public(ca.publicKeyRaw) expect(await leaf.verify({ publicKey: caPub, signatureOnly: true })).toBe(true) }) test('SAN carries dNSName .terminal.yaojia.wang + the device SPIFFE URI', async () => { const { store, signer, csr, embeddedSpki } = await fixture() await store.insert(record({ deviceId: 'dev-1', accountId: ACCOUNT_A, subdomainScope: 'alice', ecPubkeySpki: embeddedSpki })) const { cert } = await signer.signDeviceLeaf('dev-1', csr) const san = new x509.X509Certificate(cert).getExtension(x509.SubjectAlternativeNameExtension) const names = san!.names.toJSON() expect(names).toContainEqual({ type: 'dns', value: 'alice.terminal.yaojia.wang' }) const uri = names.find((n) => n.type === 'url')!.value const parsed = parseSpiffeId(uri) expect(parsed).toEqual({ accountId: ACCOUNT_A, kind: 'device', id: 'dev-1' }) }) test('leaf is EKU clientAuth and CA:false', async () => { const { store, signer, csr, embeddedSpki } = await fixture() await store.insert(record({ deviceId: 'dev-1', ecPubkeySpki: embeddedSpki })) const { cert } = await signer.signDeviceLeaf('dev-1', csr) const leaf = new x509.X509Certificate(cert) const bc = leaf.getExtension(x509.BasicConstraintsExtension) expect(bc!.ca).toBe(false) const eku = leaf.getExtension(x509.ExtendedKeyUsageExtension) expect(eku!.usages).toContain(x509.ExtendedKeyUsage.clientAuth) }) test('result reports serial + validity from the gated record', async () => { const { store, signer, csr, embeddedSpki } = await fixture() const rec = record({ deviceId: 'dev-1', ecPubkeySpki: embeddedSpki }) await store.insert(rec) const res = await signer.signDeviceLeaf('dev-1', csr) expect(res.serial).toBe(rec.serial) expect(res.notAfter.toISOString()).toBe(rec.notAfter) expect(res.notBefore.getTime()).toBeLessThan(Date.now()) }) test('gate rejects an UNKNOWN device uniformly', async () => { const { signer, csr } = await fixture() await expect(signer.signDeviceLeaf('ghost', csr)).rejects.toBeInstanceOf(DeviceLeafSignError) }) test('gate rejects a REVOKED device uniformly', async () => { const { store, signer, csr, embeddedSpki } = await fixture() await store.insert(record({ deviceId: 'dev-1', status: 'revoked', revokedAt: new Date().toISOString(), ecPubkeySpki: embeddedSpki })) await expect(signer.signDeviceLeaf('dev-1', csr)).rejects.toBeInstanceOf(DeviceLeafSignError) }) test('gate rejects a pubkey MISMATCH (CSR key ≠ registered key) uniformly', async () => { const { store, signer, csr } = await fixture() // register with a DIFFERENT pubkey than the CSR embeds await store.insert(record({ deviceId: 'dev-1', ecPubkeySpki: new Uint8Array([7, 7, 7, 7]) })) await expect(signer.signDeviceLeaf('dev-1', csr)).rejects.toBeInstanceOf(DeviceLeafSignError) }) test('gate rejects a malformed CSR uniformly', async () => { const { store, signer, embeddedSpki } = await fixture() await store.insert(record({ deviceId: 'dev-1', ecPubkeySpki: embeddedSpki })) await expect(signer.signDeviceLeaf('dev-1', new Uint8Array([0, 1, 2, 3]))).rejects.toBeInstanceOf(DeviceLeafSignError) }) })