From 79dd8298f45b9f5dd97c06c397d40e45f905d5d3 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Thu, 17 Apr 2014 13:12:59 +0200 Subject: [PATCH 1/3] nss: split Curl_nss_connect() into 4 functions [upstream commit a43bba3a34ed8912c4ca10f213590d1998ba0d29] Signed-off-by: Kamil Dudka --- lib/vtls/nss.c | 134 +++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 94 insertions(+), 40 deletions(-) diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index 80e26e2..4f4e6c8 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -1296,9 +1296,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; @@ -1306,9 +1359,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 */ @@ -1534,16 +1584,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; @@ -1552,12 +1618,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; @@ -1585,40 +1645,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 */ -- 1.7.1 From f6c04350401c111f92f1428f80a28b66f6609cac Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Thu, 17 Apr 2014 13:27:39 +0200 Subject: [PATCH 2/3] nss: implement non-blocking SSL handshake [upstream commit 8868a226cdad66a9a07d6e3f168884817592a1df] Signed-off-by: Kamil Dudka --- lib/urldata.h | 1 + lib/vtls/nss.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++-------- lib/vtls/nssg.h | 1 + 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/urldata.h b/lib/urldata.h index 25f9676..d3bb350 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -318,6 +318,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; diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index 4f4e6c8..e076e54 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -1611,7 +1611,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; @@ -1649,32 +1652,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/vtls/nssg.h b/lib/vtls/nssg.h index 38181a9..21e96ce 100644 --- a/lib/vtls/nssg.h +++ b/lib/vtls/nssg.h @@ -68,6 +68,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 -- 1.7.1 From 9fb78efb737ea8c2a9f7c27ea501b1fcf6a90599 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Wed, 23 Apr 2014 15:37:26 +0200 Subject: [PATCH 3/3] 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/vtls/nss.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/lib/http.c b/lib/http.c index 4a29058..3f8a4c0 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1361,7 +1361,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_POLARSSL) + defined(USE_DARWINSSL) || defined(USE_POLARSSL) || defined(USE_NSS) /* This function is for OpenSSL, GnuTLS, darwinssl, schannel and polarssl only. It should be made to query the generic SSL layer instead. */ static int https_getsock(struct connectdata *conn, diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index e076e54..3447f97 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -180,6 +180,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); @@ -940,6 +944,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; @@ -1004,6 +1062,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; @@ -1353,6 +1426,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; @@ -1525,11 +1600,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; @@ -1612,7 +1710,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; -- 1.7.1