NodeJS SSH authentication host and command execution - node.js

I want to have a NodeJS application to which I can connect over SSH with a public key and send some data to. To be more explicit it should go as follow:
NodeJS application has some functions written
From a server I ssh the nodejs application and tries to identify me by my public key
After I am authenticated, I can send some strings to it, the app is going to parse the string and execute different functions
The only problem is that I cannot manage to do this with any SSH npm package existing. I want the nodejs app to just accept SSH connection and do the authentication and wait for some strings. Is this possible?
EDIT: I want to go with this approach because I only want to call the node functions to execute something only from some allowed clients (servers) and I don't want to send those requests via HTTP so anyone could access it

You're probably better off using HTTPS with client certificates rather than using an SSH server within node (although you can do that with the ssh module, a binding to libssh2), if you want to use certificates.
Here's how you'd set up the HTTPS server:
var https = require('https'),
fs = require('fs');
var options = {
key: fs.readFileSync('server.key'), // server private key
cert: fs.readFileSync('server.crt'), // server certificate
ca: fs.readFileSync('server_ca.crt'), // server CA, this can be an array of CAs too
requestCert: true
};
https.createServer(options, function(req, res) {
if (req.client.authorized) {
res.writeHead(200);
res.end('Hello world!');
} else {
res.writeHead(401);
res.end();
}
}).listen(443);
Then it's just a matter of generating a client certificate using the server's CA that you use with your HTTPS client.
For connecting to the HTTPS server:
For cURL, the command line would look something like: curl -v -s --cacert server_ca.crt --key client.key --cert client.crt https://localhost or to skip server verification: curl -v -s -k --key client.key --cert client.crt https://localhost
For node.js, you might use client code like:
var https = require('https'),
fs = require('fs');
var options = {
// normal http.request()-specific options
method: 'GET',
path: '/',
// tls.connect()-specific options
key: fs.readFileSync('client.key'), // client private key
cert: fs.readFileSync('client.crt'), // client certificate
ca: fs.readFileSync('server_ca.crt'), // server CA, this can be an array of CAs too
// or comment out the `ca` setting and use the following to skip server verification,
// similar to cURL's `-k` option:
//rejectUnauthorized: false
};
https.request(options, function(res) {
if (res.statusCode === 200)
console.log('Accepted!');
else
console.log('Rejected!');
// drain and discard any response data
res.resume();
}).end();

Related

Adding certificates to HTTP request in NestJS [duplicate]

