Skip to content

Instantly share code, notes, and snippets.

@andrewmackrodt
Created August 21, 2023 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewmackrodt/1394c0b4f1bcf1361e9127107a693854 to your computer and use it in GitHub Desktop.
Save andrewmackrodt/1394c0b4f1bcf1361e9127107a693854 to your computer and use it in GitHub Desktop.
import pem from 'pem'
import fs from 'node:fs'
import http from 'node:http'
import https from 'node:https'
import net from 'node:net'
import { parse as parseURL } from 'node:url'
import tls, { SecureContext } from 'tls'
type Scheme = 'http' | 'https'
const rootCert = await new Promise<pem.CertificateCreationResult>((resolve, reject) => {
pem.createCertificate({ days: 3650, selfSigned: true }, (err, keys) => {
if (err) {
reject(err)
} else {
resolve(keys)
}
})
})
await fs.promises.writeFile('localhost.crt', rootCert.certificate)
async function createCertificate(commonName: string) {
return new Promise<pem.CertificateCreationResult>((resolve, reject) => {
pem.createCertificate({
altNames: [commonName, `*.${commonName}`],
commonName,
days: 30,
organization: '',
organizationUnit: '',
serial: Date.now(),
serviceCertificate: rootCert.certificate,
serviceKey: rootCert.serviceKey,
}, (err, keys) => {
if (err) {
reject(err)
} else {
resolve(keys)
}
})
})
}
function proxy(scheme: Scheme, req: http.IncomingMessage, res: http.ServerResponse) {
const url = req.url!.match(/^https?:/i)
? parseURL(req.url!)
: parseURL(`${scheme}://${req.headers.host}${req.url}`)
const options: http.RequestOptions = {
hostname: url.hostname,
port: url.port,
path: url.path,
method: req.method,
headers: req.headers,
}
const cb = (proxyRes: http.IncomingMessage) => {
res.writeHead(proxyRes.statusCode!, proxyRes.headers)
proxyRes.pipe(res)
}
let proxyReq: http.ClientRequest
if (scheme === 'https') {
proxyReq = https.request(options, cb)
} else {
proxyReq = http.request(options, cb)
}
req.pipe(proxyReq)
}
const contexts: Record<string, SecureContext> = {}
const tlsServer = https.createServer(
{
ALPNProtocols: ['http/1.1'],
SNICallback: async (commonName: string, cb: (err: Error | null, ctx?: SecureContext) => void) => {
if ( ! (commonName in contexts)) {
const cert = await createCertificate(commonName)
const { context } = tls.createSecureContext({
key: cert.clientKey,
cert: cert.certificate,
ca: rootCert.certificate,
})
contexts[commonName] = context
}
cb(null, contexts[commonName])
},
key: rootCert.clientKey,
cert: rootCert.certificate,
},
(req, res) => proxy('https', req, res))
const tlsPort = await new Promise<number>(resolve => {
tlsServer.listen(0, () => {
const port = (tlsServer.address() as net.AddressInfo).port
resolve(port)
})
})
const server = http.createServer((req, res) => proxy('http', req, res))
server.on('connect', async (req, clientSocket, head) => {
const serverSocket = net.connect(tlsPort, '127.0.0.1', () => {
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
serverSocket.write(head)
serverSocket.pipe(clientSocket)
clientSocket.pipe(serverSocket)
})
})
server.listen(8000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment