Intercept HTTP responses on an HTTPS server in Node.JS and Express - node.js

I'm just starting with node.js and express and I'm doing a simple HTTPS server. I've been working with nginx for some time and when I make an HTTP request to an HTTPS endpoint I get a "400 Bad Request" error. However, when using node.js the request never finishes.
How can I intercept an HTTP request in Express to be able to generate the "400 Bad Request" response?
This is my code:
var express = require('express');
var https = require('https');
var fs = require('fs');
var port = process.env.PORT || 8080;
var tls_options = {
key: fs.readFileSync('certs/server.key'),
cert: fs.readFileSync('certs/server.crt'),
ca: fs.readFileSync('certs/ca.crt'),
requestCert: true,
};
var app = express();
var router = express.Router();
router.get('/', function(req, res) {
res.json({ message: 'Checkpoint!!' });
});
app.use('/', router);
var secureServer = https.createServer(tls_options, app);
secureServer.listen(port);
console.log('Listening on port ' + port);
Until now the only thing I've been able to use is getting a 'connection' event every time a request arrives to the server:
secureServer.on('connection', function (stream) {
console.log('someone connected!');
});

Done. In fact, an HTTP request to an HTTPS socket ends after the default 120secs TLS handsahke timeout. This way I can end the request without waiting. I include the solution I used just for future references if anything needs the same functionality.
var secureServer = https.createServer(options, app);
secureServer.on('connection', function(socket) {
socket.on('data', function(data) {
var first_line = data.toString().split('\r\n')[0];
var pattern = /\bhttp\/1\.[01]$\b/i;
if (pattern.test(first_line)) {
var headers = {};
headers['Date'] = new Date().toUTCString();
headers['Connection'] = 'close';
var headers_string = '';
for (var name in headers) {
headers_string = headers_string + '\r\n' + name + ': ' + headers[name];
}
socket.end('HTTP/1.1 400 Bad Request' + headers_string);
}
});

There isn't a way of starting both HTTP and HTTPS servers on the same port. What most people do is either:
Start two servers (one HTTP and one HTTPS) on different ports, and redirect the HTTP traffic to HTTPS. Using Express it would mean the additional code:
// create two ports, one for HTTP and one for HTTPS
var port = process.env.PORT || 8080;
var httpsPort = 8081;
// redirect all HTTP requests to HTTPS
app.use(function(req, res, next) {
var hostname;
if (!req.secure) {
hostname = req.get("host").split(":")[0];
return res.redirect(["https://", hostname, ":", httpsPort, req.url].join(""));
}
next();
});
app.listen(port); // listen on HTTP
https.createServer(tls_options, app).listen(httpsPort); // listen on HTTPS
Or they use nginx or apache to handle outside connections (both HTTP and HTTPS) and redirect traffic to the Node server (which can then just run on HTTP).

Related

How can I adjust my NodeJs server code to responf to HTTPS requests?

I have a small nodejs server which is working without a problem. Now I am trying to make use of "HTTPS" for security reasons. I have the following code, but when I try to open the page in Firefox via link [http://192.168.2.22:8080/api/users], on the terminal I see DIRECTING >>> https://192.168.2.22:8080/api/users but in the browser, instead of the expected response, I encounter this error:
Secure Connection Failed An error occurred during a connection to
192.168.2.22:8080. SSL received a record that exceeded the maximum permissible length.
Error code: SSL_ERROR_RX_RECORD_TOO_LONG
// Modules /////////////////////////////////////////////////////////////////////
const db = require('./db.js');
// Packages ////////////////////////////////////////////////////////////////////
const colors = require('colors');
const express = require('express');
const app = express();
const fileUpload = require('express-fileupload');
const fs = require('fs');
// Constant Variables //////////////////////////////////////////////////////////
const PORT_SERVER = 8080;
const HOST = '192.168.2.22';
////////////////////////////////////////////////////////////////////////////////
app.use(express.json({limit: '50mb'}));
// app.use(express.urlencoded({limit: '50mb'}));
// set up a route to redirect http to https
app.get('*', function(req, res) {
console.log('DIRECTING >>> https ://' + req.headers.host + req.url);
res.redirect('https://' + req.headers.host + req.url);
});
app.get('/api/users/', async (req, res) => {
console.log('CHECK POINT !!!');
let users = await db.db.get_users();
console.log("USERS : " + users);
res.send(users);
});
// have it listen on 8080
app.listen(PORT_SERVER, () => console.log(`Listen at ${PORT_SERVER}...`));
How can I resolve this? I could not find a solution that I can easily apply to my code, I am kind of a newbie for NodeJs.
Thanks in advance
You have not configured your server for SSL. Configure SSL using the https module like below. In this example, I have created two express one for Http and one for https as we can not run both http and non https on same port.
const express = require('express');
const http = require('http'),
const https = require('https')
const fs = require('fs')
const httpApp = express()
const app = express()
const httpsOptions = {
key: fs.readFileSync("server.key"),
cert: fs.readFileSync("server.crt")
};
httpApp.set('port',80);
httpApp.get("*", function (req, res, next) {
res.redirect("https://" + req.headers.host + "/" + req.path);
});
app.set('port', 443);
app.enable('trust proxy');
http.createServer(httpApp).listen(httpApp.get('port'), function() {
console.log('Express HTTP server listening on port ' + httpApp.get('port'));
});
https.createServer(httpsOptions, app).listen(app.get('port'), function() {
console.log('Express HTTPS server listening on port ' + app.get('port'));
});
The Best way to redirect from non-http to https is to use Nginx web server as a reverse proxy and define redirection rule in Nginx config file.
client--->nginx reverse proxy(with SSL and redirection rules)-->express server
try changing the port to 443, https runs on 443 by default!

ERR_CONNECTION_REFUSED when redirecting http to https using nodejs

I'm trying to redirect http requests to https using nodejs.
Here is my code so far. I have included all key, certs etc.
http and https ports are ON ( custom ports, not defaults)
Now on hitting http, site does get redirected to https but it shows err as shown in screenshot attached. screenshot.
certs are valid ( see bottom of screenshot ) but still it says THIS PAGE IS NOT SECURE.
Please guide me where i'm wrong and possible solution.
I have searched here but couldn't get solution to this. Thanks.
var fs = require('fs');
var http = require('http');
var http_port =any_port;
var app = require('express')();
// HTTPS definitions
var https = require('https');
var https_port =another_port;
var options = {
key: fs.readFileSync('example.key'),
cert:fs.readFileSync('example.crt'),
ca: fs.readFileSync('bundleexample.crt')
};
app.get('/', function (req, res) {
res.end('Hello https World!');
});
https.createServer(options, app).listen(https_port, function () {
console.log('https port ' + https_port);
});
// Redirect from http port to https
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
res.end();
}).listen(http_port);

Can an https service on nodejs listen to an http request? [duplicate]

I've been googling and looking here at stackoverflow, but I can't find an answer I like ;-)
I have a NodeJS server that runs over HTTPS and port 3001. Now I'd like to fetch all incoming HTTP requests on port 3001 and redirect them to the same URL but over HTTPS.
This must be possible. Isn't it?
Thanks!
You don't need to listen on same port if you follow convention
By convention when you request http://127.0.0.1 your browser will try to connect to port 80. If you try to open https://127.0.0.1 your browser will try to connect to port 443. So to secure all traffic it is simply conventional to listen to port 80 on http with a redirect to https where we already have a listener for https for port 443. Here's the code:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
};
https.createServer(options, function (req, res) {
res.end('secure!');
}).listen(443);
// Redirect from http port 80 to https
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
}).listen(80);
Test with https:
$ curl https://127.0.0.1 -k
secure!
With http:
$ curl http://127.0.0.1 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1/
Date: Sun, 01 Jun 2014 06:15:16 GMT
Connection: keep-alive
Transfer-Encoding: chunked
If you must listen on same port
There isn't simple way to have http / https listen on the same port. You best bet is to create proxy server on a simple net socket that pipes to (http or https) based on the nature of the incoming connection (http vs. https).
Here is the complete code (based on https://gist.github.com/bnoordhuis/4740141) that does exactly that. It listens on localhost:3000 and pipes it to http (which in turn redirects it to https) or if the incomming connection is in https it just passes it to https handler
var fs = require('fs');
var net = require('net');
var http = require('http');
var https = require('https');
var baseAddress = 3000;
var redirectAddress = 3001;
var httpsAddress = 3002;
var httpsOptions = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
};
net.createServer(tcpConnection).listen(baseAddress);
http.createServer(httpConnection).listen(redirectAddress);
https.createServer(httpsOptions, httpsConnection).listen(httpsAddress);
function tcpConnection(conn) {
conn.once('data', function (buf) {
// A TLS handshake record starts with byte 22.
var address = (buf[0] === 22) ? httpsAddress : redirectAddress;
var proxy = net.createConnection(address, function () {
proxy.write(buf);
conn.pipe(proxy).pipe(conn);
});
});
}
function httpConnection(req, res) {
var host = req.headers['host'];
res.writeHead(301, { "Location": "https://" + host + req.url });
res.end();
}
function httpsConnection(req, res) {
res.writeHead(200, { 'Content-Length': '5' });
res.end('HTTPS');
}
As a test, If you connect it with https you get the https handler:
$ curl https://127.0.0.1:3000 -k
HTTPS
if you connect it with http you get the redirect handler (which simply takes you to the https handler):
$ curl http://127.0.0.1:3000 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1:3000/
Date: Sat, 31 May 2014 16:36:56 GMT
Connection: keep-alive
Transfer-Encoding: chunked
If serving HTTP and HTTPS over a single port is an absolute requirement you can proxy the request to the relevant HTTP implementation directly, rather than piping the socket to another port.
httpx.js
'use strict';
let net = require('net');
let http = require('http');
let https = require('https');
exports.createServer = (opts, handler) => {
let server = net.createServer(socket => {
socket.once('data', buffer => {
// Pause the socket
socket.pause();
// Determine if this is an HTTP(s) request
let byte = buffer[0];
let protocol;
if (byte === 22) {
protocol = 'https';
} else if (32 < byte && byte < 127) {
protocol = 'http';
}
let proxy = server[protocol];
if (proxy) {
// Push the buffer back onto the front of the data stream
socket.unshift(buffer);
// Emit the socket to the HTTP(s) server
proxy.emit('connection', socket);
}
// As of NodeJS 10.x the socket must be
// resumed asynchronously or the socket
// connection hangs, potentially crashing
// the process. Prior to NodeJS 10.x
// the socket may be resumed synchronously.
process.nextTick(() => socket.resume());
});
});
server.http = http.createServer(handler);
server.https = https.createServer(opts, handler);
return server;
};
example.js
'use strict';
let express = require('express');
let fs = require('fs');
let io = require('socket.io');
let httpx = require('./httpx');
let opts = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.cert')
};
let app = express();
app.use(express.static('public'));
let server = httpx.createServer(opts, app);
let ws = io(server.http);
let wss = io(server.https);
server.listen(8080, () => console.log('Server started'));
I know its an old question but just putting it as a reference for someone else.
The easiest way that I found was to use the
https://github.com/mscdex/httpolyglot module. Seems to do what it says quite reliably
var httpolyglot = require('httpolyglot');
var server = httpolyglot.createServer(options,function(req,res) {
if (!req.socket.encrypted) {
// Redirect to https
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
} else {
// The express app or any other compatible app
app.apply(app,arguments);
}
});
// Some port
server.listen(11000);
If it's pure Node.JS HTTP module then you can try this:
if (!request.connection.encrypted) { // Check if the request is not HTTPS
response.writeHead(301, { // May be 302
Location: 'https://' + YourHostName + ':3001' + request.url
/* Here you can add some more headers */
});
response.end(); // End the response
} else {
// Behavior for HTTPS requests
}

