diff options
Diffstat (limited to 'lib/vtls/schannel.c')
-rw-r--r-- | lib/vtls/schannel.c | 240 |
1 files changed, 224 insertions, 16 deletions
diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 7fb4cb1fd..1afc6790c 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -57,6 +57,7 @@ #include "../curlx/version_win32.h" #include "../rand.h" #include "../curlx/strparse.h" +#include "../progress.h" /* The last #include file should be: */ #include "../curl_memory.h" @@ -65,12 +66,15 @@ /* Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These * messages are extra verbose and intended for curl developers debugging - * Schannel recv decryption. + * Schannel recv decryption and renegotiation. */ #ifdef CURL_SCHANNEL_DEV_DEBUG #define SCH_DEV(x) x +#define SCH_DEV_SHOWBOOL(x) \ + infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); #else #define SCH_DEV(x) do { } while(0) +#define SCH_DEV_SHOWBOOL(x) do { } while(0) #endif /* Offered by mingw-w64 v8+. MS SDK 7.0A+. */ @@ -1099,6 +1103,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) backend->recv_sspi_close_notify = FALSE; backend->recv_connection_closed = FALSE; backend->recv_renegotiating = FALSE; + backend->renegotiate_state.started = FALSE; backend->encdata_is_incomplete = FALSE; /* continue to second handshake step */ @@ -1246,6 +1251,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) if(!SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), 0)) { SCH_DEV(infof(data, "schannel: handshake waiting for writeable socket")); connssl->io_need = CURL_SSL_IO_NEED_SEND; + free(inbuf[0].pvBuffer); return CURLE_OK; } @@ -1453,7 +1459,7 @@ static bool cert_counter_callback(const CERT_CONTEXT *ccert_context, bool reverse_order, void *certs_count) { - (void)reverse_order; /* unused */ + (void)reverse_order; if(valid_cert_encoding(ccert_context)) (*(int *)certs_count)++; return TRUE; @@ -1679,6 +1685,205 @@ static CURLcode schannel_connect(struct Curl_cfilter *cf, return CURLE_OK; } +enum schannel_renegotiate_caller_t { + SCH_RENEG_CALLER_IS_RECV, + SCH_RENEG_CALLER_IS_SEND +}; + +/* This function renegotiates the connection due to a server request received + by schannel_recv. This function returns CURLE_AGAIN if the renegotiation is + incomplete. In that case, we remain in the renegotiation (connecting) stage + and future calls to schannel_recv and schannel_send must call this function + first to complete the renegotiation. */ +static CURLcode +schannel_recv_renegotiate(struct Curl_cfilter *cf, struct Curl_easy *data, + enum schannel_renegotiate_caller_t caller) +{ + CURLcode result; + curl_socket_t sockfd; + const timediff_t max_renegotiate_ms = 5 * 60 * 1000; /* 5 minutes */ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + struct schannel_renegotiate_state *rs = &backend->renegotiate_state; + + if(!backend || !backend->recv_renegotiating) { + failf(data, "schannel: unexpected call to schannel_recv_renegotiate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(caller == SCH_RENEG_CALLER_IS_RECV) + SCH_DEV(infof(data, "schannel: renegotiation caller is schannel_recv")); + else if(caller == SCH_RENEG_CALLER_IS_SEND) + SCH_DEV(infof(data, "schannel: renegotiation caller is schannel_send")); + else { + failf(data, "schannel: unknown caller for schannel_recv_renegotiate"); + return CURLE_SSL_CONNECT_ERROR; + } + + sockfd = Curl_conn_cf_get_socket(cf, data); + + if(sockfd == CURL_SOCKET_BAD) { + failf(data, "schannel: renegotiation missing socket"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!rs->started) { /* new renegotiation */ + infof(data, "schannel: renegotiating SSL/TLS connection"); + DEBUGASSERT(connssl->state == ssl_connection_complete); + DEBUGASSERT(connssl->connecting_state == ssl_connect_done); + connssl->state = ssl_connection_negotiating; + connssl->connecting_state = ssl_connect_2; + memset(rs, 0, sizeof(*rs)); + rs->io_need = CURL_SSL_IO_NEED_SEND; + rs->start_time = curlx_now(); + rs->started = TRUE; + } + + for(;;) { + bool block_read, block_write, blocking, done; + curl_socket_t readfd, writefd; + timediff_t elapsed; + + elapsed = curlx_timediff(curlx_now(), rs->start_time); + if(elapsed >= max_renegotiate_ms) { + failf(data, "schannel: renegotiation timeout"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + + /* the current io_need state may have been overwritten since the last time + this function was called. restore the io_need state needed to continue + the renegotiation. */ + + connssl->io_need = rs->io_need; + + result = schannel_connect(cf, data, &done); + + rs->io_need = connssl->io_need; + + if(!result && !done) + result = CURLE_AGAIN; + + if(result != CURLE_AGAIN) + break; + + readfd = (rs->io_need & CURL_SSL_IO_NEED_RECV) ? sockfd : CURL_SOCKET_BAD; + writefd = (rs->io_need & CURL_SSL_IO_NEED_SEND) ? sockfd : CURL_SOCKET_BAD; + + if(readfd == CURL_SOCKET_BAD && writefd == CURL_SOCKET_BAD) + continue; + + /* connect should not have requested io read and write together */ + DEBUGASSERT(readfd == CURL_SOCKET_BAD || writefd == CURL_SOCKET_BAD); + + /* This function is partially blocking to avoid a stoppage that would + * occur if the user is waiting on the socket only in one direction. + * + * For example, if the user has called recv then they may not be waiting + * for a writeable socket and vice versa, so we block to avoid that. + * + * In practice a wait is unlikely to occur. For caller recv if handshake + * data needs to be sent then we block for a writeable socket that should + * be writeable immediately except for OS resource constraints. For caller + * send if handshake data needs to be received then we block for a readable + * socket, which could take some time, but it's more likely the user has + * called recv since they had called it prior (only recv can start + * renegotiation and probably the user is going to call it again to get + * more of their data before calling send). + */ + + block_read = (caller == SCH_RENEG_CALLER_IS_SEND) ? TRUE : FALSE; + block_write = (caller == SCH_RENEG_CALLER_IS_RECV) ? TRUE : FALSE; + + blocking = (block_read && (readfd != CURL_SOCKET_BAD)) || + (block_write && (writefd != CURL_SOCKET_BAD)); + + SCH_DEV_SHOWBOOL(block_read); + SCH_DEV_SHOWBOOL(block_write); + SCH_DEV_SHOWBOOL(blocking); + + for(;;) { + int what; + timediff_t timeout, remaining; + + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + break; + } + + elapsed = curlx_timediff(curlx_now(), rs->start_time); + if(elapsed >= max_renegotiate_ms) { + failf(data, "schannel: renegotiation timeout"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + remaining = max_renegotiate_ms - elapsed; + + if(blocking) { + timeout = Curl_timeleft(data, NULL, FALSE); + + if(timeout < 0) { + result = CURLE_OPERATION_TIMEDOUT; + break; + } + + /* the blocking is in intervals so that the progress function can be + called every second */ + if(!timeout || timeout > 1000) + timeout = 1000; + + if(timeout > remaining) + timeout = remaining; + } + else + timeout = 0; + + SCH_DEV(infof(data, "schannel: renegotiation wait until socket is" + "%s%s for up to %" FMT_TIMEDIFF_T " ms", + ((readfd != CURL_SOCKET_BAD) ? " readable" : ""), + ((writefd != CURL_SOCKET_BAD) ? " writeable" : ""), + timeout)); + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, timeout); + + if(what > 0 && (what & (CURL_CSELECT_IN | CURL_CSELECT_OUT))) { + SCH_DEV(infof(data, "schannel: renegotiation socket %s%s", + ((what & CURL_CSELECT_IN) ? "CURL_CSELECT_IN " : ""), + ((what & CURL_CSELECT_OUT) ? "CURL_CSELECT_OUT " : ""))); + result = CURLE_AGAIN; + break; + } + else if(!what) { + SCH_DEV(infof(data, "schannel: renegotiation socket timeout")); + if(blocking) + continue; + else + return CURLE_AGAIN; + } + + failf(data, "schannel: socket error during renegotiation"); + result = CURLE_SSL_CONNECT_ERROR; + break; + } + if(result != CURLE_AGAIN) + break; + } + + DEBUGASSERT(result != CURLE_AGAIN); + + rs->started = FALSE; + backend->recv_renegotiating = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + + if(result) + failf(data, "schannel: renegotiation failed"); + else + infof(data, "schannel: SSL/TLS connection renegotiated"); + + return result; +} + static CURLcode schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, size_t *pnwritten) @@ -1696,6 +1901,12 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); *pnwritten = 0; + if(backend->recv_renegotiating) { + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_SEND); + if(result) + return result; + } + /* check if the maximum stream sizes were queried */ if(backend->stream_sizes.cbMaximumMessage == 0) { sspi_status = Curl_pSecFn->QueryContextAttributes( @@ -1827,7 +2038,6 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_connect_data *connssl = cf->ctx; unsigned char *reallocated_buffer; size_t reallocated_length; - bool done = FALSE; SecBuffer inbuf[4]; SecBufferDesc inbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; @@ -1841,6 +2051,12 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); *pnread = 0; + if(backend->recv_renegotiating) { + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_RECV); + if(result) + return result; + } + /**************************************************************************** * Do not return or set backend->recv_unrecoverable_err unless in the * cleanup. The pattern for return error is set *err, optional infof, goto @@ -2033,21 +2249,13 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto cleanup; } - /* begin renegotiation */ - infof(data, "schannel: renegotiating SSL/TLS connection"); - connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2; - connssl->io_need = CURL_SSL_IO_NEED_SEND; backend->recv_renegotiating = TRUE; - result = schannel_connect(cf, data, &done); - backend->recv_renegotiating = FALSE; - if(result) { - infof(data, "schannel: renegotiation failed"); + result = schannel_recv_renegotiate(cf, data, SCH_RENEG_CALLER_IS_RECV); + if(result) goto cleanup; - } + /* now retry receiving data */ sspi_status = SEC_E_OK; - infof(data, "schannel: SSL/TLS connection renegotiated"); continue; } /* check if the server closed the connection */ @@ -2375,7 +2583,7 @@ static size_t schannel_version(char *buffer, size_t size) return msnprintf(buffer, size, "Schannel"); } -static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, +static CURLcode schannel_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { (void)data; @@ -2517,7 +2725,7 @@ static CURLcode schannel_sha256sum(const unsigned char *input, } static void *schannel_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) + CURLINFO info) { struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; |