For a while now I have been wanting to set up a certificate authority (CA) to address the certificate needs of users and services internal to our home network. Today seems to be as good as nay other to take a first pass at doing this.

For this, I will be relying heavily on OpenSSL and will be doing the setup on Gentoo, by the way.

Setting Up the CA steps:

Create Root CA Private Key Material

  • Create directory structure for root CA and change into it.
$ sudo mkdir -p /opt/projects/rootCA/{certs,crl,private}
$
$ tree -d /opt/projects/rootCA
/opt/projects/rootCA
├── certs
├── crl
└── private

3 directories
$
$ cd /opt/projects/rootCA
$
  • Create encrypted password file
$ sudo sh -c 'echo secret > RootCApass'
$ sudo  openssl enc -aes256 -pbkdf2 -salt -in /opt/projects/rootCA/RootCApass -out /opt/projects/rootCA/RootCApass.enc
$ sudo rm /opt/projects/rootCA/RootCApass
$
$ tree -sh /opt/projects/rootCA
/opt/projects/rootCA
├── [4.0K]  certs
├── [4.0K]  crl
├── [4.0K]  private
└── [  48]  RootCApass.enc

3 directories, 1 file
$
  • Create serial file
$ sudo sh -c 'echo 01 > serial'
$
$ tree -sh /opt/projects/rootCA
/opt/projects/rootCA
├── [4.0K]  certs
├── [4.0K]  crl
├── [4.0K]  private
├── [  48]  RootCApass.enc
└── [   3]  serial

3 directories, 2 files
$
  • Create certificate index file
$ sudo touch index.txt 
$
$ tree -sh /opt/projects/rootCA
/opt/projects/rootCA
├── [4.0K]  certs
├── [4.0K]  crl
├── [   0]  index.txt
├── [4.0K]  private
├── [  48]  RootCApass.enc
└── [   3]  serial

3 directories, 3 files
$
  • Create openssl.cnf file for root CA
$ sudo cp /etc/ssl/openssl.cnf .
$ sudo vim openssl.cnf

I edited the file and wound up with this:

HOME			= .
RANDFILE                = $ENV::HOME/.rnd
oid_section		= new_oids

[ new_oids ]


####################################################################
[ ca ]
default_ca	= CA_default		# The default ca section

####################################################################
[ CA_default ]

dir		= /opt/projects/rootCA		# Where everything is kept
certs		= $dir/certs		# Where the issued certs are kept
crl_dir		= $dir/crl		# Where the issued crl are kept
database	= $dir/index.txt	# database index file.
#unique_subject	= no			# Set to 'no' to allow creation of
					# several certs with same subject.
new_certs_dir	= $dir/certs		# default place for new certs.

certificate	= $dir/certs/RootCAcert.pem # The CA certificate
serial		= $dir/serial		# The current serial number
crlnumber	= $dir/crlnumber	# the current crl number
					# must be commented out to leave a V1 CRL
crl		= $dir/crl/crl.pem	# The current CRL
private_key	= $dir/private/RootCAkey.pem # The private key

x509_extensions	= usr_cert		# The extensions to add to the cert

name_opt 	= ca_default		# Subject Name options
cert_opt 	= ca_default		# Certificate field options

default_days	= 3650  		# how long to certify for
default_crl_days= 30			# how long before next CRL
default_md	= sha256		# use public key default MD
preserve	= no			# keep passed DN ordering

policy		= policy_match

# For the CA policy
[ policy_match ]
countryName		= match
stateOrProvinceName	= match
organizationName	= match
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName		= optional
stateOrProvinceName	= optional
localityName		= optional
organizationName	= optional
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

####################################################################
[ req ]
default_bits		= 4096
default_md		= sha256
default_keyfile 	= privkey.pem
distinguished_name	= req_distinguished_name
attributes		= req_attributes
x509_extensions	        = v3_ca	# The extensions to add to the self signed cert
string_mask = nombstr

[ req_distinguished_name ]
countryName			= Country Name (2 letter code)
countryName_default		= US
countryName_min			= 2
countryName_max			= 2

