TLS/SSL Certificates


The main TLS toolkit for Fedora comprises GnuTLS, OpenSSL, and Network Security Services (NSS).

For GnuTLS, the main end-user command is certtool. Package gnutls-utils provides this along with a few other utilities:

=> rpm --query --list gnutls-utils | grep bin

Command gnutls-cli and p11tool are handy from time-to-time, too. There is a dedicated man page for each of these.

For OpenSSL, the end-user command is openssl (synonymous package). This is a container command that hands off the real work to sub-commands, like genpkey, verify, etc. There is a dedicated man page for each of these.

OpenJDK (package java-1.8.0-openjdk-headless) provides keytool for managing keys and certificates. These notes do not use keytool, however.

Retrieve a Certificate

You can download a server's certificate or certificate chain if you have a notion to have a look.

Here's how to retrieve this website's certificate into file acert.pem, for example:

-> gnutls-cli --save-cert=acert.pem
Processed 154 CA certificate(s).
Resolving ''...
- Simple Client Mode:
Type ^D to exit.

By default, gnutls-cli connects on port 443 for HTTPS, but you can use option --port to fetch a certificate for a different service, such as port 465 for SMTPS or port 585 for IMAPS. You'll need to specify STARTTLS for SMTP on port 587 or IMAP on port 993; for example:

-> gnutls-cli --save-cert acert.pem --port 587 --starttls-proto smtp

This command grabs the certificate chain—signers' certificates in addition to the server's certificate:

-> grep "BEGIN CERTIFICATE" acert.pem | wc --lines

The s_client command of OpenSSL returns the server's certificate to stdout. For example:

-> openssl s_client -connect
base64-encoded certificate data

(Note that you must append the port to the hostname.) Just copy this PEM block and paste it into a file. By default, s_client shows only the server's certificate. You can add option -showcerts to see the signers' certificates, too.

You'll need to coach s_client on STARTTLS, too:

-> openssl s_client -connect -starttls smtp

The s_client man page lists additional protocols offered.

If you want to retrieve a self-signed certificate from, say, your development rig on localhost, tell gnutls-cli not to worry about verification:

-> gnutls-cli --save-cert acert.pem --no-ca-verification --insecure --port 465 localhost

But s_client has no such qualms about ignoring failed verification; proceed with due caution. (Its man page suggests option -verify_return_error, but this does not seem to do the trick.)

Your web browser may offer to export a website's certificate with a few clicks. In Firefox, for example: From the Tools menu, select Page Info. From the page-info window that opens, select the Security tab and then the subsequent View Certificate button. In the certificate-viewer window that opens, select the Details tab and then the subsequent Export button. The file picker that opens gives you several choices for the content and format to export, in lower right-hand corner.

View Certificates

You can easily view a certificate in a file, say acert.pem, to see what's inside:

-> certtool --infile acert.pem --certificate-info
-> openssl x509 -in acert.pem -text -noout

This reports the X.509 certificate information: the version of X.509 in use; the certificate's serial number and validity dates; the subject's identification and public key; the issuer's identification and signature; the algorithms used for the public key and the signature; and any v3 extensions. Other information may be added. For example, certtool shows the certificate's fingerprints and the public key's ID and random art. openssl displays the PEM block (unless you suppress that with option --noout).

OpenSSL's x509 command lets you pick what components you'd like to see; for example:

-> openssl x509 -in acert.pem -noout -subject -issuer -serial  -fingerprint
subject=C = US, ST = California, L = Mountain View, O = Google Inc, CN =
issuer=C = US, O = Google Trust Services, CN = Google Internet Authority G3
SHA1 Fingerprint=87:F4:C9:7A:D1:A4:C0:54:6A:24:D6:7A:A6:E9:4C:D1:25:A1:1C:36

See section Display Options of the X509 man page for the full list.

Viewing options under certtool are limited: --fingerprint, --key-id, --pubkey-info, --certificate-pubkey. For example:

-> certtool --infile acert.pem --fingerprint

You can use only one such option at a time.

certtool kindly reads all certificates in a bundle, but openssl stops after the first:

-> certtool --certificate-info --infile pem/tls-ca-bundle.pem | grep Issuer: | wc --lines
-> openssl x509 -in  pem/tls-ca-bundle.pem -issuer -noout | wc --lines

But certtool can't interpret OpenSSL's "TRUSTED CERTIFICATE" blocks:

 -> certtool --certificate-info --infile openssl/ 
