Newer
Older
Daniel Stenberg
committed
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
Daniel Stenberg
committed
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
Daniel Stenberg
committed
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* This file is for implementing all "generic" SSL functions that all libcurl
internals should use. It is then responsible for calling the proper
"backend" function.
Daniel Stenberg
committed
SSL-functions in libcurl should call functions in this source file, and not
to any specific SSL-layer.
Curl_ssl_ - prefix for generic ones
Curl_ossl_ - prefix for OpenSSL ones
Curl_gtls_ - prefix for GnuTLS ones
Curl_nss_ - prefix for NSS ones
Curl_cyassl_ - prefix for CyaSSL ones
Curl_schannel_ - prefix for Schannel SSPI ones
Curl_darwinssl_ - prefix for SecureTransport (Darwin) ones
Daniel Stenberg
committed
Daniel Stenberg
committed
Note that this source code uses curlssl_* functions, and they are all
defines/macros #defined by the lib-specific header files.
Daniel Stenberg
committed
"SSL/TLS Strong Encryption: An Introduction"
https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html
Daniel Stenberg
committed
*/
#include "curl_setup.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "vtls.h" /* generic SSL protos etc */
#include "slist.h"
#include "sendf.h"
#include "rawstr.h"
#include "url.h"
#include "progress.h"
#include "share.h"
Patrick Monnerat
committed
#include "curl_md5.h"
#include "curl_base64.h"
/* The last #include files should be: */
#include "curl_memory.h"
Daniel Stenberg
committed
/* convenience macro to check if this handle is using a shared SSL session */
#define SSLSESSION_SHARED(data) (data->share && \
(data->share->specifier & \
(1<<CURL_LOCK_DATA_SSL_SESSION)))
Daniel Stenberg
committed
static bool safe_strequal(char* str1, char* str2)
{
if(str1 && str2)
/* both pointers point to something then compare them */
return (0 != Curl_raw_equal(str1, str2)) ? TRUE : FALSE;
Daniel Stenberg
committed
else
/* if both pointers are NULL then treat them as equal */
return (!str1 && !str2) ? TRUE : FALSE;
Daniel Stenberg
committed
}
bool
Curl_ssl_config_matches(struct ssl_config_data* data,
struct ssl_config_data* needle)
{
if((data->version == needle->version) &&
(data->verifypeer == needle->verifypeer) &&
(data->verifyhost == needle->verifyhost) &&
safe_strequal(data->CApath, needle->CApath) &&
safe_strequal(data->CAfile, needle->CAfile) &&
safe_strequal(data->clientcert, needle->clientcert) &&
Daniel Stenberg
committed
safe_strequal(data->random_file, needle->random_file) &&
safe_strequal(data->egdsocket, needle->egdsocket) &&
safe_strequal(data->cipher_list, needle->cipher_list))
return TRUE;
return FALSE;
}
bool
Curl_clone_ssl_config(struct ssl_config_data *source,
struct ssl_config_data *dest)
{
Daniel Stenberg
committed
dest->sessionid = source->sessionid;
Daniel Stenberg
committed
dest->verifyhost = source->verifyhost;
dest->verifypeer = source->verifypeer;
dest->version = source->version;
if(source->CAfile) {
dest->CAfile = strdup(source->CAfile);
if(!dest->CAfile)
return FALSE;
}
else
dest->CAfile = NULL;
Daniel Stenberg
committed
if(source->CApath) {
dest->CApath = strdup(source->CApath);
if(!dest->CApath)
return FALSE;
}
else
dest->CApath = NULL;
Daniel Stenberg
committed
if(source->cipher_list) {
dest->cipher_list = strdup(source->cipher_list);
if(!dest->cipher_list)
return FALSE;
}
else
dest->cipher_list = NULL;
Daniel Stenberg
committed
if(source->egdsocket) {
dest->egdsocket = strdup(source->egdsocket);
if(!dest->egdsocket)
return FALSE;
}
else
dest->egdsocket = NULL;
Daniel Stenberg
committed
if(source->random_file) {
dest->random_file = strdup(source->random_file);
if(!dest->random_file)
return FALSE;
}
else
dest->random_file = NULL;
Daniel Stenberg
committed
if(source->clientcert) {
dest->clientcert = strdup(source->clientcert);
if(!dest->clientcert)
return FALSE;
dest->sessionid = FALSE;
}
else
dest->clientcert = NULL;
Daniel Stenberg
committed
return TRUE;
}
void Curl_free_ssl_config(struct ssl_config_data* sslc)
{
Daniel Stenberg
committed
Curl_safefree(sslc->CAfile);
Curl_safefree(sslc->CApath);
Curl_safefree(sslc->cipher_list);
Curl_safefree(sslc->egdsocket);
Curl_safefree(sslc->random_file);
Curl_safefree(sslc->clientcert);
Daniel Stenberg
committed
}
/*
* Curl_rand() returns a random unsigned integer, 32bit.
*
* This non-SSL function is put here only because this file is the only one
* with knowledge of what the underlying SSL libraries provide in terms of
* randomizers.
*
* NOTE: 'data' may be passed in as NULL when coming from external API without
* easy handle!
*
*/
unsigned int Curl_rand(struct Curl_easy *data)
#ifdef CURLDEBUG
char *force_entropy = getenv("CURL_ENTROPY");
if(force_entropy) {
if(!seeded) {
size_t elen = strlen(force_entropy);
size_t clen = sizeof(randseed);
size_t min = elen < clen ? elen : clen;
memcpy((char *)&randseed, force_entropy, min);
seeded = TRUE;
}
else
randseed++;
return randseed;
}
#endif
/* data may be NULL! */
if(!Curl_ssl_random(data, (unsigned char *)&r, sizeof(r)))
/* If Curl_ssl_random() returns non-zero it couldn't offer randomness and we
instead perform a "best effort" */
if(!seeded) {
/* if there's a random file to read a seed from, use it */
int fd = open(RANDOM_FILE, O_RDONLY);
if(fd > -1) {
/* read random data into the randseed variable */
ssize_t nread = read(fd, &randseed, sizeof(randseed));
if(nread == sizeof(randseed))
if(!seeded) {
struct timeval now = curlx_tvnow();
infof(data, "WARNING: Using weak random seed\n");
randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec;
randseed = randseed * 1103515245 + 12345;
randseed = randseed * 1103515245 + 12345;
randseed = randseed * 1103515245 + 12345;
seeded = TRUE;
}
/* Return an unsigned 32-bit pseudo-random number. */
r = randseed = randseed * 1103515245 + 12345;
return (r << 16) | ((r >> 16) & 0xFFFF);
int Curl_ssl_backend(void)
{
return (int)CURL_SSL_BACKEND;
}
Daniel Stenberg
committed
#ifdef USE_SSL
/* "global" init done? */
static bool init_ssl=FALSE;
Daniel Stenberg
committed
/**
* Global SSL init
*
* @retval 0 error initializing SSL
* @retval 1 SSL initialized successfully
*/
int Curl_ssl_init(void)
{
/* make sure this is only done once */
if(init_ssl)
return 1;
init_ssl = TRUE; /* never again */
Daniel Stenberg
committed
return curlssl_init();
Daniel Stenberg
committed
}
/* Global cleanup */
void Curl_ssl_cleanup(void)
{
if(init_ssl) {
/* only cleanup if we did a previous init */
Daniel Stenberg
committed
curlssl_cleanup();
Daniel Stenberg
committed
init_ssl = FALSE;
}
}
static bool ssl_prefs_check(struct Curl_easy *data)
{
/* check for CURLOPT_SSLVERSION invalid parameter value */
if((data->set.ssl.version < 0)
|| (data->set.ssl.version >= CURL_SSLVERSION_LAST)) {
failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION");
return FALSE;
}
return TRUE;
}
Daniel Stenberg
committed
CURLcode
Curl_ssl_connect(struct connectdata *conn, int sockindex)
{
if(!ssl_prefs_check(conn->data))
return CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
/* mark this is being ssl-enabled from here on. */
Daniel Stenberg
committed
conn->ssl[sockindex].use = TRUE;
Daniel Stenberg
committed
conn->ssl[sockindex].state = ssl_connection_negotiating;
Daniel Stenberg
committed
result = curlssl_connect(conn, sockindex);
Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */
Daniel Stenberg
committed
}
Daniel Stenberg
committed
CURLcode
Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex,
bool *done)
{
if(!ssl_prefs_check(conn->data))
return CURLE_SSL_CONNECT_ERROR;
Daniel Stenberg
committed
/* mark this is being ssl requested from here on. */
Daniel Stenberg
committed
conn->ssl[sockindex].use = TRUE;
#ifdef curlssl_connect_nonblocking
result = curlssl_connect_nonblocking(conn, sockindex, done);
#else
*done = TRUE; /* fallback to BLOCKING */
result = curlssl_connect(conn, sockindex);
Daniel Stenberg
committed
#endif /* non-blocking connect support */
Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */
Daniel Stenberg
committed
}
/*
* Lock shared SSL session data
*/
void Curl_ssl_sessionid_lock(struct connectdata *conn)
{
if(SSLSESSION_SHARED(conn->data))
Curl_share_lock(conn->data,
CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
}
/*
* Unlock shared SSL session data
*/
void Curl_ssl_sessionid_unlock(struct connectdata *conn)
{
if(SSLSESSION_SHARED(conn->data))
Curl_share_unlock(conn->data, CURL_LOCK_DATA_SSL_SESSION);
}
Daniel Stenberg
committed
/*
* Check if there's a session ID for the given connection in the cache, and if
* there's one suitable, it is provided. Returns TRUE when no entry matched.
*/
bool Curl_ssl_getsessionid(struct connectdata *conn,
void **ssl_sessionid,
size_t *idsize) /* set 0 if unknown */
Daniel Stenberg
committed
{
struct curl_ssl_session *check;
struct Curl_easy *data = conn->data;
long *general_age;
bool no_match = TRUE;
*ssl_sessionid = NULL;
Daniel Stenberg
committed
DEBUGASSERT(conn->ssl_config.sessionid);
Daniel Stenberg
committed
if(!conn->ssl_config.sessionid)
/* session ID re-use is disabled */
return TRUE;
general_age = &data->share->sessionage;
else
general_age = &data->state.sessionage;
for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) {
Daniel Stenberg
committed
check = &data->state.session[i];
if(!check->sessionid)
/* not session ID means blank entry */
continue;
if(Curl_raw_equal(conn->host.name, check->name) &&
((!conn->bits.conn_to_host && !check->conn_to_host) ||
(conn->bits.conn_to_host && check->conn_to_host &&
Curl_raw_equal(conn->conn_to_host.name, check->conn_to_host))) &&
((!conn->bits.conn_to_port && check->conn_to_port == -1) ||
(conn->bits.conn_to_port && check->conn_to_port != -1 &&
conn->conn_to_port == check->conn_to_port)) &&
Daniel Stenberg
committed
(conn->remote_port == check->remote_port) &&
Curl_ssl_config_matches(&conn->ssl_config, &check->ssl_config)) {
/* yes, we have a session ID! */
(*general_age)++; /* increase general age */
check->age = *general_age; /* set this as used in this age */
Daniel Stenberg
committed
*ssl_sessionid = check->sessionid;
if(idsize)
*idsize = check->idsize;
no_match = FALSE;
break;
Daniel Stenberg
committed
}
}
Daniel Stenberg
committed
}
/*
* Kill a single session ID entry in the cache.
*/
void Curl_ssl_kill_session(struct curl_ssl_session *session)
Daniel Stenberg
committed
{
if(session->sessionid) {
/* defensive check */
/* free the ID the SSL-layer specific way */
Daniel Stenberg
committed
curlssl_session_free(session->sessionid);
Daniel Stenberg
committed
session->age = 0; /* fresh */
Curl_free_ssl_config(&session->ssl_config);
Curl_safefree(session->name);
Curl_safefree(session->conn_to_host);
Daniel Stenberg
committed
}
}
/*
* Delete the given session ID from the cache.
*/
void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid)
{
struct Curl_easy *data=conn->data;
for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) {
struct curl_ssl_session *check = &data->state.session[i];
if(check->sessionid == ssl_sessionid) {
break;
}
}
}
Daniel Stenberg
committed
/*
* Store session id in the session cache. The ID passed on to this function
* must already have been extracted and allocated the proper way for the SSL
* layer. Curl_XXXX_session_free() will be called to free/kill the session ID
* later on.
*/
CURLcode Curl_ssl_addsessionid(struct connectdata *conn,
void *ssl_sessionid,
size_t idsize)
{
struct Curl_easy *data=conn->data; /* the mother of all structs */
Daniel Stenberg
committed
struct curl_ssl_session *store = &data->state.session[0];
long oldest_age=data->state.session[0].age; /* zero if unused */
char *clone_host;
char *clone_conn_to_host;
int conn_to_port;
long *general_age;
Daniel Stenberg
committed
DEBUGASSERT(conn->ssl_config.sessionid);
Daniel Stenberg
committed
Daniel Stenberg
committed
clone_host = strdup(conn->host.name);
if(!clone_host)
return CURLE_OUT_OF_MEMORY; /* bail out */
if(conn->bits.conn_to_host) {
clone_conn_to_host = strdup(conn->conn_to_host.name);
if(!clone_conn_to_host) {
free(clone_host);
return CURLE_OUT_OF_MEMORY; /* bail out */
}
}
else
clone_conn_to_host = NULL;
if(conn->bits.conn_to_port)
conn_to_port = conn->conn_to_port;
else
conn_to_port = -1;
Daniel Stenberg
committed
/* Now we should add the session ID and the host name to the cache, (remove
the oldest if necessary) */
/* If using shared SSL session, lock! */
if(SSLSESSION_SHARED(data)) {
general_age = &data->share->sessionage;
}
else {
general_age = &data->state.sessionage;
}
Daniel Stenberg
committed
/* find an empty slot for us, or find the oldest */
for(i = 1; (i < data->set.ssl.max_ssl_sessions) &&
Daniel Stenberg
committed
data->state.session[i].sessionid; i++) {
if(data->state.session[i].age < oldest_age) {
oldest_age = data->state.session[i].age;
store = &data->state.session[i];
}
}
if(i == data->set.ssl.max_ssl_sessions)
Daniel Stenberg
committed
/* cache is full, we must "kill" the oldest entry! */
Daniel Stenberg
committed
else
store = &data->state.session[i]; /* use this slot */
/* now init the session struct wisely */
store->sessionid = ssl_sessionid;
store->idsize = idsize;
store->age = *general_age; /* set current age */
/* free it if there's one already present */
Markus Elfring
committed
free(store->name);
Daniel Stenberg
committed
store->name = clone_host; /* clone host name */
store->conn_to_host = clone_conn_to_host; /* clone connect to host name */
store->conn_to_port = conn_to_port; /* connect to port number */
Daniel Stenberg
committed
store->remote_port = conn->remote_port; /* port number */
if(!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config)) {
store->sessionid = NULL; /* let caller free sessionid */
free(clone_host);
Daniel Stenberg
committed
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
return CURLE_OK;
}
void Curl_ssl_close_all(struct Curl_easy *data)
Daniel Stenberg
committed
{
/* kill the session ID cache if not shared */
if(data->state.session && !SSLSESSION_SHARED(data)) {
for(i = 0; i < data->set.ssl.max_ssl_sessions; i++)
Daniel Stenberg
committed
/* the single-killer function handles empty table slots */
Curl_ssl_kill_session(&data->state.session[i]);
Daniel Stenberg
committed
/* free the cache data */
Curl_safefree(data->state.session);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
curlssl_close_all(data);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
void Curl_ssl_close(struct connectdata *conn, int sockindex)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
DEBUGASSERT((sockindex <= 1) && (sockindex >= -1));
Daniel Stenberg
committed
curlssl_close(conn, sockindex);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
CURLcode Curl_ssl_shutdown(struct connectdata *conn, int sockindex)
{
Daniel Stenberg
committed
if(curlssl_shutdown(conn, sockindex))
Daniel Stenberg
committed
return CURLE_SSL_SHUTDOWN_FAILED;
conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */
Daniel Stenberg
committed
conn->ssl[sockindex].state = ssl_connection_none;
Daniel Stenberg
committed
conn->recv[sockindex] = Curl_recv_plain;
conn->send[sockindex] = Curl_send_plain;
Daniel Stenberg
committed
return CURLE_OK;
}
Daniel Stenberg
committed
/* Selects an SSL crypto engine
Daniel Stenberg
committed
*/
CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
return curlssl_set_engine(data, engine);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/* Selects the default SSL crypto engine
Daniel Stenberg
committed
*/
CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
return curlssl_set_engine_default(data);
Daniel Stenberg
committed
}
/* Return list of OpenSSL crypto engine names. */
struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
return curlssl_engines_list(data);
Daniel Stenberg
committed
}
/*
* This sets up a session ID cache to the specified size. Make sure this code
* is agnostic to what underlying SSL technology we use.
*/
CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount)
Daniel Stenberg
committed
{
struct curl_ssl_session *session;
if(data->state.session)
/* this is just a precaution to prevent multiple inits */
return CURLE_OK;
session = calloc(amount, sizeof(struct curl_ssl_session));
Daniel Stenberg
committed
if(!session)
return CURLE_OUT_OF_MEMORY;
/* store the info in the SSL section */
data->set.ssl.max_ssl_sessions = amount;
Daniel Stenberg
committed
data->state.session = session;
data->state.sessionage = 1; /* this is brand new */
return CURLE_OK;
}
size_t Curl_ssl_version(char *buffer, size_t size)
{
Daniel Stenberg
committed
return curlssl_version(buffer, size);
Daniel Stenberg
committed
}
/*
* This function tries to determine connection status.
*
* Return codes:
* 1 means the connection is still in place
* 0 means the connection has been closed
* -1 means the connection status is unknown
*/
int Curl_ssl_check_cxn(struct connectdata *conn)
{
Daniel Stenberg
committed
return curlssl_check_cxn(conn);
}
bool Curl_ssl_data_pending(const struct connectdata *conn,
int connindex)
{
Daniel Stenberg
committed
return curlssl_data_pending(conn, connindex);
Daniel Stenberg
committed
void Curl_ssl_free_certinfo(struct Curl_easy *data)
Daniel Stenberg
committed
{
int i;
struct curl_certinfo *ci = &data->info.certs;
Daniel Stenberg
committed
if(ci->num_of_certs) {
/* free all individual lists used */
Daniel Stenberg
committed
curl_slist_free_all(ci->certinfo[i]);
Daniel Stenberg
committed
free(ci->certinfo); /* free the actual array too */
Daniel Stenberg
committed
ci->num_of_certs = 0;
}
}
CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num)
struct curl_certinfo *ci = &data->info.certs;
struct curl_slist **table;
/* Free any previous certificate information structures */
Curl_ssl_free_certinfo(data);
/* Allocate the required certificate information structures */
table = calloc((size_t) num, sizeof(struct curl_slist *));
if(!table)
return CURLE_OUT_OF_MEMORY;
ci->num_of_certs = num;
ci->certinfo = table;
return CURLE_OK;
}
/*
* 'value' is NOT a zero terminated string
*/
CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data,
int certnum,
const char *label,
const char *value,
size_t valuelen)
{
struct curl_certinfo * ci = &data->info.certs;
char * output;
struct curl_slist * nl;
size_t labellen = strlen(label);
size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */
output = malloc(outlen);
if(!output)
return CURLE_OUT_OF_MEMORY;
/* sprintf the label and colon */
snprintf(output, outlen, "%s:", label);
/* memcpy the value (it might not be zero terminated) */
memcpy(&output[labellen+1], value, valuelen);
/* zero terminate the output */
output[labellen + 1 + valuelen] = 0;
nl = Curl_slist_append_nodup(ci->certinfo[certnum], output);
if(!nl) {
free(output);
curl_slist_free_all(ci->certinfo[certnum]);
result = CURLE_OUT_OF_MEMORY;
}
ci->certinfo[certnum] = nl;
}
/*
* This is a convenience function for push_certinfo_len that takes a zero
* terminated value.
*/
CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data,
int certnum,
const char *label,
const char *value)
{
size_t valuelen = strlen(value);
return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen);
}
int Curl_ssl_random(struct Curl_easy *data,
unsigned char *entropy,
size_t length)
{
return curlssl_random(data, entropy, length);
}
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
/*
* 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;
}
Patrick Monnerat
committed
/*
* Generic pinned public key check.
*/
CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data,
const char *pinnedpubkey,
Patrick Monnerat
committed
const unsigned char *pubkey, size_t pubkeylen)
{
FILE *fp;
unsigned char *buf = NULL, *pem_ptr = NULL;
long filesize;
size_t size, pem_len;
CURLcode pem_read;
Patrick Monnerat
committed
CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
CURLcode encode;
size_t encodedlen, pinkeylen;
char *encoded, *pinkeycopy, *begin_pos, *end_pos;
unsigned char *sha256sumdigest = NULL;
Patrick Monnerat
committed
/* if a path wasn't specified, don't pin */
if(!pinnedpubkey)
return CURLE_OK;
if(!pubkey || !pubkeylen)
return result;
/* only do this if pinnedpubkey starts with "sha256//", length 8 */
if(strncmp(pinnedpubkey, "sha256//", 8) == 0) {
#ifdef curlssl_sha256sum
/* compute sha256sum of public key */
sha256sumdigest = malloc(SHA256_DIGEST_LENGTH);
if(!sha256sumdigest)
return CURLE_OUT_OF_MEMORY;
curlssl_sha256sum(pubkey, pubkeylen,
sha256sumdigest, SHA256_DIGEST_LENGTH);
encode = Curl_base64_encode(data, (char *)sha256sumdigest,
SHA256_DIGEST_LENGTH, &encoded, &encodedlen);
Curl_safefree(sha256sumdigest);
infof(data, "\t public key hash: sha256//%s\n", encoded);
/* it starts with sha256//, copy so we can modify it */
pinkeylen = strlen(pinnedpubkey) + 1;
pinkeycopy = malloc(pinkeylen);
if(!pinkeycopy) {
Curl_safefree(encoded);
return CURLE_OUT_OF_MEMORY;
}
memcpy(pinkeycopy, pinnedpubkey, pinkeylen);
/* point begin_pos to the copy, and start extracting keys */
begin_pos = pinkeycopy;
do {
end_pos = strstr(begin_pos, ";sha256//");
/*
* if there is an end_pos, null terminate,
* otherwise it'll go to the end of the original string
*/
if(end_pos)
end_pos[0] = '\0';
/* compare base64 sha256 digests, 8 is the length of "sha256//" */
if(encodedlen == strlen(begin_pos + 8) &&
!memcmp(encoded, begin_pos + 8, encodedlen)) {
result = CURLE_OK;
break;
}
/*
* change back the null-terminator we changed earlier,
* and look for next begin
*/
if(end_pos) {
end_pos[0] = ';';
begin_pos = strstr(end_pos, "sha256//");
}
} while(end_pos && begin_pos);
Curl_safefree(encoded);
/* without sha256 support, this cannot match */
(void)data;
return result;
}
Patrick Monnerat
committed
fp = fopen(pinnedpubkey, "rb");
if(!fp)
return result;
do {
/* Determine the file's size */
if(fseek(fp, 0, SEEK_END))
break;
filesize = ftell(fp);
Patrick Monnerat
committed
if(fseek(fp, 0, SEEK_SET))
break;
if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE)
break;
Patrick Monnerat
committed
/*
* if the size of our certificate is bigger than the file
* size then it can't match
Patrick Monnerat
committed
*/
size = curlx_sotouz((curl_off_t) filesize);
if(pubkeylen > size)
Patrick Monnerat
committed
break;
/*
* Allocate buffer for the pinned key
* With 1 additional byte for null terminator in case of PEM key
*/
buf = malloc(size + 1);
Patrick Monnerat
committed
if(!buf)
break;
/* Returns number of elements read, which should be 1 */
if((int) fread(buf, size, 1, fp) != 1)
break;
/* 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;
Patrick Monnerat
committed
break;
Patrick Monnerat
committed
/*
* 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))
Patrick Monnerat
committed
result = CURLE_OK;
} while(0);
Curl_safefree(buf);
Curl_safefree(pem_ptr);
Patrick Monnerat
committed
fclose(fp);
return result;
}
#ifndef CURL_DISABLE_CRYPTO_AUTH
CURLcode Curl_ssl_md5sum(unsigned char *tmp, /* input */
size_t tmplen,
unsigned char *md5sum, /* output */
size_t md5len)
Patrick Monnerat
committed
#ifdef curlssl_md5sum
curlssl_md5sum(tmp, tmplen, md5sum, md5len);
Patrick Monnerat
committed
#else
MD5_context *MD5pw;
(void) md5len;
Patrick Monnerat
committed
MD5pw = Curl_MD5_init(Curl_DIGEST_MD5);
if(!MD5pw)
return CURLE_OUT_OF_MEMORY;
Curl_MD5_update(MD5pw, tmp, curlx_uztoui(tmplen));
Patrick Monnerat
committed
Curl_MD5_final(MD5pw, md5sum);
Patrick Monnerat
committed
}
/*
* Check whether the SSL backend supports the status_request extension.
*/
bool Curl_ssl_cert_status_request(void)
{
#ifdef curlssl_cert_status_request
return curlssl_cert_status_request();
#else
return FALSE;
#endif
}
/*
* Check whether the SSL backend supports false start.
*/
bool Curl_ssl_false_start(void)
{
#ifdef curlssl_false_start
return curlssl_false_start();