stateOrProvinceName		= State or Province Name (full name)
stateOrProvinceName_default	= Tennessee

localityName			= Locality Name (eg, city)
localityName_default		= Portland

0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= Yidhra

organizationalUnitName		= Organizational Unit Name (eg, section)
organizationalUnitName_default	= Farm

commonName			= setback 
commonName_max			= 64

emailAddress			= Email Address
emailAddress_default		= chuck@yidhra.farm
emailAddress_max		= 64

# SET-ex3			= SET extension number 3

[ req_attributes ]
challengePassword		= A challenge password
challengePassword_min		= 4
challengePassword_max		= 20

unstructuredName		= An optional company name

[ usr_cert ]

basicConstraints=CA:FALSE

nsComment			= "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer


[ v3_req ]

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true

[ v3_intermediate_ca ]

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true, pathlen:0
keyUsage = critical, digitalSignatrue, cRLSign, keyCertSign

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always

[ proxy_cert_ext ]
basicConstraints=CA:FALSE

nsComment			= "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

File system looks like this now:

$ tree -sh /opt/projects/rootCA
/opt/projects/rootCA
├── [4.0K]  certs
├── [4.0K]  crl
├── [   0]  index.txt
├── [3.9K]  openssl.cnf
├── [4.0K]  private
├── [  48]  RootCApass.enc
└── [   3]  serial

3 directories, 4 files
$
  • Generate the private key of rootCA
$ sudo openssl genrsa -des3 -passout file:/opt/projects/rootCA/RootCApass.enc -out /opt/projects/rootCA/private/RootCAkey.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
..................................................................................................................................................................................++++
........................................................................................................................++++
e is 65537 (0x010001)
$
  • Validate generated key
$ sudo openssl rsa -noout -text -in /opt/projects/rootCA/private/RootCAkey.pem -passin file:/opt/projects/rootCA/RootCApass.enc
  • Create root CA Certificate Note: The Common Name (CN) of the CA and the Server certificates must NOT match
$ sudo openssl req -new -x509 -days 3650 -passin file:/opt/projects/rootCA/RootCApass.enc -config /opt/projects/rootCA/openssl.cnf -extensions v3_ca -key /opt/projects/rootCA/private/RootCAkey.pem -out /opt/projects/rootCA/certs/RootCAcert.pem
  • Change sertificate format to PEM format
$ sudo openssl x509 -in /opt/projects/rootCA/certs/RootCAcert.pem -out /opt/projects/rootCA/certs/RootCAcert.pem -outform PEM
  • Verify Root CA certificate
$ sudo openssl x509 -noout -text -in /opt/projects/rootCA/certs/RootCAcert.pem

Store Root CA Private Key Offline

Store Root CA Private Key Externally

Create Intermediate CA Private Key Material

  • Create directory structure for intermediate CA.
$ sudo mkdir -p /opt/projects/intermediateCA/{certs,csr,private}
$
$ tree -d /opt/projects/intermediateCA
/opt/projects/intermediateCA
├── certs
├── csr
└── private

3 directories
$
$ cd /opt/projects/intermediateCA
$
  • Create encrypted password file
$ sudo sh -c 'echo secret > IntermediateCApass'
$ sudo  openssl enc -aes256 -pbkdf2 -salt -in IntermediateCApass -out IntermediateCApass.enc
$ sudo rm IntermediateCApass
$
$ tree -sh /opt/projects/intermediateCA
/opt/projects/intermediateCA
├── [4.0K]  certs
├── [4.0K]  csr
├── [  48]  IntermediateCApass.enc
└── [4.0K]  private

3 directories, 1 file
$
  • Create serial file
$ sudo sh -c 'echo 01 > serial'
$
$ tree -sh /opt/projects/intermediateCA
/opt/projects/intermediateCA
├── [4.0K]  certs
├── [4.0K]  csr
├── [  48]  IntermediateCApass.enc
├── [4.0K]  private
└── [   3]  serial

3 directories, 2 files
$
  • Create certificate index file
$ sudo touch index.txt 
$
$ tree -sh /opt/projects/intermediateCA
/opt/projects/intermediateCA
├── [4.0K]  certs
├── [4.0K]  csr
├── [   0]  index.txt
├── [  48]  IntermediateCApass.enc
├── [4.0K]  private
└── [   3]  serial

