-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproxy.js
305 lines (251 loc) · 12.5 KB
/
proxy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
const { dialog } = require('electron')
var httpProxy = require('http-proxy'),
execSync = require('child_process').execSync,
format = require("util").format,
fs = require('fs'),
path = require('path'),
tls = require('tls'),
https = require('https'),
sys = require('sys');
const forge = require('node-forge');
// export all of this as function
module.exports = function() {
// console.log("Node.js Version:", process.version); // Node.js version
// console.log("Environment Variables:", process.env); // All environment variables
const proxyTimeout = process.env.PROXY_TIMEOUT || 5*60;
var homePath = path.resolve("_certs"),
listenPort = process.env.PORT || 443,
forwardHost = process.env.FORWARD_HOST || 'localhost',
forwardPort = process.env.FORWARD_PORT || 80;
function generateCertificate(name, CA) {
const keyPath = path.resolve(homePath, name + ".key");
const csrPath = path.resolve(homePath, name + ".csr");
const certPath = path.resolve(homePath, name + ".crt");
// If the certificate doesn't already exist, generate it
if (!fs.existsSync(certPath)) {
console.log("Generating certificate: " + certPath);
// Generate the RSA key pair
const keys = forge.pki.rsa.generateKeyPair(2048);
// Write the private key to a file
fs.writeFileSync(keyPath, forge.pki.privateKeyToPem(keys.privateKey));
// Create a CSR (Certificate Signing Request)
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
csr.setSubject([{
name: 'commonName',
value: name,
}]);
// Sign the CSR with the private key
csr.sign(keys.privateKey);
// Write the CSR to a file
fs.writeFileSync(csrPath, forge.pki.certificationRequestToPem(csr));
// If CA is provided, sign the CSR with the CA's certificate and key
if (CA) {
const caCertPem = fs.readFileSync(CA.cert, 'utf8');
const caKeyPem = fs.readFileSync(CA.key, 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caPrivateKey = forge.pki.privateKeyFromPem(caKeyPem);
// Create a new certificate signed by the CA
const cert = forge.pki.createCertificate();
cert.serialNumber = new Date().getTime().toString();
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + 1001); // Valid for 1001 days
cert.setSubject(csr.subject.attributes);
cert.setIssuer(caCert.subject.attributes);
cert.publicKey = csr.publicKey;
// Set extensions from the config file
cert.setExtensions([
{ name: 'basicConstraints', cA: false },
{ name: 'keyUsage', digitalSignature: true, keyCertSign: true },
{
name: 'subjectAltName',
altNames: [{ type: 2, value: name }] // DNS type for SAN (subjectAltName)
}
]);
// Sign the certificate with the CA's private key
cert.sign(caPrivateKey, forge.md.sha256.create());
// Write the signed certificate to a file
fs.writeFileSync(certPath, forge.pki.certificateToPem(cert));
} else {
console.log("Add this cert as a trusted CA Root to get rid of SSL warnings: " + certPath);
// Self-sign the certificate (for development purposes)
const cert = forge.pki.createCertificate();
cert.serialNumber = new Date().getTime().toString();
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + 1001); // Valid for 1001 days
cert.setSubject(csr.subject.attributes);
cert.setIssuer(csr.subject.attributes); // Self-signing so issuer == subject
cert.publicKey = csr.publicKey;
// Set extensions for self-signed certificates (similar to the OpenSSL config)
cert.setExtensions([
{ name: 'basicConstraints', cA: true },
{ name: 'keyUsage', digitalSignature: true, keyCertSign: true },
{
name: 'subjectAltName',
altNames: [{ type: 2, value: name }] // DNS type for SAN
}
]);
// Self-sign the certificate
cert.sign(keys.privateKey, forge.md.sha256.create());
// Write the self-signed certificate to a file
fs.writeFileSync(certPath, forge.pki.certificateToPem(cert));
const response = dialog.showMessageBoxSync(null, {
type: "info",
buttons: ['Cancel', 'Proceed', 'I\'ll do it myself'],
defaultId: 1,
title: 'Administrator Privileges Required',
message: 'To install the CA certificate, we need administrator privileges.',
detail: 'This is necessary to add the certificate to your system’s trusted root certificate store, ensuring your connection is secure.',
});
if (response === 1) {
addCAToTrustedRoot(certPath);
} else {
console.log("User declined :(")
dialog.showMessageBoxSync(null, {
type: "info",
title: 'CA Certificate Installation',
message: 'You can manually install the CA certificate',
detail: 'You can manually install the CA certificate by running this command:'
+ (process.platform === 'win32' ? '\n\n' + `certutil -addstore Root "${certPath}"` : '\n\n' + `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`)
});
}
}
}
return {
cert: certPath,
key: keyPath,
};
}
fs.existsSync(homePath) || fs.mkdirSync(homePath);
// force the CA
var CA = generateCertificate('ssl.proxy.hoststray.cupmanager.net');
var ssl = {
SNICallback: function (domain, callback) {
var domainCert = generateCertificate(domain, CA),
ctx = tls.createSecureContext({
key: fs.readFileSync(domainCert.key),
cert: fs.readFileSync(domainCert.cert),
ca: [fs.readFileSync(CA.cert)],
ciphers: "AES128+EECDH:AES128+EDH"
});
return callback(null, ctx);
},
key: fs.readFileSync(CA.key),
cert: fs.readFileSync(CA.cert)
};
var proxy = httpProxy.createProxyServer({target: {host: forwardHost, port: forwardPort}});
proxy.on('error', function (err, req, res) {
res.writeHead && res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end('Something went wrong.');
});
proxy.on('proxyReq', function (proxyReq, req, res, options) {
proxyReq.setHeader('X-Forwarded-Protocol', 'https');
proxyReq.setHeader('X-Forwarded-Proto', 'https');
proxyReq.setHeader('X-Forwarded-Port', listenPort);
});
const cmwebWsProxy = httpProxy.createProxyServer({
target: {
host: forwardHost,
port: 5124
},
proxyTimeout: proxyTimeout*1000
});
cmwebWsProxy.addListener("error", function() {
console.log("error in cmwebWsProxy: ", arguments);
})
const resultsapiWsProxy = httpProxy.createProxyServer({
target: {
host: forwardHost,
port: 5125
},
proxyTimeout: proxyTimeout*1000
});
resultsapiWsProxy.addListener("error", function() {
console.log("error in resultsapiWsProxy: ", arguments);
})
var server = https.createServer(ssl, function (req, res) {
console.log(req.method + " https://" + req.headers.host + req.url);
proxy.web(req, res);
}).on('upgrade', function (req, socket, head) {
// proxy.ws(req, socket, head);
console.log("Websocket: " + req.method + " https://" + req.headers.host + req.url);
if (req.url.indexOf("/cmweb") == 0) {
cmwebWsProxy.ws(req, socket, head);
} else if (req.url.indexOf("/resultsapi") == 0) {
resultsapiWsProxy.ws(req, socket, head);
} else {
console.error("No websocket handling available. Expected /cmweb or /resultsapi")
}
}).listen(listenPort);
// server.close();
console.log("Listening on %s. Forwarding to http://%s:%d (and websockets)", listenPort, forwardHost, forwardPort);
return server;
};
function addCAToTrustedRoot(caCertPath) {
const execOptions = { name: 'Hoststray' };
const platform = process.platform;
let command = '';
if (platform === 'win32') {
// Windows command for adding a CA cert
command = `certutil -addstore Root "${caCertPath}"`;
} else if (platform === 'darwin') {
// macOS command for adding a CA cert
command = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${caCertPath}"`;
}
// Run the command with elevated privileges
var sudo = require('sudo-prompt');
sudo.exec(command, execOptions, function (error, stdout, stderr) {
console.log({error});
if (error) {
dialog.showMessageBox(null, {
type: "error",
title: 'CA Certificate Installation Failed',
message: 'The CA certificate could not be installed.',
detail: 'Please try again or manually install the CA certificate by running this command:'
+ (platform === 'win32' ? '\n\n' + command : '\n\n' + `sudo ${command}`)
});
} else {
dialog.showMessageBox(null, {
type: "info",
title: 'CA Certificate Installed',
message: 'The CA certificate has been successfully installed.',
detail: 'You can now close this window.'
});
}
})
}
function getCertificateThumbprintFromFile(certFilePath) {
const certPem = fs.readFileSync(certFilePath, 'utf8');
const cert = forge.pki.certificateFromPem(certPem);
const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
const sha1 = forge.md.sha1.create();
sha1.update(der);
return sha1.digest().toHex().toUpperCase();
}
function removeCertificate(certFilePath) {
const thumbprint = getCertificateThumbprintFromFile(certFilePath);
const command = `certutil -delstore Root ${thumbprint}`;
const execOptions = { name: 'Hoststray' };
sudo.exec(command, execOptions, function (error, stdout, stderr) {
if (error) {
dialog.showMessageBox(null, {
type: "error",
title: 'CA Certificate Removal Failed',
message: 'The CA certificate could not be removed.',
detail: 'Please try again or manually remove the CA certificate by running this command:'
+ (process.platform === 'win32' ? '\n\n' + command : '\n\n' + `sudo ${command}`)
});
} else {
dialog.showMessageBox(null, {
type: "info",
title: 'CA Certificate Removed',
message: 'The CA certificate has been successfully removed.',
detail: 'You can now close this window.'
});
}
})
}