Post

Setup a Local Certificate Authority (CA) for your Development Environment

Stop Clicking "Advanced" to accept a self signed cert. Use a custom root CA certificate.

Setup a Local Certificate Authority (CA) for your Development Environment

Have you been handed a root CA cert and need to get your browser to trust it? On a Linux system there are a few things to consider, depending on how your browser was installed.

The problem to solve

Whenever loading a local site (e.g. test-env.local), you get a warning that the site is not secure. These kind of warnings and errors are common when using a self signed SSL/TLS cert.

Think of a SSL/TLS cert like a passport. What would happen when the authority figure in front of you checked your passport and saw it was issued by Mypos? Since Mypos is a fictional nation, you will most likely be detained. Explaining you are royalty of Mypos won’t help… Unless you create the nation of Mypos (i.e. the root CA cert) to show that your passport (i.e. SSL/TLS cert) is legit. Once you create a root CA cert for signing your self signed SSL/TLS cert, you will not get any warnings.

The location where the cert is to be stored varies depending on how the browser was installed. On Ubuntu, when you run apt install firefox the installation is really done by snap. Snap packages are normally sandboxed and cannot access files in your home directory. So standard installation of a root CA cert will not be discovered.

The solution, add the root CA certificate

Add the cert in the correct location. If you want to do this through the GUI, you can find out how on many other sites. Scripting is the way I am solving it. Below are a few examples of where I found the root CA cert databases. Chromium based browsers use an NSSDB directory and Firefox has cert files.

  • $HOME/.pki/nssdb (default location)
  • $HOME/snap/chromium/current/.pki/nssdb
  • $HOME/snap/firefox/common/.mozilla/firefox/ (look for cert8.db or cert9.db)

A script that will setup the CA cert for browsers

Rather than remember all of the places, script it. The script below accounts for different ways the browser may have been installed. It should work for most users but might miss a few cases. If this script doesn’t work for you, hopefully this helps you make progress toward a solution.

If you are on a system that does not use apt, be sure libnss3-tools is installed so you can run the certutil command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/bin/env bash

# This script adds your custom root CA cert to a Debian
# based Linux system for Firefox and Chromium-based web
# browsers.
#
# Once this is added, your self signed certs will no
# longer present warnings or errors when visiting your
# local site (e.g. my-test-environment.local).

#########################################################
# Configure variables
#########################################################

# Path to the root CA certificate file.
cert_path="$HOME/.config/devcert/certificate-authority/certificate.cert"

# The name used for the certificate in the trust store.
cert_name="devcert-root-ca"

#########################################################
# Validation
#########################################################

# Check if the certificate file exists.
if [ ! -f "$cert_path" ]; then

 echo "Error: Certificate not found at $cert_path"
 echo "Please run your devcert generation command first."

 exit 1
fi

#########################################################
# Prerequisites
#########################################################

# Check if certutil is installed, else install it.
if ! command -v certutil &> /dev/null; then

 echo "Installing libnss3-tools (required for cert management)..."
 sudo apt update && sudo apt install -y libnss3-tools

else

 echo "libnss3-tools is already installed."

fi

#########################################################
# System-Wide Trust
#########################################################

echo "Configuring System-wide Trust..."

# Copy the certificate to the system ca-certificates directory.
sudo cp "$cert_path" "/usr/local/share/ca-certificates/$cert_name.crt"

# Update the system certificate store.
sudo update-ca-certificates

echo "System-wide trust updated."

#########################################################
# Firefox Configuration
#########################################################

echo "Checking for Firefox..."

# Check if a Firefox configuration directory exists and get the directory it lives in.
firefox_config_dir=$(dirname $(find ~ -name profiles.ini | grep firefox));

# Additional layer of certainty that we are dealing with a dir.
if [ -d $firefox_config_dir ]; then

    echo "Firefox detected. Scanning profiles..."

    # Find and loop through all Firefox certificate databases to apply the certificate.
    find "${firefox_config_dir}" -name "cert9.db" -o -name "cert8.db" | while read -r dbfile; do

        # The directory containing the certificate database.
        dbdir=$(dirname "$dbfile")

        echo "Trusting in profile: $dbdir"

        # Import the certificate into the database.
        certutil -d "sql:$dbdir" -A -t "C,," -n "$cert_name" -i "$cert_path"

    done

    echo "Firefox profiles updated."

else

     echo "Firefox not detected. Skipping."

fi

#########################################################
# Chromium-based Browsers
# 
# Currently checks for Google Chrome, Chromium, or Edge.
#
# Depending on the OS and how the browser was installed
# the location of the cert will vary.
#########################################################

echo "Checking for Chromium-based browsers..."

# Check if a Chromium-based browser is installed. There are other
# Chromium-based browsers that are not included. Some popular
# ones are targeted.
if command -v google-chrome &> /dev/null || command -v chromium-browser &> /dev/null || command -v microsoft-edge &> /dev/null; then
    
    echo "Chromium-based browser detected."

    # Update the core/shared NSSDB in the users home directory.

    # Possible paths to the shared NSSDB used by Chromium browsers.
    nssdb_dirs=(
        "$HOME/.pki/nssdb" # User's core nssdb.
        "$HOME/snap/chromium/current/.pki/nssdb" # Chromium installed via snap.
    );

    # Loop over each possible Chromium-based NSSDB dir.
    for nssdb_path in "${nssdb_dirs[@]}"; do

        # Exclude ".pki/nssdb" to get the base path.
        nssdb_base_path="${nssdb_path%/.pki/nssdb*}"

        # Check if the base path exists. If a browser is not installed the
        # base path will not exist and can be skipped.
        if [ -d $nssdb_base_path ]; then

            # Create the NSSDB directory if it does not exist.
            mkdir -p "$nssdb_path"

            echo "Trusting in NSSDB: $nssdb_path"

            # Import the certificate into the shared database.
            certutil -d "sql:$nssdb_path" -A -t "C,," -n "$cert_name" -i "$cert_path"

        fi

    done;

        echo "Cert added for Chromium-based browsers."

else

    echo "No Chromium-based browser detected. Skipping."

fi

echo "Done! Please restart your browsers for changes to take effect."

Image attribution

This work by Jason Raveling is licensed under CC BY-ND 4.0 .