Home Assistant TLS Client Certificate - HTTP Basic Auth on Steroids

Posted on | 514 words | ~3mins
home-assistant nginx tls-client-certificate

TLS has a rarely used, but very useful feature: Client Authentication using certificates. This allows us to safely reach Home Assistant behind a Reverse Proxy without exposing Home Assistant directly without the hassle of dealing with additional passwords like with Basic Auth (which doesn’t seem to be fully supported with the Android App) or having to connect to your home with a VPN everytime you want to check your Home Assistant. This is not a beginner tutorial, I assume that you know what you are doing. We will use nginx as a Reverse Proxy and configure Home Assistant to allow the connection. I will use these IP addresses in this blog post:

  • IP Address Reverse Proxy: 2001:db8::affe
  • IP Address Home Assistant 2001:db8::beef

We first need to generate our own CA which will be used to create the client certificates.

Generate CA and Client Certificates

(Thanks to https://gist.github.com/mtigas/952344)

Generate RSA key for CA (keep secret)

openssl ecparam -genkey -name secp256r1 | openssl ec -out ca.key

Generate CA root cert (used in reverse proxy for validating user certificates)

openssl req -new -x509 -days 3650 -key ca.key -out ca.pem

Create A Client Certificate

# some identifier for certificate
CLIENT_ID="01-alice"
CLIENT_SERIAL=01

openssl ecparam -genkey -name secp256r1 | openssl ec -out ${CLIENT_ID}.key

# create Certificate Signing Request 
openssl req -new -key ${CLIENT_ID}.key -out ${CLIENT_ID}.csr

# issue user certificate
openssl x509 -req -days 3650 -in ${CLIENT_ID}.csr -CA ca.pem -CAkey ca.key -set_serial ${CLIENT_SERIAL} -out ${CLIENT_ID}.pem

# combined pem file
cat ${CLIENT_ID}.key ${CLIENT_ID}.pem ca.pem > ${CLIENT_ID}.full.pem

# combined PFX file
openssl pkcs12 -export -out ${CLIENT_ID}.full.pfx -inkey ${CLIENT_ID}.key -in ${CLIENT_ID}.pem -certfile ca.pem

# combined PFX file (ANDROID!!!)
openssl pkcs12 -export -legacy -out ${CLIENT_ID}.full.legacy.pfx -inkey ${CLIENT_ID}.key -in ${CLIENT_ID}.pem -certfile ca.pem

Now copy one of these files to the client:

Configure NGINX

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name yourpublichost.example.com

    ssl_certificate /etc/ssl/private/example.com_ecc/fullchain.cer;
    ssl_certificate_key /etc/ssl/private/example.com_ecc/example.com.key;
    ssl_trusted_certificate /etc/ssl/private/example.com_ecc/fullchain.cer;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;

    ssl_ecdh_curve prime256v1:secp384r1;

    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';

    ssl_prefer_server_ciphers on;

    ssl_client_certificate /etc/nginx/hass-ca.pem;
    ssl_verify_client on;

    location / {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://[2001:db8::beef]:8123/;
    }
}

Some notes for config above:

We specify the CA(s) certificates (must be concatenated into a single file) and force that the client must supply a valid client certificate:

ssl_client_certificate /etc/nginx/hass-ca.pem;
ssl_verify_client on;

Home Assistant uses websockets, to proxy them successfully we need these lines:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;

The original IP address is passed on in the “X-Forwarded-For” HTTP Header.

Configure Home Assistant

Add to your Home Assistant configuration:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 2001:db8::affe

Install Client Cert in Firefox and on Android

Firefox

Settings -> Show Certificates -> Your Certificates -> Import

Android

Settings -> Security -> Encryption & Login Data -> Install from SD card -> VPN & App Certificate

If you get the error that “a private certificate is needed” or that you used a wrong password (even though it is correct), you didn’t use the legacy format pfx file. Thanks to https://stackoverflow.com/a/73512646/7418348