I'm trying to make a request with axios to an api endpoint and I'm getting the following error: Error: unable to verify the first certificate
It seems the https module, which axios uses, is unable to verify the SSL certificate used on the server.
When visiting the server with my browser, the certificate is valid and I can see/download it. I can also make requests to the api on my browser through https.
I can work around it by turning off verification. This code works.
const result = await axios.post(
`https://${url}/login`,
body,
{
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
}
)
Problem is, this doesn't verify the SSL certificate and therefore opens up security holes.
How can I configure axios to trust the certificate and correctly verify it?
Old question but chiming in for those who land here. No expert. Please consult with your local security gurus and what not.
Axios is an http(s) client and http clients usually participate in TLS anonymously. In other words, the server accepts their connection without identifying who is trying to connect. This is different then say, Mutual TLS where both the server and client verify each other before completing the handshake.
The internet is a scary place and we want to protect our clients from connecting to spoofed public endpoints. We do this by ensuring our clients identify the server before sending any private data.
// DO NOT DO THIS IF SHARING PRIVATE DATA WITH SERVICE
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
This is often posted (and more egregiously upvoted) as the answer on StackOverflow regarding https client connection failures in any language. And what's worse is that it usually works, unblocks the dev and they move on their merry way. However, while they certainly get in the door, whose door is it? Since they opted out of verifying the server's identity, their poor client has no way of knowing if the connection they just made to the company's intranet has bad actors listening on the line.
If the service has a public SSL cert, the https.Agent usually does not need to be configured further because your operating system provides a common set of publicly trusted CA certs. This is usually the same set of CA certs your browser is configured to use and is why a default axios client can hit https://google.com with little fuss.
If the service has a private SSL cert (self signed for testing purposes or one signed by your company's private CA to protect their internal secrets), the https agent must be configured to trust the private CA used to sign the server cert:
const httpsAgent = new https.Agent({ ca: MY_CA_BUNDLE });
where MY_CA_BUNDLE is an array of CA certs with both the server cert for the endpoint you want to hit and that cert's complete cert chain in .pem format. You must include all certs in the chain up to the trust root.
Where are these options documented?
HTTPS is the HTTP protocol over TLS/SSL. In Node.js this is implemented as a separate module.
Therefore options passed to the https.Agent are a merge of the options passed to tls.connect() and tls.createSecureContext().
Create a custom agent with SSL certificate:
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // (NOTE: this will disable client verification)
cert: fs.readFileSync("./usercert.pem"),
key: fs.readFileSync("./key.pem"),
passphrase: "YYY"
})
axios.get(url, { httpsAgent })
// or
const instance = axios.create({ httpsAgent })
From https://github.com/axios/axios/issues/284
For me, when my application is running in development mode, I have disabled rejectUnauthorized directly in axios.defaults.options. This works very well. be careful and use this only in developer mode.
import https from 'https'
import axios from 'axios'
import config from '~/config'
/**
* Axios default settings
*/
axios.defaults.baseURL = config.apiURL
/**
* Disable only in development mode
*/
if (process.env.NODE_ENV === 'development') {
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
axios.defaults.httpsAgent = httpsAgent
// eslint-disable-next-line no-console
console.log(process.env.NODE_ENV, `RejectUnauthorized is disabled.`)
}
These configuration worked for me (In a Mutual Authentication scenario).
const httpsAgent = new https.Agent({
ca: fs.readFileSync("./resource/bundle.crt"),
cert: fs.readFileSync("./resrouce/thirdparty.crt"),
key: fs.readFileSync("./resource/key.pem"),
})
Note: bundle.crt was prepared from provided certificates (root,intermediate,end entry certificate). Unfortunately no clear documentation found in this regards.
This is very dirty, but at the top of your script, just put:
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
This basically tells node to not check SSL certificates, which is very convenient when you get self signed certificates rejected in development.
Please don't use this in production.
This what worked for me , using axios with nodejs + express
exports.test_ssl = async (req,res) => {
let cert_file = fs.readFileSync("./ssl/my_self_signed_certificate.crt")
let ca_file = fs.readFileSync("./ssl/my_self_signed_certificate_ca.crt")
const agent = new https.Agent({
requestCert: true,
rejectUnauthorized: true,
cert: cert_file,
ca: ca_file
});
const options = {
url: `https://51.195.45.154/test`, // <---this is a fake ip do not bother
method: "POST",
httpsAgent : agent,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/txt;charset=UTF-8'
},
data: {}
};
console.log(cert_file.toString())
axios(options)
.then(response => {
payload = response.data ;
return res.status(200).send({"status":1});
}).catch(err => {
console.log(err);
return false
});
}
This worked for me:
import axios from 'axios'
import https from 'https'
const headers = {};
const httpsAgent = new https.Agent({
ca: fs.readFileSync('./certs/cert.pem'),
cert: fs.readFileSync('./certs/cert.pem'),
})
const data = await axios.get(url, { httpsAgent, headers })
const https = require('https');
const axios = require('axios')
const CA = "-----BEGIN CERTIFICATE-----$$$$$-----END CERTIFICATE-----"
const url = "bla"
const httpsAgent = new https.Agent({
ca: CA
});
const response = await axios.get(url, { httpsAgent });
This is what work for me.
Good morning dear.
My problem is the following:
"Enable to verify the first certificate" with an error code 'ENABLE_TO_VERIFY_LEAF_SIGNATURE'.
They sent me a certificate with a .pfx extension and with the following commands I generated the .pem certificate and the key also with a .pem extension.
I attach the commands.
openssl pkcs12 -in certificate.pfx -nocerts -out key.pem -nodes
openssl pkcs12 -in certificate.pfx -nokeys -out certificate.pem
It should be noted that I am using axios to make the request.
I attach my agent configuration in axios.
const httpsAgent = new https.Agent ({
pfx: fs.readFileSync ("path.pfx"),
passphrase: 'password',
requestCert: true,
rejectUnauthorized: true
});

Launch a nodejs server on AWS EC2 with SSL certificate

I tried to configure server with https. On local server, the local private key and certificate works. The code is:
var https = require('https');
const options = {
key: fs.readFileSync('./privatekey.key'),
cert: fs.readFileSync('./certificate.crt')
};
var server = https.createServer(options, app);
But when I purchased a SSL certificate and try to use it on the AWS server, it didn't work at all.
var https = require('https');
const options = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('./ssl/portal.crt'),
ca: [fs.readFileSync('./ssl/portal.ca-bundle')]
};
It always showed webpage is not available. I also tried to upload the server certificate to AWS configuration, still failed. Does anyone know how to fix this?