3 directories, 3 files
$
  • Add crlnumber file for intermediate CA.
$ sudo sh -c 'echo 01 > crlnumber'
$
$ tree -sh /opt/projects/intermediateCA
/opt/projects/intermediateCA
├── [4.0K]  certs
├── [   3]  crlnumber
├── [4.0K]  csr
├── [   0]  index.txt
├── [  48]  IntermediateCApass.enc
├── [4.0K]  private
└── [   3]  serial

3 directories, 4 files
$
  • Create openssl.cnf file for intermediate CA
$ sudo cp /opt/projects/rootCA/openssl.cnf .
$ sudo vim openssl.cnf

I edited the file and wound up with this:

HOME			= .
RANDFILE                = $ENV::HOME/.rnd
oid_section		= new_oids

[ new_oids ]


####################################################################
[ ca ]
default_ca	= CA_default		# The default ca section

####################################################################
[ CA_default ]

dir		= /opt/projects/intermediateCA	# Where everything is kept
certs		= $dir/certs		# Where the issued certs are kept
crl_dir		= $dir/crl		# Where the issued crl are kept
database	= $dir/index.txt	# database index file.
#unique_subject	= no			# Set to 'no' to allow creation of
					# several certs with same subject.
new_certs_dir	= $dir/certs		# default place for new certs.

certificate	= $dir/certs/IntermediateCAcert.pem # The CA certificate
serial		= $dir/serial 		# The current serial number
crlnumber	= $dir/crlnumber	# the current crl number
					# must be commented out to leave a V1 CRL
crl		= $dir/crl/crl.pem	# The current CRL
private_key	= $dir/private/IntermediateCAkey.pem # The private key

x509_extensions	= usr_cert		# The extensions to add to the cert

name_opt 	= ca_default		# Subject Name options
cert_opt 	= ca_default		# Certificate field options

default_days	= 3650  		# how long to certify for
default_crl_days= 30			# how long before next CRL
default_md	= sha256		# use public key default MD
preserve	= no			# keep passed DN ordering

policy		= policy_anything

# For the CA policy
[ policy_match ]
countryName		= match
stateOrProvinceName	= match
organizationName	= match
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName		= optional
stateOrProvinceName	= optional
localityName		= optional
organizationName	= optional
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

####################################################################
[ req ]
default_bits		= 4096
default_md		= sha256
default_keyfile 	= privkey.pem
distinguished_name	= req_distinguished_name
attributes		= req_attributes
x509_extensions	        = v3_ca	# The extensions to add to the self signed cert
string_mask = nombstr

[ req_distinguished_name ]
countryName			= Country Name (2 letter code)
countryName_default		= US
countryName_min			= 2
countryName_max			= 2

stateOrProvinceName		= State or Province Name (full name)
stateOrProvinceName_default	= Tennessee

localityName			= Locality Name (eg, city)
localityName_default		= Portland

0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= Yidhra

organizationalUnitName		= Organizational Unit Name (eg, section)
organizationalUnitName_default	= Farm

commonName			= setback 
commonName_max			= 64

emailAddress			= Email Address
emailAddress_default		= chuck@yidhra.farm
emailAddress_max		= 64

# SET-ex3			= SET extension number 3

[ req_attributes ]
challengePassword		= A challenge password
challengePassword_min		= 4
challengePassword_max		= 20

unstructuredName		= An optional company name

[ usr_cert ]

basicConstraints=CA:FALSE

nsComment			= "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer


[ v3_req ]

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true

[ v3_intermediate_ca ]

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true, pathlen:0
keyUsage = critical, digitalSignatrue, cRLSign, keyCertSign

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always

[ proxy_cert_ext ]
basicConstraints=CA:FALSE

nsComment			= "OpenSSL Generated Certificate"

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
  • Generate Intermediate CA Key
$ sudo openssl genrsa -des3 -passout file:/opt/projects/intermediateCA/IntermediateCApass.enc -out /opt/projects/intermediateCA/private/IntermediateCAkey.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
................................................................................................................................................++++
...........................................................................................................................................................................................++++
e is 65537 (0x010001)
$