import error: No certificate was found.

A PEM file (.pem) takes an X509 certificate's binary data stream, encodes that into base64 (ASCII armor), and sandwiches the result between a header and footer:

-> cat acert.pem 
text/base64 rendition of certificate's binary data stream goes here

The delimiters allow two conveniences. The program generating the certificate may include a readable version of the data stream outside of the delimiters; certtool does this. And multiple PEM blocks may be concatenated into a single file, called a certificate bundle. You can view a PEM block with any text editor or browser, but it's ASCII noise between delimiters.

By origin, "PEM" refers to "Privacy-enhanced Electronic Mail", which was an IETF proposal that failed to launch. In practice, "PEM" simply indicates this format for incorporating keys and certificates in text files.

Under the hood, X.509 uses Abstract Syntax Notation One, or ASN.1, to structure a certificate's public key and metadata. It then encodes this ASN.1 representation into a binary data stream according to Distinguished Encoding Rules, or DER, to facilitate data exchange. A PEM block subsequently encodes the latter stream into a base64 stream in deference to applications that can't handle binary data.

You can see what ASN.1 looks like:

-> openssl asn1parse -in
    0:d=0  hl=4 l=1458 cons: SEQUENCE          
    4:d=1  hl=4 l=1178 cons:  SEQUENCE          
    8:d=2  hl=2 l=   3 cons:   cont [ 0 ]        
   10:d=3  hl=2 l=   1 prim:    INTEGER           :02
   13:d=2  hl=2 l=  17 prim:   INTEGER           :D33C081C650F6C4EC4EB3C3B59A6CA43
 1188:d=2  hl=2 l=   9 prim:   OBJECT            :sha256WithRSAEncryption
 1199:d=2  hl=2 l=   0 prim:   NULL              
 1201:d=1  hl=4 l= 257 prim:  BIT STRING  

See man page asn1parse for more examples and options. It notes: "Some knowledge of the ASN.1 structure is needed to interpret the output."

Going just a little deeper: DER is a restricted variant of Basic Encoding Rules, or BER. DER removes some of the flexibility that BER supports in order to gain specificity required for cryptography. The X.509 standard for rendering abstract information from ASN.1 into a concrete data stream for transfer comprises encoding formats BER, DER, and a third variant CER (Canononical Encoding Rules).

A certificate's fingerprint is a digest of its DER data stream. Conceptually, start with the PEM block, remove its envelope, and decode the remaining, base64 innards. What you get is the DER stream. This is the idea:

-> grep -v -P "\-----(BEGIN|END) CERTIFICATE-----" acert.pem | base64 --decode > acert.der

But just delegate this chore to openssl:

-> openssl x509 -in acert.pem -outform der -out acert.der

Now compute a digest of acert.der explicitly and compare the result to what openssl reports. With SHA1, for example:

-> sha1sum --tag acert.der 
SHA1 (acert.der) = 9dd2e2eacfbee08dc61956933601a489e2edd39d
->  openssl x509 -in acert.pem -fingerprint -sha1 -noout
SHA1 Fingerprint=9D:D2:E2:EA:CF:BE:E0:8D:C6:19:56:93:36:01:A4:89:E2:ED:D3:9D

Incidentally, OpenSSL offers a digest command, dgst, which offers multiple algorithms:

-> openssl list --digest-commands
blake2b512        blake2s256        gost              md2               
md4               md5               rmd160            sha1              
sha224            sha256            sha384            sha512 

Specify the algorithm by prefixing a hyphen to the name:

-> openssl dgst -sha1 -c acert.der
SHA1 (acert.der) = 9dd2e2eacfbee08dc61956933601a489e2edd39d
-> openssl dgst -md5 -c acert.der
MD5(acert.der)= a9:90:03:9e:f8:2a:32:4b:7e:ae:9f:a3:f9:99:83:53

Option -c tells dgst to infix those colons. The dgst command hashes any file, related to PKI or not.

Verifying Certificates

-> certtool --infile rnb.pem --verify
Loaded system trust (154 CAs available)
Loaded 3 certificates, 154 CAs and 0 CRLs
Chain verification output: Verified. The certificate is trusted. 
-> certtool --infile rnb.pem --verify --load-ca-certificate cacert-curl.pem 
Loaded 3 certificates, 138 CAs and 0 CRLs
Chain verification output: Verified. The certificate is trusted.