/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2021, Daniel Stenberg, , et al. * * 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.se/docs/copyright.html. * * 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. SSL-functions in libcurl should call functions in this source file, and not to any specific SSL-layer. Curl_ssl_ - prefix for generic ones Note that this source code uses the functions of the configured SSL backend via the global Curl_ssl instance. "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html */ #include "curl_setup.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #include "urldata.h" #include "vtls.h" /* generic SSL protos etc */ #include "slist.h" #include "sendf.h" #include "strcase.h" #include "url.h" #include "progress.h" #include "share.h" #include "multiif.h" #include "timeval.h" #include "curl_md5.h" #include "warnless.h" #include "curl_base64.h" #include "curl_printf.h" #include "strdup.h" /* The last #include files should be: */ #include "curl_memory.h" #include "memdebug.h" /* convenience macro to check if this handle is using a shared SSL session */ #define SSLSESSION_SHARED(data) (data->share && \ (data->share->specifier & \ (1<var) { \ dest->var = strdup(source->var); \ if(!dest->var) \ return FALSE; \ } \ else \ dest->var = NULL; \ } while(0) #define CLONE_BLOB(var) \ do { \ if(blobdup(&dest->var, source->var)) \ return FALSE; \ } while(0) static CURLcode blobdup(struct curl_blob **dest, struct curl_blob *src) { DEBUGASSERT(dest); DEBUGASSERT(!*dest); if(src) { /* only if there's data to dupe! */ struct curl_blob *d; d = malloc(sizeof(struct curl_blob) + src->len); if(!d) return CURLE_OUT_OF_MEMORY; d->len = src->len; /* Always duplicate because the connection may survive longer than the handle that passed in the blob. */ d->flags = CURL_BLOB_COPY; d->data = (void *)((char *)d + sizeof(struct curl_blob)); memcpy(d->data, src->data, src->len); *dest = d; } return CURLE_OK; } /* returns TRUE if the blobs are identical */ static bool blobcmp(struct curl_blob *first, struct curl_blob *second) { if(!first && !second) /* both are NULL */ return TRUE; if(!first || !second) /* one is NULL */ return FALSE; if(first->len != second->len) /* different sizes */ return FALSE; return !memcmp(first->data, second->data, first->len); /* same data */ } bool Curl_ssl_config_matches(struct ssl_primary_config *data, struct ssl_primary_config *needle) { if((data->version == needle->version) && (data->version_max == needle->version_max) && (data->verifypeer == needle->verifypeer) && (data->verifyhost == needle->verifyhost) && (data->verifystatus == needle->verifystatus) && blobcmp(data->cert_blob, needle->cert_blob) && blobcmp(data->ca_info_blob, needle->ca_info_blob) && Curl_safe_strcasecompare(data->CApath, needle->CApath) && Curl_safe_strcasecompare(data->CAfile, needle->CAfile) && Curl_safe_strcasecompare(data->clientcert, needle->clientcert) && Curl_safe_strcasecompare(data->random_file, needle->random_file) && Curl_safe_strcasecompare(data->egdsocket, needle->egdsocket) && Curl_safe_strcasecompare(data->cipher_list, needle->cipher_list) && Curl_safe_strcasecompare(data->cipher_list13, needle->cipher_list13) && Curl_safe_strcasecompare(data->curves, needle->curves) && Curl_safe_strcasecompare(data->pinned_key, needle->pinned_key)) return TRUE; return FALSE; } bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source, struct ssl_primary_config *dest) { dest->version = source->version; dest->version_max = source->version_max; dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; dest->sessionid = source->sessionid; CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); CLONE_STRING(CApath); CLONE_STRING(CAfile); CLONE_STRING(clientcert); CLONE_STRING(random_file); CLONE_STRING(egdsocket); CLONE_STRING(cipher_list); CLONE_STRING(cipher_list13); CLONE_STRING(pinned_key); CLONE_STRING(curves); return TRUE; } void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) { Curl_safefree(sslc->CApath); Curl_safefree(sslc->CAfile); Curl_safefree(sslc->clientcert); Curl_safefree(sslc->random_file); Curl_safefree(sslc->egdsocket); Curl_safefree(sslc->cipher_list); Curl_safefree(sslc->cipher_list13); Curl_safefree(sslc->pinned_key); Curl_safefree(sslc->cert_blob); Curl_safefree(sslc->ca_info_blob); Curl_safefree(sslc->curves); } #ifdef USE_SSL static int multissl_setup(const struct Curl_ssl *backend); #endif int Curl_ssl_backend(void) { #ifdef USE_SSL multissl_setup(NULL); return Curl_ssl->info.id; #else return (int)CURLSSLBACKEND_NONE; #endif } #ifdef USE_SSL /* "global" init done? */ static bool init_ssl = FALSE; /** * 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 */ return Curl_ssl->init(); } #if defined(CURL_WITH_MULTI_SSL) static const struct Curl_ssl Curl_ssl_multi; #endif /* Global cleanup */ void Curl_ssl_cleanup(void) { if(init_ssl) { /* only cleanup if we did a previous init */ Curl_ssl->cleanup(); #if defined(CURL_WITH_MULTI_SSL) Curl_ssl = &Curl_ssl_multi; #endif init_ssl = FALSE; } } static bool ssl_prefs_check(struct Curl_easy *data) { /* check for CURLOPT_SSLVERSION invalid parameter value */ const long sslver = data->set.ssl.primary.version; if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) { failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); return FALSE; } switch(data->set.ssl.primary.version_max) { case CURL_SSLVERSION_MAX_NONE: case CURL_SSLVERSION_MAX_DEFAULT: break; default: if((data->set.ssl.primary.version_max >> 16) < sslver) { failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION"); return FALSE; } } return TRUE; } #ifndef CURL_DISABLE_PROXY static CURLcode ssl_connect_init_proxy(struct connectdata *conn, int sockindex) { DEBUGASSERT(conn->bits.proxy_ssl_connected[sockindex]); if(ssl_connection_complete == conn->ssl[sockindex].state && !conn->proxy_ssl[sockindex].use) { struct ssl_backend_data *pbdata; if(!(Curl_ssl->supports & SSLSUPP_HTTPS_PROXY)) return CURLE_NOT_BUILT_IN; /* The pointers to the ssl backend data, which is opaque here, are swapped rather than move the contents. */ pbdata = conn->proxy_ssl[sockindex].backend; conn->proxy_ssl[sockindex] = conn->ssl[sockindex]; memset(&conn->ssl[sockindex], 0, sizeof(conn->ssl[sockindex])); memset(pbdata, 0, Curl_ssl->sizeof_ssl_backend_data); conn->ssl[sockindex].backend = pbdata; } return CURLE_OK; } #endif CURLcode Curl_ssl_connect(struct Curl_easy *data, struct connectdata *conn, int sockindex) { CURLcode result; #ifndef CURL_DISABLE_PROXY if(conn->bits.proxy_ssl_connected[sockindex]) { result = ssl_connect_init_proxy(conn, sockindex); if(result) return result; } #endif if(!ssl_prefs_check(data)) return CURLE_SSL_CONNECT_ERROR; /* mark this is being ssl-enabled from here on. */ conn->ssl[sockindex].use = TRUE; conn->ssl[sockindex].state = ssl_connection_negotiating; result = Curl_ssl->connect_blocking(data, conn, sockindex); if(!result) Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ else conn->ssl[sockindex].use = FALSE; return result; } CURLcode Curl_ssl_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, bool isproxy, int sockindex, bool *done) { CURLcode result; #ifndef CURL_DISABLE_PROXY if(conn->bits.proxy_ssl_connected[sockindex]) { result = ssl_connect_init_proxy(conn, sockindex); if(result) return result; } #endif if(!ssl_prefs_check(data)) return CURLE_SSL_CONNECT_ERROR; /* mark this is being ssl requested from here on. */ conn->ssl[sockindex].use = TRUE; result = Curl_ssl->connect_nonblocking(data, conn, sockindex, done); if(result) conn->ssl[sockindex].use = FALSE; else if(*done && !isproxy) Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ return result; } /* * Lock shared SSL session data */ void Curl_ssl_sessionid_lock(struct Curl_easy *data) { if(SSLSESSION_SHARED(data)) Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); } /* * Unlock shared SSL session data */ void Curl_ssl_sessionid_unlock(struct Curl_easy *data) { if(SSLSESSION_SHARED(data)) Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); } /* * 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 Curl_easy *data, struct connectdata *conn, const bool isProxy, void **ssl_sessionid, size_t *idsize, /* set 0 if unknown */ int sockindex) { struct Curl_ssl_session *check; size_t i; long *general_age; bool no_match = TRUE; #ifndef CURL_DISABLE_PROXY struct ssl_primary_config * const ssl_config = isProxy ? &conn->proxy_ssl_config : &conn->ssl_config; const char * const name = isProxy ? conn->http_proxy.host.name : conn->host.name; int port = isProxy ? (int)conn->port : conn->remote_port; #else /* no proxy support */ struct ssl_primary_config * const ssl_config = &conn->ssl_config; const char * const name = conn->host.name; int port = conn->remote_port; #endif (void)sockindex; *ssl_sessionid = NULL; #ifdef CURL_DISABLE_PROXY if(isProxy) return TRUE; #endif DEBUGASSERT(SSL_SET_OPTION(primary.sessionid)); if(!SSL_SET_OPTION(primary.sessionid) || !data->state.session) /* session ID re-use is disabled or the session cache has not been setup */ return TRUE; /* Lock if shared */ if(SSLSESSION_SHARED(data)) general_age = &data->share->sessionage; else general_age = &data->state.sessionage; for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { check = &data->state.session[i]; if(!check->sessionid) /* not session ID means blank entry */ continue; if(strcasecompare(name, check->name) && ((!conn->bits.conn_to_host && !check->conn_to_host) || (conn->bits.conn_to_host && check->conn_to_host && strcasecompare(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)) && (port == check->remote_port) && strcasecompare(conn->handler->scheme, check->scheme) && Curl_ssl_config_matches(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 */ *ssl_sessionid = check->sessionid; if(idsize) *idsize = check->idsize; no_match = FALSE; break; } } DEBUGF(infof(data, "%s Session ID in cache for %s %s://%s:%d", no_match? "Didn't find": "Found", isProxy ? "proxy" : "host", conn->handler->scheme, name, port)); return no_match; } /* * Kill a single session ID entry in the cache. */ void Curl_ssl_kill_session(struct Curl_ssl_session *session) { if(session->sessionid) { /* defensive check */ /* free the ID the SSL-layer specific way */ Curl_ssl->session_free(session->sessionid); session->sessionid = NULL; session->age = 0; /* fresh */ Curl_free_primary_ssl_config(&session->ssl_config); Curl_safefree(session->name); Curl_safefree(session->conn_to_host); } } /* * Delete the given session ID from the cache. */ void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) { size_t i; for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { struct Curl_ssl_session *check = &data->state.session[i]; if(check->sessionid == ssl_sessionid) { Curl_ssl_kill_session(check); break; } } } /* * 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 Curl_easy *data, struct connectdata *conn, const bool isProxy, void *ssl_sessionid, size_t idsize, int sockindex) { size_t i; struct Curl_ssl_session *store; long oldest_age; char *clone_host; char *clone_conn_to_host; int conn_to_port; long *general_age; #ifndef CURL_DISABLE_PROXY struct ssl_primary_config * const ssl_config = isProxy ? &conn->proxy_ssl_config : &conn->ssl_config; const char *hostname = isProxy ? conn->http_proxy.host.name : conn->host.name; #else struct ssl_primary_config * const ssl_config = &conn->ssl_config; const char *hostname = conn->host.name; #endif (void)sockindex; if(!data->state.session) return CURLE_OK; store = &data->state.session[0]; oldest_age = data->state.session[0].age; /* zero if unused */ DEBUGASSERT(SSL_SET_OPTION(primary.sessionid)); clone_host = strdup(hostname); 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; /* 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; } /* find an empty slot for us, or find the oldest */ for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) && 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.general_ssl.max_ssl_sessions) /* cache is full, we must "kill" the oldest entry! */ Curl_ssl_kill_session(store); 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 */ free(store->name); free(store->conn_to_host); 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 */ /* port number */ store->remote_port = isProxy ? (int)conn->port : conn->remote_port; store->scheme = conn->handler->scheme; if(!Curl_clone_primary_ssl_config(ssl_config, &store->ssl_config)) { Curl_free_primary_ssl_config(&store->ssl_config); store->sessionid = NULL; /* let caller free sessionid */ free(clone_host); free(clone_conn_to_host); return CURLE_OUT_OF_MEMORY; } DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]", store->scheme, store->name, store->remote_port, isProxy ? "PROXY" : "server")); return CURLE_OK; } void Curl_ssl_associate_conn(struct Curl_easy *data, struct connectdata *conn) { if(Curl_ssl->associate_connection) { Curl_ssl->associate_connection(data, conn, FIRSTSOCKET); if(conn->sock[SECONDARYSOCKET] && conn->bits.sock_accepted) Curl_ssl->associate_connection(data, conn, SECONDARYSOCKET); } } void Curl_ssl_detach_conn(struct Curl_easy *data, struct connectdata *conn) { if(Curl_ssl->disassociate_connection) { Curl_ssl->disassociate_connection(data, FIRSTSOCKET); if(conn->sock[SECONDARYSOCKET] && conn->bits.sock_accepted) Curl_ssl->disassociate_connection(data, SECONDARYSOCKET); } } void Curl_ssl_close_all(struct Curl_easy *data) { /* kill the session ID cache if not shared */ if(data->state.session && !SSLSESSION_SHARED(data)) { size_t i; for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) /* the single-killer function handles empty table slots */ Curl_ssl_kill_session(&data->state.session[i]); /* free the cache data */ Curl_safefree(data->state.session); } Curl_ssl->close_all(data); } int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks) { struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; if(connssl->connecting_state == ssl_connect_2_writing) { /* write mode */ socks[0] = conn->sock[FIRSTSOCKET]; return GETSOCK_WRITESOCK(0); } if(connssl->connecting_state == ssl_connect_2_reading) { /* read mode */ socks[0] = conn->sock[FIRSTSOCKET]; return GETSOCK_READSOCK(0); } return GETSOCK_BLANK; } void Curl_ssl_close(struct Curl_easy *data, struct connectdata *conn, int sockindex) { DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); Curl_ssl->close_one(data, conn, sockindex); conn->ssl[sockindex].state = ssl_connection_none; } CURLcode Curl_ssl_shutdown(struct Curl_easy *data, struct connectdata *conn, int sockindex) { if(Curl_ssl->shut_down(data, conn, sockindex)) return CURLE_SSL_SHUTDOWN_FAILED; conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */ conn->ssl[sockindex].state = ssl_connection_none; conn->recv[sockindex] = Curl_recv_plain; conn->send[sockindex] = Curl_send_plain; return CURLE_OK; } /* Selects an SSL crypto engine */ CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine) { return Curl_ssl->set_engine(data, engine); } /* Selects the default SSL crypto engine */ CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data) { return Curl_ssl->set_engine_default(data); } /* Return list of OpenSSL crypto engine names. */ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data) { return Curl_ssl->engines_list(data); } /* * 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) { 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)); if(!session) return CURLE_OUT_OF_MEMORY; /* store the info in the SSL section */ data->set.general_ssl.max_ssl_sessions = amount; data->state.session = session; data->state.sessionage = 1; /* this is brand new */ return CURLE_OK; } static size_t multissl_version(char *buffer, size_t size); void Curl_ssl_version(char *buffer, size_t size) { #ifdef CURL_WITH_MULTI_SSL (void)multissl_version(buffer, size); #else (void)Curl_ssl->version(buffer, size); #endif } /* * 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) { return Curl_ssl->check_cxn(conn); } bool Curl_ssl_data_pending(const struct connectdata *conn, int connindex) { return Curl_ssl->data_pending(conn, connindex); } void Curl_ssl_free_certinfo(struct Curl_easy *data) { struct curl_certinfo *ci = &data->info.certs; if(ci->num_of_certs) { /* free all individual lists used */ int i; for(i = 0; inum_of_certs; i++) { curl_slist_free_all(ci->certinfo[i]); ci->certinfo[i] = NULL; } free(ci->certinfo); /* free the actual array too */ ci->certinfo = NULL; 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 null-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; CURLcode result = CURLE_OK; 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 */ msnprintf(output, outlen, "%s:", label); /* memcpy the value (it might not be null-terminated) */ memcpy(&output[labellen + 1], value, valuelen); /* null-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; return result; } /* * 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); } CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { return Curl_ssl->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. */ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey, const unsigned char *pubkey, size_t pubkeylen) { FILE *fp; unsigned char *buf = NULL, *pem_ptr = NULL; CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; /* 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) { CURLcode encode; size_t encodedlen, pinkeylen; char *encoded, *pinkeycopy, *begin_pos, *end_pos; unsigned char *sha256sumdigest; if(!Curl_ssl->sha256sum) { /* without sha256 support, this cannot match */ return result; } /* compute sha256sum of public key */ sha256sumdigest = malloc(CURL_SHA256_DIGEST_LENGTH); if(!sha256sumdigest) return CURLE_OUT_OF_MEMORY; encode = Curl_ssl->sha256sum(pubkey, pubkeylen, sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); if(encode != CURLE_OK) return encode; encode = Curl_base64_encode(data, (char *)sha256sumdigest, CURL_SHA256_DIGEST_LENGTH, &encoded, &encodedlen); Curl_safefree(sha256sumdigest); if(encode) return encode; infof(data, " public key hash: sha256//%s", 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); Curl_safefree(pinkeycopy); return result; } fp = fopen(pinnedpubkey, "rb"); if(!fp) return result; do { long filesize; size_t size, pem_len; CURLcode pem_read; /* Determine the file's size */ if(fseek(fp, 0, SEEK_END)) break; 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 is bigger than the file * size then it can't match */ size = curlx_sotouz((curl_off_t) filesize); if(pubkeylen > size) break; /* * 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, 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; 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; } /* * Check whether the SSL backend supports the status_request extension. */ bool Curl_ssl_cert_status_request(void) { return Curl_ssl->cert_status_request(); } /* * Check whether the SSL backend supports false start. */ bool Curl_ssl_false_start(void) { return Curl_ssl->false_start(); } /* * Check whether the SSL backend supports setting TLS 1.3 cipher suites */ bool Curl_ssl_tls13_ciphersuites(void) { return Curl_ssl->supports & SSLSUPP_TLS13_CIPHERSUITES; } /* * Default implementations for unsupported functions. */ int Curl_none_init(void) { return 1; } void Curl_none_cleanup(void) { } int Curl_none_shutdown(struct Curl_easy *data UNUSED_PARAM, struct connectdata *conn UNUSED_PARAM, int sockindex UNUSED_PARAM) { (void)data; (void)conn; (void)sockindex; return 0; } int Curl_none_check_cxn(struct connectdata *conn UNUSED_PARAM) { (void)conn; return -1; } CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM, unsigned char *entropy UNUSED_PARAM, size_t length UNUSED_PARAM) { (void)data; (void)entropy; (void)length; return CURLE_NOT_BUILT_IN; } void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM) { (void)data; } void Curl_none_session_free(void *ptr UNUSED_PARAM) { (void)ptr; } bool Curl_none_data_pending(const struct connectdata *conn UNUSED_PARAM, int connindex UNUSED_PARAM) { (void)conn; (void)connindex; return 0; } bool Curl_none_cert_status_request(void) { return FALSE; } CURLcode Curl_none_set_engine(struct Curl_easy *data UNUSED_PARAM, const char *engine UNUSED_PARAM) { (void)data; (void)engine; return CURLE_NOT_BUILT_IN; } CURLcode Curl_none_set_engine_default(struct Curl_easy *data UNUSED_PARAM) { (void)data; return CURLE_NOT_BUILT_IN; } struct curl_slist *Curl_none_engines_list(struct Curl_easy *data UNUSED_PARAM) { (void)data; return (struct curl_slist *)NULL; } bool Curl_none_false_start(void) { return FALSE; } static int multissl_init(void) { if(multissl_setup(NULL)) return 1; return Curl_ssl->init(); } static CURLcode multissl_connect(struct Curl_easy *data, struct connectdata *conn, int sockindex) { if(multissl_setup(NULL)) return CURLE_FAILED_INIT; return Curl_ssl->connect_blocking(data, conn, sockindex); } static CURLcode multissl_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, int sockindex, bool *done) { if(multissl_setup(NULL)) return CURLE_FAILED_INIT; return Curl_ssl->connect_nonblocking(data, conn, sockindex, done); } static int multissl_getsock(struct connectdata *conn, curl_socket_t *socks) { if(multissl_setup(NULL)) return 0; return Curl_ssl->getsock(conn, socks); } static void *multissl_get_internals(struct ssl_connect_data *connssl, CURLINFO info) { if(multissl_setup(NULL)) return NULL; return Curl_ssl->get_internals(connssl, info); } static void multissl_close(struct Curl_easy *data, struct connectdata *conn, int sockindex) { if(multissl_setup(NULL)) return; Curl_ssl->close_one(data, conn, sockindex); } static const struct Curl_ssl Curl_ssl_multi = { { CURLSSLBACKEND_NONE, "multi" }, /* info */ 0, /* supports nothing */ (size_t)-1, /* something insanely large to be on the safe side */ multissl_init, /* init */ Curl_none_cleanup, /* cleanup */ multissl_version, /* version */ Curl_none_check_cxn, /* check_cxn */ Curl_none_shutdown, /* shutdown */ Curl_none_data_pending, /* data_pending */ Curl_none_random, /* random */ Curl_none_cert_status_request, /* cert_status_request */ multissl_connect, /* connect */ multissl_connect_nonblocking, /* connect_nonblocking */ multissl_getsock, /* getsock */ multissl_get_internals, /* get_internals */ multissl_close, /* close_one */ Curl_none_close_all, /* close_all */ Curl_none_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ Curl_none_false_start, /* false_start */ NULL, /* sha256sum */ NULL, /* associate_connection */ NULL /* disassociate_connection */ }; const struct Curl_ssl *Curl_ssl = #if defined(CURL_WITH_MULTI_SSL) &Curl_ssl_multi; #elif defined(USE_WOLFSSL) &Curl_ssl_wolfssl; #elif defined(USE_SECTRANSP) &Curl_ssl_sectransp; #elif defined(USE_GNUTLS) &Curl_ssl_gnutls; #elif defined(USE_GSKIT) &Curl_ssl_gskit; #elif defined(USE_MBEDTLS) &Curl_ssl_mbedtls; #elif defined(USE_NSS) &Curl_ssl_nss; #elif defined(USE_RUSTLS) &Curl_ssl_rustls; #elif defined(USE_OPENSSL) &Curl_ssl_openssl; #elif defined(USE_SCHANNEL) &Curl_ssl_schannel; #elif defined(USE_MESALINK) &Curl_ssl_mesalink; #elif defined(USE_BEARSSL) &Curl_ssl_bearssl; #else #error "Missing struct Curl_ssl for selected SSL backend" #endif static const struct Curl_ssl *available_backends[] = { #if defined(USE_WOLFSSL) &Curl_ssl_wolfssl, #endif #if defined(USE_SECTRANSP) &Curl_ssl_sectransp, #endif #if defined(USE_GNUTLS) &Curl_ssl_gnutls, #endif #if defined(USE_GSKIT) &Curl_ssl_gskit, #endif #if defined(USE_MBEDTLS) &Curl_ssl_mbedtls, #endif #if defined(USE_NSS) &Curl_ssl_nss, #endif #if defined(USE_OPENSSL) &Curl_ssl_openssl, #endif #if defined(USE_SCHANNEL) &Curl_ssl_schannel, #endif #if defined(USE_MESALINK) &Curl_ssl_mesalink, #endif #if defined(USE_BEARSSL) &Curl_ssl_bearssl, #endif #if defined(USE_RUSTLS) &Curl_ssl_rustls, #endif NULL }; static size_t multissl_version(char *buffer, size_t size) { static const struct Curl_ssl *selected; static char backends[200]; static size_t backends_len; const struct Curl_ssl *current; current = Curl_ssl == &Curl_ssl_multi ? available_backends[0] : Curl_ssl; if(current != selected) { char *p = backends; char *end = backends + sizeof(backends); int i; selected = current; backends[0] = '\0'; for(i = 0; available_backends[i]; ++i) { char vb[200]; bool paren = (selected != available_backends[i]); if(available_backends[i]->version(vb, sizeof(vb))) { p += msnprintf(p, end - p, "%s%s%s%s", (p != backends ? " " : ""), (paren ? "(" : ""), vb, (paren ? ")" : "")); } } backends_len = p - backends; } if(!size) return 0; if(size <= backends_len) { strncpy(buffer, backends, size - 1); buffer[size - 1] = '\0'; return size - 1; } strcpy(buffer, backends); return backends_len; } static int multissl_setup(const struct Curl_ssl *backend) { const char *env; char *env_tmp; if(Curl_ssl != &Curl_ssl_multi) return 1; if(backend) { Curl_ssl = backend; return 0; } if(!available_backends[0]) return 1; env = env_tmp = curl_getenv("CURL_SSL_BACKEND"); #ifdef CURL_DEFAULT_SSL_BACKEND if(!env) env = CURL_DEFAULT_SSL_BACKEND; #endif if(env) { int i; for(i = 0; available_backends[i]; i++) { if(strcasecompare(env, available_backends[i]->info.name)) { Curl_ssl = available_backends[i]; free(env_tmp); return 0; } } } /* Fall back to first available backend */ Curl_ssl = available_backends[0]; free(env_tmp); return 0; } CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) { int i; if(avail) *avail = (const curl_ssl_backend **)&available_backends; if(Curl_ssl != &Curl_ssl_multi) return id == Curl_ssl->info.id || (name && strcasecompare(name, Curl_ssl->info.name)) ? CURLSSLSET_OK : #if defined(CURL_WITH_MULTI_SSL) CURLSSLSET_TOO_LATE; #else CURLSSLSET_UNKNOWN_BACKEND; #endif for(i = 0; available_backends[i]; i++) { if(available_backends[i]->info.id == id || (name && strcasecompare(available_backends[i]->info.name, name))) { multissl_setup(available_backends[i]); return CURLSSLSET_OK; } } return CURLSSLSET_UNKNOWN_BACKEND; } #else /* USE_SSL */ CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) { (void)id; (void)name; (void)avail; return CURLSSLSET_NO_BACKENDS; } #endif /* !USE_SSL */