This is an automated email from the ASF dual-hosted git repository.

cliffjansen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git


The following commit(s) were added to refs/heads/main by this push:
     new c33f1abc1 PROTON-2535: TLS OpenSSL library: remove BIO on decrypt side 
for accurate errors and byte counts
c33f1abc1 is described below

commit c33f1abc17936ea4f8a4d1545ea626cd9fd3577f
Author: Clifford Jansen <[email protected]>
AuthorDate: Fri Jun 24 10:32:04 2022 -0700

    PROTON-2535: TLS OpenSSL library: remove BIO on decrypt side for accurate 
errors and byte counts
---
 c/experimental/raw_plus_tls2.c | 408 ++++++++++++++++++++---------------------
 c/src/tls/openssl.c            | 108 +++++------
 c/tests/tls_test.cpp           | 236 +++++++++++++++++++++++-
 3 files changed, 486 insertions(+), 266 deletions(-)

diff --git a/c/experimental/raw_plus_tls2.c b/c/experimental/raw_plus_tls2.c
index 9b285e6ed..f65c290d1 100644
--- a/c/experimental/raw_plus_tls2.c
+++ b/c/experimental/raw_plus_tls2.c
@@ -29,20 +29,23 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
-#include <assert.h>
 #include <unistd.h>
 
 
 /*
  * Jabberwock raw connection example with and without TLS.
  *
- * One client and one server take turns sending some lines of the poem.
- * The simple "application" logic resides in some_jabber() and gobble_jabber().
+ * One client and one server take turns sending a line of the poem.
+ * The simple "application" logic resides in line_of_jabber() and 
gobble_jabber().
  * handle_outgoing() and handle_incoming() handle the application IO
  * plumbing to use Proton raw connections, with or without TLS.
  *
  * See the "no_tls" option to contrast the approach compared to TLS usage.
  *
+ * This example is frugal in the number of buffers it uses, giving at most one
+ * pn_raw_buffer_t at any time to the read or write sides of raw connections 
and
+ * the TLS engine.
+ *
  * In this example orderly termination of the connection is initiated by one 
side.
  * The initiator does not wait for any close handshake from the peer.
  * The peer looks for confirmation of orderly closure from the initiator.
@@ -70,23 +73,44 @@
   pn_tls_config_set_credentials(DOMAIN, CERTIFICATE(NAME), SSL_FILE(NAME 
"-private-key.pem"), SSL_PW)
 #endif
 
+static void jfatal(const char *file, int line) {
+  fprintf(stderr, "epoll proactor failure in %s:%d\n", __FILE__, __LINE__);
+  abort();                                      \
+}
+#define jcheck( EXPR )                                     \
+  { if (!(EXPR)) jfatal(__FILE__,__LINE__); }
+
+static uintptr_t JBR_UNUSED = 0;
+static uintptr_t JBR_INUSE = 1;
+
 // A raw buffer "pool", in name only
-static void rbuf_pool_get(pn_raw_buffer_t *bufs, uint32_t num) {
-  memset(bufs, 0, sizeof(pn_raw_buffer_t) * num);
-  while (num--) {
-    bufs->bytes = calloc(1, 4096);
-    bufs->capacity = 4096;
-    bufs++;
-  }
+static void rbuf_pool_get(pn_raw_buffer_t *buf) {
+  memset(buf, 0, sizeof(pn_raw_buffer_t));
+  buf->bytes = calloc(1, 4096);
+  buf->capacity = 4096;
+  buf->context = JBR_UNUSED;
 }
 
-static void rbuf_pool_return(pn_raw_buffer_t *buf) {
-  free(buf->bytes);
+static void rbuf_pool_return(pn_raw_buffer_t *rbuf) {
+  free(rbuf->bytes);
+}
+
+// Buffer is "in use" if ownership transferred to raw connection or TLS until 
a future event.
+static void buf_set_in_use(pn_raw_buffer_t *rbuf, bool in_use) {
+  rbuf->context = in_use ? JBR_INUSE : JBR_UNUSED;
+}
+static bool buf_in_use(pn_raw_buffer_t *rbuf) {
+  return rbuf->context == JBR_INUSE;
+}
+static bool buf_unused(pn_raw_buffer_t *rbuf) {
+  return !buf_in_use(rbuf);
 }
 
-static void rbuf_pool_multi_return(pn_raw_buffer_t *buf, size_t n) {
-  while(n--)
-    rbuf_pool_return(buf++);
+static void buf_reset(pn_raw_buffer_t *rbuf) {
+  rbuf->size = 0;
+  rbuf->offset = 0;
+  memset(rbuf->bytes, 0, rbuf->capacity);
+  buf_set_in_use(rbuf, false);
 }
 
 static size_t size_t_min(size_t a, size_t b) {
@@ -120,10 +144,15 @@ typedef struct jabber_connection_t {
 
   bool connecting;
   bool tls_closing;
+  bool input_done;
   bool orderly_close_initiated;
   bool orderly_close_detected;
   bool tls_error;
 
+  pn_raw_buffer_t out_wire_buf;
+  pn_raw_buffer_t in_wire_buf;
+  pn_raw_buffer_t out_app_buf;
+  pn_raw_buffer_t in_app_buf;
   bool is_server;
   bool jabber_turn;
   char *alpn_protocol;
@@ -145,30 +174,24 @@ static const char* jlines[] = {
 
 static size_t jlines_count = sizeof(jlines) / sizeof(jlines[0]);
 
-static size_t some_jabber(jabber_connection_t *jc, pn_raw_buffer_t *rbufp, 
size_t nrbufs) {
+// Provide one line of poem into rbuf.
+static void line_of_jabber(jabber_connection_t *jc, pn_raw_buffer_t *rbufp) {
   jabber_t *j = jc->parent;
   const char *self = jc->is_server ? "server" : "client";
-  size_t actual = 0;
-  // Simuate varying output for each run by choosing a random number of lines.
-  int desired = (rand() % nrbufs) + 1;
-  while (desired && j->current_jline < jlines_count) {
+  if (j->current_jline < jlines_count) {
     size_t len = strlen(jlines[j->current_jline]);
-    assert(len < room(rbufp));
+    jcheck( len < room(rbufp) );
     memcpy(rbufp->bytes + rbufp->offset, jlines[j->current_jline++], len);
     rbufp->size = len;
     j->total_bytes_sent += len;
-    desired--;
-    actual++;
-    rbufp++;
   }
-  printf("-->  %s supplied %d lines\n", self, actual);
-  return actual;
 }
 
+// Consume content of poem sent by peer.
 static void gobble_jabber(jabber_connection_t* jc, pn_raw_buffer_t* rbuf) {
   jabber_t *j = jc->parent;
   const char *self = jc->is_server ? "server" : "client";
-  assert(rbuf->size != 0);
+  jcheck( rbuf->size != 0 );
   printf("<--  %s received:  %.4096s\n", self, rbuf->bytes + rbuf->offset);
 
   j->total_bytes_recv += rbuf->size;
@@ -179,8 +202,6 @@ static void gobble_jabber(jabber_connection_t* jc, 
pn_raw_buffer_t* rbuf) {
       pn_raw_connection_wake(jc->rawc);
     }
   }
-
-  rbuf_pool_return(rbuf);
 }
 
 // Return false if TLS error encountered.
@@ -188,13 +209,14 @@ static bool jabber_tls_process(jabber_connection_t* jc) {
   int err = pn_tls_process(jc->tls);
   if (err && !jc->tls_error) {
     jc->tls_error = true;
-    // Stop all application data processing.
-    // Close input.  Continue non-application output in case we have a TLS 
protocol error to send to peer.
     char buf[256];
     pn_tls_get_session_error_string(jc->tls, buf, sizeof(buf));
     fprintf(stderr, "TLS processing error: %s\n", buf);
     fprintf(stderr, "Jabber %s connection terminated.\n", jc->is_server ? 
"server" : "client");
+    // Stop all application data processing.
+    // Close input.  Continue non-application output in case we have a TLS 
protocol error to send to peer.
     pn_raw_connection_read_close(jc->rawc);
+    jc->input_done = true;
     jc->tls_has_output = pn_tls_is_encrypt_output_pending(jc->tls) > 0;
     return false;
   }
@@ -208,7 +230,7 @@ static void jabber_tls_begin_close(jabber_connection_t* jc) 
{
     jc->tls_closing = true;
     pn_tls_close_output(jc->tls);
     jc->need_rawc_write_close = true;  // Remember to eventually close the raw 
connection.
-    pn_tls_process(jc->tls);                // Best efforts. No error check.
+    pn_tls_process(jc->tls);           // Best efforts. No error check.
     pn_raw_connection_wake(jc->rawc);  // Ensure handle_outgoing is called.
   }
 }
@@ -221,7 +243,7 @@ static void check_alpn(jabber_connection_t* jc) {
     size_t len;
     if (pn_tls_get_alpn_protocol(jc->tls, &protocol_name, &len)) {
       jc->alpn_protocol = (char *) malloc(len+1);
-      assert(jc->alpn_protocol);
+      jcheck( jc->alpn_protocol );
       memmove(jc->alpn_protocol, protocol_name, len);
       jc->alpn_protocol[len] = 0;
       printf("**%s: using ALPN protocol %s\n", self, jc->alpn_protocol);
@@ -247,42 +269,29 @@ static void load_alpn_strings(pn_tls_config_t 
*cli_domain, pn_tls_config_t *srv_
 }
 
 static void handle_outgoing(jabber_connection_t* jc) {
-  // Handle here as much outgoing data as possible.  Limits include how much
-  // data is available to send, how much data the raw_connection can accept
-  // before blocking, and if TLS is involved, how much TLS data can be produced
-  // before running out of result buffers.
-  //
-  // For this simplified example, max 4 buffers of application output are dealt
-  // with at a time and "enough" TLS result buffers are always available from
-  // the buffer pool.
-
-  // Do accounting for previous raw connection writes and make room for new 
ones.
+  // For this simplified example, 1 buffer of application output is dealt
+  // with at a time.
+
   jabber_t *j = jc->parent;
-  pn_raw_buffer_t rbuf;
-  while (1 == pn_raw_connection_take_written_buffers(jc->rawc, &rbuf, 1))
-    rbuf_pool_return(&rbuf);
-  size_t max_wire_bufs = pn_raw_connection_write_buffers_capacity(jc->rawc);
-  if (max_wire_bufs == 0)
-    return;  // Nothing to do until notified of future raw connection write 
completion
+  if (buf_in_use(&jc->out_wire_buf)) {
+    // See if raw connection done with the buffer
+    if (1 == pn_raw_connection_take_written_buffers(jc->rawc, 
&jc->out_wire_buf, 1)) {
+      buf_reset(&jc->out_wire_buf);
+    } else {
+      return;  // Nothing to do until notified of future raw connection write 
completion
+    }
+  }
 
   if (!jc->jabber_turn && !jc->tls_has_output && !jc->need_rawc_write_close)
     return;  // nothing to send at this time
 
-  pn_raw_buffer_t wire_buffers[4];  // An arbitrary chunking size for this 
example
-  size_t wire_buf_count = 0;
-
   if (!jc->tls) {
-    // Initialize wire_buffers from pool and insert available application data.
-    // max 4 wanted.  Arbitrary decision for example.
-    size_t max_bufs = size_t_min(max_wire_bufs, 4);
-    rbuf_pool_get(wire_buffers, max_bufs);
-    wire_buf_count = some_jabber(jc, wire_buffers, max_bufs);
-    for (size_t tail = 4; tail > wire_buf_count; tail--)
-      rbuf_pool_return(wire_buffers + tail - 1);  // not used, back to pool.
-    jc->jabber_turn = false;    // Peer gets to do next jabber.
-
-    pn_raw_connection_write_buffers(jc->rawc, wire_buffers, wire_buf_count);
-
+    line_of_jabber(jc, &jc->out_wire_buf);
+    if (jc->out_wire_buf.size > 0) {
+      jc->jabber_turn = false;    // Peer gets to do next jabber.
+      jcheck( pn_raw_connection_write_buffers(jc->rawc, &jc->out_wire_buf, 1) 
== 1 );
+      buf_set_in_use(&jc->out_wire_buf, true);
+    }
     // If no more application data to write, start orderly close.
     if (j->current_jline == jlines_count) {
       pn_raw_connection_write_close(jc->rawc);
@@ -291,170 +300,150 @@ static void handle_outgoing(jabber_connection_t* jc) {
   } else {
     // TLS
 
+    // Reacquire encryption buffer if TLS library is done with it.
+    if (buf_in_use(&jc->out_app_buf) && 
pn_tls_take_encrypt_input_buffers(jc->tls, &jc->out_app_buf, 1) == 1)
+      buf_reset(&jc->out_app_buf);
+
     if (pn_tls_is_secure(jc->tls) && jc->jabber_turn && !jc->tls_closing) {
-      assert(jc->alpn_protocol || !jc->parent->alpn_enabled);
+      jcheck( jc->alpn_protocol || !jc->parent->alpn_enabled );
       // Add jabber data if there is room.
-      size_t max_bufs_to_encrypt = size_t_min(4, 
pn_tls_get_encrypt_input_buffer_capacity(jc->tls));
-      if (max_bufs_to_encrypt) {
-        pn_raw_buffer_t unencrypted_buffers[4];
-        rbuf_pool_get(unencrypted_buffers, max_bufs_to_encrypt);
-        size_t unencrypted_buf_count = some_jabber(jc, unencrypted_buffers, 
max_bufs_to_encrypt);
-        for (size_t tail = max_bufs_to_encrypt; tail > unencrypted_buf_count; 
tail--)
-          rbuf_pool_return(unencrypted_buffers + tail - 1);  // not used, back 
to pool.
-        jc->jabber_turn = false;  // Peer gets to do next jabber.
-        size_t consumed = pn_tls_give_encrypt_input_buffers(jc->tls, 
unencrypted_buffers, unencrypted_buf_count);
-        if (consumed != unencrypted_buf_count) abort();  // Our careful 
counting was meant to prevent this.
-
-        // If no more application data to write, start orderly close.
-        // For TLS, indicate we are done writing to generate the protocol's 
EOS (closure alert).
-        if (j->current_jline == jlines_count) {
-          jc->orderly_close_initiated = true;
-          jabber_tls_begin_close(jc);
+      if (buf_unused(&jc->out_app_buf)) {
+        line_of_jabber(jc, &jc->out_app_buf);
+        if (jc->out_app_buf.size > 0) {
+          jc->jabber_turn = false;  // Peer gets to do next jabber.
+          buf_set_in_use(&jc->out_app_buf, true);
+          size_t consumed = pn_tls_give_encrypt_input_buffers(jc->tls, 
&jc->out_app_buf, 1);
+          if (consumed != 1) abort();
+
+          // If no more application data to write, start orderly close.
+          // For TLS, indicate we are done writing to generate the protocol's 
EOS (closure alert).
+          if (j->current_jline == jlines_count) {
+            jc->orderly_close_initiated = true;
+            jabber_tls_begin_close(jc);
+          }
+
+          jabber_tls_process(jc);
         }
       }
     }
 
-    // Even if no application output (i.e. call to 
pn_tls_give_encrypt_input_buffers()),
-    // there may be encrypt result buffers.
-
-    if (!jabber_tls_process(jc))
-      return;
-    for ( ; wire_buf_count < 4
-           && pn_raw_connection_write_buffers_capacity(jc->rawc) > 
(wire_buf_count)
-           && pn_tls_need_encrypt_output_buffers(jc->tls) ;
-         wire_buf_count++ ) {
-
-      rbuf_pool_get(&rbuf, 1);
-      assert(pn_tls_get_encrypt_output_buffer_capacity(jc->tls) > 0);
-      pn_tls_give_encrypt_output_buffers(jc->tls, &rbuf, 1);
-      if (!jabber_tls_process(jc))
-        return;
-      if (pn_tls_take_encrypt_output_buffers(jc->tls, wire_buffers + 
wire_buf_count, 1) != 1)
-        abort();
+    // Even if no application output just queued for processing (i.e. no call 
to
+    // pn_tls_give_encrypt_input_buffers()), there may be straggling encrypted 
output.
+    // Also possible we are flushing TLS error information to peer.
+    if (buf_unused(&jc->out_wire_buf) && 
pn_tls_need_encrypt_output_buffers(jc->tls)) {
+      pn_tls_give_encrypt_output_buffers(jc->tls, &jc->out_wire_buf, 1);
+      jabber_tls_process(jc);
+      jcheck( pn_tls_take_encrypt_output_buffers(jc->tls, &jc->out_wire_buf, 
1) == 1 );
+
+      // Write the application data we just encrypted.
+      jcheck( pn_raw_connection_write_buffers(jc->rawc, &jc->out_wire_buf, 1) 
== 1);
+      buf_set_in_use(&jc->out_wire_buf, true);
     }
 
-    // Reacquire and release encryption buffers that TLS library is done with.
-    while (pn_tls_take_encrypt_input_buffers(jc->tls, &rbuf, 1) == 1)
-      rbuf_pool_return(&rbuf);
-
-    // Write the application data we just encrypted.
-    pn_raw_connection_write_buffers(jc->rawc, wire_buffers, wire_buf_count);
-
     // Remember whether we missed some output buffered in the TLS library and 
need to come back.
     jc->tls_has_output = pn_tls_is_encrypt_output_pending(jc->tls) > 0;
 
     if (jc->need_rawc_write_close && !jc->tls_has_output) {
-      // We have written to the raw connection all the output the TLS library 
can give us.
+      // We have written to the raw connection all the output the TLS library 
can ever give us.
       pn_raw_connection_write_close(jc->rawc);
       jc->need_rawc_write_close = false;
     }
   }
-
 }
 
 static void handle_incoming(jabber_connection_t* jc, bool 
rawc_input_closed_event) {
-  bool done = false;
-  pn_raw_buffer_t wire_buffers[4];
-  pn_raw_buffer_t rbuf;
-  bool rawc_input_closed = rawc_input_closed_event;
+  // There may be more received wire buffers than sent.
+  if (jc->input_done)
+    return;
+  bool wire_bytes = false;
   const char *self = jc->is_server ? "server" : "client";
 
-  while (!done) {
-    int max_bufs = 4;
-    if (jc->tls) {
-      // Don't read more buffers than the TLS lib will accept.
-      if (max_bufs > pn_tls_get_decrypt_input_buffer_capacity(jc->tls))
-        max_bufs = pn_tls_get_decrypt_input_buffer_capacity(jc->tls);
-      // Since we are always extracting, there should always be room
-      assert(max_bufs > 0);
-    }
-
-    size_t buf_count = pn_raw_connection_take_read_buffers(jc->rawc, 
wire_buffers, max_bufs);
-    if (buf_count < 4)
-      done = true;
-
-    if (buf_count) {
-
-      if (!jc->tls) {
-        // Use wire data directly
-        for (size_t i = 0; i < buf_count; i++) {
-          if (wire_buffers[i].size > 0)
-            gobble_jabber(jc, wire_buffers + i);  // buffer ownership 
transferred to application
-          else {
-            // size == 0 implies orderly close on the raw connection
-            rawc_input_closed = true;
-            if (!jc->orderly_close_initiated && jc->parent->current_jline == 
jlines_count)
-              jc->orderly_close_detected = true;
-            rbuf_pool_return(wire_buffers + i);
-          }
-        }
-      } else {
-        // TLS case
-        if (wire_buffers[buf_count-1].size == 0) {
-          rawc_input_closed = true;
-          rbuf_pool_return(&wire_buffers[buf_count-1]);
-          buf_count -= 1;
-        }
-
-        if (buf_count) {
-          size_t consumed = pn_tls_give_decrypt_input_buffers(jc->tls, 
wire_buffers, buf_count);
-          if (consumed != buf_count) {
-            if (consumed == 0 && pn_tls_is_input_closed(jc->tls)) {
-              // Library will not process anything after TLS EOS and we are 
not expecting any.
-              printf("data after TLS EOF\n");
-            }
-            else  // Should never happen if we counted correctly.
-              printf("Decryption input buffer failure\n");
-            abort();
-          }
+  if (!rawc_input_closed_event) {
+    if (!buf_in_use(&jc->in_wire_buf) ||
+        pn_raw_connection_take_read_buffers(jc->rawc, &jc->in_wire_buf, 1) != 
1)
+      return;  // No wire buffer to process - come back later.
+    if (jc->in_wire_buf.size > 0)
+      wire_bytes = true;
+  }
 
-          if (!jabber_tls_process(jc))
-            return;
+  if (!jc->tls) {
+    if (wire_bytes) {
+      // Use wire data directly
+      gobble_jabber(jc, &jc->in_wire_buf);
+      buf_reset(&jc->in_wire_buf);
+    } else {
+      if (!jc->orderly_close_initiated && jc->parent->current_jline == 
jlines_count)
+        jc->orderly_close_detected = true;
+      // Whether unexpected hangup or expected EOS, Jabber has no more data to 
send.
+      pn_raw_connection_close(jc->rawc);
+      jc->input_done = true;
+    }
+  } else {
+    // TLS case
+    if (wire_bytes) {
+      jcheck( pn_tls_get_decrypt_input_buffer_capacity(jc->tls) > 0 );
+      size_t consumed = pn_tls_give_decrypt_input_buffers(jc->tls, 
&jc->in_wire_buf, 1);
+      if (consumed == 0) {
+        if (pn_tls_is_input_closed(jc->tls))
+          // Library will not process anything after TLS EOS, and we are not 
expecting any trailing data.
+          printf("data after TLS EOF\n"); // Error by peer or garbage bytes 
from attacker.
+        else
+          printf("Unexpected TLS decrypt input buffer failure\n");
+        abort();
+      }
 
-          if (jc->parent->alpn_enabled && !jc->alpn_protocol) {
-            check_alpn(jc);
-          }
+      if (!jabber_tls_process(jc))
+        return;
 
-          while (pn_tls_need_decrypt_output_buffers(jc->tls)) {
-            rbuf_pool_get(&rbuf, 1);
-            assert(pn_tls_get_decrypt_output_buffer_capacity(jc->tls) > 0);
-            pn_tls_give_decrypt_output_buffers(jc->tls, &rbuf, 1);
-            if (!jabber_tls_process(jc))
-              return;
-            size_t decrypted_count = 
pn_tls_take_decrypt_output_buffers(jc->tls, &rbuf, 1);
-            if (decrypted_count != 1)
-              abort();
-            gobble_jabber(jc, &rbuf); // buffer ownership transferred to 
application
-          }
+      if (jc->parent->alpn_enabled && !jc->alpn_protocol)
+        check_alpn(jc);
+
+      while (pn_tls_need_decrypt_output_buffers(jc->tls)) {
+        jcheck( pn_tls_give_decrypt_output_buffers(jc->tls, &jc->in_app_buf, 
1) == 1 );
+        if (!jabber_tls_process(jc))
+          return;
+        jcheck( pn_tls_take_decrypt_output_buffers(jc->tls, &jc->in_app_buf, 
1) == 1 );
+        jcheck( jc->in_app_buf.size > 0 );
+        gobble_jabber(jc, &jc->in_app_buf);  // Application layer consumes all 
bytes.
+        buf_reset(&jc->in_app_buf);
+      }
 
-          // Reclaim and recycle the TLS encoded input buffers (from the wire) 
processed by the TLS library
-          while (pn_tls_take_decrypt_input_buffers(jc->tls, &rbuf, 1) == 1)
-            rbuf_pool_return(&rbuf);
-        }
+      // Reclaim and recycle the TLS encoded input buffer (from the wire) 
processed by the TLS library.
+      // If a partial TLS record remains, it is buffered in the TLS engine.
+      jcheck( pn_tls_take_decrypt_input_buffers(jc->tls, &jc->in_wire_buf, 1) 
== 1);
+      buf_reset(&jc->in_wire_buf);
 
-        if (pn_tls_is_input_closed(jc->tls) && !jc->orderly_close_initiated) {
-          printf("**%s: received TLS end of session notification from peer\n", 
self);
-          jc->orderly_close_detected = true;
-          jabber_tls_begin_close(jc);
-        }
-        // TLS input can generate non-application output.  Note that here.
-        jc->tls_has_output = pn_tls_is_encrypt_output_pending(jc->tls) > 0;
+      if (pn_tls_is_input_closed(jc->tls) && !jc->orderly_close_initiated) {
+        printf("**%s: received TLS end of session notification from peer\n", 
self);
+        jc->orderly_close_detected = true;
+        jabber_tls_begin_close(jc);
       }
-    }
-  }
-
-  if (rawc_input_closed) {
-    // Whether unexpected hangup or expected EOS, Jabber has no more data to 
send.
-    if (!jc->tls) {
-      pn_raw_connection_close(jc->rawc);
-    }
-    else {
-      // TLS case
+    } else {
+      // EOS
+      jc->input_done = true;
+      if (!pn_tls_is_input_closed(jc->tls) && !jc->orderly_close_initiated)
+        // Expecting graceful close with peer in this example.
+        printf("**%s: TLS session ended abruptly\n", self);
       jabber_tls_begin_close(jc);
     }
+    // TLS input can generate non-application output.  Note that here.
+    jc->tls_has_output = pn_tls_is_encrypt_output_pending(jc->tls) > 0;
   }
 }
 
+static void allocate_wire_buffers(jabber_connection_t *jc) {
+  rbuf_pool_get(&jc->out_wire_buf);
+  rbuf_pool_get(&jc->in_wire_buf);
+}
+
+static void allocate_tls_buffers(jabber_connection_t *jc) {
+  pn_tls_set_encrypt_input_buffer_max_capacity(jc->tls, 1);
+  pn_tls_set_decrypt_input_buffer_max_capacity(jc->tls, 1);
+  pn_tls_set_encrypt_output_buffer_max_capacity(jc->tls, 1);
+  pn_tls_set_decrypt_output_buffer_max_capacity(jc->tls, 1);
+  rbuf_pool_get(&jc->in_app_buf);
+  rbuf_pool_get(&jc->out_app_buf);
+}
 
 static void create_client_connection(jabber_t *j) {
   char addr[PN_MAX_ADDR];
@@ -470,10 +459,12 @@ static void create_client_connection(jabber_t *j) {
   if (j->cli_domain) {
     jc->tls = pn_tls(j->cli_domain);
     pn_tls_set_peer_hostname(jc->tls, "test_server");
+    allocate_tls_buffers(jc);
     pn_tls_start(jc->tls);
     jc->tls_has_output = true; // always true for initial client side TLS.
   }
 
+  allocate_wire_buffers(jc);
   pn_proactor_addr(addr, sizeof(addr), j->host, j->port);
   pn_proactor_raw_connect(j->proactor, c, addr);
 }
@@ -489,8 +480,10 @@ static void create_server_connection(jabber_t *j, 
pn_listener_t *listener) {
   // Server side TLS can be set up anywhere between here and first read (TLS 
clienthello).
   if (j->srv_domain) {
     jc->tls = pn_tls(j->srv_domain);
+    allocate_tls_buffers(jc);
     pn_tls_start(jc->tls);
   }
+  allocate_wire_buffers(jc);
   pn_listener_raw_accept(listener, c);
   printf("**listener accepted %p\n", (void *)jc);
   pn_listener_close(j->listener); // Single client.
@@ -501,11 +494,12 @@ static void tls_cleanup(jabber_connection_t* jc) {
   if (jc->tls) {
     pn_tls_stop(jc->tls);
     pn_raw_buffer_t rb;
-    // recycle unused result buffers, released by pn_tls_stop()
-    while (pn_tls_take_encrypt_output_buffers(jc->tls, &rb, 1) == 1)
-      rbuf_pool_return(&rb);
-    while (pn_tls_take_decrypt_output_buffers(jc->tls, &rb, 1) == 1)
-      rbuf_pool_return(&rb);
+    if (buf_in_use(&jc->out_app_buf))
+      jcheck( pn_tls_take_encrypt_input_buffers(jc->tls, &jc->out_app_buf, 1) 
== 1 );
+    jcheck( buf_unused(&jc->in_app_buf) );
+    rbuf_pool_return(&jc->in_app_buf);
+    rbuf_pool_return(&jc->out_app_buf);
+    free(jc->alpn_protocol);
     pn_tls_free(jc->tls);
     jc->tls = NULL;
   }
@@ -520,10 +514,9 @@ static bool handle_raw_connection(jabber_connection_t* jc, 
pn_event_t* event) {
     } break;
 
     case PN_RAW_CONNECTION_NEED_READ_BUFFERS: {
-      pn_raw_buffer_t buffers[4];
-      rbuf_pool_get(buffers, 4);
-      size_t n = pn_raw_connection_give_read_buffers(jc->rawc, buffers, 4);
-      if (n != 4) abort();
+      jcheck( buf_unused(&jc->in_wire_buf) );
+      pn_raw_connection_give_read_buffers(jc->rawc, &jc->in_wire_buf, 1);
+      buf_set_in_use(&jc->in_wire_buf, true);
     } break;
 
     case PN_RAW_CONNECTION_CLOSED_WRITE: {
@@ -546,10 +539,8 @@ static bool handle_raw_connection(jabber_connection_t* jc, 
pn_event_t* event) {
     case PN_RAW_CONNECTION_DRAIN_BUFFERS: {
       pn_raw_buffer_t rbuf;
       while (1 == pn_raw_connection_take_read_buffers(jc->rawc, &rbuf, 1)) {
-        rbuf_pool_return(&rbuf);
       }
       while (1 == pn_raw_connection_take_written_buffers(jc->rawc, &rbuf, 1)) {
-        rbuf_pool_return(&rbuf);
       }
     } break;
 
@@ -573,6 +564,9 @@ static bool handle_raw_connection(jabber_connection_t* jc, 
pn_event_t* event) {
           printf("**%s connection terminated with unsent TLS data\n", self);
         tls_cleanup(jc);
       }
+      rbuf_pool_return(&jc->in_wire_buf);
+      rbuf_pool_return(&jc->out_wire_buf);
+      free(jc);
     } break;
   }
   return true;
@@ -624,7 +618,7 @@ static void* j_thread(void *void_j) {
   return NULL;
 }
 
-// main is from broker.c with switch to raw connections versus AMQP 
connections.
+// main is from broker.c but using raw connections instead of AMQP connections.
 int main(int argc, char **argv) {
   int err;
   srand(time(NULL));
diff --git a/c/src/tls/openssl.c b/c/src/tls/openssl.c
index de07aca62..39d865d2d 100644
--- a/c/src/tls/openssl.c
+++ b/c/src/tls/openssl.c
@@ -188,7 +188,6 @@ struct pn_tls_t {
   const char *peer_hostname;
   SSL *ssl;
 
-  BIO *bio_ssl;         // i/o from/to SSL socket layer
   BIO *bio_ssl_io;      // SSL "half" of network-facing BIO
   BIO *bio_net_io;      // socket-side "half" of network-facing BIO
   // buffers for holding unprocessed bytes to be en/decoded when BIO is able 
to process them.
@@ -202,13 +201,11 @@ struct pn_tls_t {
   bool ssl_closed;      // shutdown complete, or SSL error
   bool dec_closed;      // Peer's TLS closure record received.  Clean EOF of 
inbound decrypted data.
   bool enc_closed;      // Self TLS closure record sent or pending flush.
-  bool read_blocked;    // SSL blocked until more network data is read
-  bool write_blocked;   // SSL blocked until data is written to network
   bool enc_rblocked;
   bool enc_wblocked;
   bool dec_rblocked;
   bool dec_wblocked;
-  bool dec_stale;
+  bool dec_rpending;    // At least one decrypted byte immediately readable.
   bool handshake_ok;
   bool can_shutdown;
   bool started;
@@ -393,7 +390,6 @@ static void tls_fatal(pn_tls_t *tls, int ssl_err_type, int 
pn_tls_err) {
     tls->dec_rblocked = true;
     tls->dec_wblocked = true;
     tls->enc_closed = true;
-    // TODO: recocile doc which says do this:    if (ssl_err_type == 
SSL_ERROR_SYSCALL || ssl_err_type == SSL_ERROR_SSL) {
     if (ssl_err_type == SSL_ERROR_SYSCALL) {
       // OpenSSL requires immediate halt
       tls->can_shutdown = false;
@@ -1228,14 +1224,6 @@ static int init_ssl_socket(pn_tls_t *ssl, 
pn_tls_config_t *domain)
   // restore session, if available
   ssn_restore(ssl);
 
-  // now layer a BIO over the SSL socket
-  ssl->bio_ssl = BIO_new(BIO_f_ssl());
-  if (!ssl->bio_ssl) {
-    ssl_log(NULL, PN_LEVEL_ERROR, "BIO setup failure." );
-    return -1;
-  }
-  (void)BIO_set_ssl(ssl->bio_ssl, ssl->ssl, BIO_NOCLOSE);
-
   // create the "lower" BIO "pipe", and attach it below the SSL layer
   if (!BIO_new_bio_pair(&ssl->bio_ssl_io, 0, &ssl->bio_net_io, 0)) {
     ssl_log(NULL, PN_LEVEL_ERROR, "BIO setup failure." );
@@ -1245,13 +1233,11 @@ static int init_ssl_socket(pn_tls_t *ssl, 
pn_tls_config_t *domain)
 
   if (ssl->mode == PN_TLS_MODE_SERVER) {
     SSL_set_accept_state(ssl->ssl);
-    BIO_set_ssl_mode(ssl->bio_ssl, 0);  // server mode
     ssl_log( NULL, PN_LEVEL_TRACE, "Server SSL socket created." );
     ssl->enc_rblocked = true;
     ssl->dec_rblocked = true;
   } else {      // client mode
     SSL_set_connect_state(ssl->ssl);
-    BIO_set_ssl_mode(ssl->bio_ssl, 1);  // client mode
     ssl_log( NULL, PN_LEVEL_TRACE, "Client SSL socket created." );
     // Start the handshake process.  SSL/BIO read/writes keep it going as 
necessary.
     SSL_do_handshake(ssl->ssl);
@@ -1265,14 +1251,12 @@ static int init_ssl_socket(pn_tls_t *ssl, 
pn_tls_config_t *domain)
 
 static void release_ssl_socket(pn_tls_t *ssl)
 {
-  if (ssl->bio_ssl) BIO_free(ssl->bio_ssl);
   if (ssl->ssl) {
     SSL_free(ssl->ssl);       // will free bio_ssl_io
   } else {
     if (ssl->bio_ssl_io) BIO_free(ssl->bio_ssl_io);
   }
   if (ssl->bio_net_io) BIO_free(ssl->bio_net_io);
-  ssl->bio_ssl = NULL;
   ssl->bio_ssl_io = NULL;
   ssl->bio_net_io = NULL;
   ssl->ssl = NULL;
@@ -1965,14 +1949,14 @@ static void encrypt(pn_tls_t *tls) {
 
   while (true) {
     // Insert unencrypted data into BIO.
-    // OpenSSL maps each BIO_write to a separate TLS record.
-    // The BIO can take 16KB + a bit before blocking.
+    // OpenSSL maps each write to a separate TLS record.
+    // The SSL can take 16KB + a bit before blocking.
     // TODO: consider allowing application to configure BIO buffer size on 
encrypt side.
     while (pending && !tls->enc_wblocked && tls->can_shutdown && 
!tls->pn_tls_err) {
       size_t n = pending->size - tls->encrypt_pending_offset;
       if (n) {
         char *bytes = pending->bytes + pending->offset + 
tls->encrypt_pending_offset;
-        int wcount = BIO_write(tls->bio_ssl, bytes, n);
+        int wcount = SSL_write(tls->ssl, bytes, n);
         if (wcount < (int) n)
           tls->enc_wblocked = true;
         if (wcount > 0) {
@@ -1997,7 +1981,6 @@ static void encrypt(pn_tls_t *tls) {
       if (rcount < (int) n)
         tls->enc_rblocked = true;
       if (rcount > 0) {
-        tls->write_blocked = false;
         if (!tls->pn_tls_err)
           tls->enc_wblocked = false;
         if (result->size == 0) {
@@ -2013,7 +1996,7 @@ static void encrypt(pn_tls_t *tls) {
         }
         if (try_shutdown_again)
           try_shutdown_again = !try_shutdown(tls);
-      } else if (!BIO_should_retry(tls->bio_ssl)) {
+      } else if (!BIO_should_retry(tls->bio_net_io)) {
         int reason = SSL_get_error( tls->ssl, rcount );
         switch (reason) {
         case SSL_ERROR_ZERO_RETURN:
@@ -2035,10 +2018,29 @@ static void encrypt(pn_tls_t *tls) {
   }
 }
 
+static void check_error_reason(pn_tls_t* tls, int count) {
+  int reason = SSL_get_error( tls->ssl, count );
+  switch (reason) {
+  case SSL_ERROR_ZERO_RETURN:
+    // SSL closed cleanly
+    ssl_log(NULL, PN_LEVEL_TRACE, "SSL connection has closed");
+    tls->dec_closed = true;
+    break;
+  case SSL_ERROR_SYSCALL:
+  case SSL_ERROR_SSL:
+    tls_fatal(tls, reason, PN_TLS_PROTOCOL_ERR);
+    break;
+  default:
+    // No state change, continue processing new inbound TLS records (handshake 
or user encrypted data).
+    break;
+  }
+}
+
 static void decrypt(pn_tls_t *tls) {
   assert(tls);
   buff_ptr curr_result = current_decrypted_result(tls);
   pbuffer_t *pending = next_decrypt_pending(tls);
+  bool peek_needed = false;
 
   while (true) {
     if (tls->pn_tls_err)
@@ -2057,7 +2059,8 @@ static void decrypt(pn_tls_t *tls) {
             tls->dec_rblocked = false;
           tls->enc_rblocked = false;
           tls->decrypt_pending_offset += wcount;
-          tls->dec_stale = true;  // new write side content, no read side 
calculation yet.
+          // new write side content may generate decrypted bytes, EOS, error
+          peek_needed = true;
           // tls_trace_callback("tls decrypt: scanned %d bytes", wcount);)
         }
       }
@@ -2069,10 +2072,10 @@ static void decrypt(pn_tls_t *tls) {
       pbuffer_t *result = &tls->dresult_buffers[curr_result-1];
       size_t n = room(result);
       assert(n);
-      int rcount = BIO_read(tls->bio_ssl, result->bytes + result->offset + 
result->size, n);
+      int rcount = SSL_read(tls->ssl, result->bytes + result->offset + 
result->size, n);
       if (rcount > 0) {
         tls->dec_wblocked = false;
-        tls->dec_stale = false;
+        peek_needed = true;  // May or may not have drained all available 
decrypted bytes.
         if (result->size == 0) {
           // first data inserted: convert from blank type to encrypted type
           assert(result->type == buff_dresult_blank);
@@ -2085,23 +2088,10 @@ static void decrypt(pn_tls_t *tls) {
           curr_result = tls->dresult_first_blank;
         }
       } else {
-        if (!BIO_should_retry(tls->bio_ssl)) {
-          int reason = SSL_get_error( tls->ssl, rcount );
-          switch (reason) {
-          case SSL_ERROR_ZERO_RETURN:
-            // SSL closed cleanly
-            ssl_log(NULL, PN_LEVEL_TRACE, "SSL connection has closed");
-            tls->dec_closed = true;
-            tls->dec_rblocked = true;
-            break;
-          default:
-            tls_fatal(tls, reason, PN_TLS_PROTOCOL_ERR);
-            break;
-          }
-        } else {
-          if (rcount == -1 && BIO_should_read(tls->bio_ssl))
-            tls->dec_rblocked = true;
-        }
+        tls->dec_rblocked = true;
+        peek_needed = false;
+        tls->dec_rpending = false;
+        check_error_reason(tls, rcount);
       }
     }
 
@@ -2110,17 +2100,19 @@ static void decrypt(pn_tls_t *tls) {
       break;
   }
 
-  // SSL_do_handshake() to see if handshaking is done but also to force 
handshake bytes into the BIO.
-  if (!tls->handshake_ok && SSL_do_handshake(tls->ssl) == 1) {
-    tls->handshake_ok = true;
-    tls->can_shutdown = true;
+  if (!tls->pn_tls_err && peek_needed) {
+    // Make OpenSSL examine the next buffered TLS record (if exists and 
complete)
+    char unused;
+    int pcount = SSL_peek(tls->ssl, &unused, 1);
+    tls->dec_rpending = (pcount == 1);
+    if (pcount <= 0) {
+      check_error_reason(tls, pcount);
+    }
   }
 
-  if (tls->dec_stale) {
-    // Force OpenSSL to process "enough" buffered content to correctly answer 
if BIO_pending(tls->bio_ssl) > 0.
-    char unused;
-    SSL_peek(tls->ssl, &unused, 1);
-    tls->dec_stale = false;
+  if (!tls->pn_tls_err && !tls->handshake_ok && SSL_do_handshake(tls->ssl) == 
1) {
+    tls->handshake_ok = true;
+    tls->can_shutdown = true;
   }
 }
 
@@ -2133,7 +2125,7 @@ int pn_tls_process(pn_tls_t* tls) {
     if (tls->validating) validate_strict(tls);
   }
   // We keep sending if there is a "minor" error that may result in an error 
message for the peer
-  if (!(tls->pn_tls_err && (tls->openssl_err_type == SSL_ERROR_SYSCALL || 
tls->openssl_err_type == SSL_ERROR_SSL))) {
+  if (!(tls->pn_tls_err && tls->openssl_err_type == SSL_ERROR_SYSCALL)) {
     encrypt(tls);
     if (tls->validating) validate_strict(tls);
   }
@@ -2143,7 +2135,7 @@ int pn_tls_process(pn_tls_t* tls) {
 bool pn_tls_need_encrypt_output_buffers(pn_tls_t* tls) {
   if (tls && tls->started && !tls->stopped && tls->eresult_empty_count) {
     // We keep sending if there is a "minor" error that may result in an error 
message for the peer
-    if (!(tls->pn_tls_err && (tls->openssl_err_type == SSL_ERROR_SYSCALL || 
tls->openssl_err_type == SSL_ERROR_SSL))) {
+    if (!(tls->pn_tls_err && tls->openssl_err_type == SSL_ERROR_SYSCALL)) {
       if (!current_encrypted_result(tls)) {
         // Existing result buffers all full.  Check if OpenSSL has data to 
read.
         return (BIO_pending(tls->bio_net_io) > 0);
@@ -2155,9 +2147,9 @@ bool pn_tls_need_encrypt_output_buffers(pn_tls_t* tls) {
 
 bool pn_tls_need_decrypt_output_buffers(pn_tls_t* tls) {
   if (tls && tls->started && !tls->stopped && tls->dresult_empty_count && 
!tls->dec_closed && !tls->pn_tls_err) {
-    if (!current_decrypted_result(tls)) {
-      // Existing result buffers all full.  Check if OpenSSL has data to read.
-      return (BIO_pending(tls->bio_ssl) > 0);
+    if (!current_decrypted_result(tls) && tls->dec_rpending) {
+      // No current buffer and OpenSSL has valid decrypted data to read.
+      return true;
     }
   }
   return false;
@@ -2166,7 +2158,7 @@ bool pn_tls_need_decrypt_output_buffers(pn_tls_t* tls) {
 bool pn_tls_is_encrypt_output_pending(pn_tls_t *tls)
 {
   if (tls && tls->started && !tls->stopped) {
-    if (!(tls->pn_tls_err && (tls->openssl_err_type == SSL_ERROR_SYSCALL || 
tls->openssl_err_type == SSL_ERROR_SSL)))
+    if (!(tls->pn_tls_err && tls->openssl_err_type == SSL_ERROR_SYSCALL))
       return tls->eresult_first_encrypted || (BIO_pending(tls->bio_net_io) > 
0);
   }
   return false;
@@ -2175,7 +2167,7 @@ bool pn_tls_is_encrypt_output_pending(pn_tls_t *tls)
 bool pn_tls_is_decrypt_output_pending(pn_tls_t *tls)
 {
   if (tls && tls->started && !tls->stopped &&!tls->dec_closed && 
!tls->pn_tls_err) {
-    return tls->dresult_first_decrypted || (BIO_pending(tls->bio_ssl) > 0);
+    return tls->dresult_first_decrypted || tls->dec_rpending;
   }
   return false;
 }
diff --git a/c/tests/tls_test.cpp b/c/tests/tls_test.cpp
index a6a50a411..44b4b972f 100644
--- a/c/tests/tls_test.cpp
+++ b/c/tests/tls_test.cpp
@@ -215,7 +215,7 @@ TEST_CASE("handshake and data") {
   REQUIRE( pn_tls_give_decrypt_input_buffers(srv_tls, rb_array, 1) == 1 );
   REQUIRE( pn_tls_process(srv_tls) == 0 );
   REQUIRE( pn_tls_take_decrypt_input_buffers(srv_tls, rb_array, 2) == 1 );
-  REQUIRE( pn_tls_is_input_closed(srv_tls) == false );
+  REQUIRE( pn_tls_is_input_closed(srv_tls) == true );
 
   /* clean up */
 
