About

This is a private blog by Jens Lechtenbörger.

Jens Lechtenbörger

OpenPGP key: 0xA142FD84
(What is OpenPGP? Learn how to protect your e-mail.)


Creative Commons License
Unless explicitly stated otherwise, my posts on this blog are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Certificate Pinning with GnuTLS in the Mess of SSL/TLS

Lots of modern communication is “protected” from spying eyes and other criminals via an Internet standard called Transport Layer Security (TLS) or its outdated predecessor Secure Sockets Layer (SSL). In the following, I’m using the term “SSL/TLS” to refer to both of them. In a nutshell, SSL/TLS is a mess. It’s security has been, can be, and is being broken on several layers. In this post, I’m trying to clarify my understanding and recommend the use of certificate pinning by default. In particular, I start to describe how I’m using GnuTLS for certificate pinning in the form of trust-on-first-use. In subsequent posts, I’ll explain certificate pinning in real use cases.

I assume a basic understanding of TLS. Please read the Wikipedia entry on TLS first, if necessary.

The Mess

SSL/TLS is a mess for at least three major reasons.

First, SSL/TLS requires certificates issued by “trusted” certificate authorities (CAs). Previously, I wrote on trust vs. “trust” in the context of e-mail encryption, and that reasoning applies to SSL/TLS as well: Our software (browsers, e-mail clients, apps) “trusts” all certificates issued by “trusted” CAs. However, I do not trust (without quotes, in the original meaning of the term) a single CA. How could I? I don’t know anything about them, except for the recurring horror reports where someone was able to pay, bribe, trick, compel, force, or operate a “trusted” CA to issue “trusted” certificates. See my previous post for more details. In essence, if you “trust” without reason, you are vulnerable to so-called man-in-the-middle attacks, where third parties steal your secrets, your passwords, and your credit card details. And you will be blissfully ignorant, unable to see that anything bad is going on.

Second, lots of software is simply broken when it comes to certificate validation. SSL/TLS APIs and libraries are too complicated for the average software developer to get it right. And some developers just don’t care. If you are a software developer, you must read the following two peer-reviewed, highly accessible publications. Really, please read them.

As a software developer you are going to implement certificate pinning, right? Otherwise, all users of your software will be defenseless against man-in-the-middle attacks.

Third, client and server need to negotiate a cipher suite, and there are lots of insecure choices. If you operate a server, please have a look at the Internet-Draft Recommendations for Secure Use of TLS and DTLS and the companion document Summarizing Current Attacks on TLS and DTLS, which states concerning the BREACH attack:

“We are not aware of mitigations at the protocol level to the latter attack, and so application-level mitigations are needed (see [BREACH]).”

Anyways, configure your server to use AES and Diffie-Hellman key exchange for perfect forward secrecy as recommended.

Certificate or Public Key Pinning

Several times I mentioned certificate pinning already. The core idea is to make a trusted (without quotation marks) certificate for a specific purpose directly accessible to the client software. Suppose you develop an app that needs to communicate with your own server. Then you can embed the server’s certificate directly into the app’s source code. Whenever the app contacts the server, it checks the certificate presented by the server (or a man-in-the-middle) against the one embedded in the source code. If they do not match, your apps stops working and displays a big warning to the user. (Of course, you need to update the source code before the embedded certificate expires.) See the paper Rethinking SSL Development in an Appified World mentioned above for details. Also note that certificate pinning works perfectly well with self-signed certificates, without the need for unwarranted “trust.”

[Added on 2014-04-22] I’d like to clarify one fact which I overlooked when writing this post: Although “certificate pinning” is a popular term, from a security perspective “public key pinning” is typically sufficient. In fact, a certificate is a digitally signed document containing a public key, where the digital signature is meant to provide some assurance that the public key belongs to a certain organization, user, or machine. Now, if pinning is used to embed key material within source code, the digital signature of a “trusted” certificate does not offer added value. Instead, it is sufficient to embed the public key. (Of course, the app itself should be digitally signed to provide assurance of its authenticity.)

