Node.js で HTTPS もいじれるローカルプロキシを作る

ローカルプロキシ大好き nash です。HTTPS を通せるローカルプロキシを作ろう、といったような記事はよく見ますが、HTTPS もいじれるものはあまり見たことがないので作ってみました。

なお、pem というモジュールが必要なので、npm install pem とかでインストールしてください。

const http = require('http')
const https = require('https')
const fs = require('fs')
const url = require('url')
const net = require('net')
const tls = require('tls')
const pem = require('pem')

const ConnectionEstablishedMsg = new Buffer('HTTP/1.1 200 Connection Established\r\n\r\n')
const cache = { }

const key = fs.readFileSync('../keys/fake-ca.key.pem')
const cert = fs.readFileSync('../keys/fake-ca.crt.pem')

const httpsServer = https.createServer({
	key,
	cert,
	SNICallback: (serverName, cb) => {
		if (cache[serverName]) {
			cb(null, cache[serverName])

			return
		}

		pem.createCertificate({
			country: 'JP',
			state: 'Tokyo',
			locality: 'Shibuya',
			organization: 'preflight.cc',
			commonName: serverName,
			altNames: [ serverName ],
			serviceKey: key,
			serviceCertificate: cert,
			serial: Date.now(),
			days: 300
		}, (err, generated) => {
			if (err) {
				cb(err)

				return
			}

			const ctx = tls.createSecureContext({
				key: generated.clientKey,
				cert: generated.certificate
			})

			cache[serverName] = ctx

			cb(null, ctx)
		})
	}
}, (req, res) => {
	const fullUrl = `https://${req.headers.host}${req.url}`

	const parsedUrl = url.parse(fullUrl)

	const pReq = https.request({
		method: req.method,
		host: parsedUrl.hostname,
		port: parsedUrl.port || 443,
		path: parsedUrl.path,
		headers: req.headers
	}, pRes => {
		res.writeHead(pRes.statusCode, pRes.headers)
		pRes.pipe(res)
	})

	req.pipe(pReq)
})

httpsServer.listen()

const server = http.createServer((req, res) => {
	const parsedUrl = url.parse(req.url)

	const pReq = http.request({
		method: req.method,
		host: parsedUrl.hostname,
		port: parsedUrl.port || 80,
		path: parsedUrl.path,
		headers: req.headers
	}, (pRes) => {
		res.writeHead(pRes.statusCode, pRes.headers)
		pRes.pipe(res)
	})

	req.pipe(pReq)
})

server.on('connect', (req, sock, head) => {
	const addr = httpsServer.address()

	const pSock = net.connect(addr.port, addr.address, () => {
		sock.write(ConnectionEstablishedMsg)
		pSock.write(head)

		sock.pipe(pSock)
		pSock.pipe(sock)
	})
})

server.listen(8081)

コード中に出てくる fake-ca.key.pem, fake-ca.crt.pem は次のコマンドで作れます。

$ openssl genrsa -out fake-ca.key.pem 2048
$ openssl req -x509 -new -nodes -key fake-ca.key.pem -days 1024 -out fake-ca.crt.pem -subj "/C=JP/ST=Tokyo/L=Shibuya/CN=example.com/"

宣伝

主に宣伝のためにこの記事を書いたようなものですが、8/11 開催の C92 の 1日目でローカルプロキシの本を出します。東た-12bです。このコードの解説や、ここからどうやってリクエスト・レスポンスを改変していくかというのが主な内容になります。ぜひぜひお越しくださいませ。

事前におっしゃっていただければ取り置きしておきますので、お知らせください!前もって言っていただけるとこちらも印刷部数を増やすなどの対応ができるので、ぜひぜひよろしくお願いします :bow:

コミケ Web カタログのページはここです: https://webcatalog.circle.ms/Circle/13315280/
こちらもお気に入りなどに入れていただけると、どのくらい刷るかの目安になるのでよろしくお願いします :bow:

謝辞

このローカルプロキシの実装にあたって次のプロジェクトのソースコードを参考にさせていただきました。ありがとうございます。


Author: nash (About)