Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ node_modules/*
.DS_Store
*.key
*.crt
*.env
dev.sh
2 changes: 1 addition & 1 deletion anyproxy/lib/certMgr.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const EasyCert = require('node-easy-cert');
const EasyCert = require('./node-easy-cert/index.js');
const co = require('co');
const os = require('os');
const inquirer = require('inquirer');
Expand Down
126 changes: 126 additions & 0 deletions anyproxy/lib/node-easy-cert/certGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use strict'

const forge = require('node-forge');
const Util = require('./util');

let defaultAttrs = [
{ name: 'countryName', value: 'CN' },
{ name: 'organizationName', value: 'EasyCert' },
{ shortName: 'ST', value: 'SH' },
{ shortName: 'OU', value: 'EasyCert SSL' }
];

/**
* different domain format needs different SAN
*
*/
function getExtensionSAN(domain = '') {
const isIpDomain = Util.isIpDomain(domain);
if (isIpDomain) {
return {
name: 'subjectAltName',
altNames: [{ type: 7, ip: domain }]
};
} else {
return {
name: 'subjectAltName',
altNames: [{ type: 2, value: domain }]
};
}
}

function getKeysAndCert(serialNumber) {
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = (Math.floor(Math.random() * 100000) + '');
console.log(`serial #${cert.serialNumber}`)
var now = Date.now();
// compatible with apple's updated cert policy: https://support.apple.com/en-us/HT210176
cert.validity.notBefore = new Date(now - 24 * 60 * 60 * 1000); // 1 day before
cert.validity.notAfter = new Date(now + 824 * 24 * 60 * 60 * 1000); // 824 days after
return {
keys,
cert
};
}

function generateRootCA(commonName) {
const keysAndCert = getKeysAndCert();
const keys = keysAndCert.keys;
const cert = keysAndCert.cert;

commonName = commonName || 'CertManager';

const attrs = defaultAttrs.concat([
{
name: 'commonName',
value: commonName
}
]);
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([
{ name: 'basicConstraints', cA: true },
// { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
// { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
// { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
// { name: 'subjectKeyIdentifier' }
]);

cert.sign(keys.privateKey, forge.md.sha256.create());

return {
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
certificate: forge.pki.certificateToPem(cert)
};
}

function generateCertsForHostname(domain, rootCAConfig) {
// generate a serialNumber for domain
const md = forge.md.md5.create();
md.update(domain);

const keysAndCert = getKeysAndCert(md.digest().toHex());
const keys = keysAndCert.keys;
const cert = keysAndCert.cert;

const caCert = forge.pki.certificateFromPem(rootCAConfig.cert);
const caKey = forge.pki.privateKeyFromPem(rootCAConfig.key);

// issuer from CA
cert.setIssuer(caCert.subject.attributes);

const attrs = defaultAttrs.concat([
{
name: 'commonName',
value: domain
}
]);

const extensions = [
{ name: 'basicConstraints', cA: false },
getExtensionSAN(domain)
];

cert.setSubject(attrs);
cert.setExtensions(extensions);

cert.sign(caKey, forge.md.sha256.create());

return {
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
certificate: forge.pki.certificateToPem(cert)
};
}

// change the default attrs
function setDefaultAttrs(attrs) {
defaultAttrs = attrs;
}

module.exports.generateRootCA = generateRootCA;
module.exports.generateCertsForHostname = generateCertsForHostname;
module.exports.setDefaultAttrs = setDefaultAttrs;
12 changes: 12 additions & 0 deletions anyproxy/lib/node-easy-cert/errorConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Map all the error code here
*
*/

'use strict';

module.exports = {
ROOT_CA_NOT_EXISTS: 'ROOT_CA_NOT_EXISTS', // root CA has not been generated yet
ROOT_CA_EXISTED: 'ROOT_CA_EXISTED', // root CA was existed, be ware that it will be overwrited
ROOT_CA_COMMON_NAME_UNSPECIFIED: 'ROOT_CA_COMMON_NAME_UNSPECIFIED' // commonName for rootCA is required
};
235 changes: 235 additions & 0 deletions anyproxy/lib/node-easy-cert/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
'use strict'

delete require.cache['./certGenerator'];

const path = require('path'),
fs = require('fs'),
color = require('colorful'),
certGenerator = require('./certGenerator'),
util = require('./util'),
Errors = require('./errorConstants'),
https = require('https'),
AsyncTask = require('async-task-mgr'),
winCertUtil = require('./winCertUtil'),
exec = require('child_process').exec;

const DOMAIN_TO_VERIFY_HTTPS = 'localtest.me';

function getPort() {
return new Promise((resolve, reject) => {
const server = require('net').createServer();
server.unref();
server.on('error', reject);
server.listen(0, () => {
const port = server.address().port;
server.close(() => {
resolve(port);
});
});
});
}

function CertManager(options) {
options = options || {};
const rootDirName = util.getDefaultRootDirName();
const rootDirPath = options.rootDirPath || path.join(util.getUserHome(), '/' + rootDirName + '/');

if (options.defaultCertAttrs) {
certGenerator.setDefaultAttrs(options.defaultCertAttrs);
}

const certDir = rootDirPath,
rootCAcrtFilePath = path.join(certDir, 'rootCA.crt'),
rootCAkeyFilePath = path.join(certDir, 'rootCA.key'),
createCertTaskMgr = new AsyncTask();
let cacheRootCACrtFileContent,
cacheRootCAKeyFileContent;
let rootCAExists = false;

if (!fs.existsSync(certDir)) {
try {
fs.mkdirSync(certDir, '0777');
} catch (e) {
console.log('===========');
console.log('failed to create cert dir ,please create one by yourself - ' + certDir);
console.log('===========');
}
}

function getCertificate(hostname, certCallback) {
if (!_checkRootCA()) {
console.log(color.yellow('please generate root CA before getting certificate for sub-domains'));
certCallback && certCallback(Errors.ROOT_CA_NOT_EXISTS);
return;
}
const keyFile = path.join(certDir, '__hostname.key'.replace(/__hostname/, hostname)),
crtFile = path.join(certDir, '__hostname.crt'.replace(/__hostname/, hostname));

if (!cacheRootCACrtFileContent || !cacheRootCAKeyFileContent) {
cacheRootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, { encoding: 'utf8' });
cacheRootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, { encoding: 'utf8' });
}

createCertTaskMgr.addTask(hostname, (callback) => {
if (!fs.existsSync(keyFile) || !fs.existsSync(crtFile)) {
try {
const result = certGenerator.generateCertsForHostname(hostname, {
cert: cacheRootCACrtFileContent,
key: cacheRootCAKeyFileContent
});
fs.writeFileSync(keyFile, result.privateKey);
fs.writeFileSync(crtFile, result.certificate);
callback(null, result.privateKey, result.certificate);
} catch (e) {
callback(e);
}
} else {
callback(null, fs.readFileSync(keyFile), fs.readFileSync(crtFile));
}
}, (err, keyContent, crtContent) => {
if (!err) {
certCallback(null, keyContent, crtContent);
} else {
certCallback(err);
}
});
}

function clearCerts(cb) {
util.deleteFolderContentsRecursive(certDir);
cb && cb();
}

function isRootCAFileExists() {
return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath));
}

function generateRootCA(config, certCallback) {
if (!config || !config.commonName) {
console.error(color.red('The "config.commonName" for rootCA is required, please specify.'));
certCallback(Errors.ROOT_CA_COMMON_NAME_UNSPECIFIED);
return;
}

if (isRootCAFileExists()) {
if (config.overwrite) {
startGenerating(config.commonName, certCallback);
} else {
console.error(color.red('The rootCA exists already, if you want to overwrite it, please specify the "config.overwrite=true"'));
certCallback(Errors.ROOT_CA_EXISTED);
}
} else {
startGenerating(config.commonName, certCallback);
}

function startGenerating(commonName, cb) {
// clear old certs
clearCerts(() => {
console.log(color.green('temp certs cleared'));
try {
const result = certGenerator.generateRootCA(commonName);
fs.writeFileSync(rootCAkeyFilePath, result.privateKey);
fs.writeFileSync(rootCAcrtFilePath, result.certificate);

console.log(color.green('rootCA generated'));
console.log(color.green(color.bold('PLEASE TRUST the rootCA.crt in ' + certDir)));

cb && cb(null, rootCAkeyFilePath, rootCAcrtFilePath);
} catch (e) {
console.log(color.red(e));
console.log(color.red(e.stack));
console.log(color.red('fail to generate root CA'));
cb && cb(e);
}
});
}
}

function getRootCAFilePath() {
return isRootCAFileExists() ? rootCAcrtFilePath : '';
}

function getRootDirPath() {
return rootDirPath;
}

function _checkRootCA() {
if (rootCAExists) {
return true;
}

if (!isRootCAFileExists()) {
console.log(color.red('can not find rootCA.crt or rootCA.key'));
console.log(color.red('you may generate one'));
return false;
} else {
rootCAExists = true;
return true;
}
}

function ifRootCATrusted(callback) {
if (!isRootCAFileExists()) {
callback && callback(new Error('ROOTCA_NOT_EXIST'));
} else if (/^win/.test(process.platform)) {
winCertUtil.ifWinRootCATrusted()
.then((ifTrusted) => {
callback && callback(null, ifTrusted)
})
.catch((e) => {
callback && callback(null, false);
})
} else {
const HTTPS_RESPONSE = 'HTTPS Server is ON';
// localtest.me --> 127.0.0.1
getCertificate(DOMAIN_TO_VERIFY_HTTPS, (e, key, cert) => {
getPort()
.then(port => {
if (e) {
callback && callback(e);
return;
}
const server = https
.createServer(
{
ca: fs.readFileSync(rootCAcrtFilePath),
key,
cert
},
(req, res) => {
res.end(HTTPS_RESPONSE);
}
)
.listen(port);

// do not use node.http to test the cert. Ref: https://github.com/nodejs/node/issues/4175
const testCmd = `curl https://${DOMAIN_TO_VERIFY_HTTPS}:${port}`;
exec(testCmd, { timeout: 1000 }, (error, stdout, stderr) => {
server.close();
if (error) {
callback && callback(null, false);
}
if (stdout && stdout.indexOf(HTTPS_RESPONSE) >= 0) {
callback && callback(null, true);
} else {
callback && callback(null, false);
}
});
})
.catch(callback);
});
}
}

return {
getRootCAFilePath,
generateRootCA,
getCertificate,
clearCerts,
isRootCAFileExists,
ifRootCATrusted,
getRootDirPath,
};
}

module.exports = CertManager;
Loading