Nodejs TLS with self-signed Certificate Authority

Background:
I'm trying to communicate between a server and one (should be able to be multiple - hence the need of a CA) client through TLS.
Each node has a certificate that is signed with a common CA.
The CA is in turn self-signed.
The private key of each node is exported as key.pem.
The certificate of each node is exported as certificate.crt.
The CA certificate is exported as ca.crt.
The certificates are not bundled, just exported as is.
The server uses the following setup:
var tls = require("tls");
var fs = require("fs");
var options = {
key: fs.readFileSync("keys/key.pem", "utf8"),
cert: fs.readFileSync("keys/certificate.crt", "utf8"),
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('keys/ca.crt')]
}
var server = tls.createServer(options, function(res) {
console.log("Client connected");
console.log('Client connected',
res.authorized ? 'authorized' : 'unauthorized');
res.write("Hello World!");
res.setEncoding("utf8");
res.pipe(res);
}).listen(3000);
The client uses the following setup:
var tls = require("tls");
var fs = require("fs");
var options = {
key: fs.readFileSync("keys/key.pem", "utf8"),
cert: fs.readFileSync("keys/certificate.crt", "utf8"),
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('keys/ca.crt')]
}
var client = tls.connect(3000, options, function(){
console.log("Connected to server");
console.log(client.authorized ? "Authorized" : "Not authorized");
});
client.on("data", function(data){
console.log("Received from server", data);
client.end();
});
Note on keys / certificates:
The keys and certificates are generated with the openssl GUI / manager XCA.
The tree looks as follows:
The problem:
As you can see I am using explicit client certificate authentication and I want to disallow any non-permitted connections.
The problem with this is that the client is not able to connect, even though all the certificates come from the same CA.
The error I get from both the server (when a client connects) and the client(when it connects) is:
Error: socket hang up, code: ECONNRESET
If I disable rejectUnauthorized the client can connect, but res.authorized returns false.
What is causing authorised clients to not being able to be authenticated?
Your code is fine. I expect there is a problem with your certificates. The fact that there is no expiry date sticks out to me. I have found this OpenSSL Certificate Authority by Jamie Nguyen to be very useful.
Remember that Nodejs does not support multiple certificates in one cert file, so if you are following the guide, there is no need to copy the root ca and intermediate ca into one file. You will have to add them as separate file entries in the ca list argument.
Afaik the xca tool is build on openssl, so might be able to map the commands in openssl to xca.

Enabling HTTPS on express.js

