Local HTTPS Server
Goal
Setup a local HTTPS server that works just like a “real” HTTPS server. Especially regarding certificates.
Also I am not a cryptography or security expert so the terminology used might be incorrect.
My usecase for this is testing and developing PWAs in a local network.
Requirements
- openssl
- golang
A quick outline of the steps:
- Become a CA (Certificate Authority)
- Generate CA signed certificates
- Trust the “root certificate”
- Use the CA signed certificates to serve a website over HTTPS
1. Certificate Authority
What is a CA
A CA acts as an entity to verify and sign certificates. Well known CAs include IdenTrust, DigiCert or Let’s Encrypt. Their root certificates are very likely already trusted by your machine and that’s why you don’t have to do anything to get a secure HTTPS connection to many servers on the internet.
Becoming a CA
Basically you just need a private key and an X.509 public certificate.
For the private key run:
openssl genrsa -out CA.key 2048
To generate the X.509 public certificate:
openssl req -x509 -new -nodes -key CA.key -sha256 -days 3650 -out CA_public.pem
A few questions will be asked, the only one that matters for our purposes is the Common Name. Pick something easily recognizable.
2. CA-signed certificates
This are the things you (used to) have to pay for. (Thank you Let’s Encrypt for free certificates)
As the “customer” of the CA
First we need another private key that belongs to “us” and not the CA. I am going to name it localhost.key
since that’s where it’s going to be used. The hostname would also be a sensible choice.
openssl genrsa -out localhost.key 2048
Now we need to generate a CSR (Certificate Signing Request). As the name kind of implies this will be used by us to request a certificate signed by the CA.
openssl req -new -key localhost.key -out localhost.csr
As the CA
We receive the CSR and the desired domain names (and some money). With that and our CA.key
and CA_public.pem
we can generate and sign a certificate.
Well actually we also need a small configuration file ssl.conf
with following contents:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = hostname.local
Do not forget to insert the hostname of your machine.
Now we can generate the public X.509 certificate:
openssl x509 -req -in localhost.csr -CA CA_public.pem -CAkey CA.key -CAcreateserial -out localhost.crt -sha256 -extfile ssl.conf -days 365
It might be tempting to increase -days 365
but many implementations will reject certificates with a long validity period. Apple, for example, limits it to 398 days on their implementation.
Done
You should now have following files:
- CA.key
- CA_public.pem
- localhost.key
- localhost.csr
- localhost.crt
3. Trust
That’s the hard part about being a CA. Getting people/machines to trust you.
The process is different for most operating systems, but the relevant file is CA_public.pem
Since I am currently on macOS/iOS I will only describe the process for those two for now.
macOS (14.0)
- Open CA_public.pem in finder
- In Keychain Access look for “Default Keychains -> Login -> Certificates”
- Double click on your certificate
- Expand “Trust”
- When using this certificate: Always Trust
iOS (17.1)
- Download/Open CA_public.pem in Files
- iOS should ask for permission to install the certificate/profile
- Check “Settings -> General -> VPN & Device Management” and make sure the profile is trusted/verified
- “Settings -> General -> About -> Certificate Trust Settings (at bottom) -> ”
- Enable Full Trust For Root Certificates
4. HTTPS Server
Almost done, let’s see if the certificate works as expected. Golang makes it very easy to create a HTTPS server:
package main
import (
"log"
"net/http"
)
func main() {
http.Handle("/", http.FileServer(http.Dir("www")))
err := http.ListenAndServeTLS("0.0.0.0:8443", "certs/localhost.crt", "certs/localhost.key", nil)
if err != nil {
log.Fatal(err)
}
}
I put all the certificates in a subfolder ./certs
and the website in ./www
Now start the server:
go run main.go
And visit https://localhost:8443
The result should be a website served over HTTPS without any security warnings.
Note#1
When accessing the server from another machine you have to use a hostname like devpc.local and it has to be listed in the SAN in ssl.conf
when generating the X.509 public certificate.
(If anybody knows a way to create a wildcard certificate for *.local I would be happy to know about it)
Note#2
Trusted root certificates are powerful, so be careful with your CA.key
especially once you trusted the root certificate CA_public.pem