mirror of
https://github.com/curl/curl.git
synced 2026-07-24 10:57:16 +03:00
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
This commit is contained in:
parent
d1b56d0043
commit
93e450793c
20 changed files with 311 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -2362,6 +2362,107 @@ static CURLcode get_cert_chain(struct connectdata *conn,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue