blob: fab25a2cc7f79a0944948a49c7dcfd9378d6927c [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <proton/tls.h>
#include "pn_test.hpp"
#ifdef _WIN32
#include <errno.h>
#else
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#endif
#include <cstring>
#include <filesystem>
using namespace pn_test;
using Catch::Matchers::Contains;
using Catch::Matchers::Equals;
static std::string ssl_file_path(const std::string &name) {
auto env = getenv("TEST_CERT_DIR");
const char* cert_dir = env ? env : "ssl-certs";
auto p = std::filesystem::path(cert_dir) / name;
return p.string();
}
#define SSL_FILE(NAME) ssl_file_path(NAME)
#define SSL_PW(NAME) NAME "pw"
/* Windows vs. OpenSSL certificates */
#if defined(_WIN32)
#define CERTIFICATE(NAME) SSL_FILE(NAME "-certificate.p12").c_str()
#define SSL_CRED1(NAME) SSL_FILE(NAME "-full.p12").c_str()
#define SSL_CRED2(NAME) (NAME)
#else
#define CERTIFICATE(NAME) SSL_FILE(NAME "-certificate.pem").c_str()
#define SSL_CRED1(NAME) CERTIFICATE(NAME)
#define SSL_CRED2(NAME) SSL_FILE(NAME "-private-key.pem").c_str()
#endif
#define SET_CREDENTIALS(DOMAIN, NAME) pn_tls_config_set_credentials(DOMAIN, SSL_CRED1(NAME), SSL_CRED2(NAME), SSL_PW(NAME))
static void reset_rbuf(pn_raw_buffer_t *rb) {
memset(rb, 0, sizeof(*rb));
}
static void set_rbuf(pn_raw_buffer_t *rb, char * bytes, uint32_t capacity, uint32_t size) {
rb->bytes = bytes;
rb->capacity = capacity;
rb->size = size;
rb->offset = 0;
}
TEST_CASE("handshake and data") {
static char cli_data[] = {"sample client data (request)"};
static char srv_data[] = {"sample server data (response)"};
pn_tls_config_t *client_config = pn_tls_config(PN_TLS_MODE_CLIENT);
REQUIRE( client_config );
pn_tls_config_t *server_config = pn_tls_config(PN_TLS_MODE_SERVER);
REQUIRE( server_config );
REQUIRE( pn_tls_config_set_trusted_certs(client_config, CERTIFICATE("tserver")) == 0 );
REQUIRE(SET_CREDENTIALS(server_config, "tserver") == 0);
pn_tls_t *cli_tls = pn_tls(client_config);
pn_tls_set_peer_hostname(cli_tls, "test_server");
pn_tls_t *srv_tls = pn_tls(server_config);
CHECK( cli_tls != NULL );
CHECK( srv_tls != NULL );
// Config complete on both sides.
REQUIRE( pn_tls_start(cli_tls) == 0 );
REQUIRE( pn_tls_start(srv_tls) == 0 );
char wire_bytes[4096]; // encrypted data, sent between client and server
char app_bytes[4096]; // plain text data read or written at either peer
pn_raw_buffer_t app_buf, wire_buf, rb_array[2];
reset_rbuf(&wire_buf);
set_rbuf(&wire_buf, wire_bytes, sizeof(wire_bytes), 0);
reset_rbuf(&app_buf);
set_rbuf(&app_buf, app_bytes, sizeof(app_bytes), 0);
/* client hello part 1: client side */
REQUIRE( pn_tls_need_encrypt_output_buffers(cli_tls) == true );
REQUIRE( pn_tls_need_encrypt_output_buffers(srv_tls) == false );
pn_tls_give_encrypt_output_buffers(cli_tls, &wire_buf, 1);
REQUIRE( pn_tls_process(cli_tls) == 0 );
REQUIRE( pn_tls_take_encrypt_output_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( rb_array[0].size < sizeof(wire_bytes) ); // Strictly less expected.
/* client hello part 2: server side */
REQUIRE( pn_tls_need_encrypt_output_buffers(srv_tls) == false );
REQUIRE( pn_tls_give_decrypt_input_buffers(srv_tls, rb_array, 1) == 1 );
REQUIRE( pn_tls_process(srv_tls) == 0 );
REQUIRE( pn_tls_need_decrypt_output_buffers(srv_tls) == false ); // nothing yet
REQUIRE( pn_tls_take_decrypt_input_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
/* server hello part1: server side */
REQUIRE( pn_tls_is_encrypt_output_pending(srv_tls) == true );
set_rbuf(&wire_buf, wire_bytes, sizeof(wire_bytes), 0);
REQUIRE( pn_tls_give_encrypt_output_buffers(srv_tls, &wire_buf, 1) == 1 );
REQUIRE( pn_tls_process(srv_tls) == 0 );
REQUIRE( pn_tls_take_encrypt_output_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( rb_array[0].size < sizeof(wire_bytes) ); // Strictly less expected.
REQUIRE( pn_tls_is_secure(cli_tls) == false ); // negotiation incomplete both sides
REQUIRE( pn_tls_is_secure(srv_tls) == false );
/* server hello part2: client side */
REQUIRE( pn_tls_need_encrypt_output_buffers(cli_tls) == false );
REQUIRE( pn_tls_give_decrypt_input_buffers(cli_tls, rb_array, 1) == 1 );
REQUIRE( pn_tls_process(cli_tls) == 0 );
REQUIRE( pn_tls_take_decrypt_input_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( pn_tls_is_secure(cli_tls) == true ); // Protocol and certificate acceptable to client
REQUIRE( pn_tls_is_secure(srv_tls) == false ); // Server doesn't know yet
/* client side finish record and application data */
REQUIRE( pn_tls_need_encrypt_output_buffers(cli_tls) == true );
set_rbuf(&wire_buf, wire_bytes, sizeof(wire_bytes), 0);
REQUIRE( pn_tls_give_encrypt_output_buffers(cli_tls, &wire_buf, 1) == 1 );
size_t len = sizeof(cli_data);
memcpy(app_bytes, cli_data, len);
app_buf.size = len;
REQUIRE( pn_tls_give_encrypt_input_buffers(cli_tls, &app_buf, 1) );
REQUIRE( pn_tls_process(cli_tls) == 0 );
REQUIRE( pn_tls_take_encrypt_input_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == app_bytes );
REQUIRE( pn_tls_take_encrypt_output_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( rb_array[0].size < sizeof(wire_bytes) ); // Strictly less expected.
/* server side */
REQUIRE( pn_tls_give_decrypt_input_buffers(srv_tls, rb_array, 1) == 1 );
REQUIRE( pn_tls_process(srv_tls) == 0 );
REQUIRE( pn_tls_is_secure(srv_tls) == true ); // Handshake complete at server
REQUIRE( pn_tls_need_decrypt_output_buffers(srv_tls) == true ); // have client app data
memset(app_bytes, 0, sizeof(app_bytes));
set_rbuf(&app_buf, app_bytes, sizeof(app_bytes), 0);
REQUIRE( pn_tls_give_decrypt_output_buffers(srv_tls, &app_buf, 1) == 1 );
REQUIRE( pn_tls_process(srv_tls) == 0 );
REQUIRE( pn_tls_take_decrypt_output_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == app_bytes );
REQUIRE( rb_array[0].size == sizeof(cli_data) );
REQUIRE( strncmp(rb_array[0].bytes, cli_data, sizeof(cli_data)) == 0 );
REQUIRE( pn_tls_take_decrypt_input_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
memset(app_bytes, 0, sizeof(app_bytes)); // Received client data, send server data
set_rbuf(&app_buf, app_bytes, sizeof(app_bytes), 0);
len = sizeof(srv_data);
memcpy(app_bytes, srv_data, len);
app_buf.size = len;
REQUIRE( pn_tls_give_encrypt_input_buffers(srv_tls, &app_buf, 1) );
set_rbuf(&wire_buf, wire_bytes, sizeof(wire_bytes), 0);
REQUIRE( pn_tls_give_encrypt_output_buffers(srv_tls, &wire_buf, 1) == 1 );
pn_tls_close_output(srv_tls); // Finished sending.
REQUIRE( pn_tls_process(srv_tls) == 0 );
REQUIRE( pn_tls_take_encrypt_input_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == app_bytes );
REQUIRE( pn_tls_take_encrypt_output_buffers(srv_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( rb_array[0].size < sizeof(wire_bytes) );
/* client side: read server data and confirm end of TLS session */
REQUIRE( pn_tls_is_input_closed(cli_tls) == false );
memset(app_bytes, 0, sizeof(app_bytes));
set_rbuf(&app_buf, app_bytes, sizeof(app_bytes), 0);
REQUIRE( pn_tls_give_decrypt_output_buffers(cli_tls, &app_buf, 1) == 1 );
REQUIRE( pn_tls_give_decrypt_input_buffers(cli_tls, rb_array, 1) == 1 );
REQUIRE( pn_tls_process(cli_tls) == 0 );
REQUIRE( pn_tls_take_decrypt_input_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( pn_tls_take_decrypt_output_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == app_bytes );
REQUIRE( rb_array[0].size == sizeof(srv_data) );
REQUIRE( strncmp(rb_array[0].bytes, srv_data, sizeof(srv_data)) == 0 );
REQUIRE( pn_tls_is_input_closed(cli_tls) == true );
set_rbuf(&wire_buf, wire_bytes, sizeof(wire_bytes), 0);
REQUIRE( pn_tls_give_encrypt_output_buffers(cli_tls, &wire_buf, 1) == 1 );
pn_tls_close_output(cli_tls); // Initiate symetric closure record
REQUIRE( pn_tls_process(cli_tls) == 0 );
REQUIRE( pn_tls_take_encrypt_output_buffers(cli_tls, rb_array, 2) == 1 );
REQUIRE( rb_array[0].bytes == wire_bytes );
REQUIRE( rb_array[0].size < sizeof(wire_bytes) );
/* server side */
REQUIRE( pn_tls_is_input_closed(srv_tls) == false );
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) == true );
/* clean up */
pn_tls_stop(cli_tls);
REQUIRE( pn_tls_take_encrypt_input_buffers(cli_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_encrypt_output_buffers(cli_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_decrypt_input_buffers(cli_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_decrypt_output_buffers(cli_tls, rb_array, 1) == 0 );
pn_tls_free(cli_tls);
pn_tls_stop(srv_tls);
REQUIRE( pn_tls_take_encrypt_input_buffers(srv_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_encrypt_output_buffers(srv_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_decrypt_input_buffers(srv_tls, rb_array, 1) == 0 );
REQUIRE( pn_tls_take_decrypt_output_buffers(srv_tls, rb_array, 1) == 0 );
pn_tls_free(srv_tls);
pn_tls_config_free(client_config);
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);
}
TEST_CASE("buffer release on pn_tls_stop()") {
pn_raw_buffer_t bufs[4];
size_t give_count = 0;
size_t take_count = 0;
TestPeer client(false);
client.init(); // ... up to pn_tls_start()
pn_tls_t *tls = client.tls;
// Confirm we can add them but not take out empty buffers until after pn_tls_top()
size_t count = 1;
for (size_t i = 0; i < count; i++)
bufs[i] = new_rbuf(512);
give_count += count;
REQUIRE(pn_tls_give_encrypt_input_buffers(tls, bufs, count) == count);
REQUIRE(pn_tls_take_encrypt_input_buffers(tls, bufs, count) == 0);
count = 2;
for (size_t i = 0; i < count; i++)
bufs[i] = new_rbuf(512);
give_count += count;
REQUIRE(pn_tls_give_decrypt_input_buffers(tls, bufs, count) == count);
REQUIRE(pn_tls_take_decrypt_input_buffers(tls, bufs, count) == 0);
count = 3;
for (size_t i = 0; i < count; i++)
bufs[i] = new_rbuf(512);
give_count += count;
REQUIRE(pn_tls_give_encrypt_output_buffers(tls, bufs, count) == count);
REQUIRE(pn_tls_get_encrypt_output_buffer_count(tls) == 0);
REQUIRE(pn_tls_take_encrypt_output_buffers(tls, bufs, count) == 0);
count = 4;
for (size_t i = 0; i < count; i++)
bufs[i] = new_rbuf(512);
give_count += count;
REQUIRE(pn_tls_give_decrypt_output_buffers(tls, bufs, count) == count);
REQUIRE(pn_tls_get_decrypt_output_buffer_count(tls) == 0);
REQUIRE(pn_tls_take_decrypt_output_buffers(tls, bufs, count) == 0);
pn_tls_stop(tls);
count = pn_tls_take_encrypt_input_buffers(tls, bufs, 4);
for (size_t i = 0; i < count; i++)
free_rbuf(bufs[i]);
take_count += count;
count = pn_tls_take_decrypt_input_buffers(tls, bufs, 4);
for (size_t i = 0; i < count; i++)
free_rbuf(bufs[i]);
take_count += count;
size_t avail = pn_tls_get_encrypt_output_buffer_count(tls);
count = pn_tls_take_encrypt_output_buffers(tls, bufs, 4);
REQUIRE(avail == count);
for (size_t i = 0; i < count; i++)
free_rbuf(bufs[i]);
take_count += count;
avail = pn_tls_get_decrypt_output_buffer_count(tls);
count = pn_tls_take_decrypt_output_buffers(tls, bufs, 4);
REQUIRE(avail == count);
for (size_t i = 0; i < count; i++)
free_rbuf(bufs[i]);
take_count += count;
REQUIRE(take_count == give_count);
}