@@ -237,3 +237,237 @@ TEST_CASE("handshake and data") {
   pn_tls_config_free(server_config);
 }
 
+pn_tls_config_t * default_client_config(void) {
+  pn_tls_config_t *cfg = pn_tls_config(PN_TLS_MODE_CLIENT);
+  if (cfg && pn_tls_config_set_trusted_certs(cfg, CERTIFICATE("tserver")) == 0)
+    return cfg;
+  pn_tls_config_free(cfg);
+  return NULL;
+}
+
+pn_tls_config_t *default_server_config(void) {
+  pn_tls_config_t *cfg = pn_tls_config(PN_TLS_MODE_SERVER);
+  if (cfg && SET_CREDENTIALS(cfg, "tserver") == 0)
+    return cfg;
+  pn_tls_config_free(cfg);
+  return NULL;
+}
+
+pn_raw_buffer_t new_rbuf(size_t sz) {
+  pn_raw_buffer_t rb = {0};
+  rb.bytes = (char *) malloc(sz);
+  // REQUIRE(rb.bytes != NULL);
+  rb.capacity = sz;
+  return rb;
+}
+
+void free_rbuf(pn_raw_buffer_t &rb) {
+  free(rb.bytes);
+  rb = {0};
+}
+
+bool is_null(pn_raw_buffer_t &rb) {
+  return rb.bytes == NULL && rb.capacity == 0 && rb.size == 0 && rb.offset == 
0;
+}
+
+bool is_valid(pn_raw_buffer_t &rb) {
+  return rb.bytes && rb.capacity && (rb.size + rb.offset <= rb.capacity);
+}
+
+void drain_processed_input_bufs(pn_tls_t *tls) {
+  pn_raw_buffer_t rb;
+  while (pn_tls_take_encrypt_input_buffers(tls, &rb, 1) == 1)
+    free_rbuf(rb);
+  while (pn_tls_take_decrypt_input_buffers(tls, &rb, 1) == 1)
+    free_rbuf(rb);
+}
+
+void drain_processed_output_bufs(pn_tls_t *tls) {
+  pn_raw_buffer_t rb;
+  while (pn_tls_take_encrypt_output_buffers(tls, &rb, 1) == 1)
+    free_rbuf(rb);
+  while (pn_tls_take_decrypt_output_buffers(tls, &rb, 1) == 1)
+    free_rbuf(rb);
+}
+
+// Call after a single pump.  Data will be flushed if an output buffer is 
staged.
+bool has_encrypted_data(pn_tls_t *tls) {
+  return pn_tls_get_last_encrypt_output_buffer_size(tls) ||
+    pn_tls_need_encrypt_output_buffers(tls);
+}
+
+bool has_encrypt_room(pn_tls_t *tls) {
+  return pn_tls_get_encrypt_input_buffer_capacity(tls) > 1;
+}
+
+bool has_decrypt_room(pn_tls_t *tls) {
+  return pn_tls_get_decrypt_input_buffer_capacity(tls) > 1;
+}
+
+struct TestPeer {
+  bool isServer;
+  pn_tls_config_t *config;
+  pn_tls_t *tls;
+  TestPeer(bool is_server) : isServer(is_server), config(NULL), tls(NULL) {}
+  ~TestPeer() {
+    if (tls) {
+      cleanup();
+      pn_tls_free(tls);
+    }
+    if (config) pn_tls_config_free(config);
+  }
+  void init() {
+    bool dflt = false;
+    if (!config) {
+      config = isServer ? default_server_config() : default_client_config();
+      dflt = true;
+    }
+    REQUIRE(config);
+    if (!tls) tls = pn_tls(config);
+    REQUIRE(tls);
+    if (dflt && !isServer)
+      pn_tls_set_peer_hostname(tls, "test_server");
+    REQUIRE(pn_tls_start(tls) == 0);
+  }
+  void cleanup() {
+    if (tls) {
+      pn_tls_stop(tls);
+      drain_processed_input_bufs(tls);
+      drain_processed_output_bufs(tls);
+    }
+  }
+};
+
+// Transfer one raw buffer of encrypted data from each peer to the other.  
Test programs
+// can call this at any time so number of processed/unprocessed input/output 
buffers is
+// unknown.
+void pump(TestPeer &a, TestPeer &b) {
+  pn_raw_buffer_t from_a = {0};
+  pn_raw_buffer_t from_b = {0};
+  pn_tls_process(a.tls);
+  pn_tls_process(b.tls);
+
+  drain_processed_input_bufs(b.tls);
+  if (has_decrypt_room(b.tls)) {
+    if (pn_tls_take_encrypt_output_buffers(a.tls, &from_a, 1) == 1) {
+      REQUIRE(is_valid(from_a));
+    } else {
+      REQUIRE(is_null(from_a));
+      if (pn_tls_need_encrypt_output_buffers(a.tls)) {
+        pn_raw_buffer_t rbuf = new_rbuf(65536);
+        REQUIRE(pn_tls_give_encrypt_output_buffers(a.tls, &rbuf, 1) == 1);
+        REQUIRE(pn_tls_process(a.tls) == 0);
+        REQUIRE(pn_tls_take_encrypt_output_buffers(a.tls, &from_a, 1) == 1);
+        REQUIRE(is_valid(from_a));
+      }
+    }
+  }
+
+  // Symetrically opposite.
+  drain_processed_input_bufs(a.tls);
+  if (has_decrypt_room(a.tls)) {
+    if (pn_tls_take_encrypt_output_buffers(b.tls, &from_b, 1) == 1) {
+      REQUIRE(is_valid(from_b));
+    } else {
+      REQUIRE(is_null(from_b));
+      if (pn_tls_need_encrypt_output_buffers(b.tls)) {
+        pn_raw_buffer_t rbuf = new_rbuf(65536);
+        REQUIRE(pn_tls_give_encrypt_output_buffers(b.tls, &rbuf, 1) == 1);
+        pn_tls_process(b.tls);
+        REQUIRE(pn_tls_take_encrypt_output_buffers(b.tls, &from_b, 1) == 1);
+        REQUIRE(is_valid(from_b));
+      }
+    }
+  }
+
+  // Now allow each peer see and act on the other's transferred data.
+  if (!is_null(from_a)) {
+    REQUIRE(pn_tls_give_decrypt_input_buffers(b.tls, &from_a, 1) == 1);
+    pn_tls_process(b.tls);
+  }
+  if (!is_null(from_b)) {
+    REQUIRE(pn_tls_give_decrypt_input_buffers(a.tls, &from_b, 1) == 1);
+    pn_tls_process(a.tls);
+  }
+}
+
+struct TestPair {
+  TestPeer client;
+  TestPeer server;
+
+  TestPair() : client(false), server(true) {}
+  void init() {
+    client.init();
+    server.init();
+  }
+  void singlePump() {
+    pump(client, server);
+  }
+  bool canPump() {
+    if (has_encrypted_data(client.tls) && has_encrypt_room(server.tls))
+      return true;
+    if (has_encrypted_data(server.tls) && has_encrypt_room(client.tls))
+      return true;
+    // No data or no place to put it.
+    return false;
+  }
+  void multiPump() {
+    do {
+      singlePump();
+    } while (canPump());
+  }
+};
+
+TEST_CASE("default - no data") {
+  TestPair tp;
+  tp.init();  // just use default setup
+  tp.multiPump();
+  REQUIRE(pn_tls_is_secure(tp.client.tls));
+  REQUIRE(pn_tls_is_secure(tp.server.tls));
+  // Handshake OK.  Close session.
+  pn_tls_close_output(tp.client.tls);
+  pn_tls_close_output(tp.server.tls);
+  tp.multiPump();
+  REQUIRE(pn_tls_is_input_closed(tp.client.tls));
+  REQUIRE(pn_tls_get_session_error(tp.client.tls) == 0);
+  REQUIRE(pn_tls_is_input_closed(tp.server.tls));
+  REQUIRE(pn_tls_get_session_error(tp.server.tls) == 0);
+}
+
+TEST_CASE("missing client cert") {
+  TestPair tp;
+  tp.server.config = default_server_config();
+  REQUIRE(pn_tls_config_set_peer_authentication(tp.server.config, 
PN_TLS_VERIFY_PEER, CERTIFICATE("tclient")) == 0);
+  tp.init();
+  const char *early_data = "early data";
+  size_t len = strlen(early_data);
+  pn_raw_buffer_t early_data_buf = new_rbuf(1024);
+  memcpy(early_data_buf.bytes, early_data, len);
+  early_data_buf.size = len;
+  REQUIRE(pn_tls_give_encrypt_input_buffers(tp.client.tls, &early_data_buf, 1) 
== 1);
+
+  tp.singlePump(); // Client hello
+  tp.singlePump(); // Server hello and request for cert
+  tp.singlePump(); // Client finish (and early data if TLS1.3).  No client 
cert configured or provided.
+
+  REQUIRE(pn_tls_need_decrypt_output_buffers(tp.server.tls) == false); // 
PROTON-2535
+  REQUIRE(pn_tls_get_session_error(tp.server.tls) != 0);
+  REQUIRE(pn_tls_get_session_error(tp.client.tls) == 0);
+
+  tp.singlePump(); // Server error alert
+  REQUIRE(pn_tls_get_session_error(tp.client.tls) != 0);
+
+  char ebuf[4096];
+  memset(ebuf, 0, sizeof(ebuf));
+  len = pn_tls_get_session_error_string(tp.server.tls, ebuf, sizeof(ebuf));
+  REQUIRE(len != 0);
+  REQUIRE(strstr(ebuf, "peer did not return a certificate"));
+
+  memset(ebuf, 0, sizeof(ebuf));
+  len = pn_tls_get_session_error_string(tp.client.tls, ebuf, sizeof(ebuf));
+  REQUIRE(len != 0);
+  REQUIRE(strstr(ebuf, "certificate required"));
+
+  set_rbuf(&early_data_buf, NULL, 0, 0);
+  reset_rbuf(&early_data_buf);
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to