[curl/f19] nss: implement non-blocking SSL handshake
Kamil Dudka
kdudka at fedoraproject.org
Fri Apr 25 16:23:30 UTC 2014
commit 650a01ccd92bc0d55e60f72a69b8c5f7c2c81851
Author: Kamil Dudka <kdudka at redhat.com>
Date: Fri Apr 25 17:36:51 2014 +0200
nss: implement non-blocking SSL handshake
0019-curl-7.29.0-8868a226.patch | 527 +++++++++++++++++++++++++++++++++++++++
curl.spec | 9 +-
2 files changed, 535 insertions(+), 1 deletions(-)
---
diff --git a/0019-curl-7.29.0-8868a226.patch b/0019-curl-7.29.0-8868a226.patch
new file mode 100644
index 0000000..0eabd89
--- /dev/null
+++ b/0019-curl-7.29.0-8868a226.patch
@@ -0,0 +1,527 @@
+From 245feaa0827d1f68168c8512aee4102f0d6c4155 Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+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 <kdudka at redhat.com>
+---
+ lib/nss.c | 134 +++++++++++++++++++++++++++++++++++++++++++------------------
+ 1 files changed, 95 insertions(+), 39 deletions(-)
+
+diff --git a/lib/nss.c b/lib/nss.c
+index 2d4bf9e..b21daf0 100644
+--- a/lib/nss.c
++++ b/lib/nss.c
+@@ -1172,9 +1172,62 @@ static CURLcode nss_load_ca_certificates(struct connectdata *conn,
+ return CURLE_OK;
+ }
+
+-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 ssl2 = PR_FALSE;
+ PRBool ssl3 = PR_FALSE;
+@@ -1186,9 +1239,6 @@ CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex)
+ struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+ CURLcode curlerr;
+ const int *cipher_to_enable;
+- PRSocketOptionData sock_opt;
+- long time_left;
+- PRUint32 timeout;
+
+ if(connssl->state == ssl_connection_complete)
+ return CURLE_OK;
+@@ -1391,16 +1441,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;
+@@ -1409,12 +1475,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;
+@@ -1442,40 +1502,36 @@ 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(ssl3 && tlsv1 && 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 */
+ int sockindex, /* socketindex */
+ const void *mem, /* send this data */
+--
+1.7.1
+
+
+From f373ea815c8130f9f3c0b412c1fc092bb242699b Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+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 <kdudka at redhat.com>
+---
+ 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 b21daf0..589e5b8 100644
+--- a/lib/nss.c
++++ b/lib/nss.c
+@@ -1468,7 +1468,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;
+@@ -1506,32 +1509,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 cd32706..e1ae464 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 cbf4102..62413e6 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;
+--
+1.7.1
+
+
+From 6ce73c703fb27b1214642a7635850151bd38a928 Mon Sep 17 00:00:00 2001
+From: Kamil Dudka <kdudka at redhat.com>
+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 <kdudka at redhat.com>
+---
+ 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 c78036b..d9b792e 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 589e5b8..424c0b2 100644
+--- a/lib/nss.c
++++ b/lib/nss.c
+@@ -171,6 +171,10 @@ static const int enable_ciphers_by_default[] = {
+ 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);
+@@ -852,6 +856,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)
+ {
+ #ifdef HAVE_NSS_INITCONTEXT
+@@ -936,6 +994,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;
+@@ -1229,6 +1302,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 ssl2 = PR_FALSE;
+ PRBool ssl3 = PR_FALSE;
+ PRBool tlsv1 = PR_FALSE;
+@@ -1422,11 +1497,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;
+@@ -1469,7 +1567,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
+
diff --git a/curl.spec b/curl.spec
index d723910..75fe3a8 100644
--- a/curl.spec
+++ b/curl.spec
@@ -1,7 +1,7 @@
Summary: A utility for getting files from remote servers (FTP, HTTP, and others)
Name: curl
Version: 7.29.0
-Release: 17%{?dist}
+Release: 18%{?dist}
License: MIT
Group: Applications/Internet
Source: http://curl.haxx.se/download/%{name}-%{version}.tar.lzma
@@ -58,6 +58,9 @@ Patch17: 0017-curl-7.29.0-ffb8a21d.patch
# fix connection re-use when using different log-in credentials (CVE-2014-0138)
Patch18: 0018-curl-7.29.0-517b06d6.patch
+# nss: implement non-blocking SSL handshake
+Patch19: 0019-curl-7.29.0-8868a226.patch
+
# patch making libcurl multilib ready
Patch101: 0101-curl-7.29.0-multilib.patch
@@ -183,6 +186,7 @@ documentation of the library, too.
%patch16 -p1
%patch17 -p1
%patch18 -p1
+%patch19 -p1
# Fedora patches
%patch101 -p1
@@ -303,6 +307,9 @@ rm -rf $RPM_BUILD_ROOT
%{_datadir}/aclocal/libcurl.m4
%changelog
+* Fri Apr 25 2014 Kamil Dudka <kdudka at redhat.com> 7.29.0-18
+- nss: implement non-blocking SSL handshake
+
* Wed Mar 26 2014 Kamil Dudka <kdudka at redhat.com> 7.29.0-17
- fix connection re-use when using different log-in credentials (CVE-2014-0138)
More information about the scm-commits
mailing list