Difference between https://<ip> and <ip>:443 - node.js

I'm currently trying to understand the difference between 2 similar curl commands.
I spun up an AWS EC2 Ubuntu instance, and installed node.js on the instance. I then created a new directory called test-server. From within this directory, I ran npm init, then npm install express. I then wrote a simple web listener called test-server.js, which looks like this:
const express = require('express')
const app = express()
app.get('/', function(req, res){
console.log('Got a request!')
res.send('Request received!\n')
})
app.listen(443, function(){
console.log('Server started')
})
Finally, I started the server with the command sudo node test-server.js, and the server started successfully.
Using aws ec2 authorize-security-group-ingress, I allowed communication from my local IP address to the instance over port 443. I then attempted to test the connection using the following 2 curl commands: curl <ip>:443 and curl https://<ip>. The first command gave the expected output of Request received!. However, I received this error from the second command: curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to <ip>:443.
Now, I'm FAR from a networking expert, but I would have expected both of these commands to function in the same way. Based on this result, I have 2 questions:
What is the difference between these 2 commands?
How can I change the configuration such that the second command works as well as the first?

Your application is serving regular HTTP on 443 port. In order to use https protocol you need to use encryption keys
var fs = require('fs');
var http = require('http');
var https = require('https');
var privateKey = fs.readFileSync('sslcert/server.key', 'utf8');
var certificate = fs.readFileSync('sslcert/server.crt', 'utf8');
var credentials = {key: privateKey, cert: certificate};
var express = require('express');
var app = express();
app.get('/', function(req, res){
console.log('Got a request!')
res.send('Request received!\n')
})
var httpsServer = https.createServer(credentials, app);
httpsServer.listen(443);
Command curl <ip>:443 is opening http connection to port 443 on givenip,curl https://<ip> is opening https connection to given ip.
If you want your traffic to be encrypted, stick to https:// version (and expanded code for server).

What is the difference between these 2 commands?
:443 is simply the port configuration, no different than :80. It just tells the server to serve content on that port. 443 is traditionally the port reserved for HTTPS connections, similar to how 80 is traditionally standard HTTP.
How can I change the configuration such that the second command works as well as the first?
You'll need to install a TLS cert. HTTPS indicates that a certificate is being used to secure the connection, and the absence of that certificate will cause the request to fail.

Related

Socket.io client not connecting

I have a Socket.io server running on port 3000 and when running it (and the website / client) locally everything works fine. But when I push it to the server the client can't connect anymore.
The production server is running over SSL so I assumed that I need the Socket.io server to run over SSL as well. I've setup it up like this:
var app = express();
var fs = require('fs');
var is_production = process.env.NODE_ENV === 'production';
if(is_production){
var options = {
key: fs.readFileSync('/etc/letsencrypt/live/mywebsite.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/mywebsite.com/cert.pem'),
requestCert: true
};
var server = require('https').createServer(options, app);
}else{
var server = require('http').createServer(app);
}
var io = require('socket.io')(server);
server.listen(3000);
This still doesn't work. I don't have much experience with Socket.io so any help would be appreciated. Also note that everything worked fine before I got an SSL certificate setup on the web server.
The client is connecting to ws://mywebsite.com:3000. I've tried using http://, https:// and wss:// as well, but nothing works.
EDIT: I've tried making a request through curl and I get the following error:
curl: (35) gnutls_handshake() failed: The TLS connection was non-properly terminated.
I couldn't figure out what the problem was, so here's what I did.
I have Nginx running on the same server to serve my website so what I ended up doing was configuring Nginx to proxy all SSL connections to port 3000 and forward them to the node.js server running on port 8080. This way Nginx takes care of the SSL so the node.js server doesn't need any additional configuration.

Node.js returning empty response for HTTPS

