From 355f7594877a62f9aa657e8a72d3f92b3c887d73 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Thu, 17 Apr 2014 13:12:59 +0200 Subject: [PATCH 1/4] nss: split Curl_nss_connect() into 4 functions Upstream-commit: a43bba3a34ed8912c4ca10f213590d1998ba0d29 Signed-off-by: Kamil Dudka --- lib/nss.c | 134 +++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index 1381dc4..4d57a24 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -1216,9 +1216,62 @@ static CURLcode nss_init_sslver(SSLVersionRange *sslver, return CURLE_SSL_CONNECT_ERROR; } -CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) +static CURLcode nss_fail_connect(struct ssl_connect_data *connssl, + struct SessionHandle *data, + CURLcode curlerr) { + SSLVersionRange sslver; PRErrorCode err = 0; + + /* reset the flag to avoid an infinite loop */ + data->state.ssl_connect_retry = FALSE; + + if(is_nss_error(curlerr)) { + /* read NSPR error code */ + err = PR_GetError(); + if(is_cc_error(err)) + curlerr = CURLE_SSL_CERTPROBLEM; + + /* print the error number and error string */ + infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err)); + + /* print a human-readable message describing the error if available */ + nss_print_error_message(data, err); + } + + /* cleanup on connection failure */ + Curl_llist_destroy(connssl->obj_list, NULL); + connssl->obj_list = NULL; + + if((SSL_VersionRangeGet(connssl->handle, &sslver) == SECSuccess) + && (sslver.min == SSL_LIBRARY_VERSION_3_0) + && (sslver.max == SSL_LIBRARY_VERSION_TLS_1_0) + && isTLSIntoleranceError(err)) { + /* schedule reconnect through Curl_retry_request() */ + data->state.ssl_connect_retry = TRUE; + infof(data, "Error in TLS handshake, trying SSLv3...\n"); + return CURLE_OK; + } + + return curlerr; +} + +/* Switch the SSL socket into non-blocking mode. */ +static CURLcode nss_set_nonblock(struct ssl_connect_data *connssl, + struct SessionHandle *data) +{ + static PRSocketOptionData sock_opt; + sock_opt.option = PR_SockOpt_Nonblocking; + sock_opt.value.non_blocking = PR_TRUE; + + if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS) + return nss_fail_connect(connssl, data, CURLE_SSL_CONNECT_ERROR); + + return CURLE_OK; +} + +static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) +{ PRFileDesc *model = NULL; PRBool ssl_no_cache; PRBool ssl_cbc_random_iv; @@ -1226,9 +1279,6 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) curl_socket_t sockfd = conn->sock[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; CURLcode curlerr; - PRSocketOptionData sock_opt; - long time_left; - PRUint32 timeout; SSLVersionRange sslver = { SSL_LIBRARY_VERSION_3_0, /* min */ @@ -1402,16 +1452,32 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) SSL_SetURL(connssl->handle, conn->host.name); + return CURLE_OK; + +error: + if(model) + PR_Close(model); + + return nss_fail_connect(connssl, data, curlerr); +} + +static CURLcode nss_do_connect(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + CURLcode curlerr = CURLE_SSL_CONNECT_ERROR; + PRUint32 timeout; + /* check timeout situation */ - time_left = Curl_timeleft(data, NULL, TRUE); + const long time_left = Curl_timeleft(data, NULL, TRUE); if(time_left < 0L) { failf(data, "timed out before SSL handshake"); curlerr = CURLE_OPERATION_TIMEDOUT; goto error; } - timeout = PR_MillisecondsToInterval((PRUint32) time_left); /* Force the handshake now */ + timeout = PR_MillisecondsToInterval((PRUint32) time_left); if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) curlerr = CURLE_PEER_FAILED_VERIFICATION; @@ -1420,12 +1486,6 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) goto error; } - /* switch the SSL socket into non-blocking mode */ - sock_opt.option = PR_SockOpt_Nonblocking; - sock_opt.value.non_blocking = PR_TRUE; - if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS) - goto error; - connssl->state = ssl_connection_complete; conn->recv[sockindex] = nss_recv; conn->send[sockindex] = nss_send; @@ -1453,40 +1513,34 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) return CURLE_OK; - error: - /* reset the flag to avoid an infinite loop */ - data->state.ssl_connect_retry = FALSE; +error: + return nss_fail_connect(connssl, data, curlerr); +} - if(is_nss_error(curlerr)) { - /* read NSPR error code */ - err = PR_GetError(); - if(is_cc_error(err)) - curlerr = CURLE_SSL_CERTPROBLEM; +CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + CURLcode rv; - /* print the error number and error string */ - infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err)); + rv = nss_setup_connect(conn, sockindex); + if(rv) + return rv; - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); + rv = nss_do_connect(conn, sockindex); + switch(rv) { + case CURLE_OK: + break; + default: + return rv; } - if(model) - PR_Close(model); - - /* cleanup on connection failure */ - Curl_llist_destroy(connssl->obj_list, NULL); - connssl->obj_list = NULL; - - if((sslver.min == SSL_LIBRARY_VERSION_3_0) - && (sslver.max == SSL_LIBRARY_VERSION_TLS_1_0) - && isTLSIntoleranceError(err)) { - /* schedule reconnect through Curl_retry_request() */ - data->state.ssl_connect_retry = TRUE; - infof(data, "Error in TLS handshake, trying SSLv3...\n"); - return CURLE_OK; - } + /* switch the SSL socket into non-blocking mode */ + rv = nss_set_nonblock(connssl, data); + if(rv) + return rv; - return curlerr; + return CURLE_OK; } static ssize_t nss_send(struct connectdata *conn, /* connection data */ -- 2.1.0 From b5132ce96009510656e5f719c8805647c246685b Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Thu, 17 Apr 2014 13:27:39 +0200 Subject: [PATCH 2/4] nss: implement non-blocking SSL handshake Upstream-commit: 8868a226cdad66a9a07d6e3f168884817592a1df Signed-off-by: Kamil Dudka --- lib/nss.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++--------- lib/nssg.h | 1 + lib/urldata.h | 1 + 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index 4d57a24..5be1058 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -1479,7 +1479,10 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex) /* Force the handshake now */ timeout = PR_MillisecondsToInterval((PRUint32) time_left); if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { - if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) + if(PR_GetError() == PR_WOULD_BLOCK_ERROR) + /* TODO: propagate the blocking direction from the NSPR layer */ + return CURLE_AGAIN; + else if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) curlerr = CURLE_PEER_FAILED_VERIFICATION; else if(conn->data->set.ssl.certverifyresult!=0) curlerr = CURLE_SSL_CACERT; @@ -1517,32 +1520,68 @@ error: return nss_fail_connect(connssl, data, curlerr); } -CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) +static CURLcode nss_connect_common(struct connectdata *conn, int sockindex, + bool *done) { struct ssl_connect_data *connssl = &conn->ssl[sockindex]; struct SessionHandle *data = conn->data; + const bool blocking = (done == NULL); CURLcode rv; - rv = nss_setup_connect(conn, sockindex); - if(rv) - return rv; + if(connssl->connecting_state == ssl_connect_1) { + rv = nss_setup_connect(conn, sockindex); + if(rv) + /* we do not expect CURLE_AGAIN from nss_setup_connect() */ + return rv; + + if(!blocking) { + /* in non-blocking mode, set NSS non-blocking mode before handshake */ + rv = nss_set_nonblock(connssl, data); + if(rv) + return rv; + } + + connssl->connecting_state = ssl_connect_2; + } rv = nss_do_connect(conn, sockindex); switch(rv) { case CURLE_OK: break; + case CURLE_AGAIN: + if(!blocking) + /* CURLE_AGAIN in non-blocking mode is not an error */ + return CURLE_OK; + /* fall through */ default: return rv; } - /* switch the SSL socket into non-blocking mode */ - rv = nss_set_nonblock(connssl, data); - if(rv) - return rv; + if(blocking) { + /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */ + rv = nss_set_nonblock(connssl, data); + if(rv) + return rv; + } + else + /* signal completed SSL handshake */ + *done = TRUE; + connssl->connecting_state = ssl_connect_done; return CURLE_OK; } +CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) +{ + return nss_connect_common(conn, sockindex, /* blocking */ NULL); +} + +CURLcode Curl_nss_connect_nonblocking(struct connectdata *conn, + int sockindex, bool *done) +{ + return nss_connect_common(conn, sockindex, done); +} + static ssize_t nss_send(struct connectdata *conn, /* connection data */ int sockindex, /* socketindex */ const void *mem, /* send this data */ diff --git a/lib/nssg.h b/lib/nssg.h index a881a9a..6d9aea6 100644 --- a/lib/nssg.h +++ b/lib/nssg.h @@ -64,6 +64,7 @@ void Curl_nss_md5sum(unsigned char *tmp, /* input */ #define curlssl_init Curl_nss_init #define curlssl_cleanup Curl_nss_cleanup #define curlssl_connect Curl_nss_connect +#define curlssl_connect_nonblocking Curl_nss_connect_nonblocking /* NSS has its own session ID cache */ #define curlssl_session_free(x) Curl_nop_stmt diff --git a/lib/urldata.h b/lib/urldata.h index e5d85ff..c91bcff 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -303,6 +303,7 @@ struct ssl_connect_data { struct SessionHandle *data; struct curl_llist *obj_list; PK11GenericObject *obj_clicert; + ssl_connect_state connecting_state; #endif /* USE_NSS */ #ifdef USE_QSOSSL SSLHandle *handle; -- 2.1.0 From 2f1f1b1ca2d9c60c5fca5d73303ae2ec4c3d94b2 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Wed, 23 Apr 2014 15:37:26 +0200 Subject: [PATCH 3/4] nss: propagate blocking direction from NSPR I/O ... during the non-blocking SSL handshake Upstream-commit: 9c941e92c4bd3d2a5dbe243f7517b6a6029afc6e Signed-off-by: Kamil Dudka --- lib/http.c | 2 +- lib/nss.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/lib/http.c b/lib/http.c index d1b0405..c007226 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1351,7 +1351,7 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done) #endif #if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \ - defined(USE_DARWINSSL) + defined(USE_DARWINSSL) || defined(USE_NSS) /* This function is for OpenSSL, GnuTLS, darwinssl, and schannel only. It should be made to query the generic SSL layer instead. */ static int https_getsock(struct connectdata *conn, diff --git a/lib/nss.c b/lib/nss.c index 5be1058..dadeb58 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -179,6 +179,10 @@ static const cipher_s cipherlist[] = { static const char* pem_library = "libnsspem.so"; SECMODModule* mod = NULL; +/* NSPR I/O layer we use to detect blocking direction during SSL handshake */ +static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER; +static PRIOMethods nspr_io_methods; + static const char* nss_error_to_name(PRErrorCode code) { const char *name = PR_ErrorToName(code); @@ -861,6 +865,60 @@ isTLSIntoleranceError(PRInt32 err) } } +/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */ +static void nss_update_connecting_state(ssl_connect_state state, void *secret) +{ + struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret; + if(PR_GetError() != PR_WOULD_BLOCK_ERROR) + /* an unrelated error is passing by */ + return; + + switch(connssl->connecting_state) { + case ssl_connect_2: + case ssl_connect_2_reading: + case ssl_connect_2_writing: + break; + default: + /* we are not called from an SSL handshake */ + return; + } + + /* update the state accordingly */ + connssl->connecting_state = state; +} + +/* recv() wrapper we use to detect blocking direction during SSL handshake */ +static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) +{ + const PRRecvFN recv_fn = fd->lower->methods->recv; + const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout); + if(rv < 0) + /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ + nss_update_connecting_state(ssl_connect_2_reading, fd->secret); + return rv; +} + +/* send() wrapper we use to detect blocking direction during SSL handshake */ +static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) +{ + const PRSendFN send_fn = fd->lower->methods->send; + const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout); + if(rv < 0) + /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ + nss_update_connecting_state(ssl_connect_2_writing, fd->secret); + return rv; +} + +/* close() wrapper to avoid assertion failure due to fd->secret != NULL */ +static PRStatus nspr_io_close(PRFileDesc *fd) +{ + const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close; + fd->secret = NULL; + return close_fn(fd); +} + static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir) { NSSInitParameters initparams; @@ -925,6 +983,21 @@ static CURLcode nss_init(struct SessionHandle *data) } } + if(nspr_io_identity == PR_INVALID_IO_LAYER) { + /* allocate an identity for our own NSPR I/O layer */ + nspr_io_identity = PR_GetUniqueIdentity("libcurl"); + if(nspr_io_identity == PR_INVALID_IO_LAYER) + return CURLE_OUT_OF_MEMORY; + + /* the default methods just call down to the lower I/O layer */ + memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(), sizeof nspr_io_methods); + + /* override certain methods in the table by our wrappers */ + nspr_io_methods.recv = nspr_io_recv; + nspr_io_methods.send = nspr_io_send; + nspr_io_methods.close = nspr_io_close; + } + rv = nss_init_core(data, cert_dir); if(rv) return rv; @@ -1273,6 +1346,8 @@ static CURLcode nss_set_nonblock(struct ssl_connect_data *connssl, static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) { PRFileDesc *model = NULL; + PRFileDesc *nspr_io = NULL; + PRFileDesc *nspr_io_stub = NULL; PRBool ssl_no_cache; PRBool ssl_cbc_random_iv; struct SessionHandle *data = conn->data; @@ -1433,11 +1508,34 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) goto error; } - /* Import our model socket onto the existing file descriptor */ - connssl->handle = PR_ImportTCPSocket(sockfd); - connssl->handle = SSL_ImportFD(model, connssl->handle); - if(!connssl->handle) + /* wrap OS file descriptor by NSPR's file descriptor abstraction */ + nspr_io = PR_ImportTCPSocket(sockfd); + if(!nspr_io) + goto error; + + /* create our own NSPR I/O layer */ + nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods); + if(!nspr_io_stub) { + PR_Close(nspr_io); goto error; + } + + /* make the per-connection data accessible from NSPR I/O callbacks */ + nspr_io_stub->secret = (void *)connssl; + + /* push our new layer to the NSPR I/O stack */ + if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) { + PR_Close(nspr_io); + PR_Close(nspr_io_stub); + goto error; + } + + /* import our model socket onto the current I/O stack */ + connssl->handle = SSL_ImportFD(model, nspr_io); + if(!connssl->handle) { + PR_Close(nspr_io); + goto error; + } PR_Close(model); /* We don't need this any more */ model = NULL; @@ -1480,7 +1578,7 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex) timeout = PR_MillisecondsToInterval((PRUint32) time_left); if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { if(PR_GetError() == PR_WOULD_BLOCK_ERROR) - /* TODO: propagate the blocking direction from the NSPR layer */ + /* blocking direction is updated by nss_update_connecting_state() */ return CURLE_AGAIN; else if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) curlerr = CURLE_PEER_FAILED_VERIFICATION; -- 2.1.0 From 813f39b34ecc2634aa8ff332709ddde9235f6891 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Mon, 20 Oct 2014 18:18:57 +0200 Subject: [PATCH 4/4] nss: reset SSL handshake state machine ... when the handshake succeeds This fixes a connection failure when FTPS handle is reused. Upstream-commit: 0aecdf682895b42c25b232e91529f48bdf7738b3 Signed-off-by: Kamil Dudka --- lib/nss.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index dadeb58..36fa097 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -1360,9 +1360,6 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) SSL_LIBRARY_VERSION_TLS_1_0 /* max */ }; - if(connssl->state == ssl_connection_complete) - return CURLE_OK; - connssl->data = data; /* list of all NSS objects we need to destroy in Curl_nss_close() */ @@ -1587,10 +1584,6 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex) goto error; } - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = nss_recv; - conn->send[sockindex] = nss_send; - display_conn_info(conn, connssl->handle); if(data->set.str[STRING_SSL_ISSUERCERT]) { @@ -1626,6 +1619,9 @@ static CURLcode nss_connect_common(struct connectdata *conn, int sockindex, const bool blocking = (done == NULL); CURLcode rv; + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + if(connssl->connecting_state == ssl_connect_1) { rv = nss_setup_connect(conn, sockindex); if(rv) @@ -1665,7 +1661,12 @@ static CURLcode nss_connect_common(struct connectdata *conn, int sockindex, /* signal completed SSL handshake */ *done = TRUE; - connssl->connecting_state = ssl_connect_done; + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = nss_recv; + conn->send[sockindex] = nss_send; + + /* ssl_connect_done is never used outside, go back to the initial state */ + connssl->connecting_state = ssl_connect_1; return CURLE_OK; } -- 2.1.0