From 0241b90e4d7209791d9609cafdc66c148d85d5dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A0=9A=E7=84=B6?= <yanran.wwj@alipay.com>
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?= <yanran.wwj@alipay.com>
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');
+};