====== Self-hosted Root CA using OpenSSL ======
Do you hate how modern web browsers complain about using HTTP as if it's actually insecure to read plain text? It's extremely dumb to think that self-hosted infrastructure is insecure and must be accessed via HTTPS, especially when it's only accessible locally or via a VPN, but here we are.
Modern browsers block these "insecure" websites from accessing your camera, microphone, and even the clipboard, and some of these features may be important for some homelab applications. Thus the need for you becoming your own Root CA arrises.
===== Building the Certificate Authority =====
This is how you can run your own CA infrastructure easily using only the default install of [[https://www.openbsd.org/|OpenBSD 7]]. Unless otherwise noted all steps taken here assume the root folder of the CA resides in the ''/ca'' directory at the root of your system drive.
==== Creating the Folder Structure ====
A Trusted Root Certificate Authority is a serious business, so you should be serious about the organization of your operation, for this I have created the following folder structure to ensure the CA infrastructure that is built is actually scalable and organized:
mkdir /ca # The root of our CA infrastructure.
mkdir /ca/root # Stores the Root Certificate.
mkdir /ca/certs # Stores the self-signed domain certificates we issue.
mkdir /ca/utils # Contains utility scripts to manage our operation.
If you're following this guide, this folder structure is expected to be in place.
==== Getting a Root Certificate ====
The root certificate will be responsible for creating the chain of trust of all your issued certificates. It's also **the only one that will have to be installed** in your clients to allow them to accept your self-signed certificates for all your locally-hosted websites. To create a root certificate you'll have to do the following:
cd /ca/root
openssl genrsa -des3 -out root.key 2048
openssl req -x509 -new -nodes -key root.key -sha256 -days 3650 -out root.pem
I won't pretend to understand exactly the implications of my decisions here in terms of parameters, but this should create a root certificate that will be good for 10 years.
==== Generating OpenSSL Configuration ====
The next step in achieving your status as a Trusted Certificate Authority is to create the OpenSSL configuration files used when generating new certificate requests. The OpenSSL certificate creation process must always go through a signing request procedure, even if you're doing so on the same machine.
This configuration file is the base of every certificate we generate and most of its contents are the same for every new request, so I have built this handy Perl script that takes care of the configuration file generation for you:
#!/usr/bin/env perl
# Save this script to /ca/utils/make-config.pl
=head1 DESCRIPTION
Builds the OpenSSL configuration file to request a certificate for a domain.
=cut
use warnings;
use strict;
use autodie;
use File::Basename;
# Main entry point.
sub main {
if (scalar(@ARGV) != 1) {
die "The domain name of the certificate must be provided.\n";
}
# Get the domain name.
my $domain = $ARGV[0];
# Remind the user that this tool always generates wild card certs.
if (substr($domain, 0, 1) eq '*') {
die "This tool generates wildcard certificates. Remove *.\n";
}
# Make the folder and write the configuration file.
my ($cldomain, $folder) = make_folder($domain);
open (my $fh, '>', "$folder/openssl.conf");
print $fh build_config($domain);
close $fh;
print "$cldomain\n";
}
# Creates the folder for the certificate and config, and returns its path.
sub make_folder {
my ($domain) = @_;
my $cld = $domain;
# Get the path to the certificate folder and make it.
my $folder = dirname(dirname(__FILE__)) . "/certs/$cld";
mkdir $folder;
return ($cld, $folder);
}
# Builds the configuration for the certificate.
sub build_config {
my ($domain) = @_;
return <<"CONFIG";
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = XX
ST = Your State
L = Your Neighbourhood
O = Your Totally Legit Company
OU = CA
CN = *.$domain
[v3_req]
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = \@alt_names
[alt_names]
DNS.1 = $domain
DNS.2 = *.$domain
CONFIG
}
main();
__END__
=head1 AUTHOR
Nathan Campos
=head1 COPYRIGHT
Copyright (c) 2024- Nathan Campos.
=cut
You should edit the contents of the configuration template just below the ''[req_distinguished_name]'' line to suit your environment. Be sure to be clever when populating these values in.
This script should be placed in the ''/ca/utils'' directory with the name ''make-config.pl'' so that the certificate issuing script can make use of it.
==== Issuing Certificates ====
Issuing a certificate using OpenSSL is a painful process and extremely easy to get wrong or simply get bored of typing a bunch of commands for something that you just want to get over with. Just like I did with the configuration file, here's a shell script that should automate the whole process for you:
#!/usr/local/bin/bash
# Ensure we stop on error.
set -e
# Ensure we have the right amount of command-line arguments.
if [[ $# -ne 2 ]]; then
echo "usage: $0 " >&2
exit 2
fi
# Create certificate directory and configuration file.
echo "Creating the configuration for the certificate request..."
CLDOMAIN=$(./utils/make-config.pl $1)
CERTDESC=$2
pushd "certs/$CLDOMAIN"
# Create the certificate request.
echo "Building private key and certificate request for $1 as $CLDOMAIN"
openssl genrsa -out "$CLDOMAIN.key" 2048
#openssl genrsa -des3 -out "$CLDOMAIN.key" 2048
openssl req -new -out request.csr -key "$CLDOMAIN.key" -config openssl.conf
# Create signed certificate.
echo "Signing certificate for 5 years..."
openssl x509 -req -in request.csr -CA ../../root/root.pem -CAkey ../../root/root.key \
-CAcreateserial -out "$CLDOMAIN.crt" -days 1827 -sha256 \
-extensions v3_req -extfile openssl.conf
# Packaging the certificate.
echo "Packaging the certificate in PKCS#12 format..."
openssl pkcs12 -export -out "$CLDOMAIN.p12" -inkey "$CLDOMAIN.key" -in "$CLDOMAIN.crt" -certfile "$CLDOMAIN.crt" -name "$CERTDESC"
# Extracting keys in SSH format.
echo "Extracting keys in SSH format..."
cat "$CLDOMAIN.key" | openssl rsa > id_rsa
openssl rsa -in id_rsa -pubout | ssh-keygen -f /dev/stdin -i -m PKCS8 > id_rsa.pub
# Check our certificate.
echo "Checking the generated certificate..."
openssl verify -CAfile ../../root/root.pem "$CLDOMAIN.crt"
openssl x509 -text -noout -in "$CLDOMAIN.crt" | head -14
echo "..."
openssl x509 -text -noout -in "$CLDOMAIN.crt" | grep DNS
popd
This script should issue certificates that are valid for 5 years and exports the public and private keys to every format that you may need, so the generated certificate can be used for web servers, SSH, and basically anything under the sun.
As can be inferred from the usage line in the script, it takes a domain and a description. The description is a bit of text that's associated with the certificate and can be viewed by visitors of your website. The ''domain'' argument should not contain a wildcard since the ''make-config.pl'' script should take care of this automatically for you.
===== Adding SSL to Your Servers =====
The next logical step is to ensure that your servers can actually serve content with your self-signed certificates.
==== Nginx Proxy Manager ====
I like my home servers to be as easy to configure and maintain as possible, not to be the most secure or follow enterprise-grade best practices, so I use the humble [[https://nginxproxymanager.com/|Nginx Proxy Manager]] as the [[https://en.wikipedia.org/wiki/Reverse_proxy|reverse proxy]] for all my self-hosted applications.
To add your self-signed certificates to your NPM instance you'll need to download the ''domain.crt'' and ''domain.key'' files from the CA server.
Inside NPM you'll navigate to the **SSL Certificates** tab and click **Add SSL Certificate** and select ''Custom''. Now all you have to do is fill in the form with the following:
* Name: Whatever you want
* Certificate Key: ''domain.key''
* Certificate: ''domain.crt''
The last step is to associate the newly added SSL certificate to the domain it belongs to in the Proxy Hosts page by selecting the proxy host you want to edit and going to the SSL tab.
===== Getting Clients to Trust You =====
Having your own CA is all well and good, but you need to ensure that your clients trust your self-signed certificates, for this you'll have to add the ''root.pem'' Root Certificate to the list of trusted sources in your clients, and this process is extremely different from system to system, and even may require you to add the certificate to individual applications such as web browsers.
==== Chromium and ChromeOS ====
On [[https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium|Chromium-based browsers]] and [[https://chromeos.google/|ChromeOS]] you can add your Root Certificate by navigating to the Certificate Manager located at [[chrome://certificate-manager/localcerts]] and in the **Custom** section click on the **Installed by you** card.
In the next window you should import the ''root.pem'' certificate file to the **Trusted Certificates** section. You should now be able to access your self-hosted services using the needlessly complex HTTPS.