Generate CSR of Intermediate CA to Root CA

  • Create intermediate CA certificate signing request
$ sudo openssl req -new -sha256 -config /opt/projects/intermediateCA/openssl.cnf -passin file:/opt/projects/intermediateCA/IntermediateCApass.enc  -key /opt/projects/intermediateCA/private/IntermediateCAkey.pem -out /opt/projects/intermediateCA/csr/IntermediateCAcsr.pem

Root CA signs CSR for Intermediate CA Certificate

  • Sign and generate Intermediate CA certificate
$ sudo openssl ca -config /opt/projects/rootCA/openssl.cnf -extensions v3_ca -days 2650 -notext -batch -passin file:/opt/projects/rootCA/RootCApass.enc -in /opt/projects/intermediateCA/csr/IntermediateCAcsr.pem -out /opt/projects/intermediateCA/certs/IntermediateCAcert.pem 
  • Verify intermediate certificate
$ sudo openssl x509 -noout -text -in /opt/projects/intermediateCA/certs/IntermediateCAcert.pem
  • Verify intermediate certificate
$ sudo openssl verify -CAfile /opt/projects/rootCA/certs/RootCAcert.pem /opt/projects/intermediateCA/certs/IntermediateCAcert.pem
/opt/projects/intermediateCA/certs/IntermediateCAcert.pem: OK
$
  • Convert intermediate CA certificate to PEM format
$ sudo openssl x509 -in /opt/projects/intermediateCA/certs/IntermediateCAcert.pem -out /opt/projects/intermediateCA/certs/IntermediateCAcert.pem -outform PEM

Create Root and Intermediate CA Cert Bundle

  • Create certificate chain/bundle
$ sudo sh -c 'cat /opt/projects/intermediateCA/certs/IntermediateCAcert.pem /opt/projects/rootCA/certs/RootCAcert.pem > /opt/projects/intermediateCA/certs/CA-chain-bundle.cert.pem'
  • Verify certificate chain
$ sudo openssl verify -CAfile /opt/projects/rootCA/certs/RootCAcert.pem /opt/projects/intermediateCA/certs/CA-chain-bundle.cert.pem 

Store Intermediate CA Certificate Offline

Intermediate certificates are not to be included in truststores. Backups are important, and CA information should also be storred offline

Trusting the CA Certificate

System

Place symbolic link to rootCACcert.pem in /usr/local/share/ca-certs directory. Certificates in this directory will be added to /etc/ssl/certs by the update-ca-certificates script bundled with app-misc/ca-certificates.

rootCACert.crt -> /root/internalca/rootCACert.pem

Firefox

In Firefox, go to Settings, Privacy & Security, Security, Certificates, View Certificates. On Authorities tab, click on the Import button to open dialog to import certificate to the store.

Use Case: Monit (Server Certificate)

Create directory structure

$ sudo mkdir -p /var/certs
$ cd /var/certs

Create server PEM

Only one of the following is needed. RSA has greatest compatibility, but is expected to become insecure by 2025.

$ sudo openssl genpkey -algorithm x25519 -out server.key.pem (Curve25519 private key)
$ sudo openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out server.key.pem (ECDSA private key)
$ sudo openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /var/certs/server.key.pem (RSA private key)

Create server.cnf file that supports Subject Alternative Names

Use sudo vim /var/certs/server_cert.cnf and create a file like this:

[req]
distinguished_name = req_distinguished_name
req_extensions =  req_ext
prompt = no

[req_distinguished_name]
C	= US
ST	= Tennessee
L	= Portland
O	= Yidhra
OU	= Farm
CN	= setback.local

[req_ext]
subjectAltName = @alt_names

[alt_names]
IP.1 = 192.168.100.96
	DNS.1 = kdc1.local
DNS.2 = dns.local
DNS.3 = salt.local
DNS.4 = ldap.local
DNS.5 = jellyfin.local
DNS.6 = setback.local

Create server CSR

