test(proxy): generate TLS test fixtures at runtime (#2389)

Generate ephemeral TLS fixtures once per test file.

Preserve transport assertions and add certificate validation coverage.
This commit is contained in:
JATMN
2026-10-24 07:23:47 -07:00
committed by GitHub
parent 94e22e19d3
commit d97567b44a
2 changed files with 92 additions and 17 deletions
+77
View File
@@ -0,0 +1,77 @@
import { generateKeyPairSync, sign, X509Certificate } from 'node:crypto'
// Minimal DER encoding for this test certificate, including long-form lengths.
function der(tag: number, ...parts: Buffer[]): Buffer {
const content = Buffer.concat(parts)
const length: number[] = []
if (content.length < 128) {
length.push(content.length)
} else {
for (let remaining = content.length; remaining > 0; remaining >>>= 8) {
length.unshift(remaining & 0xff)
}
length.unshift(0x80 | length.length)
}
return Buffer.concat([Buffer.from([tag, ...length]), content])
}
const sequence = (...parts: Buffer[]) => der(0x30, ...parts)
const oid = (hex: string) => der(0x06, Buffer.from(hex, 'hex'))
const booleanTrue = der(0x01, Buffer.from([0xff]))
function certificateTime(date: Date): Buffer {
const digits = date.toISOString().replace(/[-:T]/g, '').replace(/\.\d{3}Z$/, 'Z')
// X.509 uses UTCTime for 1950–2049 and GeneralizedTime otherwise.
const utc = date.getUTCFullYear() >= 1950 && date.getUTCFullYear() < 2050
return der(utc ? 0x17 : 0x18, Buffer.from(utc ? digits.slice(2) : digits))
}
function extension(oidHex: string, value: Buffer, critical = false): Buffer {
return sequence(
oid(oidHex),
...(critical ? [booleanTrue] : []),
der(0x04, value),
)
}
export function createTestTlsFixture() {
const { privateKey, publicKey } = generateKeyPairSync('ec', {
namedCurve: 'prime256v1',
})
// AlgorithmIdentifier for ecdsa-with-SHA256 (1.2.840.10045.4.3.2).
const algorithm = sequence(oid('2a8648ce3d040302'))
const name = sequence(der(0x31, sequence(
oid('550403'), // commonName (2.5.4.3)
der(0x0c, Buffer.from('localhost')),
)))
const now = Date.now()
const day = 24 * 60 * 60 * 1000
const tbsCertificate = sequence(
der(0xa0, der(0x02, Buffer.from([2]))), // explicit v3 version
der(0x02, Buffer.from([1])), // positive serial, unique for this fresh issuer
algorithm,
name, // issuer and subject are identical for a self-signed certificate
sequence(certificateTime(new Date(now - day)), certificateTime(new Date(now + day))),
name,
publicKey.export({ type: 'spki', format: 'der' }),
der(0xa3, sequence(
extension('551d13', sequence(booleanTrue), true), // basicConstraints: CA
// keyUsage: digitalSignature + keyCertSign; two unused bits.
extension('551d0f', der(0x03, Buffer.from([2, 0x84])), true),
extension('551d11', sequence( // subjectAltName
der(0x82, Buffer.from('localhost')),
der(0x87, Buffer.from([127, 0, 0, 1])),
)),
)),
)
const certificate = sequence(
tbsCertificate,
algorithm,
der(0x03, Buffer.from([0]), sign('sha256', tbsCertificate, privateKey)),
)
return {
key: privateKey.export({ type: 'pkcs8', format: 'pem' }),
cert: new X509Certificate(certificate).toString(),
publicKey,
}
}
+15 -17
View File
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
import { X509Certificate } from 'node:crypto'
import { once } from 'node:events'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { createServer as createHttpServer, type IncomingMessage } from 'node:http'
@@ -13,6 +14,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { Duplex } from 'node:stream'
import { snapshotEnv } from '../test/envSnapshot.js'
import { createTestTlsFixture } from '../test/tlsFixture.js'
import { clearCACertsCache } from './caCerts.js'
import { clearMTLSCache, getMTLSAgent, getWebSocketTLSOptions } from './mtls.js'
@@ -46,23 +48,19 @@ const { createAxiosInstance, getWebSocketProxyAgent } = await import(
`data:text/javascript;base64,${Buffer.from(await proxyBuild.outputs[0].text()).toString('base64')}`
) as typeof import('./proxy.js')
// Self-signed test certificate and key, valid only for the loopback target.
const cert = `-----BEGIN CERTIFICATE-----
MIIBmzCCAUGgAwIBAgIUAtOYg4xV7C9catkY/bwZc7XywMgwCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MTAyNDA4NTUxNloYDzIxMjYwOTMw
MDg1NTE2WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAS5HiqEES/b1Oe+NOKT+Cg3Gh89Rk0O7bSu0aDJpwdMzOxgOT2jTBmK
2H5TUBnDWHjLktPtbmQQ4nMAb1kD4oKWo28wbTAdBgNVHQ4EFgQUu+xqnGfB/gu8
DbNNHlVonV69Y64wHwYDVR0jBBgwFoAUu+xqnGfB/gu8DbNNHlVonV69Y64wDwYD
VR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCgYIKoZI
zj0EAwIDSAAwRQIgPy5jbT1ECH7LFy5r5JrV7xfsOkhwtbuwqVeidDBynFACIQCp
sTyTz0llkGsTStOnpDBTzTUQzcDT8z6ZgqqxbY29nw==
-----END CERTIFICATE-----`
const key = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXc/iw7bBMrZkWHsr
tDvMDJ6qPLsspu6IH8lHZ3ccGvihRANCAAS5HiqEES/b1Oe+NOKT+Cg3Gh89Rk0O
7bSu0aDJpwdMzOxgOT2jTBmK2H5TUBnDWHjLktPtbmQQ4nMAb1kD4oKW
-----END PRIVATE KEY-----`
const { key, cert, publicKey } = createTestTlsFixture()
test('runtime TLS fixture parses, verifies and covers the loopback target', () => {
const certificate = new X509Certificate(cert)
expect(certificate.verify(publicKey)).toBe(true)
expect(certificate.ca).toBe(true)
expect(certificate.subject).toBe('CN=localhost')
expect(certificate.subjectAltName).toBe('DNS:localhost, IP Address:127.0.0.1')
expect(certificate.checkHost('localhost')).toBe('localhost')
expect(certificate.checkIP('127.0.0.1')).toBe('127.0.0.1')
expect(Date.parse(certificate.validFrom)).toBeLessThan(Date.now())
expect(Date.parse(certificate.validTo)).toBeGreaterThan(Date.now())
})
describe('HTTPS_PROXY CONNECT transport', () => {
let restoreEnv: () => void