@small-tech/auto-encrypt-localhost

Automatically provisions and installs locally-trusted TLS certificates for Node.js https servers (including Express.js, etc.) using mkcert.

Usage no npm install needed!

<script type="module">
  import smallTechAutoEncryptLocalhost from 'https://cdn.skypack.dev/@small-tech/auto-encrypt-localhost';
</script>

README

Auto Encrypt Localhost

Automatically provisions and installs locally-trusted TLS certificates for Node.js® https servers (including Express.js, etc.) using mkcert.

How it works

At npm package installation time, Auto Encrypt Localhost installs mkcert.

At runtime, Auto Encrypt Localhost uses mkcert to create a local certificate authority and add it to the various trust stores. It then uses it to create locally-trusted TLS certificates that are automatically used by your server.

You can reach your server via the local loopback addresses (localhost, 127.0.0.1) on the device itself and also from other devices on the local area network by using your device’s external IPv4 address(es).

System requirements

Tested and supported on:

  • Linux (tested with elementary OS 5.x/Hera)
  • macOS (tested on Big Sur)
  • Windows 10 (tested with Windows Terminal and PowerShell)

(WSL is not supported for certificates at localhost unless you’re running your browser under WSL also).

Installation

npm i @small-tech/auto-encrypt-localhost

Note that during installation, Auto Encrypt Localhost will download and install the correct mkcert binary for your system.

Usage

Instructions

  1. Import the module:

    import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
    
  2. Prefix your server creation code with a reference to the Auto Encrypt Localhost class:

    // const server = https.createServer(…) becomes
    const server = AutoEncryptLocalhost.https.createServer(…)
    

Example

(You can find this example in the example/ folder in the source code. Run it by typing node example.)

// Create an https server using locally-trusted certificates.

import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'

const server = AutoEncryptLocalhost.https.createServer((request, response) => {
  response.end('Hello, world!')
})

server.listen(443, () => {
  console.log('Web server is running at https://localhost')
})

On first run, Auto Encrypt Localhost will use mkcert to create your local certificate authority and install it in the system root store and generate locally-trusted certificates. These actions require elevated privileges and you will be prompted for your password unless you have passwordless sudo set up for your system.

Once your server is up and running, you can reach it via https://localhost, https://127.0.0.1, and via its external IPv4 address(es) on your local area network. To find the list of IP addresses that your local server is reachable from, you can run the following code in the Node interpreter:

Object.entries(os.networkInterfaces())
  .map(iface =>
    iface[1].filter(addresses =>
      addresses.family === 'IPv4')
      .map(addresses => addresses.address)).flat()

Plain Node.js example

If you just want to use the TLS certificates generated at installation time without using the Auto Encrypt Localhost library itself at runtime, you should install Auto Encrypt Localhost into your dev-dependencies. Post install, you can find your certificates in the ~/.small-tech.org/auto-encrypt-localhost folder.

Here’s a somewhat equivalent example to the one above but using Node’s regular https module instead of Auto Encrypt Localhost at runtime:

import os from 'os'
import fs from 'fs'
import path from 'path'
import https from 'https'

const certificatesPath = path.join(os.homedir(), '.small-tech.org', 'auto-encrypt-localhost')
const keyFilePath = path.join(certificatesPath, 'localhost-key.pem')
const certFilePath = path.join(certificatesPath, 'localhost.pem')

const options = {
  key: fs.readFileSync(keyFilePath, 'utf-8'),
  cert: fs.readFileSync(certFilePath, 'utf-8')
}

const server = https.createServer(options, (request, response) => {
  response.end('Hello, world!')
})

server.listen(443, () => {
  console.log('Web server is running at https://localhost')
})

Note that if you don’t use Auto Encrypt Localhost at runtime, you won’t get some of the benefits that it provides, like automatically adding the certificate authority to Node’s trust store (for hitting your server using Node.js without certificate errors), the /.ca convenience route, and HTTP to HTTPS forwarding, etc.

