22

I am looking for a node.js way to verify a client certificate in X509 format with a CA certificate which was given to me (none of those are created/managed by me, my software only has to verify what is beeing sent to it).

I have found several modules for this job, however I am having issues with each of them:

  • X509 is able to do it using x509.verify(cert, CABundlePath, cb), however it needs to read the certificates from FS, and I am having them in memory already. This is cumbersome as it will be done with each web request which reaches my app.
  • It seems like PKI.js is able to do it, however their examples don't work for me but complain about missing files, so I can't even try it out.
  • I tried node-forge, but while I am unsure if I use it correctly (they don't have any API documentation) its throwing a forge.pki.BadCertificate error from forge.pki.verifyCertificateChain(caStore, [ cer ], cb).
  • When trying pem, using a simple pem.verifySigningChain(cer, [ ca ], cb) would throw some error complaining about loading a file from /var/.... Even if it would work, I would avoid using this lib as its relying on the openssl command line tool, which I would like to avoid

Now I feel pretty stupid because I failed to get this simple task done with any of the above modules. Could someone point me to a simple solution which will allow me to verify the signature/validity of a X509 certificate using a given CA certificate? :s

[edit] Basically I would need openssl verify -verbose -CAfile ca-crt.pem client1-crt.pem in Node.js but without dependencies to the openssl command line tool and without temporarily saving the certs to disk.

[edit2] Would it be possible to just use https://nodejs.org/api/crypto.html#crypto_verify_verify_object_signature_signatureformat?

user826955
  • 3,137
  • 2
  • 30
  • 71

3 Answers3

14

I finally managed to do it using node-forge. Heres a working code example:

let pki = require('node-forge').pki;
let fs = require('fs');

let caCert;
let caStore;

try {
    caCert = fs.readFileSync('path/to/ca-cert.pem').toString();
    caStore = pki.createCaStore([ caCert ]);
} catch (e) {
    log.error('Failed to load CA certificate (' + e + ')');
    return....;
}

try {
    pki.verifyCertificateChain(caStore, [ cert ]);
} catch (e) {
    return handleResponse(new Error('Failed to verify certificate (' + e.message || e + ')'));
}

Both certificates shall be given in base64 encoded PEM format/js string.

verifyCertificateChain checks the certifitate validity (notBefore/notAfter) as well as verifies the given CA chain.

I am not 100% sure if this is the best approach, or if this library is doing a good job, since their source code of verifyCertificateChain is full of #TODOs, so maybe this is not ready for production? But at least I have a somewhat working solution. Probably it would be better to create a node module which wraps the libssl c calls, but thats just a lot of effort for this small task.

J'e
  • 3,014
  • 4
  • 31
  • 55
user826955
  • 3,137
  • 2
  • 30
  • 71
  • where is `cert` defined? – J'e Jul 01 '19 at 12:12
  • Its the certificate you want to verify. Its provided by you. – user826955 Jul 01 '19 at 13:51
  • Worked for me, but I had to convert a PEM-encoded (string) certificate to a forge cert object: `const cert = pki.certificateFromPem(certificate.data);` (certificate.data was provided in my Electron app on the `certificate-error` event.) – ZeeCoder Feb 12 '20 at 15:03
4

You can also do like this if you want to check the using the client certificates from the http request directly :

// retrieve certificates from the request ( in der format )
clientCert = req.connection.getPeerCertificate(true).raw.toString('base64'))

Method to convert the der certificate to pem and verify against the castore.

  const caCert = fs....
  const ca = pki.certificateFromPem(caCert)
  const caStore = pki.createCaStore([ ca ])

  const verify = (clientCert, next) => {
    try {
      const derKey = forge.util.decode64(clientCert)
      const asnObj = forge.asn1.fromDer(derKey)
      const asn1Cert = pki.certificateFromAsn1(asnObj)
      const pemCert = pki.certificateToPem(asn1Cert)
      const client = pki.certificateFromPem(pemCert)
      return pki.verifyCertificateChain(caStore, [ client ], cb)
    } catch (err) {
      next(new Error(err))
    }
  }

I did not find a better way to verify the client "der" certificate from the request.

fas3r

fas3r
  • 53
  • 6
0

This works for me:

const fs = require('fs'), pki = require('node-forge').pki
var ca = pki.certificateFromPem(fs.readFileSync('ca.pem', 'ascii'))
var client = pki.certificateFromPem(fs.readFileSync('client.pem', 'ascii'))
try {
    if (!ca.verify(client)) throw 'verify failed'
} catch (err) {
    console.log(err)
}

try/catch was required, because .verify threw an error (instead of returning false) in my case.

Joerg Veigel
  • 117
  • 7