jLuger.de - Creating your own CA and certificates to sign with

As I was taking actions to increase my privacy I found it wrong to use unencrypted connections. It is a proven fact now that everything that can be intercepted will be intercepted. I had already used self signed certificates and knew all the trouble you get with them. I expected things to get worse as I planned to set up some services on other computers in my home network. The solution for this is to be my own CA (Certificate Authority). This sounds difficult but I've found plenty tutorials explaining everything in detail. I've decided to start with https://jamielinux.com/docs/openssl-certificate-authority/index.html.

At first I've just started to follow the tutorial. Soon I've learned that it is very ease to screw things in a way that you have to delete all and begin from start on. You know, one missed entry here, one overlooked command there and that's it. To counter that I've began to put all the commands in a script so that I only had to fix the script and not to redo everything.
I've put the script below. Please note that the script will not only create a CA certificate but also an intermediate certificate which in turn is used to sign the server certificates. Any client that wants to verify such a signed server certificates needs to get to know the intermediate certificate. The easiest way is to chain the root certificate and the intermediate certificate and let the webserver use the chain. That's why my script creates the file ca-cain.cert.pem.

For the server certificates I've created an extra script per server. The cool thing for using scripts is that you can copy an existing script and then just have to replace the server name. Please note the option "-aes256" in the genrsa call to openssl. This option will encrypt the private key. Encryption sounds good but it will also mean that you have to type in the password every time you have to start the server.

If you use real names and not just IP addresses you are done now. As I didn't had a name server in my home network I wanted to use IP addresses as CN. For some applications that wasn't a problem but I had one app that didn't connect. After some research I've found out that you can't put an IP address in a CN (at least not alone). See e.g. http://stackoverflow.com/questions/28760709/got-ioexception-hostname-was-not-verified-although-given-hostname-ip-addres.
To get get the required data (DNS and IP in subject alternative name) to the certificate I had to use a custom openssl.conf for each certificate. Not really nice.
As I had now a root CA and it was already distributed to some systems I could do a rm and restart but had to use the existing CA data. Creating a second certificate for the same server IP gave me this error message:
TXT_DB error number 2 failed to update database
See also http://zeldor.biz/2013/11/txt_db-error-number-2-failed-to-update-database/. I've used the solution mention in the first comment and deleted the entry from the file ca/intermediate/index.txt.

Script to create the root CA:
mkdir /root/ca
cd /root/ca
mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
cp /etc/ssl/openssl.cnf openssl.cnf
crudini --set openssl.cnf " ca " default_ca CA_default
crudini --set openssl.cnf " CA_default " dir /root/ca
crudini --set openssl.cnf " CA_default " certs '$dir/certs'
crudini --set openssl.cnf " CA_default " crl_dir '$dir/crl'
crudini --set openssl.cnf " CA_default " new_certs_dir '$dir/newcerts'
crudini --set openssl.cnf " CA_default " database '$dir/index.txt'
crudini --set openssl.cnf " CA_default " serial '$dir/serial'
crudini --set openssl.cnf " CA_default " RANDFILE '$dir/private/.rand'
crudini --set openssl.cnf " CA_default " private_key '$dir/private/ca.key.pem'
crudini --set openssl.cnf " CA_default " certificate '$dir/certs/ca.cert.pem'
crudini --set openssl.cnf " CA_default " crlnumber '$dir/crlnumber'
crudini --set openssl.cnf " CA_default " crl '$dir/crl/ca.crl.pem'
crudini --set openssl.cnf " CA_default " crl_extensions 'crl_ext'
crudini --set openssl.cnf " CA_default " default_crl_days '30'
crudini --set openssl.cnf " CA_default " default_md 'sha256'
crudini --set openssl.cnf " CA_default " name_opt 'ca_default'
crudini --set openssl.cnf " CA_default " cert_opt 'ca_default'
crudini --set openssl.cnf " CA_default " default_days '375'
crudini --set openssl.cnf " CA_default " preserve 'no'
crudini --set openssl.cnf " CA_default " policy 'policy_strict'

