Skip to content

Commit f8b97a4

Browse files
author
Matthew Bryant (mandatory)
committed
Fixes some issues with certificate generation causing requests to fail and also *hopefully* fixes the proxy auth errors
1 parent 0770015 commit f8b97a4

File tree

10 files changed

+480
-13
lines changed

10 files changed

+480
-13
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ node_modules/*
22
.DS_Store
33
*.key
44
*.crt
5+
*.env
6+
dev.sh

anyproxy/lib/certMgr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const EasyCert = require('node-easy-cert');
3+
const EasyCert = require('./node-easy-cert/index.js');
44
const co = require('co');
55
const os = require('os');
66
const inquirer = require('inquirer');
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
'use strict'
2+
3+
const forge = require('node-forge');
4+
const Util = require('./util');
5+
6+
let defaultAttrs = [
7+
{ name: 'countryName', value: 'CN' },
8+
{ name: 'organizationName', value: 'EasyCert' },
9+
{ shortName: 'ST', value: 'SH' },
10+
{ shortName: 'OU', value: 'EasyCert SSL' }
11+
];
12+
13+
/**
14+
* different domain format needs different SAN
15+
*
16+
*/
17+
function getExtensionSAN(domain = '') {
18+
const isIpDomain = Util.isIpDomain(domain);
19+
if (isIpDomain) {
20+
return {
21+
name: 'subjectAltName',
22+
altNames: [{ type: 7, ip: domain }]
23+
};
24+
} else {
25+
return {
26+
name: 'subjectAltName',
27+
altNames: [{ type: 2, value: domain }]
28+
};
29+
}
30+
}
31+
32+
function getKeysAndCert(serialNumber) {
33+
const keys = forge.pki.rsa.generateKeyPair(2048);
34+
const cert = forge.pki.createCertificate();
35+
cert.publicKey = keys.publicKey;
36+
cert.serialNumber = (Math.floor(Math.random() * 100000) + '');
37+
console.log(`serial #${cert.serialNumber}`)
38+
var now = Date.now();
39+
// compatible with apple's updated cert policy: https://support.apple.com/en-us/HT210176
40+
cert.validity.notBefore = new Date(now - 24 * 60 * 60 * 1000); // 1 day before
41+
cert.validity.notAfter = new Date(now + 824 * 24 * 60 * 60 * 1000); // 824 days after
42+
return {
43+
keys,
44+
cert
45+
};
46+
}
47+
48+
function generateRootCA(commonName) {
49+
const keysAndCert = getKeysAndCert();
50+
const keys = keysAndCert.keys;
51+
const cert = keysAndCert.cert;
52+
53+
commonName = commonName || 'CertManager';
54+
55+
const attrs = defaultAttrs.concat([
56+
{
57+
name: 'commonName',
58+
value: commonName
59+
}
60+
]);
61+
cert.setSubject(attrs);
62+
cert.setIssuer(attrs);
63+
cert.setExtensions([
64+
{ name: 'basicConstraints', cA: true },
65+
// { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
66+
// { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
67+
// { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
68+
// { name: 'subjectKeyIdentifier' }
69+
]);
70+
71+
cert.sign(keys.privateKey, forge.md.sha256.create());
72+
73+
return {
74+
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
75+
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
76+
certificate: forge.pki.certificateToPem(cert)
77+
};
78+
}
79+
80+
function generateCertsForHostname(domain, rootCAConfig) {
81+
// generate a serialNumber for domain
82+
const md = forge.md.md5.create();
83+
md.update(domain);
84+
85+
const keysAndCert = getKeysAndCert(md.digest().toHex());
86+
const keys = keysAndCert.keys;
87+
const cert = keysAndCert.cert;
88+
89+
const caCert = forge.pki.certificateFromPem(rootCAConfig.cert);
90+
const caKey = forge.pki.privateKeyFromPem(rootCAConfig.key);
91+
92+
// issuer from CA
93+
cert.setIssuer(caCert.subject.attributes);
94+
95+
const attrs = defaultAttrs.concat([
96+
{
97+
name: 'commonName',
98+
value: domain
99+
}
100+
]);
101+
102+
const extensions = [
103+
{ name: 'basicConstraints', cA: false },
104+
getExtensionSAN(domain)
105+
];
106+
107+
cert.setSubject(attrs);
108+
cert.setExtensions(extensions);
109+
110+
cert.sign(caKey, forge.md.sha256.create());
111+
112+
return {
113+
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
114+
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
115+
certificate: forge.pki.certificateToPem(cert)
116+
};
117+
}
118+
119+
// change the default attrs
120+
function setDefaultAttrs(attrs) {
121+
defaultAttrs = attrs;
122+
}
123+
124+
module.exports.generateRootCA = generateRootCA;
125+
module.exports.generateCertsForHostname = generateCertsForHostname;
126+
module.exports.setDefaultAttrs = setDefaultAttrs;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Map all the error code here
3+
*
4+
*/
5+
6+
'use strict';
7+
8+
module.exports = {
9+
ROOT_CA_NOT_EXISTS: 'ROOT_CA_NOT_EXISTS', // root CA has not been generated yet
10+
ROOT_CA_EXISTED: 'ROOT_CA_EXISTED', // root CA was existed, be ware that it will be overwrited
11+
ROOT_CA_COMMON_NAME_UNSPECIFIED: 'ROOT_CA_COMMON_NAME_UNSPECIFIED' // commonName for rootCA is required
12+
};

anyproxy/lib/node-easy-cert/index.js

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
'use strict'
2+
3+
delete require.cache['./certGenerator'];
4+
5+
const path = require('path'),
6+
fs = require('fs'),
7+
color = require('colorful'),
8+
certGenerator = require('./certGenerator'),
9+
util = require('./util'),
10+
Errors = require('./errorConstants'),
11+
https = require('https'),
12+
AsyncTask = require('async-task-mgr'),
13+
winCertUtil = require('./winCertUtil'),
14+
exec = require('child_process').exec;
15+
16+
const DOMAIN_TO_VERIFY_HTTPS = 'localtest.me';
17+
18+
function getPort() {
19+
return new Promise((resolve, reject) => {
20+
const server = require('net').createServer();
21+
server.unref();
22+
server.on('error', reject);
23+
server.listen(0, () => {
24+
const port = server.address().port;
25+
server.close(() => {
26+
resolve(port);
27+
});
28+
});
29+
});
30+
}
31+
32+
function CertManager(options) {
33+
options = options || {};
34+
const rootDirName = util.getDefaultRootDirName();
35+
const rootDirPath = options.rootDirPath || path.join(util.getUserHome(), '/' + rootDirName + '/');
36+
37+
if (options.defaultCertAttrs) {
38+
certGenerator.setDefaultAttrs(options.defaultCertAttrs);
39+
}
40+
41+
const certDir = rootDirPath,
42+
rootCAcrtFilePath = path.join(certDir, 'rootCA.crt'),
43+
rootCAkeyFilePath = path.join(certDir, 'rootCA.key'),
44+
createCertTaskMgr = new AsyncTask();
45+
let cacheRootCACrtFileContent,
46+
cacheRootCAKeyFileContent;
47+
let rootCAExists = false;
48+
49+
if (!fs.existsSync(certDir)) {
50+
try {
51+
fs.mkdirSync(certDir, '0777');
52+
} catch (e) {
53+
console.log('===========');
54+
console.log('failed to create cert dir ,please create one by yourself - ' + certDir);
55+
console.log('===========');
56+
}
57+
}
58+
59+
function getCertificate(hostname, certCallback) {
60+
if (!_checkRootCA()) {
61+
console.log(color.yellow('please generate root CA before getting certificate for sub-domains'));
62+
certCallback && certCallback(Errors.ROOT_CA_NOT_EXISTS);
63+
return;
64+
}
65+
const keyFile = path.join(certDir, '__hostname.key'.replace(/__hostname/, hostname)),
66+
crtFile = path.join(certDir, '__hostname.crt'.replace(/__hostname/, hostname));
67+
68+
if (!cacheRootCACrtFileContent || !cacheRootCAKeyFileContent) {
69+
cacheRootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, { encoding: 'utf8' });
70+
cacheRootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, { encoding: 'utf8' });
71+
}
72+
73+
createCertTaskMgr.addTask(hostname, (callback) => {
74+
if (!fs.existsSync(keyFile) || !fs.existsSync(crtFile)) {
75+
try {
76+
const result = certGenerator.generateCertsForHostname(hostname, {
77+
cert: cacheRootCACrtFileContent,
78+
key: cacheRootCAKeyFileContent
79+
});
80+
fs.writeFileSync(keyFile, result.privateKey);
81+
fs.writeFileSync(crtFile, result.certificate);
82+
callback(null, result.privateKey, result.certificate);
83+
} catch (e) {
84+
callback(e);
85+
}
86+
} else {
87+
callback(null, fs.readFileSync(keyFile), fs.readFileSync(crtFile));
88+
}
89+
}, (err, keyContent, crtContent) => {
90+
if (!err) {
91+
certCallback(null, keyContent, crtContent);
92+
} else {
93+
certCallback(err);
94+
}
95+
});
96+
}
97+
98+
function clearCerts(cb) {
99+
util.deleteFolderContentsRecursive(certDir);
100+
cb && cb();
101+
}
102+
103+
function isRootCAFileExists() {
104+
return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath));
105+
}
106+
107+
function generateRootCA(config, certCallback) {
108+
if (!config || !config.commonName) {
109+
console.error(color.red('The "config.commonName" for rootCA is required, please specify.'));
110+
certCallback(Errors.ROOT_CA_COMMON_NAME_UNSPECIFIED);
111+
return;
112+
}
113+
114+
if (isRootCAFileExists()) {
115+
if (config.overwrite) {
116+
startGenerating(config.commonName, certCallback);
117+
} else {
118+
console.error(color.red('The rootCA exists already, if you want to overwrite it, please specify the "config.overwrite=true"'));
119+
certCallback(Errors.ROOT_CA_EXISTED);
120+
}
121+
} else {
122+
startGenerating(config.commonName, certCallback);
123+
}
124+
125+
function startGenerating(commonName, cb) {
126+
// clear old certs
127+
clearCerts(() => {
128+
console.log(color.green('temp certs cleared'));
129+
try {
130+
const result = certGenerator.generateRootCA(commonName);
131+
fs.writeFileSync(rootCAkeyFilePath, result.privateKey);
132+
fs.writeFileSync(rootCAcrtFilePath, result.certificate);
133+
134+
console.log(color.green('rootCA generated'));
135+
console.log(color.green(color.bold('PLEASE TRUST the rootCA.crt in ' + certDir)));
136+
137+
cb && cb(null, rootCAkeyFilePath, rootCAcrtFilePath);
138+
} catch (e) {
139+
console.log(color.red(e));
140+
console.log(color.red(e.stack));
141+
console.log(color.red('fail to generate root CA'));
142+
cb && cb(e);
143+
}
144+
});
145+
}
146+
}
147+
148+
function getRootCAFilePath() {
149+
return isRootCAFileExists() ? rootCAcrtFilePath : '';
150+
}
151+
152+
function getRootDirPath() {
153+
return rootDirPath;
154+
}
155+
156+
function _checkRootCA() {
157+
if (rootCAExists) {
158+
return true;
159+
}
160+
161+
if (!isRootCAFileExists()) {
162+
console.log(color.red('can not find rootCA.crt or rootCA.key'));
163+
console.log(color.red('you may generate one'));
164+
return false;
165+
} else {
166+
rootCAExists = true;
167+
return true;
168+
}
169+
}
170+
171+
function ifRootCATrusted(callback) {
172+
if (!isRootCAFileExists()) {
173+
callback && callback(new Error('ROOTCA_NOT_EXIST'));
174+
} else if (/^win/.test(process.platform)) {
175+
winCertUtil.ifWinRootCATrusted()
176+
.then((ifTrusted) => {
177+
callback && callback(null, ifTrusted)
178+
})
179+
.catch((e) => {
180+
callback && callback(null, false);
181+
})
182+
} else {
183+
const HTTPS_RESPONSE = 'HTTPS Server is ON';
184+
// localtest.me --> 127.0.0.1
185+
getCertificate(DOMAIN_TO_VERIFY_HTTPS, (e, key, cert) => {
186+
getPort()
187+
.then(port => {
188+
if (e) {
189+
callback && callback(e);
190+
return;
191+
}
192+
const server = https
193+
.createServer(
194+
{
195+
ca: fs.readFileSync(rootCAcrtFilePath),
196+
key,
197+
cert
198+
},
199+
(req, res) => {
200+
res.end(HTTPS_RESPONSE);
201+
}
202+
)
203+
.listen(port);
204+
205+
// do not use node.http to test the cert. Ref: https://github.com/nodejs/node/issues/4175
206+
const testCmd = `curl https://${DOMAIN_TO_VERIFY_HTTPS}:${port}`;
207+
exec(testCmd, { timeout: 1000 }, (error, stdout, stderr) => {
208+
server.close();
209+
if (error) {
210+
callback && callback(null, false);
211+
}
212+
if (stdout && stdout.indexOf(HTTPS_RESPONSE) >= 0) {
213+
callback && callback(null, true);
214+
} else {
215+
callback && callback(null, false);
216+
}
217+
});
218+
})
219+
.catch(callback);
220+
});
221+
}
222+
}
223+
224+
return {
225+
getRootCAFilePath,
226+
generateRootCA,
227+
getCertificate,
228+
clearCerts,
229+
isRootCAFileExists,
230+
ifRootCATrusted,
231+
getRootDirPath,
232+
};
233+
}
234+
235+
module.exports = CertManager;

0 commit comments

Comments
 (0)