Certificate or public key pinning is not restricted to cases where certificates or keys are embedded within the source code. Instead, every client software can consult some storage for pinned certificates or keys to verify whether keys are authentic. Then, the challenge is to populate that storage in a trustworthy fashion. A common approach is to rely on trust-on-first-use (TOFU), which is well-known to users of OpenSSH: With ssh, the pinning storage is implemented as file known_hosts, which contains public keys for servers that have been accepted as trusted by the user previously. With TOFU in general, when the client connects for the first time, it does not know the server’s certificate or public key yet. So the client presents the server’s (or man-in-the-middle’s) public key (or its fingerprint) to the user, who needs to decide whether that key really belongs to the server or not. If the user recognizes that key as authentic, the server’s key is stored as pinned key; otherwise, the connection is aborted.

If you run your own server, e.g., an ownCloud, then you know the correct certificate (containing the public key) and its fingerprint. Otherwise, things may get complicated.

The canonical way to verify a public key or certificate as authentic is to compare the fingerprint of the presented certificate with the “real” fingerprint, which needs to be obtained via some out-of-band method. E.g., for e-mail with GnuPG fingerprints are compared offline at key signing parties or over the phone, some companies distribute fingerprints of CAs and servers in print, some banks provide fingerprints of their online banking servers to customers via snail mail. Quite likely, none of this will be available to you if you try to verify the certificate of a remote server, say, of some News server such as news.gmane.org.

GnuTLS

My current tool of choice to perform certificate pinning for insecure applications is gnutls-cli, a command line tool provided by GnuTLS. For example, to open a TLS connection to news.gmane.org on the NNTPS port 563, you can invoke:

$ gnutls-cli -p 563 news.gmane.org

The response looks as follows:

Processed 141 CA certificate(s).
Resolving 'news.gmane.org'...
Connecting to '80.91.229.13:563'...
- Certificate type: X.509
- Got a certificate list of 1 certificates.
- Certificate[0] info:
 - subject `C=NO,ST=Some-State,O=Gmane,CN=news.gmane.org', issuer `C=NO,ST=Some-State,O=Gmane,CN=news.gmane.org', RSA key 1024 bits, signed using RSA-SHA1, activated `2011-12-04 06:38:42 UTC', expires `2014-12-03 06:38:42 UTC', SHA-1 fingerprint `c0ec2f016cff4a43c1a7c7834b480b3ac54e90f9'
	Public Key ID:
		d21a01452b5a9b06106946930e64717869ff7ae0
	Public key's random art:
		+--[ RSA 1024]----+
		|=O+.ooo          |
		|+*o+ . .         |
		|= + + o          |
		| . + = o         |
		|  . + + S        |
		|   . . =         |
		|    . +          |
		|     E .         |
		|      .          |
		+-----------------+

- Status: The certificate is NOT trusted. The certificate issuer is unknown.
*** Verifying server certificate failed...
*** Fatal error: Error in the certificate.
*** Handshake has failed
GnuTLS error: Error in the certificate.

Apparently, GnuTLS refuses to connect because the CA (C=NO,ST=Some-State,O=Gmane,CN=news.gmane.org) issuing the certificate is unknown. As I’m interested in certificate pinning, that “trust” issue is not really important to me. However, I’d like to gain evidence that the displayed fingerprint c0ec2f016cff4a43c1a7c7834b480b3ac54e90f9 is actually the correct one (and not one belonging to a forged certificate under an active MITM attack). First, I assume that it is indeed correct and start gnutls-cli with the option --tofu to activate trust-on-first-use:

$ gnutls-cli --tofu -p 563 news.gmane.org

This time, gnutls-cli displays the same information as before but asks whether I trust the certificate. I answer with “y,” and the public key contained in the certificate is recorded in the file ~/.gnutls/known_hosts. (Yes, GnuTLS really pins public keys, not certificates). Now, subsequent connections with option --tofu succeed.

