How to Install Free Certificates for the UniFi Network Application

WTF, Ubiquiti? Why is it so difficult to import certificates into the (nondescriptly named) UniFi Network Application? (I’ll just call it UniFi in this article.) There must be a hundred blog entries that explain how to do it, most of which are plain wrong and almost all of which are useless to do what I want. There is no document that I found that explains what exactly the web server expects to find inside the key store file. And there is no excuse for a lack of a GUI method to do it.

I wanted to use certificates from a free CA on my UniFi that runs on Ubuntu Server 20.04. I like to use acme.sh to download and maintain these free certificates, but I could not find a practical method to use the script for UniFi. acme.sh downloads the certificate and chain as X.509 PEM files, but Unifi doesn’t use PEM files. It doesn’t use PKCS12 (.pfx) files, popular on Windows, for example, either. It uses the (apparently deprecated) Java KeyStore.

I normally know what to do with certificate files. It is my job. But no matter how I tried, I could not get UniFi to start without overwriting the keystore file after it failed to open it successfully. After several hours of trial-and-error and Googling, I finally figured out how to update the damn thing in a way that UniFi likes. Writing a script to automate the process with acme.sh was simple after that.

This Article’s Scope

What follows is the process that should work for a fresh install of Ubuntu 20.04 and a UniFi package at or about version 6.5. I do not explain how to install UniFi here and just assume that it is up and running already.

Make a backup of your machine before you do anything else. At least make a copy of .../data/keystore somewhere so that you can copy it back if something should fail. Ubiquiti or Canonical or acme.sh or severe solar flares may change something at any time that will make the following procedure cause your UniFi installation to melt. Make a backup. Don’t blame me.

Connect to the shell on your instance to perform these steps and elevate yourself to root. (Alternatively, you can use a user that has write permission to the UniFi base directory and use sudo before each command, as necessary.) These steps assume that your domain is hosted by GoDaddy, but API keys for other registrars should work just as well, as should other methods of getting the certificate files. You need to log in to your registrar (GoDaddy or whatever) and generate an API key and a secret to do these steps. (Here are instructions for GoDaddy.) If you use acme.sh with DNS, you should already have credentials. If you do not, you can use the ones you make now for this or other servers you want to generate certificates for.

Ubuntu installs with openssl and keytool. The script I wrote assumes that the commands exist.

Install acme.sh

The following will install prerequisites and the acme.sh script. Use your email address instead of the example. The script is installed in ~/.acme.sh by default.

apt -y install socat
curl https://get.acme.sh | sh -s email=me@mydomain.org

Issue a New Certificate

If you are getting your first certificate using DNS authentication, you must provide the script with the API credentials it will use to temporarily create DNS TXT records in your domain in order to authenticate ownership of said domain. The script stores these credentials for future certificate updates or additional requests, so these variables only have to be set the first time you request a certificate. These variable names below are specific to GoDaddy. Check the documentation for your registrar.

export GD_Key=<GoDaddy-API-key>
export GD_Secret=<GoDaddy-API-secret>

Assuming your domain name for UniFi is unifi.mydomain.org (and mydomain.org is the domain you provided the API credentials for), this should generate a request and issue a certificate:

~/.acme.sh/acme.sh --issue --dns dns_gd -d unifi.mydomain.org

The certificates are copied into the ~/.acme.sh/unifi.mydomain.org directory. The structure of the .acme.sh directory is subject to change, so we want to avoid using these files directly. Instead, we will ask acme.sh to copy the files to the UniFi application directory for further manipulation. So, let us look at the script that will do all that for us.

Make an Installation Script

Create the following script at ~/install_unifi_cert.sh. This is the script that acme.sh will run after it “installs” the certificates we just requested, and its job is to prepare the Java KeyStore and restart the UniFi service to load the new certificate. We will tell acme.sh to do those things after this script is ready. (Make sure you run acme.sh as the same user that ~ [the current user’s home directory] refers to. acme.sh cron jobs will run as that user.)

#!/bin/sh

unifi_pw="aircontrolenterprise"
unifi_root="/usr/lib/unifi"
unifi_keystore="$unifi_root/data/keystore"
unifi_newkey="$unifi_root/data/key.pem"
unifi_newcert="$unifi_root/data/cert.pem"
unifi_newchain="$unifi_root/data/fullchain.pem"
unifi_pkcs12="$unifi_root/data/unifi.pfx"

openssl pkcs12 -export \
-out $unifi_pkcs12 \
-inkey $unifi_newkey \
-in $unifi_newcert \
-name unifi \
-CAfile $unifi_newchain \
-caname root \
-password pass:$unifi_pw

keytool -importkeystore \
-deststorepass "$unifi_pw" \
-destkeypass "$unifi_pw" \
-destkeystore $unifi_keystore \
-srckeystore $unifi_pkcs12 \
-srcstoretype PKCS12 \
-alias unifi \
-srcstorepass "$unifi_pw" \
-noprompt

rm $unifi_pkcs12 \
$unifi_newkey \
$unifi_newcert \
$unifi_newchain

systemctl restart unifi

The unifi_* variables are set to where UniFi is installed by default on Ubuntu. The location is different on Red Hat and other distributions. You can change the unifi_root variable to point to a different location.

The openssl command creates an encrypted PKCS12 store that contains our certificate and private key, along with the CA chain for the certificate.

The keytool command decrypts the PKCS12 store and inserts the certificate into the existing Java KeyStore at $unifi_root/data/keystore. The self-signed certificate created by default when UniFi first ran has an alias of “unifi”, so this command will overwrite that certificate. Also note that in addition to encrypting the store overall, the command also encrypts the private key with a password, albeit the same one. This may be an overlooked detail or more recent feature that makes most instructions I see online fail.

After the certificates are processed and the Java KeyStore is updated, the PEM files that acme.sh copies from its .acme.sh directory and the PKCS12 store we made are no longer needed, so the script deletes them.

Finally, the script restarts the UniFi service with systemctl for the new certificate to take effect.

Make sure to mark your new script as executable with

chmod +x ~/install_unifi_cert.sh

Install the Certificate

To copy the certificate files to the UniFi data directory and run the above script, we run this:

~/.acme.sh/acme.sh --install-cert -d unifi.mydomain.org \
--cert-file /usr/lib/unifi/data/cert.pem \
--key-file /usr/lib/unifi/data/key.pem \
--fullchain-file /usr/lib/unifi/data/fullchain.pem \
--reloadcmd "~/install_unifi_cert.sh"

Use your domain instead of the example and, if your paths are different, make those changes as you did in the script. The --reloadcmd parameter will make acme.sh run our script after the certificates are copied. Note that acme.sh now “remembers” both where to copy the files and that it should run the script. It will do the copy and run the script automatically next time it updates the certificate, which will be a bit before expiration time. It has put that process into a cron schedule.

For the sake of simplicity, I did not put any validation code in my script. If your UniFi environment is critical, you may want to add some return value checking and logging to the script.

Join the Conversation

3 Comments

  1. i did above steps: result:
    ERR_SSL_VERSION_OR_CIPHER_MISMATCH
    Unsupported protocol
    The client and server don’t support a common SSL protocol version or cipher suite.

    no errors in script log.
    What could have gone wrong here.

  2. –keylength 2048 is required to add to acme.sh to force RSA key.

Leave a comment

Your email address will not be published. Required fields are marked *