- What is a client-authenticated TLS handshake?
- Generate a random password
- Certificate Authority
- Configure Apache
- Configure the browser
- Curl test
- Public Key Cryptography
A client-authenticated TLS handshake is one where both parties authenticate each other.
In layman terms, it’s like HTTPS but when you try to access, the server asks you to prove your identity presenting a certificate. The first time you access a website you’ll see this:
In this tutorial I intend to secure a private server for my exclusive access. Thus, I’ll be creating certificates and keys for my own certificate authority, my server, and my browser.
At this point you can
- read a mini refresher on public key cryptography
- inspect the code that generates the certificates
- or keep reading for an explanation of the code
If you are going to run the code, I recommend you use the script instead typing manually. The script makes some assumptions so you need to read it. For instance, I’m using jano.com.es as domain, and ubuntu@lightsail as SSH user@server.
OpenSSL is a library that implements TLS, SSL, and general-purpose cryptographic utilities. It has two main forks: LibreSSL, BoringSSL. The main OpenSSL man page may give you an idea of the functionality included.
This tutorial is written for OpenSSL version 1.1.1. Previous versions would require significant changes. Install 1.1.1, add it to the path, and check it is version 1.1.1.
Generate a .rnd file in your home folder with random content.
What’s the .rnd file? While using OpenSSL you may get an error “PNRG not seeded”. This means it can’t access a .rnd file in the home folder. On systems without a good random generator, OpenSSL generates entropy from other means, one of them is reading random bytes from this file. macOS has a random generator of cryptographic quality, but OpenSSL still complains. I ignore why.
Why do you need to install OpenSSL? In macOS Apple uses Common Crypto instead OpenSSL, because the later doesn’t provide binary compatibility between versions. This article explains further. AFAIK, there isn’t a terminal command to this library, but even if it was, it would have to be much nicer to compete with the well known OpenSSL. By the way, Common Crypto is a C library, but Swift 4.2 ships with a CommonCrypto module that bridges the library directly to Swift. Another way of using it in Swift is through IDZSwiftCommonCrypto, a third party wrapper.
Where is the OpenSSL configuration file? It’s at
I need a password from 4 to 1023 characters to protect the private keys. There are several one-liners to generate passwords of cryptographic quality. They all generate entropy using a hash implementation, or a pseudorandom generator. Then they discard non ASCII characters, and finally cut the amount of bytes needed.
Generate a random password with 32 characters:
/dev/random generates an unlimited amount of random bytes. It is passed to base64, which encodes that information using a 64 character alphabet. The head command gets the first 32 characters and discards the rest. The output is piped to a file.
In Unix systems,
/dev is a virtual file system that exposes device drivers as files of the filesystem. The random device driver is actually a pseudorandom generator.
The standard key sizes today are 2048, and 4096. Given the increasing computational power, the NIST recommends 4096 bits for keys used beyond 2031 (see Should We Start Using 4096 bit RSA keys?, or NIST Recommendation for Key Management, Part 1. So basically, in the unlikely event that your key will be active beyond 2031, no system will complain that it is too weak if you used 4096 bits. The downside is that it will require a bit more CPU.
PEM is a container format for certificates, private keys, and root certificates. It usually has extensions .pem, .key, or .cer. PEM stands for Privacy Enhanced Email. It was defined in RFCs 1421, 1422, 1423, 1424. It didn’t succeed as a way to secure mail, but it is still in use as a container format.
ca-key.pem file contains the attributes of the RSA private key, which are: modulus, public exponent, private exponent, prime1, prime2, exponent1, exponent2, and coefficient. These many numbers are suitable to speed up RSA calculations using the Chinese Remainder Theorem.
These elements are encoded in ASN.1, which is a cross-platform way of expressing data structures. Then the ASN.1 is encoded to binary in DER format, and then it is encoded again in PEM, which is a base64 version of the DER. In summary: attributes + format metadata → ASN.1 → DER → PEM.
You can dump the key back to ASN visiting this website, or using your terminal:
openssl asn1parse -i -in ca-key.pem. You will see the metadata, but the key itself shows as a large number because it was encoded in Triple DES. Decoding this number would show the ASN.1 PKCS#1 structure for the PKCS#1 format.
To skip the ASN.1 and see the attributes in plain text, run:
Given how the RSA algorithm works, the attributes of the private key can be used to create the PEM public key:
Create a subject:
Create a root CA certificate request:
If you are on macOS, add the certificate to the keychain and trust it:
You will find it in the keychain under the category Certificates. Filter by the domain name “jano.com.es” to find it.
||The city where your organization is located.|
||The state/region where your organization is located.|
||The city where your organization is located.|
||Legal name of the organisation.|
||The division of your organization handling the certificate.|
||A fully qualified domain name (FQDN). This is either the real name of a person, or the hostname of a computer.|
Create a file with name
ssl.conf and the following content:
This is the minimal file that will let you generate a certificate with a SAN. AFAIK you can’t replace it with command line options. Note that the DNS.1 entry is my server name. If you need more domains, add each on a new line with prefix DNS.2, DNS.3, etc.
SAN stands for “Subject Alternative Name”. It is a way to generate a certificate that may be use with multiple domains, like lingcars.com, blog.lingcars.com, etc. Not long ago, if the SAN was not present, the CN was used as a fallback. Since Chrome 58, a certificate without SAN fails with the misleading error NET::ERR_CERT_COMMON_NAME_INVALID.
Create a private key and certificate request for the server:
It’s a petition to the CA to create a certificate that proves your identity. The CSR contains some personal information like your name, and also your public key. It’s up to the CA to verify that you are indeed who says in the certificate. This usually requires an official document like your passport or similar. The CA will then generate a certificate containing your personal information and public key, and sign it. If a browser trusts the CA, it will also trust that this certificate has been awarded to the company or person that matches the information contained on it.
The CSR information is encoded as ASN.1 according to the PKCS #10 specification. You may print the certificate request and verify it has a SAN:
Note that I passed the subject information with a -subj option. Without it, the command will ask for it in an interactive manner, but for that it needs the following template in the req_distinguished_name section of the configuration file. It contains the questions, and their default values.
Create a certificate for the server:
Same command in one line:openssl x509 -req -days 800 -in jano.csr -CA ca-crt.pem -CAkey ca-key.pem -set_serial 01 -out jano.crt -passin file:passphrase.txt -extensions req_ext -extfile ssl.conf
Check that the certificate has a subject alternative name. In macOS you can preview the certificate selecting it and pressing space. From the terminal you may display the certificate with:
Same thing we did with the server. It doesn’t really matter what you put in CN and alternate name, but I’m changing it to “Notes user”.
Create a private key and certificate request for the client
Create a certificate for the client
If you are using Lightsail and you configured Let’s Encrypt you will have a file at
Its contents are:
Copy the needed files:
Obviously listen on port 444 too:
It will look like this:
sudo apachectl restart
SSLVerifyDepthis the maximum number of intermediate certificate issuers to be followed while verifying the client certificate.
- 0 means self-signed client certificates are accepted only
- 1 means the client certificate can be either self-signed, or signed by a CA which is directly known to the server (i.e. the CA’s certificate is under SSLCACertificatePath).
SSLVerifyClientrequire will require a SSL certificate before processing the request.
See Apache Module mod_ssl for reference.
Convert certificate to PKCS12 format. Don’t leave the password blank as it may cause problems when importing to macOS keychain.
This will ask for the pfx password (it’s in the clipboard) and the admin password. Don’t confuse them.
In macOS drag and drop the following files in the login section of your keychain.app:
Then open the ca.crt entry (it’s the one with the orange icon) and choose Trust Always.
In iPhone send by mail: ca.crt, client.pfx, passphrase.txt. Save the files to the iCloud drive, then go to Files in your iPhone and open both. Then go to Settings > General > About > Certificate Trust Settings, and trust Jano CA. I find that this works well and never asks for certificates again.
In Safari open a page and you will get a dialog prompt you to choose a certificate. Safari will ask you again on any URL you visit and store your choice in the Keychain. This creates a preference item per URL. However, you can alter these choices to apply to the whole site. Do the following:
- Open the keychain, select All Items, in the search bar start typing the URL you were visiting (e.g.
https://jano.com.es). You should be able to see a identity preference item. Open it.
- On the identity preference item set the field Where: to
https://jano.com.es/(don’t forget the trailing slash). The Preferred certificate should already point to the certificate you chose before.
In Firefox go to Firefox > Preferences… > Privacy & Security
- In Certificates choose Select one automatically
- Click View Certificates…
- In the tab Your Certificates click Import… and select the client.pfx file. The password to open it is in the file passphrase.txt.
- In the tab Servers click Import… and select the file ca.crt.
- In Chrome we don’t have to do anything. It reads the CA from the keychain. However, it asks us to select a certificate once per session.
Test that you can’t access without certificate:
Test that you can access with the certificate:
This gist has a Makefile and two configuration files. It automates most things contained in this article. If you intend to use it, you will have to configure your remote Apache manually, and change the domain name in the script. Happy reading. If you don’t understand anything, refer to this article.
Usually, the HTTPS protocol performs a TLS handshake that creates a secure channel of communication. This means that communication between server and browser is
- private (no one else can snoop),
- reliable (information can’t be altered),
- and authenticated (the server identity is verified).
This is possible thanks to public key cryptography, which is a procedure to generate two keys for each entity involved in a conversation. These keys are called public and private key, and have the following properties:
- The public key is able to decrypt a message encrypted by the private key.
- It’s practically impossible to guess one of the keys by examining the other.
- Both keys of a given pair are unique. There isn’t a third key able to replace either of the two.
If you think about it, this leads to the following use case scenario:
- I encrypt a message using the private key.
- I keep my private key hidden, but I distribute my public key to everyone.
- Since everyone has my public key, they can use it to decrypt my messages.
- Since no one has my private key, I’m the only one able to create messages that the public key can decrypt.
- Therefore people can check if I’m the author by attempting to decrypt my messages. If they succeed it means I’m the author.
But, how do you know the public key was really mine? Let’s say I’m Jon Doe and I want to prove my identity to visitors of my website JonDoe.com
- I generate a public/private pair.
- I send my public key to the CA attaching legal documents that confirm my identity.
- The CA sends me back a message signed by their private key saying “Here is the public key of a guy named Jon Doe: 9D306BA967C…”.
- I install the keys on my server.
All internet browsers include the public key of the main “Certification Authorities”. Thus,
- you trust your browser
- your browser trusts the CA
- the CA recognizes the public key as valid
And so, your browser will say this server is owned by the real Jon Doe, and you can communicate securely with him.