When Cryptographic API Design Goes Wrong

keys with no locksWhether we like to admit it or not, failing to account for human factors and usability issues when designing secure systems can have unwanted consequences. And while Security Usability is a broad field, today I’d like to focus on what I like to call the [lack of] usability of [some] cryptographic APIs.

A paper on SSL Certificate Validation

To get my point across, I’d like to bring forth a paper written in 2012 by Martin Georgiev, Subodh Iyengar, Suman Jana, Rishita Anubhai, Dan Boneh, and Vitaly Shmatikov, called The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software.

In this paper, the authors claim and empirically confirm that SSL certificate validation is completely broken in many security-critical applications and libraries, meaning that any SSL connection initiated from any of these applications and libraries is insecure against a man-in-the-middle attack.

They credit these vulnerabilities to badly designed APIs of SSL implementations and data-transport libraries, which present developers with a confusing array of settings and options.

The case of libcurl

Of all the data-transport libraries whose APIs were studied for this research, I find the unfortunate case of libcurl to be particularly interesting.

Apparently, since version 7.10, cURL validates SSL certificates by default . In order to achieve this, it internally uses a lower level library backend to verify the chain of trust and verifies the hostname itself.

As a user of the library, even though you don’t have to, you could control the certificate validation functionality by fiddling with the CURLOPT_SSL_VERIFYPEER (a long whose default value is 1) and CURLOPT_SSL_VERIFYHOST (a long whose default value is 2) parameters.

According to this C example, you should write something along the lines of:

#include <stdio.h>
#include <curl/curl.h>

int main(void)
{
  CURL *curl;
  CURLcode res;

  curl_global_init(CURL_GLOBAL_DEFAULT);

  curl = curl_easy_init();

  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

    res = curl_easy_perform(curl);

    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));

    curl_easy_cleanup(curl);
  }

  curl_global_cleanup();

  return 0;
}

And this is where things get interesting, some developers did not read (or understand) these parameters, so they set both parameters to boolean true (which gets transformed internally to 1), thereby deactivating hostname verification. According to Github, some people still do it in PHP, and even in C++.

Fortunately, after the paper was published, even though Daniel Stenberg, the cURL and libcurl author and maintainer, failed to admit the  interface was unintuitive (his thoughts on the article can be read in this blog post), starting with version 7.28.1 of libcurl, setting CURLOPT_SSL_VERIFYHOST to 1 (and implicitly boolean true, which was actually never a valid option for this parameter) has been banned.

Conclusion

While the wrong usage of libcurl’s interface no longer poses a security risk, I believe the lesson to be learned here is that cryptographic APIs should be designed with security usability in mind. The authors should also move away from using cryptic options such as CURLOPT_SSL_VERIFYPEER or CURLOPT_SSL_VERIFYHOST and move towards higher level names such as “confidential and authenticated channel ” (something along the lines of what NaCl is doing).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s