This guide shows how to restrict access to Raspberry Pi web server using server-side and client-side certificates, dynamic DNS and port forwarding.
On the client-side, HTTPS guarantees the identity of the web server.
From the web server, the client-side cert guarantees the device is authorized.
This is also called mutual SSL authentication.
Required:
Raspberry Pi running latest version of Raspberry Pi OS
username = pi
password = ♣raspberry-pi-password♣
With a working Apache2 web server on port 80 (http) and port 443 (https)
SSH enabled
python3
Step 1. Open Terminal and Login to Server Side Raspberry Pi
Run the commands:
$ cd ~/certs
$ pwd
/home/pi/certs
$ hostname
♣hostname♣
Step 2. Ensure Web Server is running
Open a browser on MacBook, and enter the URI:
♣hostname♣.local
You should see your server. If not then go Setup Web Server, and return here when finished.
Step 3. Dynamic DNS
Certs cannot be applied to IP addresses. A dynamic DNS name is needed for your web server.
You'll need this
♣dynamic-dns-name♣
Step 4. Setup Certificate Authority
Before creating server/client certificate, setup a self-signed Certificate Authority (CA), which can be used to sign the server/client certificates. Once created, the CA cert will act as the trusted authority for both your server and client certificates (or certs).
Complete the steps in the link and then return here. After the steps in the link, you should have:
♣cacert♣ = /home/pi/certs/ca-crt.pem
♣cakey♣ = /home/pi/cert/ca-key.pem
♣caconf♣ = = /etc/ssl/openssl.cnf
♣ca-password♣ = whatever password you enter
♣PEM-pass-phrase♣ = whatever password you enter
Step 5. Download Server-side Script
This step requires the previous steps to be complete.
Scripting cuts out many steps and sub-tasks.
Get the script.
$ cd ~/certs # /home/pi/certs was created during CA Setup
$ pwd
Note: There should be no need to wget private-data.sh because it was downloaded during the Certificate Authority Setup. Also, there should be no need to edit private-data.sh.
$ wget https://raw.githubusercontent.com/dumbo25/garage-door/main/home/pi/certs/server.sh
Once the script is downloaded, execute the script:
$ sudo bash server.sh
Take the defaults. You'll need to enter:
♣PEM-pass-phrase♣
♣key-pass-phrase♣
♣export-password♣
Step 6. Download Client-side Script
The client script can be run multiple times; once for each device connecting to the web server
Get the script.
$ cd ~/certs
Note: There should be no need to wget private-data.sh because it was downloaded during the Certificate Authority Setup. Also, there should be no need to edit private-data.sh.
$ wget https://raw.githubusercontent.com/dumbo25/garage-door/main/home/pi/certs/client.sh
Once the script is downloaded, execute the script:
$ sudo bash client.sh
Take the defaults. You'll need to enter:
♣device-or-user-name♣ for example, bobs-iphone, bobs-mac
♣PEM-pass-phrase♣
♣key-pass-phrase♣
♣export-password♣
Overview of the script server.sh and client.sh:
The scripts will do the following:
check if required steps have been met (e.g., has certificate authority has been created?)
generate server key, request and cert
♣server-crt♣ = /home/pi/certs/server-crt.pem
♣server-request♣ = /home/pi/certs/server-csr.pem
♣server-key♣ = /home/pi/certs/server-key.pem
generate client-side key, request, cert and p12 files
♣client-key♣ = /home/pi/certs/client-key.pem
♣client-request♣ = /home/pi/certs/client-csr.pem
♣client-crt♣ = /home/pi/certs/client-crt.pem
♣clientp12♣ = /home/pi/certs/client.p12
Step 7. Add cron to check when each Cert is expiring
If not already downloaded, get the script:
$ cd ~/certs
$ wget https://raw.githubusercontent.com/dumbo25/garage-door/main/home/pi/certs/cert-check.py
Edit cron to run at every day:
$ sudo crontab -e
# check every day if cert is expiring at 12:05pm plus 1 minute and send email if within 14 days of expiring
6 12 * * * python3 /home/pi/certs/cert-check.py -c "♣server-crt♣" -d 14
7 12 * * * python3 /home/pi/certs/cert-check.py -c "♣client-crt♣" -d 14
Step 8. Port Forwarding
In general, your gateway should block all in-coming requests.
To access your web server, your gateway needs to forward port 443 (https) to your web server in this link
Do not port forward port 80 or HTTP
Step 9. Require client-side cert by Apache
We’ll need to specify our CA cert in Apache since it is a self generated CA and not one that is included in operating systems everywhere.
Edit the apache2 configuration file:
$ sudo nano /etc/apache2/sites-available/default-ssl.conf
change the following lines to:
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
#SSLCACertificatePath /etc/ssl/certs/
#SSLVerifyClient require
#SSLVerifyDepth 10
to:
SSLCertificateFile ♣server-crt♣
SSLCertificateKeyFile ♣server-key♣
SSLCACertificatePath /home/pi/certs/
# the following line enables client verification - this must be done !
SSLVerifyClient require
SSLVerifyDepth 2
Activate the SSL module in Apache.
$ sudo a2enmod ssl
Activate the SSL site in Apache
$ sudo a2ensite default-ssl
Disable the HTTP site
$ sudo a2dissite default
Restart Apache to activate the SSL module:
$ sudo systemctl restart apache2
Check for configuration errors:
$ sudo apache2ctl configtest
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 0.0.0.0. Set the 'ServerName' directive globally to suppress this message
Syntax OK
If you get the error above, do the following:
$sudo nano /etc/apache2/apache2.conf
add this line to the end of the file
ServerName 127.0.0.1
Run these commands and now it should be okay
$ sudo systemctl restart apache2.service
$ sudo apachectl configtest
Syntax OK
Reload and restart apache2
$ sudo systemctl reload apache2
$ sudo service apache2 restart
Right now if you visit your https site, you will get an SSL error similar to “SSL peer was unable to negotiate an acceptable set of security parameters.” That is good – it means your site won’t accept a connection unless your browser is using a trusted client cert. We’ll generate one now.
Brief explanation of changes made to the Apache2 configuration file
SSLCertificateFile: This directive points to a file with certificate data in PEM format.
SSLCertificateKeyFile: PEM-encoded private key file for the server.
SSLCACertificatePath: The directory where Certification Authorities (CAs) certificates are kept for the clients. The directory must contain PEM-encoded files that are \accessed through hash filenames. The files should have a symbolic link named hash-value.N.
SSLVerifyClient: Enables verification of the client by the web server
SSLVerifyDepth: Depth is the maximum number of intermediate certificates. 0 means only self-signed client certificates are accepted. 1 means the client certificate can be self-signed or signed by a CA known to the server (i.e. the CA's certificate is under SSLCACertificatePath).
Step 10. Disable HTTP in Apache
Edit the Apache2 ports file:
$ sudo nano /etc/apache2/ports.conf
Comment out these lines:
# NameVirtualHost *:80
# Listen 80
And these lines should be added or exist:
<IfModule ssl_module>
Listen 443
</IfModule>
To view my ISP's gateway, I enter 192.168.1.254 in a browser's URL. To find the gateway's public IP address, I navigate to the Broadband, and Status tabs. The Public IP address is Broadband IPv4 Address.
Restart Apache
$ sudo service apache2 restart
Step 11. Disable HTTP on Gateway
On MacBook, open browser and enter: ♣gateway-ip-address♣. My gateway's IP is 192.168.1.254.
Login to Gateway.
Go to Firewall tab and then Packet Filter
One of the filters drops all port 80 traffic
Step 12. Add Client-side Certificate to MacBook
Note: If the form of the output is not specified, then openssl gets the form from the extension name (e.g., p12, pem, or pfx).
Import the ♣clientp12♣ file into your browser.
To copy ♣clientp12♣ from the Raspberry Pi to a Mac, open a new terminal window and enter the command:
On MacBook, run:
$ scp pi@♣hostname♣:/home/pi/♣clientp12♣ .
On the Raspberry Pi, run the commands:
$ sudo chmod 600 ♣certname♣-client.p12
On the MacBook, open a finder window and go to /Users/♣your-username♣/Desktop
Click ♣clientp12♣ to import into the operating system’s keystore
Enter password from above
Open Keychain Access
Double click on ♣clientp12♣
In the pop up window, click on Trust
Change dropdown to Always Trust
Close the popup window
Quit Keychain Access
Step 13. Get MacBook browsers to Trust the Certificate
To get Safari to trust the certificate
Open Safari
Enter URL of web server
This message appears: This connection is not private
Click Show Details
Click visit this website
Visit the website and modify trust settings
Use MacBook password and Always Allow
To get Chrome to trust the certificate
On the Raspberry Pi:
$ cp /home/pi/certs/♣ca-crt♣ /home/pi/.
$ sudo chmod +r home/pi/certs/♣ca-crt♣
On the MacBook:
$ scp pi@hostname:/home/pi/♣ca-crt♣ .
Then open a finder window, go to Desktop and click on ♣ca-crt♣
And mark the certifcate as Always Trust
Open Chrome
Go to the URL of web server. The Common Name is invalid because there is no FQDN for the server. It takes chrome a long time! You should see something like:
NET::ERR_CERT_COMMON_NAME_INVALID
This is because it is a self-signed cert, and the name isn't really an FQDN to your web server, but to your gateway.
Click Proceed to site (unsafe)
Note: I needed to delete a bunch of my certificates in Keychain Access on my MacBook created using old methods before this would work. I got the message: NET::ERR_CERT_INVALID. Also, re-downloading the ca-crt.pem was needed.
Firefox
Open Firefox
Enter URL of web server
Click Advanced
Accept the risk and continue
Note: In Firefox, the certificate can be added via: Preferences, Privacy & Security, Certificates, View Certificates, Import ♣client♣.p12
Step 14. Add Client-side Certificate to Smart Phones
iPhone:
The iPhone wants a file with a pfx extension, so run these commands on the MacBook
$ cd ~/Desktop
$ scp pi@hostname:/home/pi/♣clientp12♣ .
$ scp pi@hostname:/home/pi/♣ca-crt♣ .
For Apple phones with iCloud, the certs should be on your MacBook's Desktop
Go to Files on iPhone and do the following for ♣ca-crt♣
Find ♣ca-crt♣ and click on it.
Profile Downloaded ...
Close pop up
Go to Settings, Profile Downloaded
Click Install and follow the directions
Go to Settings, About, Certificate Trust Settings
Enable full trust for ♣ca-crt♣
Go to Files on iPhone and do the following for ♣clientp12♣
Find ♣clientp12♣ and click on it.
Profile Downloaded ...
Close pop up
Go to Settings, Profile Downloaded
Click Install and follow the directions
Open safari and go to the URL
If you get something like data;text in the URL box or it flickers, then clear cache and website data:
Settings, Safari, Clear History and Website Data
Notes: if the URL fails go to the public IP address (https://public-ip)
Android (not tested):
For Android phones, the browser must be Chrome.
Email ♣certname♣-client.p12 as an attachment to your device.
Open the email on the Android phone and save the attachment to downloads
Go to home screen and open Settings → Security → Credential Storage → Install from device storage → Open the ♣certname♣-client.p12 file
Enter pass phrase: ♣cert-password♣
Step 15. Try it!
Try accessing the web server with and without Wi-Fi enabled from Smart Phone.
With Wi-Fi, you should go to local address, and without Wi-Fi you should go through the mobile network to the domain name
Background [skip]:
Certs are one way to secure communication between servers and clients.
A self-signed cert requires a Certificate Authority (CA). The CA is used to generate a web server cert and any client certs that are approved to connect to the web seerver.
There are several problem with self-signed certs.
Need Dynamic DNS: One problem is my web servers port address translation (or port forwarding). My ISP's router has a public IP address and devices on my home network have a private IP address in the range: 192.168.0.0/24. So, the packets coming from the internet need to have the public IP address forwarded one or more of the private IP addresses.
The ISP router's IP address is in a different IP range (102.xxx.xxx.xxx). I use a dynamic DNS name to point at my router's public IP address, which allows me to connect to my gateway from anywhere on the internet and not need to remember the public IP address. I use port forwarding on my ISP router to get valid inbound, unsolicited packets to my web server.
Browsers and Smart Phones: Even if I do everything correct when I generate a self-signed cert, a second problem arises because browsers and Smart Phones don't recognize a home Certificate Authority, preferring Global Sign, Let's Encrypt or other public CAs. So, even if I create a valid self-signed cert, a browser or smart phone is going to give me grief.
Browsers and smart phones are continually updating security. When I first created these steps, a cert could be created without using sha1 and it could last for 10 years. Now, sha256 or better is recommended and certs shouldn't exceed 365 days.
FQDN: And a third problem is servers on my home network do not have a Fully Qualified Domain Name (e.g., www.google.com), and so they cannot be added to DNS. My dynamic DNS domain only goes to my ISP's router.
Certs prevent everyone on the net from accessing my web servers. Any inbound packets, valid or invalid will be forwarded to my web server, but only if they are using the correct port. The web server has several security mechanisms in place, but the cert is critical. Only devices with a valid cert can access my web servers.
Each web browser has its own certificate store. Self-signed certs must be imported to the browser's certificate store.
But, I'd still like only people with a valid cert to be able to use my web servers.
References:
Browsers and phones are constantly updating their security. Steps that may have worked in the past may fail because of enhanced security. When googling a solution always use Tools, and set Anytime to Past Year.
Mini Tutorial Client-Side Certs
Apache Documentation:
$ cp /usr/share/doc/apache2/README.Debian.gz .
$ gunzip README.Debian.gz
How to create a self-signed cert, TechRepublic
How to create a self-signed cert, DigitalOcean
v3 Certificate Extension Configuration Format
Apache SSL WebDav Server by koff1979
Further reading on SSL and Certificates
Step-by-Step SSL Client Authentication
Linux Babe: VPN cert authentication - couldn't import .p12 to MacBook
Troubleshooting:
Read contents of cert
$ openssl x509 -text -in cert.pem
iPhone profiles: Settings, General, Profiles
Test a cert
$ openssl s_server -cert mycert.pem -key mykey.key
Check Subject of cert file:
$ openssl x509 -noout -subject -in /etc/ssl//certs/♣certname♣.pem
To check if apache2 is running
$ sudo systemctl status apache2
To check Certificate Authority, server and certs:
$ openssl verify -CAfile ♣certname♣-server-ca.pem ♣certname♣-server.pem ♣certname♣-client.pem
Check for Apache2 configuration errors:
$ sudo apache2ctl configtest
To delete a certificate from MacBook:
Open Applications, Utilities, Keychain Access
Scroll down to certificate
<CTRL> click on the certificate and select Delete from the drop down
Run from laptop connected to home LAN
$ curl -iv https://♣hostname♣.local
$ openssl s_client -connect ♣hostname♣.local:443 -prexit -debug
Run from laptop not on home LAN
$ curl -iv https://♣dynamic-dns-name♣.duckdns.com
$ openssl s_client -connect ♣dynamic-dns-name♣.duckdns.com -prexit -debug
How's my SSL? go to this page from your Raspberry Pi desktop browser.
Some useful commands for troubleshooting certs. There are too many things that can be wrong to describe how to interpret the results of the commands. FeistyDuck provides useful descriptions of how to debug.
Inspect MacBook keychain
Open Finder window, select Applications, select Utilities, double-click on Keychain Access
From a MacBook Terminal window, run the command:
$ openssl s_client -connect ssl.♣hostname♣.local:443
From a MacBook Terminal window, run the command:
$ openssl s_client -port 443 -CApath /usr/share/ssl/certs/ -host ♣hostname♣.local -prexit
From a MacBook Terminal window, run the command:
$ openssl s_client -connect ♣hostname♣.local:443 -CAfile ♣cacert♣
Logged into a Raspberry Pi, run the command:
$ sudo apt-get install ssldump -y
Is this useful, should it be on the MacBook?
https://maulwuff.de/research/ssl-debugging.html
Alternative to openssl certtool
$ sudo apt-get install gnutls-bin
Get certutil
$ sudo -get install libnss3-tools