On Linux

To access your server on port 443, make sure you’ve disabled privileged ports:

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0

(On Linux, ports 80 and 443 require special privileges. Please see A note on Linux and the security farce that is “privileged ports”. If you just need a Node web server that handles all that and more for you – or to see how to implement privilege escalation seamlessly in your own servers – see Site.js).

Multiple servers

You are not limited to running your server on port 443. You can listen on any port you like and you can have multiple servers with the following caveat: the HTTP server that redirects HTTP calls to HTTPS and serves your local root certificate authority public key (see below) will only be created for the first server and then only if port 80 is free.

Accessing your local machine from other devices on your local area network

You can access local servers via their IPv4 address over a local area network.

This is useful when you want to test your site with different devices without having to expose your server over the Internet using a service like ngrok. For example, if your machine’s IPv4 address on the local area network is 192.168.2.42, you can just enter that IP to access it from, say, your iPhone.

To access your local machine from a different device on your local area network, you must transfer the public key of your generated local root certificate authority to that device and install and trust it.

For example, if you’re on an iPhone, hit the /.ca route in your browser:

http://192.168.2.42/.ca

The browser will download the local root certificate authority’s public key and prompt you to install profile on your iPhone. You then have to go to Settings → Profile Downloaded → Tap Install when the Install Profile pop-up appears showing you the mkcert certificate you downloaded. Then, go to Settings → General → About → Certificate Trust Settings → Turn on the switch next to the mkcert certificate you downloaded. You should now be able to hit https://192.168.2.42 and see your site from your iPhone.

You can also transfer your key manually. You can find the key at ~/.small-tech/auto-encrypt-localhost/rootCA.pem after you’ve created at least one server. For more details on transferring your key to other devices, please refer to the relevant section in the mkcert documentation.

Developer documentation

If you want to help improve Auto Encrypt Localhost or better understand how it is structured and operates, please see the developer documentation.

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.

Audience

This is small technology.

If you’re evaluating this for a “startup” or an enterprise, let us save you some time: this is not the right tool for you. This tool is for individual developers to build personal web sites and apps for themselves and for others in a non-colonial manner that respects the human rights of the people who use them.

Caveats

Windows

Locally-trusted certificates do not work under Firefox. Please use Edge or Chrome on this platform. This is a mkcert limitation.

Related projects

From lower-level to higher-level:

Auto Encrypt

Adds automatic provisioning and renewal of Let’s Encrypt TLS certificates with OCSP Stapling to Node.js https servers (including Express.js, etc.)

HTTPS

A drop-in replacement for the standard Node.js HTTPS module with automatic development-time (localhost) certificates via Auto Encrypt Localhost and automatic production certificates via Auto Encrypt.

Site.js

A tool for developing, testing, and deploying a secure static or dynamic personal web site or app with zero configuration.

Place (work-in-progress)

Small Web Protocol Reference Server.

A note on Linux and the security farce that is “privileged ports”

Linux has an outdated feature dating from the mainframe days that requires a process that wants to bind to ports < 1024 to have elevated privileges. While this was a security feature in the days of dumb terminals, today it is a security anti-feature. (macOS has dropped this requirement as of macOS Mojave.)

On modern Linux systems, you can disable privileged ports like this:

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0

Or, if you want to cling to ancient historic relics like a conservative to a racist statue, ensure your Node process has the right to bind to so-called “privileged” ports by issuing the following command before use:

sudo setcap cap_net_bind_service=+ep $(which node)

If you are wrapping your Node app into an executable binary using a module like Nexe, you will have to ensure that every build of your app has that capability set. For an example of how we do this in Site.js, see this listing.

Help wanted

  • Linux: certutil (nss) auto-installation has not been tested with yum.
  • macOS: certutil (nss) auto-installation has not been tested with MacPorts.

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.

Copyright

Copyright © 2019-2021 Aral Balkan, Small Technology Foundation.

License

Auto Encrypt Localhost is released under AGPL 3.0 or later.