Nodejs SSL for SockJS + Express

I've got a simple SockJS and Express server in nodejs. Now id like to add SSL support for these servers.
Here is my server code:
var sockjs = require('sockjs');
var my_http = require("http");
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('test/keys/key.pem'),
cert: fs.readFileSync('test/keys/cert.pem')
};
// Create a service (the app object is just a callback).
var app = express();
// Create an HTTP service.
http.createServer(app).listen(8008);
// Create an HTTPS service identical to the HTTP service.
https.createServer(options, app).listen(443);
var echo = sockjs.createServer({
log: function (severity, message) {}
});
echo.on('connection', function (conn) {
conn.on('data', function (message) {
conn.write(message);
});
conn.on('close', function () {
});
});
var server = my_http.createServer();
echo.installHandlers(server, {
prefix: '/echo'
});
server.listen(8081, '0.0.0.0');
var server_https = my_http.createServer(options);
echo.installHandlers(server_https, {
prefix: '/echo'
});
server_https.listen(443, '0.0.0.0');
app.get('/type/:channel', function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.send("Hello");
res.end();
});
Problem is that i get the port already in use error:
Error: listen EADDRINUSE
I've got Nginx listening on 443 otherwise my site would not work on ssl.
Any ideas how to set this up?
Inside of your nginx config you should have your port listed in 'upstream'. In your case you probably have the same port listed under server. It shows that error when you do that. See below for proper configuration (If you change "listen 80" to "listen 8000" you'll see that error):
upstream app_yourAppName {
server 127.0.0.1:8000;
}
# the nginx server instance
server {
listen 80;
...
...
}

creating a forward https proxy using http-node-proxy

I am trying to create a forward proxy capable of handling HTTPS websites as well. I am trying to observe and modify traffic for different sites. This is my code which works for http sites but not for https sites.
httpProxy.createServer(function(req, res, next) {
//custom logic
next();
}, function(req, res) {
var proxy = new httpProxy.RoutingProxy();
var buffer = httpProxy.buffer(req);
var urlObj = url.parse(req.url);
req.headers.host = urlObj.host;
req.url = urlObj.path;
console.log(urlObj.protocol);
setTimeout(function() {
proxy.proxyRequest(req, res, {
host: urlObj.host,
port: 80,
buffer: buffer,
}
)}, 5000);
}).listen(9000, function() {
console.log("Waiting for requests...");
});
Thanks for your help guys!
There are https options which must be specified when handling the https traffic. Here is what I am doing in my proxy setup.
var fs = require('fs'),
httpProxy = require('http-proxy');
var proxyTable = {};
proxyTable['domain.com'] = 'localhost:3001';
proxyTable['domain.com/path'] = 'localhost:3002';
proxyTable['sub.domain.com'] = 'localhost:3003';
var httpOptions = {
router: proxyTable
};
var httpsOptions = {
router: proxyTable,
https: {
passphrase: 'xxxxxxx',
key: fs.readFileSync('/path/to/key'),
ca: fs.readFileSync('/path/to/ca'),
cert: fs.readFileSync('/path/to/crt')}
};
httpProxy.createServer(httpOptions).listen(80);
httpProxy.createServer(httpsOptions).listen(443);
The documentation for https is on their github page as well.
https://github.com/nodejitsu/node-http-proxy
If you're just doing a forward proxy there's a few things you'll have to take into account.
A regular request is NOT triggered on a proxy for a HTTPS request - instead you'll see a HTTP CONNECT.
Here's the sequence flow you'll need to handle.
CONNECT event is sent from the browser to the proxy specified in the HTTPS section. You'll catch this here: http://nodejs.org/api/http.html#http_event_connect Note that this comes over the HTTP module, not the HTTPS connection.
You create a new socket connection to the requested domain (or your mapped domain). [srvSocket]
You'll respond back to the CONNECT socket with a 200
You'll write the buffer you received with the CONNECT event to srvSocket, then pipe the two sockets together srvSocket.pipe(socket);
socket.pipe(srvSocket);
Since you're trying to spoof the requested domain locally you'll need a few more things in place
You'll need to generate a root CA.
You will need to import this cert as a trusted authority to your OS
You'll use this cert to create a new key/cert file for the domains you're trying to access
Your mapped hosts will need to respond with the appropriate key/cert file generated in step 3 for EACH domain you are mapping.
https://github.com/substack/bouncy
var bouncy = require('bouncy');
var server = bouncy(function (req, res, bounce) {
if (req.headers.host === 'beep.example.com') {
bounce(8001);
}
else if (req.headers.host === 'boop.example.com') {
bounce(8002);
}
else {
res.statusCode = 404;
res.end('no such host');
}
});
server.listen(8000);
If you specify opts.key and opts.cert, the connection will be set to secure mode using tls. Do this if you want to make an https router.
We can have a middleware as below
request = require("request");
app.use(function (req, res, next) {
request('http://anotherurl.that.serves/the/request').pipe(res);
});
See example https://github.com/manuks/proxy
Basically, underneath the http-proxy npm is some networking libraries Node uses (specifically http://nodejs.org/api/https.html and TLS). Even though my Apache was able to connect me just fine on a self-signed certificate w/o the proxy by accessing it in my browser:
https://localhost:8002
You need to establish a certificate authority to get past the "unable to verify leaf signature" error in Node (I used the SSLCACertificateFile option). Then, you'll get hit with "self_signed_cert_in_chain". This led to some Google results indicating npm abandoned self-signed certificates, but I'm pretty sure this does not regard Node.
What you end up with are some people indicating you use process.env.NODE_TLS_REJECT_UNAUTHORIZED or rejectUnauthorized within your https agent. If you dig through the http-proxy souce, you'll find it accepts an agent option. Use this:
/**
* Module dependencies
*/
// basic includes
express = require('express');
fs = require('fs');
http = require('http');
https = require('https');
httpProxy = require('http-proxy');
require('child_process').spawn(__dirname+'/../../../dependencies/apache/bin/httpd.exe',['-f',__dirname+'/../../../dependencies/apache/conf/httpd-payments.conf']);
var app = module.exports = express();
app.set('port', process.env.PORT || 8001); // we sometimes change the port
// creates an output object for this particular request
//app.use(express.cookieParser(''));
//app.use(express.bodyParser());
//app.use(express.methodOverride());
proxy = httpProxy.createProxyServer();
proxy.on('error', function (err, req, res) {
console.log(err);
res.send(500,err);
res.end();
});
app.all('*',function(req,res,next) {
var options = {
hostname: '127.0.0.1',
port: 8002,
rejectUnauthorized: false,
key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
};
agent = new https.Agent(options);
try {
proxy.web(req,res, {
target: "https://localhost:8002",
proxyTimeout: 30,
agent: agent
});
} catch(e) {
// 500 error
res.send(500,e);
}
})
/**
* Start Server
*/
var options = {
key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
};
server = https.createServer(options,app).listen(app.get('port'), function () {
console.log('Running payments server on port ' + app.get('port'));
});

Resources