diff options
| author | moparisthebest <admin@moparisthebest.com> | 2014-09-30 22:31:17 -0400 | 
|---|---|---|
| committer | Daniel Stenberg <daniel@haxx.se> | 2014-10-07 14:44:19 +0200 | 
| commit | 93e450793ce289925dfd1d5e3b2d14e781f8dfd4 (patch) | |
| tree | 3ceea898922e067a4a692204f6388ab633deebef /lib | |
| parent | d1b56d00439ab26d7fc43e37ab18ae331ddc400d (diff) | |
SSL: implement public key pinning
Option --pinnedpubkey takes a path to a public key in DER format and
only connect if it matches (currently only implemented with OpenSSL).
Provides CURLOPT_PINNEDPUBLICKEY for curl_easy_setopt().
Extract a public RSA key from a website like so:
openssl s_client -connect google.com:443 2>&1 < /dev/null | \
sed -n '/-----BEGIN/,/-----END/p' | openssl x509 -noout -pubkey \
| openssl rsa -pubin -outform DER > google.com.der
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/strerror.c | 3 | ||||
| -rw-r--r-- | lib/url.c | 8 | ||||
| -rw-r--r-- | lib/urldata.h | 1 | ||||
| -rw-r--r-- | lib/vtls/openssl.c | 108 | 
4 files changed, 120 insertions, 0 deletions
| diff --git a/lib/strerror.c b/lib/strerror.c index 66033f219..1a1360607 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -298,6 +298,9 @@ curl_easy_strerror(CURLcode error)    case CURLE_NO_CONNECTION_AVAILABLE:      return "The max connection limit is reached"; +  case CURLE_SSL_PINNEDPUBKEYNOTMATCH: +    return "SSL public key does not matched pinned public key"; +      /* error codes not used by current libcurl */    case CURLE_OBSOLETE20:    case CURLE_OBSOLETE24: @@ -1991,6 +1991,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,      result = CURLE_NOT_BUILT_IN;  #endif      break; +  case CURLOPT_PINNEDPUBLICKEY: +    /* +     * Set pinned public key for SSL connection. +     * Specify file name of the public key in DER format. +     */ +    result = setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], +                       va_arg(param, char *)); +    break;    case CURLOPT_CAINFO:      /*       * Set CA info for SSL connection. Specify file name of the CA certificate diff --git a/lib/urldata.h b/lib/urldata.h index 8594c2f7d..fd59d781d 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1385,6 +1385,7 @@ enum dupstring {    STRING_SET_URL,         /* what original URL to work on */    STRING_SSL_CAPATH,      /* CA directory name (doesn't work on windows) */    STRING_SSL_CAFILE,      /* certificate file to verify peer against */ +  STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */    STRING_SSL_CIPHER_LIST, /* list of ciphers to use */    STRING_SSL_EGDSOCKET,   /* path to file containing the EGD daemon socket */    STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 2d1fa5bd3..aacd2778f 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2363,6 +2363,107 @@ static CURLcode get_cert_chain(struct connectdata *conn,  }  /* + * Heavily modified from: + * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL + */ +static int pkp_pin_peer_pubkey(X509* cert, char *pinnedpubkey) +{ +  /* Scratch */ +  FILE* fp = NULL; +  int len1 = 0, len2 = 0; +  unsigned char *buff1 = NULL, *buff2 = NULL, *temp = NULL; +  long size = 0; + +  /* Result is returned to caller */ +  int ret = 0, result = FALSE; + +  /* if a path wasn't specified, don't pin */ +  if(NULL == pinnedpubkey) return TRUE; +  if(NULL == cert) return FALSE; + +  do { +    /* Begin Gyrations to get the subjectPublicKeyInfo     */ +    /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */ + +    /* http://groups.google.com/group/mailing.openssl.users/browse_thread +     /thread/d61858dae102c6c7 */ +    len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); +    if(len1 < 1) +      break; /* failed */ + +    /* http://www.openssl.org/docs/crypto/buffer.html */ +    buff1 = temp = OPENSSL_malloc(len1); +    if(NULL == buff1) +      break; /* failed */ + +    /* http://www.openssl.org/docs/crypto/d2i_X509.html */ +    len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); + +    /* +     * These checks are verifying we got back the same values as when we +     * sized the buffer.Its pretty weak since they should always be the +     * same. But it gives us something to test. +     */ +    if(len1 != len2 || temp == NULL || ((temp - buff1) != len1)) +      break; /* failed */ + +    /* End Gyrations */ + +    /* See the warning above!!! */ +    fp = fopen(pinnedpubkey, "r"); + +    if(NULL == fp) +      break; /* failed */ + +    /* Seek to eof to determine the file's size */ +    ret = fseek(fp, 0, SEEK_END); +    if(0 != ret) +      break; /* failed */ + +    /* Fetch the file's size */ +    size = ftell(fp); + +    /* +     * 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(len2 != size) +      break; /* failed */ + +    /* Rewind to beginning to perform the read */ +    ret = fseek(fp, 0, SEEK_SET); +    if(0 != ret) +      break; /* failed */ + +    /* http://www.openssl.org/docs/crypto/buffer.html */ +    buff2 = OPENSSL_malloc(len2); +    if(NULL == buff2) +      break; /* failed */ + +    /* Returns number of elements read, which should be 1 */ +    ret = (int)fread(buff2, (size_t)len2, 1, fp); +    if(1 != ret) +      break; /* failed */ + +    /* The one good exit point */ +    result = (0 == memcmp(buff1, buff2, (size_t)len2)); + +  } while(0); + +  if(NULL != fp) +    fclose(fp); + +  /* http://www.openssl.org/docs/crypto/buffer.html */ +  if(NULL != buff2) +    OPENSSL_free(buff2); + +  if(NULL != buff1) +    OPENSSL_free(buff1); + +  return result; +} + +/*   * Get the server cert, verify it and show it etc, only call failf() if the   * 'strict' argument is TRUE as otherwise all this is for informational   * purposes only! @@ -2485,6 +2586,13 @@ static CURLcode servercert(struct connectdata *conn,        infof(data, "\t SSL certificate verify ok.\n");    } +  if(data->set.str[STRING_SSL_PINNEDPUBLICKEY] != NULL && +      TRUE != pkp_pin_peer_pubkey(connssl->server_cert, +      data->set.str[STRING_SSL_PINNEDPUBLICKEY])) { +    failf(data, "SSL: public key does not matched pinned public key!"); +    return CURLE_SSL_PINNEDPUBKEYNOTMATCH; +  } +    X509_free(connssl->server_cert);    connssl->server_cert = NULL;    connssl->connecting_state = ssl_connect_done; | 
