From 0f6b1efb1469b7801a08b8fd1172808227804b49 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Fri, 25 Apr 2014 17:36:51 +0200 Subject: [PATCH] nss: implement non-blocking SSL handshake --- 0003-curl-7.36.0-8868a226.patch | 526 ++++++++++++++++++++++++++++++++ curl.spec | 9 +- 2 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 0003-curl-7.36.0-8868a226.patch diff --git a/0003-curl-7.36.0-8868a226.patch b/0003-curl-7.36.0-8868a226.patch new file mode 100644 index 0000000..954776c --- /dev/null +++ b/0003-curl-7.36.0-8868a226.patch @@ -0,0 +1,526 @@ +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 + diff --git a/curl.spec b/curl.spec index 53fa9c8..08b5dc1 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.36.0 -Release: 2%{?dist} +Release: 3%{?dist} License: MIT Group: Applications/Internet Source: http://curl.haxx.se/download/%{name}-%{version}.tar.lzma @@ -13,6 +13,9 @@ Patch1: 0001-curl-7.36.0-f82e0edc.patch # extend URL parser to support IPv6 zone identifiers (#680996) Patch2: 0002-curl-7.36.0-9317eced.patch +# nss: implement non-blocking SSL handshake +Patch3: 0003-curl-7.36.0-8868a226.patch + # patch making libcurl multilib ready Patch101: 0101-curl-7.32.0-multilib.patch @@ -127,6 +130,7 @@ documentation of the library, too. # upstream patches %patch1 -p1 %patch2 -p1 +%patch3 -p1 # Fedora patches %patch101 -p1 @@ -248,6 +252,9 @@ rm -rf $RPM_BUILD_ROOT %{_datadir}/aclocal/libcurl.m4 %changelog +* Fri Apr 25 2014 Kamil Dudka 7.36.0-3 +- nss: implement non-blocking SSL handshake + * Wed Apr 02 2014 Kamil Dudka 7.36.0-2 - extend URL parser to support IPv6 zone identifiers (#680996)