I have the following extremely basic Node.js server:
"use strict";
const http = require("http");
const https = require("https");
const fs = require("fs");
http.createServer((req, res) => {
console.log("regular works");
res.end("Regular response");
}).listen(3000);
https.createServer({
key: fs.readFileSync("/etc/letsencrypt/live/domain.com/privkey.pem"),
cert: fs.readFileSync("/etc/letsencrypt/live/domain.com/cert.pem")
}, (req, res) => {
console.log("secure works");
res.end("Secure response");
}).listen(3001);
I run this as sudo node filename.js, only because files in /etc/letsencrypt/live are root-only. I will do this properly later, this is only for testing.
When run, I can hit port 3000 just fine. The server console prints regular works, and the browser displays Regular response. However, port 3001 returns an empty response, and no message is printed to the server.
The LetsEncrypt files were generated with ./letsencrypt-auto certonly --standalone -d domain.com --email email#gmail.com --agree-tos and appear valid.
What am I missing to have the expected result?
There are two issues here:
Assuming you're not obscuring the real hostname/IP, you should use 127.0.0.1 or similar (if you're on the same machine) instead of 255.255.255.255.
HTTP is the default for cURL, so you're currently sending a plaintext HTTP request to your HTTPS server, which is not going to work (the HTTPS server sees the literal HTTP request as an invalid TLS handshake which causes the connection to end abruptly). To remedy this, explicitly include https:// (e.g. curl -I --verbose https://127.0.0.1:3001).
need to check that the URL contains https:// not http://

Having express.js or node accept an http connection over port 443

I have a REST API built with node that communicates over SSL. The server is built uses express and makes use of vhosts and cors. I have recently added a listener on port 80 as well so I can force HTTPS. As I test, I tried to access http://manage.domain.com:443/ but the request just hangs. Neither listeners seem to accept it. All I want to do is redirect that request to https.
I assume you already know this, but you'll need an https server (duh) to serve the HTTPS content. It doesn't matter what port you run it on; 443 is just the default port for HTTPS. If you want HTTP requests to redirect to HTTPS, you'll need both an http and an https server. Here's an example of how your app file should look:
var http = require('http'),
https = require('https'),
express = require('express')
fs = require('fs');
var domain = 'localhost';
var app = express();
app.get('*', function(req, res){
// redirect to HTTPS
res.redirect('https://' + domain + req.path);
});
http.createServer(app).listen(80, function(){
console.log('HTTP listening on port 80');
});
var appSecure = express();
// configure your app here
var options = {
key: fs.readFileSync('ssl_key.pem'),
cert: fs.readFileSync('ssl_cert.crt'),
};
https.createServer(options, appSecure).listen(443, function(){
console.log('HTTPS listening on port 443');
});
Obviously, you will need your SSL key and certificate to make this work.
As you probably know, most systems require elevated privileges to open a port less than 1025; so if you use port 80 and port 443, you'll have to run the app server with elevated privileges (if you're running on OSX/Linux/BSD, just do sudo node app.js).

Installing SSL Certificate On Node Server