$ sudo openssl req -new -sha256 -key /var/certs/server.key.pem -out /var/certs/server.csr -config /var/certs/server_cert.cnf
$

Verify alternative names in CSR

$ openssl req -noout -text -in server.csr | grep -A 1 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
	                    IP Address:192.168.100.96, DNS:kdc1.local, DNS:dns.local, DNS:salt.local, DNS:ldap.local, DNS:jellyfin.local

Use Intermediate CA to create server certificate.

$ sudo openssl x509 -req -days 730 -passin file:/opt/projects/intermediateCA/IntermediateCApass.enc -in /var/certs/server.csr -CA /opt/projects/intermediateCA/certs/IntermediateCAcert.pem -CAkey /opt/projects/intermediateCA/private/IntermediateCAkey.pem -CAcreateserial -out /var/certs/server.crt -extensions req_ext -extfile /var/certs/server_cert.cnf

Verify alternative names in server certificate

$ openssl x509 -text -noout -in server.crt | grep -A 1 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
	                    IP Address:192.168.100.96, DNS:kdc1.local, DNS:dns.local, DNS:salt.local, DNS:ldap.local, DNS:jellyfin.local
$			    

Create server key and certificate bundle

$ sudo sh -c 'cat server.key.pem server.crt > server-bundle.cert.pem' 

Limit permissions to server key and certificate bundle.

$ sudo chmod  700 server-bundle.cert.pem

Point monit to use server ket certificate bundle.

Edit monitrc file to enable SSL and use the created certificate. For my machines this involved removing comment # signs and additn certificate infoemation into the with ssl section. I also set the protocolversion to only support TLSv1.3. The eddits leavee the monitrc file with sections looking similar to this:

set ssl {
     verify     : enable, # verify SSL certificates (disabled by default but STRONGLY RECOMMENDED)
     selfsigned : allow   # allow self signed SSL certificates (reject by default)
}
...
    with ssl {            # enable SSL/TLS and set path to server certificate
        version: tlsv13
        pemfile: /var/certs/server-bundle.cert.pem
    }

Restart Monit

After changing the monitrc file the monit service should be restarted and validated as working.

$ sudo systemctl restart monit
$ systemctl status monit
● monit.service - Pro-active monitoring utility for unix systems
     Loaded: loaded (/lib/systemd/system/monit.service; enabled; vendor preset: disabled)
    Drop-In: /etc/systemd/system/service.d
             └─toplevel-override.conf
     Active: active (running) since Sat 2022-01-29 16:45:03 CST; 13h ago
       Docs: man:monit(1)
             https://mmonit.com/wiki/Monit/HowTo
   Main PID: 1894171 (monit)
      Tasks: 4 (limit: 28837)
     Memory: 7.1M
        CPU: 31.599s
     CGroup: /system.slice/monit.service
             └─1894171 /usr/bin/monit -I

Jan 29 16:45:03 setback systemd[1]: Started Pro-active monitoring utility for unix systems.

If service does not show running, check monitrc file for mistakes and the monit logfile for more information. On my machines the monit log file is fount in /var/log/monit.

Test Monit

I use nmap to make sure Monit is responding using using only TLSv1.3 as follows. I run Monit on the default 2812 port. The Nmap script for enumerating ciphers needs to know which port to check, so I specify the Monit port with -p 2812 in the command below. The output in of the command below is what I measure as success, Only TLSv1.3 response and no weak ciphers.

$ nmap -p 2812 --script +ssl-enum-ciphers servername.local
Starting Nmap 7.92 ( https://nmap.org ) at 2022-01-30 06:48 CST
Nmap scan report for gibson.local (192.168.100.17)
Host is up (0.052s latency).

PORT     STATE SERVICE
2812/tcp open  atmtcp
| ssl-enum-ciphers:
|   TLSv1.3:
|     ciphers:
|       TLS_AKE_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_AKE_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|     cipher preference: server
|_  least strength: A

Nmap done: 1 IP address (1 host up) scanned in 6.58 seconds

References

Gentoo Certificates Gentoo Systemwide Certificates IBM Runbook Automation Guidance Enable SSL In Monit OpenSSL Certificate Manager from golinuxcloud.com