Commit be1a5051 authored by Travis Burtrum's avatar Travis Burtrum Committed by Patrick Monnerat
Browse files

SSL: Add PEM format support for public key pinning

parent 2008c925
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -539,14 +539,14 @@ If this option is set, the default capath value will be ignored, and if it is
used several times, the last one will be used.
.IP "--pinnedpubkey <pinned public key>"
(SSL) Tells curl to use the specified public key file to verify the peer. The
file must contain a single public key in DER format.
file must contain a single public key in PEM or DER format.

When negotiating a TLS or SSL connection, the server sends a certificate
indicating its identity. A public key is extracted from this certificate and
if it does not exactly match the public key provided to this option, curl will
abort the connection before sending or receiving any data.

This is currently only implemented in the OpenSSL and GnuTLS backends.
This is currently only implemented in the OpenSSL, GnuTLS and GSKit backends.

If this option is used several times, the last one will be used.
(Added in 7.39.0)
+1 −1
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ CURLOPT_PINNEDPUBLICKEY \- set pinned public key
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PINNEDPUBLICKEY, char *pinnedpubkey);
.SH DESCRIPTION
Pass a pointer to a zero terminated string as parameter. The string should be
the file name of your pinned public key. The format expected is "DER".
the file name of your pinned public key. The format expected is "PEM" or "DER".

When negotiating a TLS or SSL connection, the server sends a certificate
indicating its identity. A public key is extracted from this certificate and
+100 −12
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@
#include "timeval.h"
#include "curl_md5.h"
#include "warnless.h"
#include "curl_base64.h"

#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
@@ -683,6 +684,64 @@ int Curl_ssl_random(struct SessionHandle *data,
  return curlssl_random(data, entropy, length);
}

/*
 * Public key pem to der conversion
 */

static CURLcode pubkey_pem_to_der(const char *pem,
                                  unsigned char **der, size_t *der_len)
{
  char *stripped_pem, *begin_pos, *end_pos;
  size_t pem_count, stripped_pem_count = 0, pem_len;
  CURLcode result;

  /* if no pem, exit. */
  if(!pem)
    return CURLE_BAD_CONTENT_ENCODING;

  begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----");
  if(!begin_pos)
    return CURLE_BAD_CONTENT_ENCODING;

  pem_count = begin_pos - pem;
  /* Invalid if not at beginning AND not directly following \n */
  if(0 != pem_count && '\n' != pem[pem_count - 1])
    return CURLE_BAD_CONTENT_ENCODING;

  /* 26 is length of "-----BEGIN PUBLIC KEY-----" */
  pem_count += 26;

  /* Invalid if not directly following \n */
  end_pos = strstr(pem + pem_count, "\n-----END PUBLIC KEY-----");
  if(!end_pos)
    return CURLE_BAD_CONTENT_ENCODING;

  pem_len = end_pos - pem;

  stripped_pem = malloc(pem_len - pem_count + 1);
  if(!stripped_pem)
    return CURLE_OUT_OF_MEMORY;

  /*
   * Here we loop through the pem array one character at a time between the
   * correct indices, and place each character that is not '\n' or '\r'
   * into the stripped_pem array, which should represent the raw base64 string
   */
  while(pem_count < pem_len) {
    if('\n' != pem[pem_count] && '\r' != pem[pem_count])
      stripped_pem[stripped_pem_count++] = pem[pem_count];
    ++pem_count;
  }
  /* Place the null terminator in the correct place */
  stripped_pem[stripped_pem_count] = '\0';

  result = Curl_base64_decode(stripped_pem, der, der_len);

  Curl_safefree(stripped_pem);

  return result;
}

/*
 * Generic pinned public key check.
 */
@@ -690,9 +749,11 @@ int Curl_ssl_random(struct SessionHandle *data,
CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
                              const unsigned char *pubkey, size_t pubkeylen)
{
  FILE *fp = NULL;
  unsigned char *buf = NULL;
  long size = 0;
  FILE *fp;
  unsigned char *buf = NULL, *pem_ptr = NULL;
  long filesize;
  size_t size, pem_len;
  CURLcode pem_read;
  CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;

  /* if a path wasn't specified, don't pin */
@@ -708,32 +769,59 @@ CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
    /* Determine the file's size */
    if(fseek(fp, 0, SEEK_END))
      break;
    size = ftell(fp);
    filesize = ftell(fp);
    if(fseek(fp, 0, SEEK_SET))
      break;
    if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE)
      break;

    /*
     * if the size of our certificate doesn't match the size of
     * the file, they can't be the same, don't bother reading it
     * if the size of our certificate is bigger than the file
     * size then it can't match
     */
    if((long) pubkeylen != size)
    size = curlx_sotouz((curl_off_t) filesize);
    if(pubkeylen > size)
      break;

    /* Allocate buffer for the pinned key. */
    buf = malloc(pubkeylen);
    /*
     * Allocate buffer for the pinned key
     * With 1 additional byte for null terminator in case of PEM key
     */
    buf = malloc(size + 1);
    if(!buf)
      break;

    /* Returns number of elements read, which should be 1 */
    if((int) fread(buf, pubkeylen, 1, fp) != 1)
    if((int) fread(buf, size, 1, fp) != 1)
      break;

    /* The one good exit point */
    /* If the sizes are the same, it can't be base64 encoded, must be der */
    if(pubkeylen == size) {
      if(!memcmp(pubkey, buf, pubkeylen))
        result = CURLE_OK;
      break;
    }

    /*
     * Otherwise we will assume it's PEM and try to decode it
     * after placing null terminator
     */
    buf[size] = '\0';
    pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len);
    /* if it wasn't read successfully, exit */
    if(pem_read)
      break;

    /*
     * if the size of our certificate doesn't match the size of
     * the decoded file, they can't be the same, otherwise compare
     */
    if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen))
      result = CURLE_OK;
  } while(0);

  Curl_safefree(buf);
  Curl_safefree(pem_ptr);
  fclose(fp);

  return result;
+4 −0
Original line number Diff line number Diff line
@@ -33,6 +33,10 @@
#include "curl_schannel.h" /* Schannel SSPI version */
#include "curl_darwinssl.h" /* SecureTransport (Darwin) version */

#ifndef MAX_PINNED_PUBKEY_SIZE
#define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */
#endif

#ifndef MD5_DIGEST_LENGTH
#define MD5_DIGEST_LENGTH 16 /* fixed size */
#endif
+2 −1
Original line number Diff line number Diff line
@@ -152,7 +152,8 @@ static const char *const helptext[] = {
  "     --oauth2-bearer TOKEN  OAuth 2 Bearer Token (IMAP, POP3, SMTP)",
  " -o, --output FILE   Write to FILE instead of stdout",
  "     --pass PASS     Pass phrase for the private key (SSL/SSH)",
  "     --pinnedpubkey FILE Public key (DER) to verify peer against (OpenSSL)",
  "     --pinnedpubkey FILE  Public key (PEM/DER) to verify peer against "
  "(OpenSSL/GnuTLS/GSKit only)",
  "     --post301       "
  "Do not switch to GET after following a 301 redirect (H)",
  "     --post302       "
Loading