I'm trying to create an HTTP/S MitM forwarding proxy using Node.js.
The way I'm tackling this project is by reusing the solution found in ./lib/proxy.js
file of the NPM Proxy Cache project created by @runk after he raised the issue on the Node HTTP Proxy project issue tracker.
My Proxy()
class looks like this:
var request = require('request')
, https = require('https')
, http = require('http')
, net = require('net')
, url = require('url')
, os = require('os')
, fs = require('fs');
var SOCKET_PATH = os.tmpdir() + 'mitm.sock';
console.log('[SOCKET PATH] ' + SOCKET_PATH);
function Proxy (config) {
config = config || {};
if(fs.existsSync(SOCKET_PATH)) {
fs.unlinkSync(SOCKET_PATH);
}
var options = {
key: fs.readFileSync('./certs/dummy.key', 'utf8'),
cert: fs.readFileSync('./certs/dummy.crt', 'utf8')
};
// HTTPS Server
https.createServer(options, this.handler).listen(config.port + 1, this.hostname, function (e) {
if(e) {
console.log('[HTTPS] Server listen() error !');
throw e;
}
});
// HTTP Server
var server = http.createServer(this.handler);
server.listen(config.port, this.hostname, function (e) {
if(e) {
console.log('[HTTP] Server listen() error !');
throw e;
}
});
// Intercept CONNECT requests for HTTPS handshake
server.addListener('connect', this.httpsHandler);
}
Proxy.prototype.handler = function (req, res) {
var schema = !!req.client.pair ? 'https' : 'http'
, path = url.parse(req.url).path;
var dest = schema + '://' + req.headers['host'] + path;
console.log('(1) - [' + schema.toUpperCase() + '] ' + req.method + ' ' + req.url);
var params = {
rejectUnauthorized: false,
url: dest
};
if(req.method.toUpperCase() !== 'GET') {
return console.log('[HTTP] Request is not HTTP GET.');
}
var onResponse = function (e, response) {
if(e == null && response.statusCode === 200) {
return r.pipe(res);
}
var body = 'Status ' + response.statusCode + ' returned';
if(e) {
body = e.toString();
}
res.end(body);
};
var r = request(params);
r.on('response', onResponse.bind(null, null));
r.on('error', onResponse.bind(null));
};
Proxy.prototype.httpsHandler = function (request, socketRequest, bodyHead) {
var httpVersion = request['httpVersion']
, url = request['url'];
console.log('(2) - [HTTPS] ' + request['method'] + ' ' + request['url']);
var proxySocket = new net.Socket();
// ProxySocket event handlers
proxySocket.connect(SOCKET_PATH, function () {
proxySocket.write(bodyHead);
proxySocket.write('HTTP/' + httpVersion + ' 200 Connection established\r\n\r\n');
});
proxySocket.on('data', function (chunk) {
console.log('ProxySocket - "data"');
socketRequest.write(chunk);
});
proxySocket.on('end', function () {
console.log('ProxySocket - "end"');
socketRequest.end();
});
proxySocket.on('error', function (e) {
console.log('ProxySocket - "error"');
console.log(e);
console.log(e.stack);
socketRequest.write('HTTP/' + httpVersion + ' 500 Connection error\r\n\r\n');
socketRequest.end();
});
// SocketRequest event handlers
socketRequest.on('data', function (chunk) {
console.log('SocketRequest - "data"');
proxySocket.write(chunk);
});
socketRequest.on('end', function () {
console.log('SocketRequest - "end"');
proxySocket.end();
});
socketRequest.on('error', function (e) {
console.log('socketRequest - "error"');
console.log(e);
console.log(e.stack);
proxySocket.end();
});
};
module.exports = Proxy;
And my Index.js
file that start my program looks like this:
var Proxy = require('./lib/proxy');
var proxy = new Proxy({
hostname: '127.0.0.1',
port: 8000
});
Here's my directory / file structure this:
/my_project
/certs
dummy.crt // Copied from the NPM Proxy Cache project
dummy.csr // Copied from the NPM Proxy Cache project
dummy.key // Copied from the NPM Proxy Cache project
/lib
proxy.js
index.js
I'm testing my program by setting (in Mac OSX Maverick
) an HTTP and HTTPS proxy as IP address 127.0.0.1
and port 8000
.
When browsing an HTTP only website everything works fine, but if I browse an HTTPS website I get the following error:
{[Error: connect ENOENT] code: 'ENOENT', errno: 'ENOENT', syscall: 'connect'}
Error: connect ENOENT
at errnoException (net.js:904:11)
at Object.afterConnect [as oncomplete] (net.js:895:19)
- Any ideas from where this issue could come from and how to fix this ?
Thank you very much in advance !
(If you want to test my code, the NPM module request
is the only dependency needed to run the code.)
EDIT: The certs can be downloaded from here : https://github.com/runk/npm-proxy-cache/tree/master/cert.