Is Your cURL Sick?

How well does it digest standards-format certificates & keys?

Summary

cURL (typically via its library, libcurl) provides connection, verification and data transport services to many different pieces of software including Git.

As part of the connection and verification step when creating a secure connection, certificates, certificate chains and certificate signatures may need to be verified. Most versions of cURL include suitable defaults that allow secure connections to many servers to be verified without any special additional configuration. But what happens if you need to supplement the defaults?

This page discusses how easily (or not) cURL (and libcurl)’s default configuration can be supplemented with additional certificates and, when necessary, the corresponding certificate keys.

PKI Primer

If you already understand how the Public Key Infrastructure (PKI) works, proceed to the next section. Otherwise you may find this companion document to be a helpful read.

cURL’s Backend

To perform the actual cryptographic operations needed for secure connections, cURL relies on a cryptographic library. cURL may be built to use one of several cryptographic libraries, but once cURL has been built, the cryptographic library selection is "baked in" and may not be changed.

To see which cryptographic backend cURL was built to use, run this command:

curl --version | sed -ne 1p | awk '{print $5}'

cURL backends differ in their support for user-supplied certificates and keys.

Typical Operation and Defaults

When curl connects securely to a server, the server sends its credentials and curl verifies them against its pre-configured set of root certificates and aborts the connection if verification fails. By default curl never sends any client certificate credentials.

These defaults can be changed. Curl can be told to ignore the server's credentials and connect no matter what, to use a different set of root certificates and/or to send client certificate credentials.

Client certificate credentials are not widely used but if required by the server and a satisfactory set of client certificate credentials are not sent, the connection will be dropped immediately by the server.

Planting New Roots

cURL generally supports user-selected root certificates. No matter what backend is in use, they all support designating a single root certificate in standard DER or PEM format as well as multiple root certificates in concatenated PEM (PEMSEQ) format.

Some backends support specifying multiple root certificates in a special directory format that depends on which backend is in use.

Yanking Your Chain

The X.509 PKIX Certificate standard (RFC 5280) requires a certificate chain to consist of at least two certificates – a leaf certificate and a root certificate – in order to be valid.

The Transport Layer Security (TLS) 1.2 standard (RFC 5246 formerly known as the Secure Sockets Layer (SSL) standard) requires the server to send the entire server certificate chain (leaf through root) to the client and, if the client is providing a client certificate, the client must send the entire client certificate chain to the server. There is, however, an exception. The root certificate (of either chain) may be omitted (presumably the receiver already has it in its list of trusted roots, but it will have to search for it when omitted).

It’s unlikely in the extreme that any commercially purchased leaf certificate would have a full certificate chain consisting of only two certificates (the leaf and root). While client leaf certificates need not be commercially purchased, good security practices will make it similarly unlikely that such a client leaf certificate has a full certificate chain consisting of only two certificates.

Certifiable Clients

cURL allows the client certificate(s) to be configured. However, some cURL backends only support setting a single client certificate (the leaf).

Even if the root certificate of the client certificate chain is omitted (as permitted by the standard), many client certificate chains will still have at least two client certificates left (the leaf and one (or more) intermediate certificates).

Attempting to make a secure connection to a server that requires such a client certificate chain using cURL built with a backend that only supports a single client certificate will always fail.

Problem Backends

A cURL backend may have two different kinds of problems with client certificates:

  1. Client certificates and/or keys are not supported in the PEM or DER standard formats.
  2. Only the client leaf certificate may be specified, not the rest of the chain as required by the standard.

While the first problem can be worked around (assuming one can figure out how to munge the PEM/DER standards-format certificate(s) and key data into whatever format the backend expects), the second problem cannot and is a fatal flaw.

While it’s relatively easy to test for standard formats support, checking for full client certificate chain support requires access to a suitably configured server.

It turns out, however, that when the first problem is present, the second is also likely present. So testing for the first problem will provide a good indication of whether or not the second is present.

In order to provide multiple certificates for the client chain, the "PEM" format is required as the "DER" format can only provide a single certificate (or key).

cURL PEM Client Certificate Support Test

The following can be used to check your cURL to see what happens when you feed it PEM format client certificates:

touch /tmp/mt
nc -l localhost 2443&
curl -sv --cert-type PEM --cert /tmp/mt --key /tmp/mt https://localhost:2443/

If your cURL spews messages similar to these:

* WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure Transport. The private key must be in the Keychain.
* WARNING: SSL: The Security framework only supports loading identities that are in PKCS#12 format.

Then your cURL got sick and rejected the good PEM food.

Users of OS X 10.9 (Mavericks) and later (including 10.10 etc.) please note that the system’s cURL library (installed as /usr/lib/libcurl.4.dylib) is known to suffer from this malady. A version of cURL that does not have this problem on OS X 10.9 (and later) can be downloaded and installed as part of the Git OS X Installer.

On the other hand, if you see messages like these:

* Connected to localhost (::1) port 2443 (#0)
* unable to use client certificate (no key found or wrong pass phrase?)

Or even like these:

* Connected to localhost (127.0.0.1) port 2443 (#0)
* unable to load certificate data file '/tmp/mt'

Then your cURL is perfectly happy on a diet of PEM client certificates.

Git Tested Too

Git uses the cURL library for secure connections. If you wish to determine whether or not Git can properly consume PEM format client certificates, the actual cURL library that Git’s been linked with must be tested. Testing the cURL library that’s linked to the cURL command line client may give different results as that may, in fact, not be the same version of the cURL library that Git is linked to.

The following can be used to check the cURL library your Git client is linked with to see what happens when you feed it PEM format client certificates:

touch /tmp/mt
nc -l localhost 2443&
GIT_SSL_CERT=/tmp/mt GIT_SSL_KEY=/tmp/mt GIT_CURL_VERBOSE=1 \
  git ls-remote https://localhost:2443/

The same results shown above apply to this test as well. If your Git suffers from PEM client certificate indigestion and you are using OS X (any version of Git that links to the system’s cURL library on OS X 10.9 or later will have this problem), a version of Git that does not suffer from this malady can be downloaded and installed using the Git OS X Installer.