From 0241b90e4d7209791d9609cafdc66c148d85d5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=9A=E7=84=B6?= Date: Sat, 30 Jun 2018 18:01:53 +0800 Subject: [PATCH 1/2] add ca helper when run anyproxy -i check and help to generate the root CA when run in https interception mode, and install the CA after user's confirmation (Mac only for now) --- bin/anyproxy | 95 ++++++++-------------------------------------- bin/rootCACheck.js | 33 ++++++++++++++++ bin/startServer.js | 86 +++++++++++++++++++++++++++++++++++++++++ lib/certMgr.js | 46 +++++++++++++++++++++- lib/log.js | 4 +- lib/util.js | 33 ++++++++-------- package.json | 4 +- 7 files changed, 202 insertions(+), 99 deletions(-) create mode 100644 bin/rootCACheck.js create mode 100644 bin/startServer.js diff --git a/bin/anyproxy b/bin/anyproxy index 108bc5d..23e3577 100755 --- a/bin/anyproxy +++ b/bin/anyproxy @@ -4,9 +4,11 @@ const program = require('commander'), color = require('colorful'), + co = require('co'), packageInfo = require('../package.json'), - ruleLoader = require('../lib/ruleLoader'), util = require('../lib/util'), + rootCACheck = require('./rootCaCheck'), + startServer = require('./startServer'), logUtil = require('../lib/log'); program @@ -33,85 +35,20 @@ if (program.clear) { process.exit(0); }); } else { - const AnyProxy = require('../proxy.js'); - let proxyServer; - - if (program.silent) { - logUtil.setPrintStatus(false); - } - - // load rule module - new Promise((resolve, reject) => { - if (program.rule) { - resolve(ruleLoader.requireModule(program.rule)); - } else { - resolve(null); - } - }) - .catch(e => { - logUtil.printLog('Failed to load rule file', logUtil.T_ERR); - logUtil.printLog(e, logUtil.T_ERR); - process.exit(); - }) - - //start proxy - .then(ruleModule => { - proxyServer = new AnyProxy.ProxyServer({ - type: 'http', - port: program.port || 8001, - throttle: program.throttle, - rule: ruleModule, - webInterface: { - enable: true, - webPort: program.web, - }, - wsIntercept: program.wsIntercept, - forceProxyHttps: program.intercept, - dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl, - silent: program.silent - }); - // proxyServer.on('ready', () => {}); - proxyServer.start(); - }) - .catch(e => { - logUtil.printLog(e, logUtil.T_ERR); - if (e && e.code) { - logUtil.printLog('code ' + e.code, logUtil.T_ERR); - } - logUtil.printLog(e.stack, logUtil.T_ERR); - }); - - process.on('exit', (code) => { - if (code > 0) { - logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR); + co(function *() { + if (program.silent) { + logUtil.setPrintStatus(false); } - process.exit(); - }); - - //exit cause ctrl+c - process.on('SIGINT', () => { - try { - proxyServer && proxyServer.close(); - } catch (e) { - console.error(e); - } - process.exit(); - }); - - process.on('uncaughtException', (err) => { - let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n'; - try { - if (err && err.stack) { - errorTipText += err.stack; - } else { - errorTipText += err; + if (program.intercept) { + try { + yield rootCACheck(); + } catch (e) { + console.error(e); } - } catch (e) {} - logUtil.printLog(errorTipText, logUtil.T_ERR); - try { - proxyServer && proxyServer.close(); - } catch (e) {} - process.exit(); - }); + } + + return startServer(program); + }) } + diff --git a/bin/rootCACheck.js b/bin/rootCACheck.js new file mode 100644 index 0000000..36b9576 --- /dev/null +++ b/bin/rootCACheck.js @@ -0,0 +1,33 @@ +/** +* check if root CA exists and installed +* will prompt to generate when needed +*/ + +const thunkify = require('thunkify'); +const AnyProxy = require('../proxy'); +const logUtil = require('../lib/log'); + +const certMgr = AnyProxy.utils.certMgr; + +function checkRootCAExists() { + return certMgr.isRootCAFileExists(); +} + +module.exports = function *() { + try { + if (!checkRootCAExists()) { + logUtil.warn('Missing root CA, generating now'); + yield thunkify(certMgr.generateRootCA)(); + yield certMgr.trustRootCA(); + } else { + const isCATrusted = yield thunkify(certMgr.ifRootCATrusted)(); + if (!isCATrusted) { + logUtil.warn('ROOT CA NOT INSTALLED YET'); + yield certMgr.trustRootCA(); + } + } + } catch (e) { + console.error(e); + } +}; + diff --git a/bin/startServer.js b/bin/startServer.js new file mode 100644 index 0000000..9fbdbf5 --- /dev/null +++ b/bin/startServer.js @@ -0,0 +1,86 @@ +/** +* start the AnyProxy server +*/ + +const ruleLoader = require('../lib/ruleLoader'); +const logUtil = require('../lib/log'); +const AnyProxy = require('../proxy'); + +module.exports = function startServer(program) { + let proxyServer; + // load rule module + new Promise((resolve, reject) => { + if (program.rule) { + resolve(ruleLoader.requireModule(program.rule)); + } else { + resolve(null); + } + }) + .catch(e => { + logUtil.printLog('Failed to load rule file', logUtil.T_ERR); + logUtil.printLog(e, logUtil.T_ERR); + process.exit(); + }) + + //start proxy + .then(ruleModule => { + proxyServer = new AnyProxy.ProxyServer({ + type: 'http', + port: program.port || 8001, + throttle: program.throttle, + rule: ruleModule, + webInterface: { + enable: true, + webPort: program.web, + }, + wsIntercept: program.wsIntercept, + forceProxyHttps: program.intercept, + dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl, + silent: program.silent + }); + // proxyServer.on('ready', () => {}); + proxyServer.start(); + }) + .catch(e => { + logUtil.printLog(e, logUtil.T_ERR); + if (e && e.code) { + logUtil.printLog('code ' + e.code, logUtil.T_ERR); + } + logUtil.printLog(e.stack, logUtil.T_ERR); + }); + + + process.on('exit', (code) => { + if (code > 0) { + logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR); + } + + process.exit(); + }); + + //exit cause ctrl+c + process.on('SIGINT', () => { + try { + proxyServer && proxyServer.close(); + } catch (e) { + console.error(e); + } + process.exit(); + }); + + process.on('uncaughtException', (err) => { + let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n'; + try { + if (err && err.stack) { + errorTipText += err.stack; + } else { + errorTipText += err; + } + } catch (e) { } + logUtil.printLog(errorTipText, logUtil.T_ERR); + try { + proxyServer && proxyServer.close(); + } catch (e) { } + process.exit(); + }); +} diff --git a/lib/certMgr.js b/lib/certMgr.js index 6947ca6..2bca293 100644 --- a/lib/certMgr.js +++ b/lib/certMgr.js @@ -1,11 +1,16 @@ 'use strict' -const util = require('./util'); const EasyCert = require('node-easy-cert'); const co = require('co'); +const os = require('os'); +const inquirer = require('inquirer'); + +const util = require('./util'); +const logUtil = require('./log'); const options = { rootDirPath: util.getAnyProxyPath('certificates'), + inMemory: false, defaultCertAttrs: [ { name: 'countryName', value: 'CN' }, { name: 'organizationName', value: 'AnyProxy' }, @@ -54,4 +59,43 @@ crtMgr.getCAStatus = function *() { }); } +/** + * trust the root ca by command + */ +crtMgr.trustRootCA = function *() { + const platform = os.platform(); + const rootCAPath = crtMgr.getRootCAFilePath(); + const trustInquiry = [ + { + type: 'list', + name: 'trustCA', + message: 'The rootCA is not trusted yet, install it to the trust store now?', + choices: ['Yes', "No, I'll do it myself"] + } + ]; + + if (platform === 'darwin') { + const answer = yield inquirer.prompt(trustInquiry); + if (answer.trustCA === 'Yes') { + logUtil.info('About to trust the root CA, this may requires your password'); + // https://ss64.com/osx/security-cert.html + const result = util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`); + if (result.status === 0) { + logUtil.info('Root CA install, you are ready to intercept the https now'); + } else { + console.error(result); + logUtil.info('Failed to trust the root CA, please trust it manually'); + } + } else { + logUtil.info('Please trust the root CA manually so https interception works'); + } + } + + + if (/^win/.test(process.platform)) { + logUtil.info('You can install the root CA manually.'); + } + logUtil.info('The root CA file path is: ' + crtMgr.getRootCAFilePath()); +} + module.exports = crtMgr; diff --git a/lib/log.js b/lib/log.js index 18adb3e..4bfde69 100644 --- a/lib/log.js +++ b/lib/log.js @@ -58,7 +58,7 @@ function printLog(content, type) { return; } - console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content)); + console.error(color.yellow(`[AnyProxy WARN][${timeString}]: ` + content)); break; } @@ -89,7 +89,7 @@ module.exports.warn = (content) => { }; module.exports.error = (content) => { - printLog(content, LogLevelMap.error); + printLog(content, LogLevelMap.system_error); }; module.exports.ruleError = (content) => { diff --git a/lib/util.js b/lib/util.js index a09e924..54332c5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -4,7 +4,7 @@ const fs = require('fs'), path = require('path'), mime = require('mime-types'), color = require('colorful'), - crypto = require('crypto'), + child_process = require('child_process'), Buffer = require('buffer').Buffer, logUtil = require('./log'); const networkInterfaces = require('os').networkInterfaces(); @@ -216,6 +216,8 @@ function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) { throw new Error('can_not_delete_this_dir'); } + console.info('==>>> delete cache ', dirPath); + if (fs.existsSync(dirPath)) { fs.readdirSync(dirPath).forEach((file) => { const curPath = path.join(dirPath, file); @@ -307,17 +309,18 @@ module.exports.isIpDomain = function (domain) { return ipReg.test(domain); }; -/** -* To generic a Sec-WebSocket-Accept value -* 1. append the `Sec-WebSocket-Key` request header with `matic string` -* 2. get sha1 hash of the string -* 3. get base64 of the sha1 hash -*/ -module.exports.genericWsSecAccept = function (wsSecKey) { - // the string to generate the Sec-WebSocket-Accept - const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; - const targetString = `${wsSecKey}${magicString}`; - const shasum = crypto.createHash('sha1'); - shasum.update(targetString); - return shasum.digest('base64'); -} +module.exports.execScriptSync = function (cmd) { + let stdout, + status = 0; + try { + stdout = child_process.execSync(cmd); + } catch (err) { + stdout = err.stdout; + status = err.status; + } + + return { + stdout: stdout.toString(), + status + }; +}; diff --git a/package.json b/package.json index e28527a..8ad7082 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "anyproxy", - "version": "4.0.10", + "version": "4.0.11", "description": "A fully configurable HTTP/HTTPS proxy in Node.js", "main": "proxy.js", "bin": { @@ -35,6 +35,7 @@ "request": "^2.74.0", "stream-throttle": "^0.1.3", "svg-inline-react": "^1.0.2", + "thunkify": "^2.1.2", "whatwg-fetch": "^1.0.0", "ws": "^5.1.0" }, @@ -68,7 +69,6 @@ "koa-send": "^3.2.0", "less": "^2.7.1", "less-loader": "^2.2.3", - "memwatch-next": "^0.3.0", "node-simhash": "^0.1.0", "nodeunit": "^0.9.1", "phantom": "^4.0.0", From e7732049dbcdb44211f04a53cccdd42f95f69f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=9A=E7=84=B6?= Date: Wed, 11 Jul 2018 15:20:39 +0800 Subject: [PATCH 2/2] guide to the homepage when there are more info to visit --- lib/certMgr.js | 2 ++ lib/util.js | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/certMgr.js b/lib/certMgr.js index 2bca293..1136ad8 100644 --- a/lib/certMgr.js +++ b/lib/certMgr.js @@ -85,9 +85,11 @@ crtMgr.trustRootCA = function *() { } else { console.error(result); logUtil.info('Failed to trust the root CA, please trust it manually'); + util.guideToHomePage(); } } else { logUtil.info('Please trust the root CA manually so https interception works'); + util.guideToHomePage(); } } diff --git a/lib/util.js b/lib/util.js index 54332c5..0d6a78c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -216,7 +216,7 @@ function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) { throw new Error('can_not_delete_this_dir'); } - console.info('==>>> delete cache ', dirPath); + logUtil.info('==>>> clearing cache ', dirPath); if (fs.existsSync(dirPath)) { fs.readdirSync(dirPath).forEach((file) => { @@ -324,3 +324,7 @@ module.exports.execScriptSync = function (cmd) { status }; }; + +module.exports.guideToHomePage = function () { + logUtil.info('Refer to http://anyproxy.io for more detail'); +};