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]