crudini --set openssl.cnf " policy_strict " countryName 'match'
crudini --set openssl.cnf " policy_strict " stateOrProvinceName 'match'
crudini --set openssl.cnf " policy_strict " organizationName 'match'
crudini --set openssl.cnf " policy_strict " organizationalUnitName 'optional'
crudini --set openssl.cnf " policy_strict " commonName 'supplied'
crudini --set openssl.cnf " policy_strict " emailAddress 'optional'

crudini --set openssl.cnf " policy_loose " countryName 'optional'
crudini --set openssl.cnf " policy_loose " stateOrProvinceName 'optional'
crudini --set openssl.cnf " policy_loose " localityName 'optional'
crudini --set openssl.cnf " policy_loose " organizationName 'optional'
crudini --set openssl.cnf " policy_loose " organizationalUnitName 'optional'
crudini --set openssl.cnf " policy_loose " commonName 'supplied'
crudini --set openssl.cnf " policy_loose " emailAddress 'optional'

crudini --set openssl.cnf " req " default_bits '2048'
crudini --set openssl.cnf " req " distinguished_name 'req_distinguished_name'
crudini --set openssl.cnf " req " string_mask 'utf8only'
crudini --set openssl.cnf " req " default_md 'sha256'
crudini --set openssl.cnf " req " x509_extensions 'v3_ca'

crudini --set openssl.cnf " req_distinguished_name " countryName 'Country Name (2 letter code)'
crudini --set openssl.cnf " req_distinguished_name " stateOrProvinceName 'State or Province Name'
crudini --set openssl.cnf " req_distinguished_name " localityName 'Locality Name'
crudini --set openssl.cnf " req_distinguished_name " 0.organizationName 'Organization Name'
crudini --set openssl.cnf " req_distinguished_name " organizationalUnitName 'Organizational Unit Name'
crudini --set openssl.cnf " req_distinguished_name " commonName 'Common Name'
crudini --set openssl.cnf " req_distinguished_name " emailAddress 'Email Address'
crudini --set openssl.cnf " req_distinguished_name " countryName_default 'DE'
crudini --set openssl.cnf " req_distinguished_name " stateOrProvinceName_default 'Bayern'
crudini --set openssl.cnf " req_distinguished_name " localityName_default ''
crudini --set openssl.cnf " req_distinguished_name " 0.organizationName_default 'MeMyselfI Private'

crudini --set openssl.cnf " v3_ca " subjectKeyIdentifier 'hash'
crudini --set openssl.cnf " v3_ca " authorityKeyIdentifier 'keyid:always,issuer'
crudini --set openssl.cnf " v3_ca " basicConstraints 'critical, CA:true'
crudini --set openssl.cnf " v3_ca " keyUsage 'critical, digitalSignature, cRLSign, keyCertSign'

crudini --set openssl.cnf " v3_intermediate_ca " subjectKeyIdentifier 'hash'
crudini --set openssl.cnf " v3_intermediate_ca " authorityKeyIdentifier 'keyid:always,issuer'
crudini --set openssl.cnf " v3_intermediate_ca " basicConstraints 'critical, CA:true, pathlen:0'
crudini --set openssl.cnf " v3_intermediate_ca " keyUsage 'critical, digitalSignature, cRLSign, keyCertSign'

crudini --set openssl.cnf " usr_cert " basicConstraints 'CA:FALSE'
crudini --set openssl.cnf " usr_cert " nsCertType 'client, email'
crudini --set openssl.cnf " usr_cert " nsComment '"OpenSSL Generated Client Certificate"'
crudini --set openssl.cnf " usr_cert " subjectKeyIdentifier 'hash'
crudini --set openssl.cnf " usr_cert " authorityKeyIdentifier 'keyid,issuer'
crudini --set openssl.cnf " usr_cert " keyUsage 'critical, nonRepudiation, digitalSignature, keyEncipherment'
crudini --set openssl.cnf " usr_cert " extendedKeyUsage 'clientAuth, emailProtection'

crudini --set openssl.cnf " server_cert " basicConstraints 'CA:FALSE'
crudini --set openssl.cnf " server_cert " nsCertType 'server'
crudini --set openssl.cnf " server_cert " nsComment '"OpenSSL Generated Server Certificate"'
crudini --set openssl.cnf " server_cert " subjectKeyIdentifier 'hash'
crudini --set openssl.cnf " server_cert " authorityKeyIdentifier 'keyid,issuer:always
crudini --set openssl.cnf " server_cert " keyUsage 'critical, digitalSignature, keyEncipherment'
crudini --set openssl.cnf " server_cert " extendedKeyUsage 'serverAuth'

