Skip to content

Instantly share code, notes, and snippets.

@ciardullo-apps
Last active August 23, 2021 21:17
Show Gist options
  • Save ciardullo-apps/1d9abd349aac48165fd08489e3e2dfbb to your computer and use it in GitHub Desktop.
Save ciardullo-apps/1d9abd349aac48165fd08489e3e2dfbb to your computer and use it in GitHub Desktop.
How to deploy self-signed certificates using a self-certifying authority

How to deploy self-signed certificates using a self-certifying authority

Background

If you want to use encrypted connections for a host that you control, you need a TLS certificate. You can create a self-signed TLS certificate, but unless it has been signed by a Certifying Authority (like Verisign), you'll see a browser warning that the site is not secure. Here, you'll learn how to become your own certifying authority for hosts used internally.

A better approach is to become your own local Certifying Authority. Here, you'll learn how to become your own certifying authority for hosts used internally.

Generate Root Certificate

Your browser will not trust a certificate that has not been signed by a Certifying Authority. You'll need a root certificate with a private key in order to employ a certifying authority to sign your certificates for use by your host's server processes, like a web server or app server. In this order, do the following:

  1. Generate a private key. You'll be prompted for a passphrase. openssl genrsa -des3 -out selfCA.key 4096
  2. Generate a root certificate, which needs to be installed on each device from which you will access the host. You'll be prompted for the same passphrase from the previous step: openssl req -x509 -new -nodes -key ./selfCA.key -sha256 -days 1820 -out ./selfCA.pem
  3. Install the root certificate for your signing authority. Here are instructions for Ubuntu 18.04 LTS:
sudo mkdir /usr/share/ca-certificates/extra
# Converts from pem to crt
sudo openssl x509 -in ./selfCA.pem -inform PEM -out /usr/share/ca-certificates/extra/selfCA.crt
sudo dpkg-reconfigure ca-certificates

Configure Browsers

These steps should theoretically let you browse to your host's urls on port 8443 without the "Your connection is not private" message. However, neither Chrome or Firefox use system-wide certificate authorities. You need to configure each browser manually. Instructions for both Chrome and Firefox found here:

  • On Chrome, go to chrome://settings/certificates, click on Authorities, choose Import and choose your selfCA.crt
  • On Firefox, go to about:preferences#advanced, click Security, click View Certificates, under Certificates, click Authorities, then import your selfCA.crt file

Note that in your browser's location bar, you must use URL https://myhost:443. localhost will not work.

Create CA-signed Certificates for each host

Now, we can create CA-signed certificates for the host

  1. Create a private key openssl genrsa -out myhost.key 4096
  2. Create a certificate signing request (CSR) openssl req -new -key myhost.key -out myhost.csr
  3. Create a config file to define the Subject Alternative Name (SAN) extension called myhost.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = myhost
  1. Create the certificate
openssl x509 -req -in myhost.csr -CA selfCA.pem -CAkey selfCA.key -CAcreateserial -out myhost.crt -days 1820 -sha256 -extfile myhost.ext

Now there are three files for your server host: myhost.key (the private key), myhost.csr (the certificate signing request), and myhost.crt (the signed certificate).

Files created by this process

selfCA.key: private key for my personal Certificate Authority (CA)

selfCA.pem: root certificate, which needs to be installed on each device from which you will access the host

selfCA.crt: converted from PEM file above to crt. Some indications you can just rename .pem to .crt, not sure

myhost.key: private key for my host

myhost.csr: the certificate signing request for the host

myhost.ext: config file in Subject Alternative Name (SAN) extension format for the host

myhost.crt: the host certificate

myhost.keystore: combines the private key and the certificate for myhost into a PKCS12 keystore

localhost-rsa.jks: the Java keystore used by Tomcat (see below)

Permissions

I copied all files to /opt/certs, changed account ownership to root,group ownership to ssl-cert, and mode to 710 (rwx--x---). I also changed user and group to root:ssl-cert on all files in /opt/certs. Lastly, I set the mode on the .key files to 640.

Be sure that any services that require access to the crt and key files are in the ssl-cert group (like the owner of the NodeJS process).

Configure Services on Host

Now you need to configure your server processes with the private key and certificate.

nginx

Confirmed with nginx version 1.17.3 on Ubuntu 18.04.3 Change /etc/nginx/conf.d as follows

    ssl_certificate     path_to_server_certificate;
    ssl_certificate_key path_to_server_private_key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

Tomcat

  1. Combine the private key and the certificate into a PKCS12 keystore
openssl pkcs12 -export -in myhost.crt -inkey myhost.key -out myhost.keystore -name tomcat -CAfile selfCA.crt -caname root

(used password "changeit", which is the default password for keytool(?)) 2. Merge the Tomcat keystore and the PKCS12 keystore to import the certificate and private key

keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore localhost-rsa.jks -srckeystore myhost.keystore -srcstoretype PKCS12 -srcstorepass changeit -alias tomcat
  1. Copy the keystore file into Tomcat's conf/ directory. IMPORTANT: if you need to install a second keystore in Tomcat, you can't copy it, you need to merge it using 2 above
sudo cp ./localhost-rsa.jks /var/lib/tomcat8/conf
  1. Add (or likely, uncomment) the following from Tomcat's server.xml file:
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
  1. Restart tomcat
sudo service tomcat8 restart

NodeJS / ExpressJS

  1. Add the following to your server's js file
var fs = require('fs');
var https = require('https');
  1. Copy your host's private key and crt files in an accessible directory. See Permissions to protect your private key.
  2. Copy your certifying authority's root certificate in an accessible location. See Permissions to protect your private key.
  3. Add the following to your server's js file
var key = fs.readFileSync('/opt/certs/myhost.key');
var cert = fs.readFileSync( '/opt/certs/myhost.crt' );
var ca = fs.readFileSync( '/opt/certs/selfCA.crt' );

var options = {
  key: key,
  cert: cert,
  ca: ca
};
var port = 8081;
https.createServer(options, app).listen(port);

// app.listen(port); // Unencrypted connections

console.log('Application listening on port ' + port);

SinatraRB

  1. Add gem 'thin' to your Gemfile
  2. Add the following to your application's server file:
class App < ::Thin::Backends::TcpServer
  def initialize(host, port, options)
    super(host, port)
    @ssl = true
    @ssl_options = options
  end
end

configure do
  set :environment, :production
  set :bind, '0.0.0.0'
  set :port, 4567
  set :server, "thin"
  class << settings
    def server_settings
      {
        :backend          => App,
        :private_key_file => "/path_to_server_private_key",
        :cert_chain_file  => "path_to_server_certificate",
        :verify_peer      => false
      }
    end
  end

Appendix

How to verify the passphrase of your private key

Using openssl, you can verify the passphrase of your private key. Unfortunately, openssl requires the passphrase to be provided on the command line. To prevent your passphrase from being stored in clear text, use the read command as follows:

$ read -s -p "Password:" PASSWORD
Password: <enter the passphrase for the private key>

$ openssl rsa -noout -in myserverCA.key -passin "pass:$PASSWORD"

Notice that no response means that your passphrase is valid. If the passphrase is invalid, openssl will respond as follows:

$ openssl rsa -noout -in myserverCA.key -passin "pass:$PASSWORD"
unable to load Private Key
140307636995392:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:../crypto/evp/evp_enc.c:610:
140307636995392:error:0906A065:PEM routines:PEM_do_header:bad decrypt:../crypto/pem/pem_lib.c:461:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment