Browsers currently require a secure context (MDN) for several Web APIs to work, such as opening cameras. In short, that means you need a HTTPS connection with a valid certificate signed by a known CA.

While it’s simple for public websites to add a CA-signed certificate, it’s much more difficult for intranets and computers with only private IPs. Testing software that requires secure context, despite the exception for localhost, is a source of pain and frustration; anyone who wanted to test on a real phone or any device that was not your own computer can attest to that. And when you’re releasing web applications for production that will run on private networks that you have no control at all, it’s just a nightmare.

We needed a solution to have secure contexts in LANs or one of our products, the Corollarium Videowall, wouldn’t work.

We needed an image for this post, so here’s the video wall in action.

What we did to solve it was a simple DNS server to provide TLS to webservices on local addresses. In short, it resolves addresses such as ‘192–168–0–1.yourdomain.net’ to 192.168.0.1 provides a valid wildcard TLS certificate for that domain.

It’s called localtls and it’s available on Github.

For the user this is transparent. They open a simple URL. The local webserver can 301-forward a request to 192.168.0.1 to https://192–168–0–1.yourdomain.net transparently.

Technically it’s a very simple DNS server written in Python, which uses Let’s Encrypt to generate a wildcard certificate for *.yourdomain.net on a real server on a public IP. This certificate, both private and public keys, is available for download via a REST call. When the local server app is started, it downloads the keys from the public server and starts a HTTPS server using those keys. So it’s composed of:

a simple DNS server that resolves to IP.yourdomain.net (for local IPs, see below) to IP and runs on a public internet server. an embedded simple HTTP server serving an index.html and a REST endpoint with the certificate keys, including the private key. a one-liner to generate and renew a valid certificate with LetsEncrypt, using DNS authentication. This script should be run once a month.

We only resolve private IPs and the naked domain:

yourdomain.net: resolves to your server IP, both A and AAAA (if it exists) records. _acme-challenge.yourdomain.net also resolves like this, since it’s necessary for the certbot authentication.

a-b-c-d.yourdomain.net, where a.b.c.d is a valid private network IPV4 (192.168.0.0–192.168.255.255, 172.16.0.0–172.31.255.255 and 10.0.0.0–10.255.255.255): resolves to A record of a.b.c.d. In other words, replace `.` by `-`.

fe80-[xxx].yourdomain.net: resolves to AAAA record of fe80:[xxx]. In other words, replace any `:` by `-`.

anything else: falls back to another DNS server.

We don’t use the wildcard TLS keys for the naked domain, but a separate certificate that is kept private.

Security considerations

“But if you publish the public and the private key, someone can do a man-in-the-middle attack!” Yes, that is correct. This is as safe as a plain HTTP website if you release the private key.

Our aim was to solve the requirement of browsers with secure contexts in LANs with a minimum fuss: as mentioned when you are developing an app that requires it, for example, and want to test it on several devices in your LAN. Or when you want to deploy a web application on private customer networks that have no expertise to setup a nameserver and TLS.

Restricting features to a secure context is a move in the right direction, but unfortunately self signed certificates are considered unsafe by browsers, even if you accept them. Adding a certificate as trusted is a complicated task that one cannot expect regular users to do.

Hopefully web committees and browser makers will come up with a solution that makes secure contexts in intranets easier (and safer) in the future, but it has been a problem for years and it’s still unsolved at this time. Perhaps self signed certificates could be handled differently for private IPs, letting people accept them with a warning but a simple procedure could work.

Meanwhile we are still running local web apps in plain HTTP without a hitch, but the change to force secure context for sensitive access created a void that is currently hard to solve. This means that IoT devices, routers, printers and any other device that provides a web server locally does not use HTTPS.

In short, we have two possible scenarios.

The first: you understand that with this solution you may be prone for a MITM attack, but you need a secure context in the browser to run your local app and you don’t need absolute certainty that your traffic will not be snooped or your application won’t be spoofed. This works for most webservices running in a LAN, and again, is as safe as running them on pure HTTP.

The second: you need not only a secure context for the browser, but actual safety of a private TLS certificate validated by the browser. In this case you can run the localtls DNS server yourself and not publish the private keys, but find someway to distribute them yourself privately to your application. Remember, any application you deploy using TLS will require a private key deployed with it. When distributing web apps that are supposed to run in intranets which you have no access this is hard to do; you’d ideally need to generate a different key for every host, even though they may use the same private IP, you have no access to a local nameserver and other complications. There is a nice proposal of how this can be done if you need this level of security.