crudini --set openssl.cnf " crl_ext " authorityKeyIdentifier 'keyid:always'

crudini --set openssl.cnf " ocsp " basicConstraints 'CA:FALSE'
crudini --set openssl.cnf " ocsp " subjectKeyIdentifier 'hash'
crudini --set openssl.cnf " ocsp " authorityKeyIdentifier 'keyid,issuer'
crudini --set openssl.cnf " ocsp " keyUsage 'critical, digitalSignature'
crudini --set openssl.cnf " ocsp " extendedKeyUsage 'critical, OCSPSigning'

openssl genrsa -aes256 -out private/ca.key.pem 4096
chmod 400 private/ca.key.pem
openssl req -config openssl.cnf -key private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem \
-subj '/C=DE/ST=Bayern/L=MyCity/O=MeMyselfI Private/OU=MeMyselfI Certificate Authority/CN=MeMyselfI Root CA/emailAddress=<__>@jluger.de'
chmod 444 certs/ca.cert.pem

mkdir /root/ca/intermediate
cd /root/ca/intermediate
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > /root/ca/intermediate/crlnumber
cp ../openssl.cnf openssl.cnf
crudini --set openssl.cnf " CA_default " dir /root/ca/intermediate
crudini --set openssl.cnf " CA_default " private_key '$dir/private/intermediate.key.pem'
crudini --set openssl.cnf " CA_default " certificate '$dir/certs/intermediate.cert.pem'
crudini --set openssl.cnf " CA_default " crl '$dir/crl/intermediate.crl.pem'
crudini --set openssl.cnf " CA_default " policy 'policy_loose'

cd /root/ca
openssl genrsa -aes256 -out intermediate/private/intermediate.key.pem 4096
chmod 400 intermediate/private/intermediate.key.pem
openssl req -config intermediate/openssl.cnf -new -sha256 -key intermediate/private/intermediate.key.pem -out intermediate/csr/intermediate.csr.pem \
-subj '/C=DE/ST=Bayern/L=MyCity/O=MeMyselfI Private/OU=MeMyselfI Certificate Authority/CN=MeMyselfI Intermediate CA/emailAddress=<__>@jluger.de'

openssl ca -config openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in intermediate/csr/intermediate.csr.pem -out \
chmod 444 intermediate/certs/intermediate.cert.pem
openssl verify -CAfile certs/ca.cert.pem intermediate/certs/intermediate.cert.pem
cat intermediate/certs/intermediate.cert.pem certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
chmod 444 intermediate/certs/ca-chain.cert.pem

Script to create and sign server certificate
cd ca
openssl genrsa -aes256 -out 4096
chmod 400
openssl req -config intermediate/openssl.cnf -key -new -sha256 -out -subj \
'/C=DE/ST=Bayern/L=MyCity/O=MeMyselfI Private/OU=MeMyselfI Certificate Authority/CN=<__>@jluger.de'
openssl ca -config intermediate/openssl.cnf -extensions server_cert -days 375 -notext -md sha256 -in -out \
chmod 444

Update script to create a certificate for IP address name with existing private key:

export V=1
cd ca
cp intermediate/openssl.cnf
crudini --set openssl.cnf " req " x509_extensions 'v3_req'
crudini --set openssl.cnf " req " req_extensions 'v3_req'
crudini --set openssl.cnf " v3_req " subjectAltName '@alt_names'
crudini --set openssl.cnf " server_cert " subjectAltName '@alt_names'
crudini --set openssl.cnf "alt_names" DNS.1 ''
crudini --set openssl.cnf "alt_names" IP.1 ''
cd ..
openssl req -config -key -new -sha256 -out${V}.csr.pem -subj \
'/C=DE/ST=Bayern/L=MyCity/O=MeMyselfI Private/OU=MeMyselfI Certificate Authority/CN=<__>@jluger.de'
openssl ca -config -extensions server_cert -days 375 -notext -md sha256 -in${V}.csr.pem -out \${V}.cert.pem
chmod 444${V}.cert.pem

This post is part of a series: