cURL / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] nss: propagate blocking direction from NSPR I/O

From: Kamil Dudka <kdudka_at_redhat.com>
Date: Wed, 23 Apr 2014 20:16:39 +0200

... during the non-blocking SSL handshake

---
 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 1a2799f..e92a714 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1389,7 +1389,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 8d00488..c1eec41 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
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html
Received on 2014-04-23