I have a node/express server running https on localhost:9002, and I want to use client certificate for a react app, running on localhost:8080 (webpack dev server). The react app is using ajax request with superagent to the https server, and I have a passport middleware to check certificate automatically.
Environment
Windows 10, Chrome Version 71.0.3578.98
Setup
Using openssl, I created a root CA. Then I generated my server certificate, and a client certificate. This is the script used (I run it with git bash, so it's UNIX style but I'm on windows):
## CREATE CERTIFICATES FOR AUTHENTICATION
#########################################
## 1. Create Root Certificate Authority #
#########################################
# Root CA private key
openssl genrsa -out ./rootCA.key 4096
# Root CA certificate to register in RootCA store on OS
openssl req -x509 -new -nodes -key ./rootCA.key -sha256 -days 3650 -out ./rootCA.pem
#################################
## 2. Create Server certificate #
#################################
# Create private key for server
openssl genrsa -out ./server.key 4096
# Create server certificate sign request (CSR) based on the private key
openssl req -new -sha256 -nodes -out ./server.csr -key ./server.key -config ./server.csr.conf
# Create server certificate linked to the previoulsy created Root CA
openssl x509 -req -in ./server.csr -CA ./rootCA.pem -CAkey ./rootCA.key -CAcreateserial -out ./server.crt -days 3650 -sha256 -extfile ./v3.ext
#################################
## 3. Create Client certificate #
#################################
# Create private key for client
openssl genrsa -out ./client.key 4096
# Create the Certificate Sign Request (CSR) file from the client private key
openssl req -new -config ./client.csr.conf -key ./client.key -out ./client.csr
# Self sign the certificate for 10 years
openssl x509 -req -days 3650 -in ./client.csr -CA ./server.crt -CAkey ./server.key -CAcreateserial -out ./client.crt
# Display the fingerprint of the newly generated fingerprint
openssl x509 -noout -fingerprint -inform pem -in ./client.crt
# Generate a PFX file for integration in browser
openssl pkcs12 -export -out ./client.pfx -inkey ./client.key -in ./client.crt -passout pass:
Here are the different configurations used:
server.csr.conf
[ req ]
default_bits = 4096
default_md = sha512
prompt = no
encrypt_key = no
distinguished_name = req_distinguished_name
# distinguished_name
[ req_distinguished_name ]
countryName = "FR"
localityName = "Lille"
organizationName = "Sopra Steria"
organizationalUnitName = "Webskillz"
commonName = "localhost"
v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = #alt_names
[alt_names]
DNS.1 = localhost
client.csr.conf
[ req ]
default_bits = 4096
default_md = sha512
default_keyfile = server.key
prompt = no
encrypt_key = no
distinguished_name = req_distinguished_name
# distinguished_name
[ req_distinguished_name ]
countryName = "FR"
localityName = "Lille"
organizationName = "Sopra Steria"
organizationalUnitName = "Webskillz"
commonName = "localhost"
Finally, I addedd rootCA.pem to the Trusted Root Certification Authorities using certmgr.msc, and I added the client.pfx and server.crt certificate to the Personnal store.
Issue 1
Chrome is annoyingly redirecting http://localhost:8080 to https://localhost:8080, and I don't want to systematically open chrome://net-internals/#hsts to delete the localhost key...
Issue 2
When I finally access to http://localhost:8080, I'm asked to choose the certificate I want to authenticate to https://localhost:9002 (yeay!), but I still get a 401, which is not caused by the passport cert-auth middleware (there is no log in my middleware).
Additional information
1. Almost working setup
I managed to make this client/server setup work without a root certificate, but the issue was that I got a NET::ERR_CERT_AUTHORITY_INVALID from Chrome... That's why I added a root CA, following some advice on the World Wide Web... And indeed it corrected the problem, but then I was not able to authenticate, and Chrome began to redirect automatically http to https ಠ෴ಠ
Oh by the way, CORS is allowed server side so no problems from CORS.
2. Server code
Passport auth strategy: we just check for the fingerprint in the database.
cert-auth.js
import { Strategy } from 'passport-client-cert';
export default new Strategy(async (clientCert, done) => {
console.log(clientCert); // NO LOG HERE!!
if (clientCert.fingerprint) {
try {
const user = await findByFingerprintInMyAwesomeDb({ fingerprint: clientCert.fingerprint });
return done(null, user);
} catch (err) {
return done(new Error(err));
}
}
return done(null, false);
});
bootstrap-express.js
import passport from 'passport';
import certificateStrategy from 'cert-auth';
export default (app) => {
// CORS setup, bodyparser stuff & all...
// ... //
// Using authentication based on certificate
passport.use(certificateStrategy);
app.use(passport.initialize());
app.use(passport.authenticate('client-cert', { session: false }));
// Api routes.
app.get('/api/stream',
passport.authenticate('client-cert', { session: false }),
(req, res) => {
// Some router stuff
});
};
index.js
import https from 'https';
import express from 'express';
import fs from 'fs';
import path from 'path';
import bootstrapExpress from 'bootstrap-express';
const certDir = path.join(__dirname, '..', 'cert');
const listenPromise = server => port => new Promise((resolve, reject) => {
const listener = server.listen(port, err => (err ? reject(err) : resolve(listener)));
});
const options = {
key: fs.readFileSync(path.join(certDir, 'server.key')),
cert: fs.readFileSync(path.join(certDir, 'server.crt')),
ca: fs.readFileSync(path.join(certDir, 'server.crt')),
requestCert: true,
rejectUnauthorized: false,
};
(async function main() {
try {
logger.info('Initializing server');
const app = express();
bootstrapExpress(app);
const httpsServer = https.createServer(options, app);
const httpsListener = await listenPromise(httpsServer)(9002);
logger.info(`HTTPS listening on port ${httpsListener.address().port} in ${app.get('env')} environment`);
} catch (err) {
logger.error(err);
process.exit(1);
}
}());
Conclusion
Any help is welcome :)
Regards
Okay, I did many changes so that the chain of certificate could be clearer, but the reason I was still having 401 after all my efforts was because of this configuration in my express server:
const options = {
key: fs.readFileSync(path.join(certDir, 'server.key')),
cert: fs.readFileSync(path.join(certDir, 'server.crt')),
ca: fs.readFileSync(path.join(certDir, 'server.crt')),
requestCert: true,
rejectUnauthorized: false,
};
The working configuration is the following (replacing ca by the rootCA):
const options = {
key: fs.readFileSync(path.join(certDir, 'server.key')),
cert: fs.readFileSync(path.join(certDir, 'server.crt')),
ca: fs.readFileSync(path.join(certDir, 'rootCA.pem')),
requestCert: true,
rejectUnauthorized: false,
};
This issue helped me by the way, but I only find it few minutes ago: https://github.com/nodejs/help/issues/253^
Additional Info: in order to avoid the redirection from http to https because my server was on the localhost DNS, I simply added a new DNS in C:\Windows\System32\drivers\etc\host
127.0.0.1 mysuperdns
Therefore, the common name for the server certificate must be mysuperdns.
Related
I want to use https on my web locally.
Using Nuxt framework, and create a nodejs server.
I follow these command to create key.
openssl genrsa 2048 > server.key
chmod 400 server.key
openssl req -new -x509 -nodes -sha256 -days 365 -key server.key -out server.crt
then have these code on my server.
const app = express()
const path = require('path')
const fs = require('fs')
const option = {
https: {
key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
}
}
const server = require('https').createServer(option, app)
server.listen(port, host)
I have hosts setting 172.0.0.1 local.xxx.com.
when I call my web on chrome https://local.xxx.com
I will get error: ERR_SSL_VERSION_OR_CIPHER_MISMATCH
try others browser, get similar error
fix it!
this error cause my option have excessive 'https',fix it then it works.
const option = {
key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
}
I have a React app that I generated using create-react-app. In the package.json file I have
"proxy": {
"/auth/google": {
"target": "https://localhost:8008"
},
"/api/*": {
"target": "https://localhost:8008"
}
},
The app runs fine until I make a call to one of the above proxied URLs. Wherein I then get for example
Proxy error: Could not proxy request /auth/google from localhost:3000 to https://localhost:8008 (DEPTH_ZERO_SELF_SIGNED_CERT).
I generated certificates with these commands
openssl genrsa -out localhost.key 2048
openssl req -new -x509 -key localhost.key -out localhost.cert -days 3650 -subj /CN=localhost
In my Node.js app, I'm using those certs as follows
sslOptions = {
key: fs.readFileSync('./config/localhost.key'),
cert: fs.readFileSync('./config/localhost.cert'),
requestCert: false,
rejectUnauthorized: false
};
https.createServer(sslOptions, app).listen(PORT, () => {
console.log(serverStartMessage);
});
Any idea how I can get this working?
I have a node js server described as this:
const fs = require('fs');
const https = require('https');
const path = require('path');
const log = require('./lib/log');
const server = https.createServer({
key: fs.readFileSync(path.join(__dirname, 'server.key')),
cert: fs.readFileSync(path.join(__dirname, 'server.crt')),
ca: [fs.readFileSync(path.join(__dirname, 'ca.pem'))],
requestCert: true,
rejectUnauthorized: true
}, function() { log.debug('ok'); });
server.listen(8080, () => log.info(`Server listening on port 8080`));
My issue is that the certificate presented by the client gets rejected while it has been signed by this CA.
I've tried to use OpenSSL to be sure:
$ openssl s_server -key server.key -cert server.crt -accept 8080 -www -CAfile ca.pem -verify 5
verify depth is 5
depth=0 C = FR, O = MyO, OU = MyOU, CN = MyCN
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = FR, O = MyO, OU = MyOU, CN = MyCN
verify error:num=21:unable to verify the first certificate
verify return:1
Is there a way to have a verbose mode or to get the client's certificate?
Following this guide https://git.coolaj86.com/coolaj86/ssl-root-cas.js/src/branch/master/Painless-Self-Signed-Certificates-in-node.js.md, I've created a Root CA and Signed Certificate with the following script:
make-certs.sh
#!/bin/bash
FQDN=`hostname`
# make directories to work from
rm -rf certs
mkdir -p certs/{server,client,ca,tmp}
# Create your very own Root Certificate Authority
openssl genrsa \
-out certs/ca/my-root-ca.key.pem \
2048
# Self-sign your Root Certificate Authority
# Since this is private, the details can be as bogus as you like
openssl req \
-x509 \
-new \
-nodes \
-key certs/ca/my-root-ca.key.pem \
-days 1024 \
-out certs/ca/my-root-ca.crt.pem \
-subj "/C=US/ST=Utah/L=Provo/O=${FQDN}/CN=${FQDN}"
# Create a Device Certificate for each domain,
# such as example.com, *.example.com, awesome.example.com
# NOTE: You MUST match CN to the domain name or ip address you want to use
openssl genrsa \
-out certs/server/privkey.pem \
2048
# Create a request from your Device, which your Root CA will sign
openssl req -new \
-key certs/server/privkey.pem \
-out certs/tmp/csr.pem \
-subj "/C=US/ST=Utah/L=Provo/O=${FQDN}/CN=${FQDN}"
# Sign the request from Device with your Root CA
# -CAserial certs/ca/my-root-ca.srl
openssl x509 \
-req -in certs/tmp/csr.pem \
-CA certs/ca/my-root-ca.crt.pem \
-CAkey certs/ca/my-root-ca.key.pem \
-CAcreateserial \
-out certs/server/cert.pem \
-days 500
# Create a public key, for funzies
# see https://gist.github.com/coolaj86/f6f36efce2821dfb046d
openssl rsa \
-in certs/server/privkey.pem \
-pubout -out certs/client/pubkey.pem
# Put things in their proper place
rsync -a certs/ca/my-root-ca.crt.pem certs/server/chain.pem
rsync -a certs/ca/my-root-ca.crt.pem certs/client/chain.pem
cat certs/server/cert.pem certs/server/chain.pem > certs/server/fullchain.pem
I then setup my package.json with the following:
{
"name": "api-server",
"version": "1.0.0",
"description": "API Server",
"main": "api-server.js",
"dependencies": {
"body-parser": "^1.15.2",
"express": "^4.14.0"
}
}
Ran the npm install and then created my api-server.js like this:
// Load libraries
var https = require('https'),
fs = require('fs'),
express = require('express'),
app = express(),
bodyParser = require('body-parser');
// Server setting
var port = process.env.PORT || 8080;
// Register body-parser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Configure router
var router = express.Router();
app.use('/api/v1', router);
// Register routes
router.get('/', function(req, res) {
res.json({ success: true });
});
// Create & run https api server
var secureServer = https.createServer({
key: fs.readFileSync('./certs/server/privkey.pem'),
cert: fs.readFileSync('./certs/server/fullchain.pem'),
requestCert: true,
rejectUnauthorized: false
}, app).listen(port, function() {
console.log('API Server Started On Port %d', port);
});
Finally, I started the app using node api-server.js and visited https://<my-ip>:8080/ in chrome.
I got the following error:
This site can’t be reached
192.168.0.21 refused to connect.
Looking on the console log of server, I saw the following:
Any ideas what I might be doing wrong here?
I have found a way to solve/simply this.
make-certs.sh
#!/bin/bash
FQDN=`hostname`
rm server.key server.crt
openssl genrsa -out server.key 2048
openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj "/C=GB/ST=Street/L=City/O=Organisation/OU=Authority/CN=${FQDN}"
openssl x509 -req -days 1024 -in server.csr -signkey server.key -out server.crt
rm server.csr
api-server.js
// Import libraries
var express = require('express');
var server = express();
var bodyParser = require('body-parser')
var https = require('https');
var fs = require('fs');
// Server setting
var port = process.env.PORT || 8080;
// Register body-parser
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));
// Configure router
var router = express.Router();
server.use('/api/v1', router);
// Create https server & run
https.createServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
}, server).listen(port, function() {
console.log('API Server Started On Port %d', port);
});
// Register routes
router.get('/', function(req, res) {
res.json({ success: true });
});
This now works.
I am trying to use the https module in Node.js.
Here's the code:
var options = {
key : <key comes here>,
cert : <key comes here>
};
https.createServer(options, app).listen(app.get('port'));
You can use OpenSSL,
A private key is created like this
openssl genrsa -out ryans-key.pem 1024
The first step to getting a certificate is to create a "Certificate Signing Request" (CSR) file. This is done with:
openssl req -new -key ryans-key.pem -out ryans-csr.pem
To create a self-signed certificate with the CSR, do this:
openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem
To create .pfx or .p12, do this:
openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \
-certfile ca-cert.pem -out agent5.pfx
Here is a simple example echo server
var tls = require('tls');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
// This is necessary only if using the client certificate authentication.
requestCert: true,
// This is necessary only if the client uses the self-signed certificate.
ca: [ fs.readFileSync('client-cert.pem') ]
};
var server = tls.createServer(options, function(cleartextStream) {
console.log('server connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized');
cleartextStream.write("welcome!\n");
cleartextStream.setEncoding('utf8');
cleartextStream.pipe(cleartextStream);
});
server.listen(8000, function() {
console.log('server bound');
});
you can refer to http://nodejs.org/api/tls.html for more info,
Regards
Follow the openssl instructions found at the Node.js website.