Home

Awesome

win-ca

Build status NPM version Store Roots

Get Windows System Root certificates for Node.js.

Rationale

Unlike Ruby, Node.js on Windows allows HTTPS requests out-of-box. But it is implemented in a rather bizarre way:

Node uses a statically compiled, manually updated, hardcoded list of certificate authorities, rather than relying on the system's trust store... Read more

It's somewhat non-intuitive under any OS, but Windows differs from most of them by having its own trust store, fully incompatible with OpenSSL.

This package is intended to fetch Root CAs from Windows' store (Trusted Root Certification Authorities) and make them available to Node.js application with minimal efforts.

Advantages

Usage

For 95% of users:

  1. Just say npm install --save win-ca
  2. Then call require('win-ca').
  3. That's it!

If you need more - proceed to API section below.

By the way, win-ca is safe to be used under other OSes (not M$ Windows). It does nothing there.

Electron

win-ca was adapted to run inside Electron applications with no additional configuration (asar supported).

See Minimal Electron application using win-ca for usage example.

VS Code extension

Special extension for VS Code was created to import win-ca in context of VS Code's Extension Host.

Since all VS Code extensions share the same process, root certificates imported by one of them are immediately available to others. This can allow VS Code extensions to connect to (properly configured) intranet sites from Windows machines.

API

<details> <summary> Click to view... </summary>

First versions of win-ca opened Windows' Trusted Root Certificate Store, fetched certificates, deduplicated them and installed to https.globalAgent.options.ca, so they are automatically used for all requests with Node.js' https module.

But sometimes one needs to get these certificates to do something else. For that case, full featured API was devised. It is the only function with numerous parameters and operation modes, eg:

const ca = require('win-ca')

rootCAs = []
// Fetch all certificates in PEM format
ca({
  format: ca.der2.pem,
  ondata: crt => rootCAs.push(crt)
})

Entry points

win-ca offers three ways of importing:

  1. Regular require('win-ca')
  2. Fallback require('win-ca/fallback')
  3. Pure API require('win-ca/api')

They all export the same API, but differ in initialization:

  1. win-ca does fetch certificates from Root store, saves them to disk and makes them available to https module with no effort.

  2. win-ca/fallback does the same, but it never uses N-API for fetching certificates, so it should work in all versions of Node.js as well as inside Electron application.

  3. win-ca/api does nothing, just exports API, so you decide yourself what to do.

API Parameters

API function may be called with no parameters, but that makes little sense. One should pass it object with some fields, ie:

Helper functions

Some internal functions are exposed:

der2

var certificate = ca.der2(format, certificate_in_der_format)

Converts certificate from DER to format specified in first parameter.

Function .der2() is curried:

var toPEM = ca.der2(ca.der2.pem)

var pem = toPEM(der)

hash

var hash = ca.hash(version, certificate_in_der_format)

Gives certificate hash (aka X509_NAME_hash), ie 8-character hexadecimal string, derived from certificate subject.

If version (first parameter) is 0, an old algorithm is used (aka X509_NAME_hash_old, used in OpenSSL v0.*), else - the new one (X509_NAME_hash of OpenSSL v1.*).

Function .hash() is also curried:

var hasher = ca.hash()
console.log(hasher(der))

inject

ca.inject(mode)
// or:
ca.inject(mode, array_of_certificates)

Manages the way certificates are passed to other modules.

This function is internally called by API when {inject:} parameter used.

First argument (mode) is injection mode:

Second parameter (array_of_certificates) is list of certificates to inject. If it is omitted, previous list is used (only inject mode is changed).

For example, simplest way to test new injection mode is:

const ca = require('win-ca') // Fetch certificates and start injecting (old way)

ca.inject('+') // Switch to new injection mode

Note, that this function should be called before first secure connection is established, since every secure connection populates different caches, that are extremely hard to invalidate. Changing injection mode in the middle of secure communication can lead to unpredictable results.

exe

Applications that use win-ca are sometimes packed / bundled. In this case one should find appropriate place for binary utility roots.exe (used in fallback mode, which is always the case with Electron apps) and then make win-ca to find the binary.

Function .exe() is intended to provide this functionality. You must call it before first invocation of library itself, eg:

var ca = require('win-ca/api')

ca.exe('/full/path/to/roots.exe')
ca({fallback: true, inject: true})

.exe() with no parameters switches to default location (inside lib folder). In any case it returns previous path to roots.exe:

console.log(require('win-ca').exe()) // Where is my root.exe?

Legacy API

<details> <summary> Click to view... </summary>

win-ca v2 had another API, which is preserved for compatibility, but discouraged to use. It consists of three functions:

var ca = require('win-ca')

do.something.with(ca.all(ca.der2.pem))

Note:

  1. All three yield certificates in node-forge's format by default (unlike modern API, that returns DER if unspecified by user).

    Unfortunately, node-forge at the time of writing is unable to parse non-RSA certificates (namely, ECC certificates becoming more popular). If your Trusted Root Certification Authorities store contains modern certificates, legacy API calls will throw exception. To tackle the problem - pass them format as the first parameter.

  2. .all() deduplicates certificates (like regular API), while both .each calls may return duplicates ({unique: false} applied)

  3. Root store always used (no way for store: option)

  4. Both .each calls require callback (with optional format)

    Synchronous .each() callback gets single argument - certificate (in specified format)

      var ca = require('win-ca')
      ca.each(ca.der2.x509, crt=>
        console.log(crt.serialNumber)
      )
    

    Asynchronous .each.async() callback gets two parameters:

    • error (which is always undefined in this version)
    • result - certificate in requested format or undefined to signal end of retrieval
    let ca = require('win-ca')
    
    ca.each.async((error, crt)=> {
      if (error) throw error;
      if(crt)
        console.log(forge.pki.certificateToPem(crt))
      else
        console.log("That's all folks!")
    })
    
</details>

N-API

Current version uses N-API, so it can be used in Node.js versions with N-API support, i.e. v6 and all versions starting from v8.

Thanks to N-API, it is possible to precompile Windows DLL and save it to package, so no compilation is needed at installation time.

For other Node.js versions (v4, 5 or 7) special fallback utility is called in the background to fetch the list anyway.

If you wish to use this fallback engine (even for modern Node.js), you can

require('win-ca/fallback')
</details>

Caveats

Windows 10 tends to have only a few certificates in its Trusted Root Certification Authorities store and lazily add them to it on first use.

If your OS does so, win-ca will still help to connect to your own sites (protected by self-signed certificates, or by the ones, distributed with GPO), but will make connection to well-known sites (like Google or Twitter) impossible!

The simplest remedy is to once open desired site in Internet Explorer / Google Chrome (certificate will be silently added to Root store).

Another option is to switch to new experimental injection method:

require('win-ca').inject('+')

Clear pem folder on publish

If you use win-ca in some Electron app or VS Code extension, be warned that node_modules/win-ca/pem folder is highly likely to be packed into your bundle with all root certificates on development machine.

You had better remove said folder before publishing (eg. in prepack npm script if it applies).

Building

This builds both x86 and x64 versions with N-API support. For older Node.js versions standalone binary utility is built.

See also

Credits

Uses node-forge and used to use node-ffi-napi (ancestor of node-ffi).