I'm trying to get HTTPS working on express.js for node, and I can't figure it out.
This is my app.js code.
var express = require('express');
var fs = require('fs');
var privateKey = fs.readFileSync('sslcert/server.key');
var certificate = fs.readFileSync('sslcert/server.crt');
var credentials = {key: privateKey, cert: certificate};
var app = express.createServer(credentials);
app.get('/', function(req,res) {
res.send('hello');
});
app.listen(8000);
When I run it, it seems to only respond to HTTP requests.
I wrote simple vanilla node.js based HTTPS app:
var fs = require("fs"),
http = require("https");
var privateKey = fs.readFileSync('sslcert/server.key').toString();
var certificate = fs.readFileSync('sslcert/server.crt').toString();
var credentials = {key: privateKey, cert: certificate};
var server = http.createServer(credentials,function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(8000);
And when I run this app, it does respond to HTTPS requests. Note that I don't think the toString() on the fs result matters, as I've used combinations of both and still no es bueno.
EDIT TO ADD:
For production systems, you're probably better off using Nginx or HAProxy to proxy requests to your nodejs app. You can set up nginx to handle the ssl requests and just speak http to your node app.js.
EDIT TO ADD (4/6/2015)
For systems on using AWS, you are better off using EC2 Elastic Load Balancers to handle SSL Termination, and allow regular HTTP traffic to your EC2 web servers. For further security, setup your security group such that only the ELB is allowed to send HTTP traffic to the EC2 instances, which will prevent external unencrypted HTTP traffic from hitting your machines.
In express.js (since version 3) you should use that syntax:
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();
// your express configuration here
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
httpServer.listen(8080);
httpsServer.listen(8443);
In that way you provide express middleware to the native http/https server
If you want your app running on ports below 1024, you will need to use sudo command (not recommended) or use a reverse proxy (e.g. nginx, haproxy).
First, you need to create selfsigned.key and selfsigned.crt files.
Go to Create a Self-Signed SSL Certificate Or do following steps.
Go to the terminal and run the following command.
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./selfsigned.key -out selfsigned.crt
After that put the following information
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]: NY
Locality Name (eg, city) []:NY
Organization Name (eg, company) [Internet Widgits Pty Ltd]: xyz (Your - Organization)
Organizational Unit Name (eg, section) []: xyz (Your Unit Name)
Common Name (e.g. server FQDN or YOUR name) []: www.xyz.com (Your URL)
Email Address []: Your email
After creation adds key & cert file in your code, and pass the options to the server.
const express = require('express');
const https = require('https');
const fs = require('fs');
const port = 3000;
var key = fs.readFileSync(__dirname + '/../certs/selfsigned.key');
var cert = fs.readFileSync(__dirname + '/../certs/selfsigned.crt');
var options = {
key: key,
cert: cert
};
app = express()
app.get('/', (req, res) => {
res.send('Now using https..');
});
var server = https.createServer(options, app);
server.listen(port, () => {
console.log("server starting on port : " + port)
});
Finally run your application using https.
More information https://github.com/sagardere/set-up-SSL-in-nodejs
I ran into a similar issue with getting SSL to work on a port other than port 443. In my case I had a bundle certificate as well as a certificate and a key. The bundle certificate is a file that holds multiple certificates, node requires that you break those certificates into separate elements of an array.
var express = require('express');
var https = require('https');
var fs = require('fs');
var options = {
ca: [fs.readFileSync(PATH_TO_BUNDLE_CERT_1), fs.readFileSync(PATH_TO_BUNDLE_CERT_2)],
cert: fs.readFileSync(PATH_TO_CERT),
key: fs.readFileSync(PATH_TO_KEY)
};
app = express()
app.get('/', function(req,res) {
res.send('hello');
});
var server = https.createServer(options, app);
server.listen(8001, function(){
console.log("server running at https://IP_ADDRESS:8001/")
});
In app.js you need to specify https and create the server accordingly. Also, make sure that the port you're trying to use is actually allowing inbound traffic.
Including Points:
SSL setup
In config/local.js
In config/env/production.js
HTTP and WS handling
The app must run on HTTP in development so we can easily debug our
app.
The app must run on HTTPS in production for security concern.
App production HTTP request should always redirect to https.
SSL configuration
In Sailsjs there are two ways to configure all the stuff, first is to configure in config folder with each one has their separate files (like database connection regarding settings lies within connections.js ). And second is configure on environment base file structure, each environment files presents in config/env folder and each file contains settings for particular env.
Sails first looks in config/env folder and then look forward to config/ *.js
Now lets setup ssl in config/local.js.
var local = {
port: process.env.PORT || 1337,
environment: process.env.NODE_ENV || 'development'
};
if (process.env.NODE_ENV == 'production') {
local.ssl = {
secureProtocol: 'SSLv23_method',
secureOptions: require('constants').SSL_OP_NO_SSLv3,
ca: require('fs').readFileSync(__dirname + '/path/to/ca.crt','ascii'),
key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key','ascii'),
cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt','ascii')
};
local.port = 443; // This port should be different than your default port
}
module.exports = local;
Alternative you can add this in config/env/production.js too. (This snippet also show how to handle multiple CARoot certi)
Or in production.js
module.exports = {
port: 443,
ssl: {
secureProtocol: 'SSLv23_method',
secureOptions: require('constants').SSL_OP_NO_SSLv3,
ca: [
require('fs').readFileSync(__dirname + '/path/to/AddTrustExternalCARoot.crt', 'ascii'),
require('fs').readFileSync(__dirname + '/path/to/COMODORSAAddTrustCA.crt', 'ascii'),
require('fs').readFileSync(__dirname + '/path/to/COMODORSADomainValidationSecureServerCA.crt', 'ascii')
],
key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key', 'ascii'),
cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt', 'ascii')
}
};
http/https & ws/wss redirection
Here ws is Web Socket and wss represent Secure Web Socket, as we set up ssl then now http and ws both requests become secure and transform to https and wss respectively.
There are many source from our app will receive request like any blog post, social media post but our server runs only on https so when any request come from http it gives “This site can’t be reached” error in client browser. And we loss our website traffic. So we must redirect http request to https, same rules allow for websocket otherwise socket will fails.
So we need to run same server on port 80 (http), and divert all request to port 443(https). Sails first compile config/bootstrap.js file before lifting server. Here we can start our express server on port 80.
In config/bootstrap.js (Create http server and redirect all request to https)
module.exports.bootstrap = function(cb) {
var express = require("express"),
app = express();
app.get('*', function(req, res) {
if (req.isSocket)
return res.redirect('wss://' + req.headers.host + req.url)
return res.redirect('https://' + req.headers.host + req.url)
}).listen(80);
cb();
};
Now you can visit http://www.yourdomain.com, it will redirect to https://www.yourdomain.com
This is how its working for me. The redirection used will redirect all the normal http as well.
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
var request = require('request');
//For https
const https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('certificates/private.key'),
cert: fs.readFileSync('certificates/certificate.crt'),
ca: fs.readFileSync('certificates/ca_bundle.crt')
};
// API file for interacting with MongoDB
const api = require('./server/routes/api');
// Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));
// API location
app.use('/api', api);
// Send all other requests to the Angular app
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
app.use(function(req,resp,next){
if (req.headers['x-forwarded-proto'] == 'http') {
return resp.redirect(301, 'https://' + req.headers.host + '/');
} else {
return next();
}
});
http.createServer(app).listen(80)
https.createServer(options, app).listen(443);
This answer is very similar to Setthase but its for LetsEncrypt (Ubuntu)
// Dependencies
const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');
const app = express();
// Certificate
const privateKey = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/chain.pem', 'utf8');
const credentials = {
key: privateKey,
cert: certificate,
ca: ca
};
app.use((req, res) => {
res.send('Hello there !');
});
// Starting both http & https servers
const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);
httpServer.listen(80, () => {
console.log('HTTP Server running on port 80');
});
httpsServer.listen(443, () => {
console.log('HTTPS Server running on port 443');
});
You might encounter : EACCES: permission denied, open '/etc/letsencrypt/live/yourdeomain.com/privkey.pem'
The answer to that is here : Let's encrypt SSL couldn't start by "Error: EACCES: permission denied, open '/etc/letsencrypt/live/domain.net/privkey.pem'"
What worked for me is this in ubuntu ssh terminal
Get user whoami
// Create group with root and nodeuser as members
$ sudo addgroup nodecert
$ sudo adduser ubuntu nodecert
$ sudo adduser root nodecert
// Make the relevant letsencrypt folders owned by said group.
$ sudo chgrp -R nodecert /etc/letsencrypt/live
$ sudo chgrp -R nodecert /etc/letsencrypt/archive
// Allow group to open relevant folders
$ sudo chmod -R 750 /etc/letsencrypt/live
$ sudo chmod -R 750 /etc/letsencrypt/archive
sudo reboot
Use greenlock-express: Free SSL, Automated HTTPS
Greenlock handles certificate issuance and renewal (via Let's Encrypt) and http => https redirection, out-of-the box.
express-app.js:
var express = require('express');
var app = express();
app.use('/', function (req, res) {
res.send({ msg: "Hello, Encrypted World!" })
});
// DO NOT DO app.listen()
// Instead export your app:
module.exports = app;
server.js:
require('greenlock-express').create({
// Let's Encrypt v2 is ACME draft 11
version: 'draft-11'
, server: 'https://acme-v02.api.letsencrypt.org/directory'
// You MUST change these to valid email and domains
, email: 'john.doe#example.com'
, approveDomains: [ 'example.com', 'www.example.com' ]
, agreeTos: true
, configDir: "/path/to/project/acme/"
, app: require('./express-app.j')
, communityMember: true // Get notified of important updates
, telemetry: true // Contribute telemetry data to the project
}).listen(80, 443);
Screencast
Watch the QuickStart demonstration: https://youtu.be/e8vaR4CEZ5s
For Localhost
Just answering this ahead-of-time because it's a common follow-up question:
You can't have SSL certificates on localhost. However, you can use something like Telebit which will allow you to run local apps as real ones.
You can also use private domains with Greenlock via DNS-01 challenges, which is mentioned in the README along with various plugins which support it.
Non-standard Ports (i.e. no 80 / 443)
Read the note above about localhost - you can't use non-standard ports with Let's Encrypt either.
However, you can expose your internal non-standard ports as external standard ports via port-forward, sni-route, or use something like Telebit that does SNI-routing and port-forwarding / relaying for you.
You can also use DNS-01 challenges in which case you won't need to expose ports at all and you can also secure domains on private networks this way.
You can also generate a self-signed certificate using node-forge
In the code below, a new certificate is generated on startup, which means you will get a new certificate every time you restart the server.
const https = require('https')
const express = require('express')
const forge = require('node-forge')
;(function main() {
const server = https.createServer(
generateX509Certificate([
{ type: 6, value: 'http://localhost' },
{ type: 7, ip: '127.0.0.1' }
]),
makeExpressApp()
)
server.listen(8443, () => {
console.log('Listening on https://localhost:8443/')
})
})()
function generateX509Certificate(altNames) {
const issuer = [
{ name: 'commonName', value: 'example.com' },
{ name: 'organizationName', value: 'E Corp' },
{ name: 'organizationalUnitName', value: 'Washington Township Plant' }
]
const certificateExtensions = [
{ 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: 'subjectAltName', altNames },
{ name: 'subjectKeyIdentifier' }
]
const keys = forge.pki.rsa.generateKeyPair(2048)
const cert = forge.pki.createCertificate()
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
cert.publicKey = keys.publicKey
cert.setSubject(issuer)
cert.setIssuer(issuer)
cert.setExtensions(certificateExtensions)
cert.sign(keys.privateKey)
return {
key: forge.pki.privateKeyToPem(keys.privateKey),
cert: forge.pki.certificateToPem(cert)
}
}
function makeExpressApp() {
const app = express()
app.get('/', (req, res) => {
res.json({ message: 'Hello, friend' })
})
return app
}
Don't forget your PEM pass phrase in the credentials !
When you generate your credentials with OpenSSL (don't forget the -sha256 flag) :
OpenSSL> req -x509 -sha256 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
Generating a RSA private key
writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Your JS/TS code :
const credentials = {key: privateKey, cert: certificate, passphrase: 'YOUR passphrase'};
Reference
This is my working code for express 4.0.
express 4.0 is very different from 3.0 and others.
4.0 you have /bin/www file, which you are going to add https here.
"npm start" is standard way you start express 4.0 server.
readFileSync() function should use __dirname get current directory
while require() use ./ refer to current directory.
First you put private.key and public.cert file under /bin folder,
It is same folder as WWW file.

Node.js: Require a client certificate for a directory (and allow a retry)

I want to create a reverse proxy in node js, where I can require client certificates for certain parts of the site.
The /secure section should require a client cert.
The /nosecure section does not require a client cert.
The user should be able to retry the client cert validation if he didn't provide a valid one before.
var options = {
key: fs.readFileSync(__dirname + '/key.pem'),
cert: fs.readFileSync(__dirname + '/cert.pem'),
ca: fs.readFileSync(__dirname + '/clientCA.pem'),
requestCert: true
};
https.createServer(options, function (req, res) {
//parse url
req.parsedUrl = url.parse(req.url);
//handle urls
switch(req.parsedUrl.pathname) {
case '/nosecure':
/*
* This location does not require a client cert
*/
res.end('nosecure');
break;
case '/secure':
/*
* This location requires a client certificate, which can be checked by getPeerCertificate()
* If no certificate is provided, the user will be redirected to secureError
*/
res.end('secure');
break;
case '/secureError':
/*
* This location does not require a client cert
* It displays the error page, in case the client cert was not provided/validated on /secure.
*/
res.end('Certificate validation failed. Try again');
break;
default:
res.end('not found');
break;
}
}).listen(9000);
Normally you can only set these requirements on server level.
But I could manually check the cert in the secure section.
The problem however remains. I can not ask the client browser to send me the cert again.
This only happens once, when the SSL session is started.
Any ideas on how to fix this?
I'm a noob myself but I'm playing around with the same thing.
Maybe something like
if (req.parsedUrl.pathname == '/secure' && !req.connection.getPeerCertificate().valid_to) req.parsedUrl.pathname = '/secureError';
I think you can simply allow unauthorized connections, and then logically check for authorization in order to allow access to some parts of your application.
Minimal Reproduceable Example
const fs = require('fs')
const https = require('https')
const opts = {
key: fs.readFileSync('some_key.pem'),
cert: fs.readFileSync('some_cert.pem'),
ca: [ fs.readFileSync('potentially_different_cert.pem') ],
requestCert: true,
rejectUnauthorized: false, // allow insecure connections
}
https.createServer(opts, (req, res) => {
const cert = req.connection.getPeerCertificate()
if (req.client.authorized) {
//secure
} else {
//insecure
}
}).listen(9000)

Resources