How to get Node.Js to allow me to provide client cert with smart card/cac

1.2k views Asked by At

I am trying to build a CAC authentication system using node.js but am having trouble. I followed a few tutorials regarding setting up a https server and I can get that to work just fine; however, using the logic below, whenever I access my server:

https://localhost:3000

I am prompted to login with the auth button that is sent from the "/" route handler; however, it immediately defaults to the else clause:

res.status(401)
        .send(`Sorry, but you need to provide a client certificate to continue.`)

Normally, when accessing a cac-enabled site, I a prompted to choose a smart card certificate, which allows me to login; however, in this case, I am not being prompted at all.

I found a similar question on SO but was not able to get anything to work from the article they referenced. They mentioned that I would have to set my 'ca' property on the https server to allow the specific CA of the CAC in question. I used my browser to locate my CAC certificates and exported my certificates and put them in the approved CA list but I still do not get any prompt.

I was initially using req.connection.getPeerCertificate() to prompt for certificates; however, I read that req.connection was deprecated in my verison of Node.JS (14.15.0) so I have also tried req.socket.getPeerCertificate(). The server seems to run just fine either way and I get no errors client-side or server-side (with exception to the 401 response that is sent from the server since it is not getting a valid certificate). Any and all insight would be greatly appreciated.

const fs = require('fs')
const express = require('express')
const https = require('https')

// Https options
const options = {
    // Path to private key (created by openssl in createSelfSignedCert.bat)
    key: fs.readFileSync('server_key.pem')
    // Path to public key
    ,cert: fs.readFileSync('server_cert.pem')
    // Indicate that https server should request client certificates
    ,requestCert: true
    // Manually handle bad requests (no certs)
    ,rejectUnauthorized: false
    //List of accepted/valid CA certs - just our own for now
    ,ca: [ 
        fs.readFileSync('server_cert.pem')
        ,fs.readFileSync('cac52.cer')
        ,fs.readFileSync('cac_export.p7b')
    ]
    //,ca: .
}


// Use express for routing
const app = express()


// Unprotected public endpoint
app.get('/', (req, res) => {
    res.send('<a href="login">Auth</a>')
})


// Protected endpoint
app.get('/login', (req, res) => {
    // req.connection is deprecated, perhaps req.socket.getPeerCertificate()
    const cert = req.connection.getPeerCertificate()
    //const cert = req.socket.getPeerCertificate()


    if (req.client.authorized) {
        res.send(`Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!`)
        console.log(`${cert}`)
    } else if (cert.subject) {
        res.status(403)
           .send(`Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.`)
           console.log(`${cert}`)
    } else {
        res.status(401)
            .send(`Sorry, but you need to provide a client certificate to continue.`)
    }

})

// Create https server with options and app routes
https.createServer(options, app).listen(3000)
1

There are 1 answers

0
nealwp On

I got this to work by changing this setting in my browser (Chrome):

chrome://flags/#allow-insecure-localhost

I got that from this answer. I think Chrome was not prompting me to choose a certificate because it was treating my dev environment as insecure. After I enabled that setting, the certificate prompt appeared as expected and the '/login' URL displayed the CN of the cert I chose.

Also, you'll need to make sure you have the correct CA's. AFAIK you cannot just export a CA certificate chain from your CAC - you need to have them provided to you from the CA. If you're working on a DoD project, you'll need the DoD Root CA's. If you already have a DoD CAC, you can get the DoD Root CA's from this link.

Remember, the certificates loaded on your CAC are client certificates, they are unique to you. The CA (Certifying Authority) is the organization that issued you those certificates. When you add the CA cert(s) to your project, what your app is effectively saying is "I can accept client certs issued by this CA."