Home
Summary¶
Client Side Authentification is the counterpart to Server Authentication in HTTPS, together making Two-way Authentication. Using a private CA (Certificate Authority) to issue both Server and Client certificates is a sensible approach. This document provides a step-by-step example where lighttpd v1.4.45 is server, and Firebox Browser v65.0 is client.
Example setup with lighttpd and Firefox¶
Specs for the certs¶
We describe the certs and what fields they must contain. A table is used to show what role they play in authentication.
We need one private root and two leaf certificates with the following specifications:
Table 1: Cert specifications¶
We will show step by step how to create these certs using our simple script file privca
.
Note
Of course you can use any method or tools to create the certs as long as they fulfill the above specifications. If you do so you may proceed directly to the step by step guide to certificate installation on and configuration of Firefox and lighttpd.
Overview of each cert role¶
The combined key+cert files shown in the bottom row of table 1 are composed as follows:
Table 2: Combined certs¶
Key Part source file | Cert Part source file | Combined File | |
---|---|---|---|
Server | Server1.key | Server1.crt | Server1.key-crt.pem |
Client | Client1.key | Client1.crt | Client1.p12 |
The next table shows to where the files will eventually be exported (server side or client side) and the role they will play (authenticator or authenticatee):
Table 3: Authenticatee/Authenticator Roles¶
Authenticator | Authenticatee | Server side file | Client side file |
---|---|---|---|
Client | Server | Server1.key-crt.pem | Root1.crt |
Server | Client | Root1.crt | Client1.p12 |
Example of making certs with privca
¶
Step by step guide to creating the certs with this simple script on gist.
certs step 1¶
On your main workstation, create a directory named Root1 which will contain the private CA and all the leaf certificates that private CA issues.
Then cd
to that directory. E.g., /etc/ssl/privca.d/Root1
sudo mkdir /etc/ssl/privca.d/Root1 cd /etc/ssl/privca.d/Root1
certs step 2¶
Copy the privca shell script to that directory, (or place it somewhere in your path). Make it executable.
Click to see `privca` script:
wzxhzdk:1or you can download it from gist directly with
wget https://gist.githubusercontent.com/craigphicks/c9dae527b30441730f62c9c9e9dab5a1/raw/dfd43fff631850e6978a6769f82eba76ef6abe60/privca.sh mv privca.sh privca
Make it executable:
sudo chmod +x privca
Note
For your reference there is a man page for privca.
Although it is not necessary for this example.
certs step 3¶
Create the CA cert.
./privca CreateCA Root1 MyOrg
certs step 4¶
Create the Server cert.
./privca CreateServer PiSrv DNS:pihole.home.lan,DNS:pihole,IP:192.168.1.20
Note: here it is assumed that you already have local DNS functionality to recognize pihole.home.lan and pihole. Test any address, including the IP, with ping
. If not you might be able to use your /etc/hosts
file to link the name and address for DNS.
certs step 5¶
Create the Client cert. Prepare a password to set for the .p12
certificate. You will need to use it again when uploading the certificate to Firefox browser. Hit return only to set no password.
./privca CreateClient Client1 <interactively enter password>
certs step 6¶
Confirm the diretory tree looks like this:
# tree . ├── ca │ ├── private │ │ └── Root1.key │ └── public │ └── Root1.crt ├── export │ ├── private │ │ ├── Client1.p12 │ │ └── Server1.key-crt.pem │ └── public │ └── Root1.crt -> ./ca/public/Root1.crt ├── private │ ├── Client1.key │ └── Server1.key ├── public │ ├── Client1.crt │ └── Server1.crt └── temp ├── Client1.csr └── Server1.csr 9 directories, 11 files
In our example configuration the files under the export directory will be used for configuration.
Install certs on Firefox¶
From Firefox -
Click through
Preferences | Privacy & Security | View Certificates | Authorities | Import
to upload
./export/ca/public/Root1.crt
Then click through
Preferences | Privacy & Security | View Certificates | Your Certificates | Import
to upload
./export/private/Root1.p12
When uploading, Firefox will ask you for the password you set when creating it, if any.
Install certs and configure for lighttpd
¶
Note
Our summary of relevant lighttpd documentation is here, although it is not necessary for following this step by step guide.
install step 2¶
Copy files to the serving device running lighttpd -
Source | Dest Dir |
---|---|
./export/ca/public/Root1.crt | /etc/lighttpd/ssl/public/ |
./export/private/Server1.key-crt.pem | /etc/lighttpd/ssl/private/ |
(The destinations can be freely chosen, this is just an example).
Set the destination owner and permission as follows -
Dir or File | owner:group | perm |
---|---|---|
/etc/lighttpd/ssl/public/ | root:www-data | 755 |
/etc/lighttpd/ssl/public/Root1.crt | root:www-data | 644 |
/etc/lighttpd/ssl/private/ | root:www-data | 750 |
/etc/lighttpd/ssl/private/Server1.key-crt.pem | root:www-data | 640 |
These settings allow read access by www-data
when serving.
install step 3¶
Configure an existing lighttpd configuration file where it configures the https port 443.
This might be in a file /etc/lighttpd/external.conf
.
In the case that lighttpd is already configured for https one-way authentication, then modify/add the following parameter settings to achieve our two-way authentication:
$SERVER["socket"] == ":443" { ... ssl.pemfile = "/etc/lighttpd/ssl/private/Server1.key-crt.pem" ssl.ca-file = "/etc/lighttpd/ssl/public/Root1.crt" ... ssl.verifyclient.activate = "enable" ssl.verifyclient.enforce = "enable" ssl.verifyclient.depth = "2" ssl.verifyclient.username = "SSL_CLIENT_S_DN_CN" }
In the case that lighttpd is not yet configured for https one-way authentication, then here is an example of settings for https two-way authentication:
$HTTP["host"] =~ "pihole($|\.home\.lan)" { # Ensure the Pi-hole Block Page knows that this is not a blocked domain # PIHOLE APPLICATION SPECIFIC - ignore otherwise #setenv.add-environment = ("fqdn" => "true") # Enable the SSL engine with a LE cert, only for this specific host $SERVER["socket"] == ":443" { ssl.engine = "enable" ssl.pemfile = "/etc/lighttpd/ssl/Server1.key-crt.pem" ssl.ca-file = "/etc/lighttpd/ssl/public/Root1.crt" ssl.honor-cipher-order = "enable" ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH" ssl.use-sslv2 = "disable" ssl.use-sslv3 = "disable" # client side authentification ssl.verifyclient.activate = "enable" ssl.verifyclient.enforce = "enable" ssl.verifyclient.depth = "10" ssl.verifyclient.username = "SSL_CLIENT_S_DN_CN" ### ssl.verifyclient.username = "SSL_CLIENT_S_DN_emailAddress" } # Redirect HTTP to HTTPS $HTTP["scheme"] == "http" { $HTTP["host"] =~ ".*" { url.redirect = (".*" => "https://%0$0") } } }
Note: The above two-way setting were adapted from [these one-way settings using an LE cert] (https://discourse.pi-hole.net/t/enabling-https-for-your-pi-hole-web-interface/5771).
install step 4¶
Now create a new additional lighttpd configuration file
sudo nano /etc/lighttpd/conf-available/02-auth-cert.conf
with content
# comment out the next line to silence warnings if "mod_auth" already loaded server.modules += ("mod_auth") auth.require = ( "" => ( "method" => "extern", "realm" => "certificate", "require" => "user=Client1" ) )
Note: To allow multiple client IDs, separate by |
and prefix each ID with user=
, e.g.,:
"require" => "user=Client1|user=Client2"
install step 5¶
Restart the lighttpd daemon -
systemctl restart lighttpd
or
service lighttpd restart
Check the status is OK -
systemctl status lighttpd
or
service lighttpd status
install step 6¶
Test access of the server from the Firefox browser, e.g., enter the address pihole.home.lan
or 192.168.1.20
into the address bar. On the first access Firefox will put up a dialog box for you to confirm the client certificate Client1.p12
. If you don't see the dialog box hunt around for it. I once found it in another workspace under an already existing window.
install step 7¶
Add more clients and servers to the network using the same CA, if required.
Summary of lighttpd documentation on two-way authorization¶
A step by step guide to configuring lighttpd with the certs you created is given in given above in Install steps 2 through 4. This section is only for reference.
The lighttpd SSL documentation describes the relevant parameters for one-way authentication:
Option | Description |
---|---|
ssl.engine | enable/disable ssl engine |
ssl.pemfile | path to the PEM file for SSL support (must contain both certificate and private key) |
ssl.ca-file | path to the CA file for support of chained certificates |
These additional parameters for two-way authentication, i.e. "Client Side Verification", are also described:
Option | Description |
---|---|
ssl.verifyclient.activate | enable/disable client verification |
ssl.verifyclient.enforce | enable/disable enforcing client verification |
ssl.verifyclient.depth | certificate depth for client verification |
ssl.verifyclient.username | client certificate entity to export as env:REMOTE_USER (eg. SSL_CLIENT_S_DN_emailAddress, SSL_CLIENT_S_DN_UID, etc.) |
Setting
ssl.verifyclient.username = SSL_CLIENT_S_DN_CN
will make the "Common Name" of the client certificate available.
We want to use this "Common Name" for authentification in the lighttpd server itself, and not just pass the value to an application. The Mod_Auth documentation describes how to do this:
auth.require = ( "" => ( "method" => "extern", "realm" => "certificate", "require" => "user=agent007|user=agent008" ) )
TLDR: Special Note on the parameter ssl.ca-file
The documentation states purpose as "path to the CA file for support of chained certificates". That statement doesn't match with the roles show in table Authenticatee/Authenticator Roles Probably this sentence was written thinking only of the case Server Authentication, where the server is the Authenticee holding a leaf secret-key & cert, and the client is the Authenticator holding the root CA public cert.
In that case, and when when there is an intermediate certificate between the Server's held cert and the client's held root CA cert, purely as a matter of convenience the server will hold and pass the intermediate to the client as part of the SSL handshare. And to make it even more convenient, the server will hold a copy the root which the client should already have a copy of. It is necessary the client use their own trusted copy of the root CA for final confirmation.
The intermediate plus root cert is usually bundled together in file named shomething like "blahblah-fullchain.pem", and ssl.ca-file
is set to point to that file on the server.
When there is no intermediate certificate involved,
and the client held root CA cert has directly issued the server held key+cert,
the server is not required to hold the root CA to pass to the client,
Server Authentication can succeed with parameter ssl.ca-file
left unset.
(Tested by the author.)
On the other hand, in the case of Client Side Authentification, the parameter ssl.ca-file
must point to a file holding the CA root certificate at the top of the trust chain which has issed the client's leaf certificate. This is a logical necessity.
The author has only tested these cases:
- Server Authetication only, with LE 3-level cert.
- Server Authetication only, with private 2-level cert.
- Server and Client Side Authentication, both with private 2-level certs, both issued by the same CA.
Specifically the author hasn't tested other cases where multiple mixed roots and intermediates all must be present in the file pointed to by ssl.ca-file
. However the lighttpd documentation does specifically say it is possible.