From d51cb0ce4b169602353d9b17fb49feee75e22fee Mon Sep 17 00:00:00 2001 From: OttoMao Date: Tue, 10 Feb 2015 12:03:21 +0800 Subject: [PATCH] using SNI when intercepting https requests --- CHANGELOG | 4 ++ README.md | 9 ++- diff.txt | 64 --------------------- lib/certMgr.js | 41 ++++++++------ lib/httpsServerMgr.js | 129 +++++++++++++++--------------------------- lib/requestHandler.js | 45 +++++++++------ 6 files changed, 105 insertions(+), 187 deletions(-) delete mode 100644 diff.txt diff --git a/CHANGELOG b/CHANGELOG index f749c8c..22d2022 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/README.md b/README.md index 7612675..0c88037 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/diff.txt b/diff.txt deleted file mode 100644 index 98ee805..0000000 --- a/diff.txt +++ /dev/null @@ -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 diff --git a/lib/certMgr.js b/lib/certMgr.js index 18dfde3..d9519e6 100644 --- a/lib/certMgr.js +++ b/lib/certMgr.js @@ -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){ diff --git a/lib/httpsServerMgr.js b/lib/httpsServerMgr.js index f140f94..eb5148f 100644 --- a/lib/httpsServerMgr.js +++ b/lib/httpsServerMgr.js @@ -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); } + diff --git a/lib/requestHandler.js b/lib/requestHandler.js index 620b7f9..bda9ff7 100644 --- a/lib/requestHandler.js +++ b/lib/requestHandler.js @@ -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);