I created a self-signed certificate and installed it on apache as well as on node.js(port 3000). On localhost both https://localhost and https://localhost:3000 works well.
So, I bought GoDaddy Standard SSL certificate and installed it on the server(http://gatherify.com). Now https://gatherify.com works well, but ssl on node isn't working.
When I access https://gatherify.com:3000 i get "The connection was interrupted".
I executed curl:
root#host [~]# curl -v -s -k https://gatherify.com:3000
* About to connect() to gatherify.com port 3000 (#0)
* Trying 108.160.156.123... connected
* Connected to gatherify.com (108.160.156.123) port 3000 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* warning: ignoring value of ssl.verifyhost
* NSS error -5938
* Closing connection #0
* SSL connect error
Any suggestions to fix this?
UPDATE
*SERVER SIDE :*
var io = require('socket.io'),
connect = require('connect'),
fs = require('fs'),
var privateKey = fs.readFileSync('cert/server.key').toString();
var certificate = fs.readFileSync('cert/server.crt').toString();
var options = {
key: privateKey,
cert: certificate
};
var app = connect(options).use(connect.static('../htdocs/node/'));
app.listen(3000);
var server = io.listen(app);
server.sockets.on('connection', function(socket) {
console.log("Connected");
});
CLIENT SIDE:
<html> <head>
<script type = "text/javascript" src = "https://gatherify.com:3000/socket.io/socket.io.js"></script>
<script type = "text/javascript">
var socket = io.connect('https://gatherify.com:3000', {secure:true});
</script>
</head><body></body></html>
If you want to run a node.js app on port 3000 with (behind) HTTPS, then you need to set up a proxy service on port 443 to proxy HTTPS requests to port 3000.
You didn't mention what server you have running on port 443 right now (is it Apache?) but you might want to
move that service to a new port (e.g. 4000), then run a node http proxy on port 443 that handles HTTPS.
Then set up a subdomain for the node.js app that you have running on port 3000 (e.g. blah.gatherify.com).
Then, using node http proxy, you will proxy all requests that are made to "gatherify.com" to port 4000, and all requests that are made to "blah.gatherify.com" to port 3000.
When all is set up properly, users can visit "https://gatherify.com" or "https://blah.gatherify.com" (without using :port numbers) and it'll all be secured with SSL. ;)
Install certificates Client Side (in Node.js)
If you need a node.js client to be able to recognize your self-assigned or cheaply-bought SSL certificates you can use ssl-root-cas, which is available on npm.
'use strict';
var https = require('https')
, cas
;
// This will add the well-known CAs
// to `https.globalAgent.options.ca`
require('ssl-root-cas').inject();
cas = https.globalAgent.options.ca;
cas.push(fs.readFileSync(path.join(__dirname, 'ssl', '01-cheap-ssl-intermediary-a.pem')));
cas.push(fs.readFileSync(path.join(__dirname, 'ssl', '02-cheap-ssl-intermediary-b.pem')));
cas.push(fs.readFileSync(path.join(__dirname, 'ssl', '03-cheap-ssl-site.pem')));
This will make your certs available to the core https module as well as modules that depend on it such as request and socket.io-client without deleting the normal ssl certs (which is the default behavior for some odd reason).

How can I configure expressjs to handle both http and https?

I've scoured stackoverflow and the express google group, but I'm still coming up short.
From what I gather, I can do one of two things:
1) create an instance of an http server and an https server and set the two to listen to two different ports. In the routes, redirect the http request to the https port.
//app
var app = express.createServer();
var app_secure = express.createServer({key: key, cert: cert});
app.listen(8080);
app_secure.listen(8443);
//routes
app.get("unsecure/path", function(req, res) {
...
}
app.get("secure/path", function(req, res) {
res.redirect("https://domain" + req.path);
}
app_secure.get("secure/path", function(req, res) {
res.send("secure page");
}
2) do what TJ Hollowaychuk says: https://gist.github.com/1051583
var http = require("http");
var https = require("https");
var app = express.createServer({key: key, cert: cert});
http.createServer(app.handle.bind(app)).listen(8080);
https.createServer(app.handle.bind(app)).listen(8443);
When I do 1, there are generally no problems. However, it feels clunky to manage two servers and I really feel like there should be a better way.
When I do 2, I get this:
(node SSL) error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher
Of course, I can just default to option 1, but I really, really want to know why I'm getting that "no shared cipher error" when I do option 2. And option 2 would be my preferred route.
Following #ypocat 's comment you can enable https in your express.js application like so
var http = require('http');
var https = require('https');
var express = require('express');
var fs = require('fs');
var app = express.createServer();
// cutomize your app as ususal
app.configure( function () { ... });
app.configure('production', function () { ... });
// ....
// attach express handler function to TWO servers, one for http and one for https
http.createServer(app.handle.bind(app)).listen(8080);
https.createServer({
ca: fs.readFileSync('./server.ca-bundle'),
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt')
}, app.handle.bind(app)).listen(8081);
Note that you should receive server.ca-bundle, server.key and server.crt from a certificate authority.
Also as you will probably run node without sudo you need to make sure port 80(http) and 443(https) are open
# in Ubuntu
sudo ufw status
sudo ufw allow 80
sudo ufw allow 443
and to forward requests on 8080 to 80 and from 8081 to 443 respectively
# in Ubuntu
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8081
Hope this helps
Is your certificate an RSA certificate rather than a DSA one? It sounds like the ciphers your browser supports are not supported by your nodejs server - you many need to update your OpenSSL and recompile NodeJS?

Resources