using SNI when intercepting https requests

This commit is contained in:
OttoMao 2015-02-10 12:03:21 +08:00
parent 6d84a95165
commit d51cb0ce4b
6 changed files with 105 additions and 187 deletions

View File

@ -1,3 +1,7 @@
10 Feb 2015: anyproxy 3.2.0:
* using SNI when intercepting https requests
28 Jan 2015: anyproxy 3.1.2:
* thanks to iconv-lite, almost webpage with any charset can be correctly decoded in web interface.

View File

@ -23,16 +23,15 @@ A fully configurable proxy in NodeJS, which can handle HTTPS requests perfectly.
------------
* 支持https明文代理
* 支持低网速模拟
* 全流程开放可以用javascript控制代理流程中的任意步骤。搭建前端个性化调试环境的利器。
* 提供web版界面观测请求情况
* 全流程开放可以用javascript控制代理流程中的任意步骤,搭建前端个性化调试环境
* 提供web版界面观测请求情况
Feature
------------
* work as http or https proxy
* fully configurable, you can modify a request at any stage by your own javascript code
* when working as https proxy, it can generate and intercept https requests for any domain without complaint by browser (after you trust its root CA)
* fully configurable, you could modify a request at any stage with your customized javascript code
* when working as https proxy, it could generate and intercept https requests for any domain without complaint by browser (after you trust its root CA)
* a web interface for you to watch realtime request details, where html string with (almost) any charset could be shown correctly
* (beta)a web UI interface for you to replace some remote response with local data
![screenshot](http://gtms01.alicdn.com/tps/i1/TB1IdgqGXXXXXa9apXXLExM2pXX-854-480.gif)

View File

@ -1,64 +0,0 @@
diff --git a/bin.js b/bin.js
index 9b7f39b..86ccdee 100644
--- a/bin.js
+++ b/bin.js
@@ -4,6 +4,7 @@ var program = require('commander'),
proxy = require("./proxy.js"),
color = require('colorful'),
fs = require("fs"),
+ path = require("path"),
packageInfo = require("./package.json");
program
@@ -33,15 +34,16 @@ if(program.clear){
var ruleModule;
if(program.rule){
- if(fs.existsSync(program.rule)){
- try{ //for abs path
+ var ruleFilePath = path.join(process.cwd(),program.rule);
+ try{
+ if(fs.existsSync(ruleFilePath)){
ruleModule = require(program.rule);
- }catch(e){ //for relative path
- ruleModule = require(process.cwd() + '/' + program.rule.replace(/^\.\//,''));
+ console.log("rule file loaded :" + ruleFilePath);
+ }else{
+ console.log(color.red("can not find rule file"));
}
- console.log(color.green("rule file loaded"));
- }else{
- console.log(color.red("can not find rule file"));
+ }catch(e){
+ console.log("failed to load rule file :" + e.toString());
}
}
diff --git a/lib/requestHandler.js b/lib/requestHandler.js
index 37f19d4..620b7f9 100644
--- a/lib/requestHandler.js
+++ b/lib/requestHandler.js
@@ -364,7 +364,7 @@ function setRules(newRule){
}
if('function' == typeof(userRule.summary)){
functions.push(function(cb){
- userRule.summary();
+ console.log(userRule.summary());
cb(null);
});
}
diff --git a/rule_sample/README.md b/rule_sample/README.md
index 7a8eabb..7c29ff5 100644
--- a/rule_sample/README.md
+++ b/rule_sample/README.md
@@ -27,6 +27,9 @@ The following are sample rules.
* rule_replace_response_status_code.js
* replace server's status code
* 改变服务端响应的http状态码
+* rule_reverse_proxy.js
+ * assign a specific ip address for request
+ * 为请求绑定目标ip
* rule_use_local_data.js
* map some requests to local file
* 把图片响应映射到本地
\ No newline at end of file

View File

@ -10,10 +10,10 @@ var exec = require('child_process').exec,
//TODO : unstable in windows
var certDir = path.join(util.getUserHome(),"/.anyproxy_certs/"),
cmdDir = path.join(__dirname,"..","./cert/"),
cmd_genRoot = path.join(cmdDir,"./gen-rootCA"),
cmd_genCert = path.join(cmdDir,"./gen-cer"),
asyncTaskMgr = new asyncTask();
cmdDir = path.join(__dirname,"..","./cert/"),
cmd_genRoot = path.join(cmdDir,"./gen-rootCA"),
cmd_genCert = path.join(cmdDir,"./gen-cer"),
createCertTaskMgr = new asyncTask();
if(!fs.existsSync(certDir)){
try{
@ -26,26 +26,31 @@ if(!fs.existsSync(certDir)){
}
}
function getCertificate(hostname,cb){
function getCertificate(hostname,certCallback){
var keyFile = path.join(certDir , "__hostname.key".replace(/__hostname/,hostname) ),
crtFile = path.join(certDir , "__hostname.crt".replace(/__hostname/,hostname) );
if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){
asyncTaskMgr.addTask(hostname,function(cb){
createCertTaskMgr.addTask(hostname,function(callback){
if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){
createCert(hostname,function(err){
cb(err ? err : null);
if(err){
callback(err);
}else{
callback(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile));
}
});
},function(err){
if(!err){
cb(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile) );
}else{
cb(err);
}
});
}else{
callback(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile));
}
}else{
cb(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile) );
}
},function(err,keyContent,crtContent){
if(!err){
certCallback(null ,keyContent,crtContent);
}else{
certCallback(err);
}
});
}
function createCert(hostname,callback){

View File

@ -5,100 +5,61 @@ var getPort = require('./getPort'),
https = require('https'),
fs = require('fs'),
net = require('net'),
url = require('url'),
tls = require('tls'),
color = require('colorful'),
certMgr = require("./certMgr"),
asyncTask = require("async-task-mgr");
var DEFAULT_RELEASE_TIME = 2000;//120*1000;
//using sni to avoid multiple ports
function SNIPrepareCert(serverName,SNICallback){
var keyContent, crtContent,ctx;
var asyncTaskMgr = new asyncTask();
module.exports =function(){
var self = this;
self.serverList = {
/* schema sample
"www.example.com":{
port:123,
server : serverInstance,
lastestUse: 99999 //unix time stamp
async.series([
function(callback){
certMgr.getCertificate(serverName,function(err,key,crt){
if(err){
callback(err);
}else{
keyContent = key;
crtContent = crt;
callback();
}
});
},
function(callback){
try{
ctx = tls.createSecureContext({
key :keyContent,
cert :crtContent
});
callback();
}catch(e){
callback(e);
}
}
*/
};
//fetch a port for https server with hostname
this.fetchPort = function(hostname,userRequesthandler,userCB){
var serverInfo = self.serverList[hostname],
port;
//server exists
if(serverInfo){
serverInfo.lastestUse = new Date().getTime();
port = serverInfo.port;
userCB && userCB(null,port);
//create server with corresponding CA
],function(err,result){
if(!err){
var tipText = "proxy server for __NAME established".replace("__NAME",serverName);
console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText));
SNICallback(null,ctx);
}else{
asyncTaskMgr.addTask(hostname, createServer ,userCB);
function createServer(cb){
async.series([
//find a clean port
function(callback){
getPort(function(cleanPort){
port = cleanPort;
callback(null,port);
});
}
//create server
,function(callback){
certMgr.getCertificate(hostname,function(err,keyContent,crtContent){
var server = createHttpsServer(port,keyContent,crtContent,userRequesthandler);
self.serverList[hostname] = {
port : port,
server : server,
lastestUse : new Date().getTime()
};
var tipText = "https server @port __portNum for __HOST established".replace(/__portNum/,port).replace(/__HOST/,hostname);
console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText));
callback && callback(null,port);
});
}
],function(err,result){
cb && cb(err,result[result.length - 1]);
});
}
console.log("err occurred when prepare certs for SNI - " + e);
}
};
//clear servers which have been idle for some time
setInterval(function(){
var timeNow = new Date().getTime();
for(var serverName in self.serverList){
var item = self.serverList[serverName];
if( (timeNow - item.lastestUse) > DEFAULT_RELEASE_TIME){
item.server.close();
asyncTaskMgr.removeTask(serverName);
delete self.serverList[serverName];
console.log(color.yellow(color.bold("[internal https]")) + color.yellow("https server released : " + serverName));
}
}
},DEFAULT_RELEASE_TIME);
});
}
function createHttpsServer(port,keyContent,crtContent,userRequesthandler){
return https.createServer({
key : keyContent,
cert: crtContent
},userRequesthandler).listen(port);
//config.port - port to start https server
//config.handler - request handler
module.exports =function(config){
var self = this;
if(!config || !config.port ){
throw(new Error("please assign a port"));
}
https.createServer({
SNICallback : SNIPrepareCert
},config.handler).listen(config.port);
}

View File

@ -9,11 +9,11 @@ var http = require("http"),
color = require("colorful"),
Buffer = require('buffer').Buffer,
util = require("./util"),
getPort = require("./getPort"),
Stream = require("stream"),
httpsServerMgr = require("./httpsServerMgr");
var httpsServerMgrInstance = new httpsServerMgr(),
defaultRule = require("./rule_default.js"),
var defaultRule = require("./rule_default.js"),
userRule = defaultRule; //init
function userRequestHandler(req,userRes){
@ -270,26 +270,40 @@ function connectReqHandler(req, socket, head){
};
resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
var proxyPort, proxyHost;
var proxyPort,
proxyHost,
internalHttpsPort,
httpsServerMgrInstance;
async.series([
//find port
//check if internal https server exists
function(callback){
if(internalHttpsPort){
callback();
}else{
getPort(function(port){
internalHttpsPort = port;
httpsServerMgrInstance = new httpsServerMgr({
port :port,
handler :userRequestHandler
});
callback();
});
}
},
//determine the target server
function(callback){
if(shouldIntercept){
//TODO : remote port other than 433
httpsServerMgrInstance.fetchPort(host,userRequestHandler,function(err,port){
if(!err && port){
proxyPort = port;
proxyHost = "127.0.0.1";
callback();
}else{
callback(err);
}
});
proxyPort = internalHttpsPort;
proxyHost = "127.0.0.1";
callback();
}else{
proxyPort = targetPort;
proxyPort = 443;
proxyHost = host;
callback();
@ -305,7 +319,6 @@ function connectReqHandler(req, socket, head){
if(GLOBAL._throttle && !shouldIntercept ){
var readable = conn.pipe(GLOBAL._throttle.throttle());
readable.pipe(socket);
socket.pipe(conn);
}else{
conn.pipe(socket);