Update on 2014-04-11: If you pin a public key by answering “y,” that key is recorded at the end of ~/.gnutls/known_hosts. If you need to replace a key (which you should expect to happen frequently these days due to the Heartbleed bug in OpenSSL), you must remove the old entry manually from ~/.gnutls/known_hosts: Search for lines containing the server’s name and service and delete all (probably just one) but the last one.

Before dealing with the question whether I actually pinned the correct public key, I’d like to point out something else. The output of a successfully established TLS session indicates what cipher suite was negotiated between gnutls-cli and the server. Here, the output contains:

...
- Description: (TLS1.0)-(RSA)-(AES-128-CBC)-(SHA1)
...

This line implies that no Diffie-Hellman key exchange was performed (in favor of compatibility with broken servers), which can be changed by adding the option --priority=PFS:

...
- Description: (TLS1.0)-(DHE-RSA-1024)-(AES-128-CBC)-(SHA1)
...

To gain some evidence that GnuTLS recorded the correct public key, I use the Tor network and connect to the server from different locations:

$ torify gnutls-cli --tofu --priority=PFS -p 563 news.gmane.org

Tor is an anonymity network, where Internet traffic is re-routed over randomly chosen Tor servers so that it appears to originate from one of these servers. With torify, the subsequent command performs its network connection via the Tor network. Thus, I essentially connect from a different place, and it is less likely that a MITM is able to compromise the paths from different places to the target server news.gmane.org. (Of course, this method does not offer any guarantees: E.g., an attacker located in the target server’s LAN might be able to hijack all traffic directed to the server.)

With Tor, you can control from what country you’d like to connect: Tor offers a so-called control port, by default the port 9051 on the local host, to change configuration options. E.g., to check the certificate from a Tor server located in Norway, instruct Tor to use only exit nodes with country code “no”:

$ telnet localhost 9051
AUTHENTICATE
SETCONF ExitNodes={no}
SIGNAL NEWNYM
QUIT

Then, connect with torify again:

$ torify gnutls-cli --tofu --priority=PFS -p 563 news.gmane.org

In case of a MITM attack (or a new certificate, e.g., to replace an expired one), the following warning is displayed:

Warning: host news.gmane.org is known and it is associated with a different key.
It might be that the server has multiple keys, or an attacker replaced the key to eavesdrop this connection .
Its certificate is valid for news.gmane.org.
Do you trust the received key? (y/N):

If you plan to use GnuTLS for certificate pinning you probably want to record the expiry date displayed by gnutls-cli when you accept a public key. Then you know when to expect a regular certificate change.

Also, you may prefer gnutls-cli to fail when a presented public key does not match the pinned one (instead of asking the above question). For that purpose, I added option --strict-tofu, which is present in GnuTLS since version 3.2.12.

For certificate pinning in situations where the connection starts out in plaintext and switches to TLS via STARTTLS (e.g., SMTP or XMPP), a command like the following can be used:

gnutls-cli --tofu --crlf --starttls -p 25 smtp.example.org

What you need to type then depends on the protocol. E.g., for an encrypted SMTP connection you can type the following commands in the gnutls-cli session; afterwards, you’ll be asked whether you trust the certificate:

EHLO localhost
STARTTLS
<Then press Ctrl-D to enter TLS mode.>

In subsequent posts I’ll explain how I’m really using the above to do something useful.

Conclusions

I motivated this post with the mess of SSL/TLS on three different levels, namely (1) “trust” issues, (2) broken applications that do not check certificates properly, and (3) the choice of insecure cipher suites.

Certificate pinning solves the first two issues. Consequently, certificate pinning is a Good Thing.

The third issue is unrelated to certificate pinning, but at least GnuTLS allows us to choose whatever cipher suite is recommended by the experts (if the server supports it).