about:benjie

Random learnings and other thoughts from an unashamed geek

Node.JS SSL Certificate Chain

| Comments

I’ve just discovered that one of our servers is not serving up it’s SSL certificate chain correctly. This is fine for modern web browsers who trust the COMODO certificate, but for older browsers/operating systems you need to support higher up the trust chain.

Previously I followed the technique in this article, but it turns out that the ca parameter of the TLS/HTTPS server should be an array. More than this, you cannot just feed it an array containing your chain file as a string/buffer (i.e. [fs.readFileSync("/path/to/mydomain.ca-bundle")]) since the Node TLS module only reads the first certificate in each entry of this array.

To solve this you can parse your existing certificate chain:

.coffee.jsParse your certificate chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
https = require 'https'
fs = require 'fs'

ca = []
chain = fs.readFileSync "/path/to/mydomain.ca-bundle", 'utf8'
chain = chain.split "\n"
cert = []
for line in chain when line.length isnt 0
  cert.push line
  if line.match /-END CERTIFICATE-/
    ca.push cert.join "\n"
    cert = []

httpsOptions =
  ca: ca
  key: "/path/to/server.key"
  cert: "/path/to/mydomain.crt"

requestHandler = (req, res) ->
  res.writeHead 501
  res.end()

httpsServer = https.createServer httpsOptions, requestHandler
httpsServer.listen 443, "localhost"

You can test your certificate chain using this command:

openssl s_client -connect hostname:443 -showcerts | grep "^ "

An EssentialSSL/COMODO certificate chain would look something like:

 0 s:/OU=Domain Control Validated/OU=Free SSL/CN=example.com
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=EssentialSSL CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=EssentialSSL CA
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority
   i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC
 3 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
 4 s:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root

Alternatively, if you prefer, you could have Node read the certificate files individually which can be more concise, but requires more maintenance:

.coffee.jsParse your certificate chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
https = require 'https'
fs = require 'fs'

files = [
  "EssentialSSLCA_2.crt"
  "ComodoUTNSGCCA.crt"
  "UTNAddTrustSGCCA.crt"
  "AddTrustExternalCARoot.crt"
]
ca = (fs.readFileSync "/path/to/#{file}" for file in files)

httpsOptions =
  ca: ca
  key: "/path/to/server.key"
  cert: "/path/to/mydomain.crt"

requestHandler = (req, res) ->
  res.writeHead 501
  res.end()

httpsServer = https.createServer httpsOptions, requestHandler
httpsServer.listen 443, "localhost"

Comments