aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/tsi
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/tsi')
-rw-r--r--src/core/tsi/fake_transport_security.c515
-rw-r--r--src/core/tsi/fake_transport_security.h62
-rw-r--r--src/core/tsi/fake_transport_security_test.cc151
-rw-r--r--src/core/tsi/ssl_transport_security.c1294
-rw-r--r--src/core/tsi/ssl_transport_security.h159
-rw-r--r--src/core/tsi/ssl_transport_security_test.cc534
-rw-r--r--src/core/tsi/test_creds/README62
-rw-r--r--src/core/tsi/test_creds/badclient.key16
-rw-r--r--src/core/tsi/test_creds/badclient.pem17
-rw-r--r--src/core/tsi/test_creds/badserver.key16
-rw-r--r--src/core/tsi/test_creds/badserver.pem17
-rw-r--r--src/core/tsi/test_creds/ca-openssl.cnf17
-rw-r--r--src/core/tsi/test_creds/ca.key16
-rw-r--r--src/core/tsi/test_creds/ca.pem15
-rw-r--r--src/core/tsi/test_creds/client.key16
-rw-r--r--src/core/tsi/test_creds/client.pem14
-rw-r--r--src/core/tsi/test_creds/server0.key16
-rw-r--r--src/core/tsi/test_creds/server0.pem14
-rw-r--r--src/core/tsi/test_creds/server1-openssl.cnf26
-rw-r--r--src/core/tsi/test_creds/server1.key16
-rw-r--r--src/core/tsi/test_creds/server1.pem16
-rw-r--r--src/core/tsi/transport_security.c372
-rw-r--r--src/core/tsi/transport_security.h118
-rw-r--r--src/core/tsi/transport_security_interface.h389
-rw-r--r--src/core/tsi/transport_security_test_lib.cc363
-rw-r--r--src/core/tsi/transport_security_test_lib.h154
26 files changed, 4405 insertions, 0 deletions
diff --git a/src/core/tsi/fake_transport_security.c b/src/core/tsi/fake_transport_security.c
new file mode 100644
index 0000000000..7807e71949
--- /dev/null
+++ b/src/core/tsi/fake_transport_security.c
@@ -0,0 +1,515 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/tsi/fake_transport_security.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <grpc/support/log.h>
+#include "src/core/tsi/transport_security.h"
+
+/* --- Constants. ---*/
+#define TSI_FAKE_FRAME_HEADER_SIZE 4
+#define TSI_FAKE_FRAME_INITIAL_ALLOCATED_SIZE 64
+#define TSI_FAKE_DEFAULT_FRAME_SIZE 16384
+
+/* --- Structure definitions. ---*/
+
+/* a frame is encoded like this:
+ | size | data |
+ where the size field value is the size of the size field plus the size of
+ the data encoded in little endian on 4 bytes. */
+typedef struct {
+ unsigned char* data;
+ uint32_t size;
+ uint32_t allocated_size;
+ uint32_t offset;
+ int needs_draining;
+} tsi_fake_frame;
+
+typedef enum {
+ TSI_FAKE_CLIENT_INIT = 0,
+ TSI_FAKE_SERVER_INIT = 1,
+ TSI_FAKE_CLIENT_FINISHED = 2,
+ TSI_FAKE_SERVER_FINISHED = 3,
+ TSI_FAKE_HANDSHAKE_MESSAGE_MAX = 4
+} tsi_fake_handshake_message;
+
+typedef struct {
+ tsi_handshaker base;
+ int is_client;
+ tsi_fake_handshake_message next_message_to_send;
+ int needs_incoming_message;
+ tsi_fake_frame incoming;
+ tsi_fake_frame outgoing;
+ tsi_result result;
+} tsi_fake_handshaker;
+
+typedef struct {
+ tsi_frame_protector base;
+ tsi_fake_frame protect_frame;
+ tsi_fake_frame unprotect_frame;
+ uint32_t max_frame_size;
+} tsi_fake_frame_protector;
+
+
+/* --- Utils. ---*/
+
+static const char* tsi_fake_handshake_message_strings[] = {
+ "CLIENT_INIT", "SERVER_INIT", "CLIENT_FINISHED", "SERVER_FINISHED"};
+
+static const char* tsi_fake_handshake_message_to_string(int msg) {
+ if (msg < 0 || msg >= TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
+ gpr_log(GPR_ERROR, "Invalid message %d", msg);
+ return "UNKNOWN";
+ }
+ return tsi_fake_handshake_message_strings[msg];
+}
+
+static tsi_result tsi_fake_handshake_message_from_string(
+ const char* msg_string, tsi_fake_handshake_message* msg) {
+ int i;
+ for (i = 0; i < TSI_FAKE_HANDSHAKE_MESSAGE_MAX; i++) {
+ if (!strncmp(msg_string, tsi_fake_handshake_message_strings[i],
+ strlen(tsi_fake_handshake_message_strings[i]))) {
+ *msg = i;
+ return TSI_OK;
+ }
+ }
+ gpr_log(GPR_ERROR, "Invalid handshake message.");
+ return TSI_DATA_CORRUPTED;
+}
+
+static uint32_t load32_little_endian(const unsigned char* buf) {
+ return ((uint32_t)(buf[0]) | (uint32_t)(buf[1] << 8) |
+ (uint32_t)(buf[2] << 16) | (uint32_t)(buf[3] << 24));
+}
+
+static void store32_little_endian(uint32_t value, unsigned char* buf) {
+ buf[3] = (unsigned char)(value >> 24) & 0xFF;
+ buf[2] = (unsigned char)(value >> 16) & 0xFF;
+ buf[1] = (unsigned char)(value >> 8) & 0xFF;
+ buf[0] = (unsigned char)(value) & 0xFF;
+}
+
+static void tsi_fake_frame_reset(tsi_fake_frame* frame, int needs_draining) {
+ frame->offset = 0;
+ frame->needs_draining = needs_draining;
+ if (!needs_draining) frame->size = 0;
+}
+
+/* Returns 1 if successful, 0 otherwise. */
+static int tsi_fake_frame_ensure_size(tsi_fake_frame* frame) {
+ if (frame->data == NULL) {
+ frame->allocated_size = frame->size;
+ frame->data = malloc(frame->allocated_size);
+ if (frame->data == NULL) return 0;
+ } else if (frame->size > frame->allocated_size) {
+ unsigned char* new_data = realloc(frame->data, frame->size);
+ if (new_data == NULL) {
+ free(frame->data);
+ frame->data = NULL;
+ return 0;
+ }
+ frame->data = new_data;
+ frame->allocated_size = frame->size;
+ }
+ return 1;
+}
+
+/* This method should not be called if frame->needs_framing is not 0. */
+static tsi_result fill_frame_from_bytes(const unsigned char* incoming_bytes,
+ uint32_t* incoming_bytes_size,
+ tsi_fake_frame* frame) {
+ uint32_t available_size = *incoming_bytes_size;
+ uint32_t to_read_size = 0;
+ const unsigned char* bytes_cursor = incoming_bytes;
+
+ if (frame->needs_draining) return TSI_INTERNAL_ERROR;
+ if (frame->data == NULL) {
+ frame->allocated_size = TSI_FAKE_FRAME_INITIAL_ALLOCATED_SIZE;
+ frame->data = malloc(frame->allocated_size);
+ if (frame->data == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+
+ if (frame->offset < TSI_FAKE_FRAME_HEADER_SIZE) {
+ to_read_size = TSI_FAKE_FRAME_HEADER_SIZE - frame->offset;
+ if (to_read_size > available_size) {
+ /* Just fill what we can and exit. */
+ memcpy(frame->data + frame->offset, bytes_cursor, available_size);
+ bytes_cursor += available_size;
+ frame->offset += available_size;
+ *incoming_bytes_size = bytes_cursor - incoming_bytes;
+ return TSI_INCOMPLETE_DATA;
+ }
+ memcpy(frame->data + frame->offset, bytes_cursor, to_read_size);
+ bytes_cursor += to_read_size;
+ frame->offset += to_read_size;
+ available_size -= to_read_size;
+ frame->size = load32_little_endian(frame->data);
+ if (!tsi_fake_frame_ensure_size(frame)) return TSI_OUT_OF_RESOURCES;
+ }
+
+ to_read_size = frame->size - frame->offset;
+ if (to_read_size > available_size) {
+ memcpy(frame->data + frame->offset, bytes_cursor, available_size);
+ frame->offset += available_size;
+ bytes_cursor += available_size;
+ *incoming_bytes_size = bytes_cursor - incoming_bytes;
+ return TSI_INCOMPLETE_DATA;
+ }
+ memcpy(frame->data + frame->offset, bytes_cursor, to_read_size);
+ bytes_cursor += to_read_size;
+ *incoming_bytes_size = bytes_cursor - incoming_bytes;
+ tsi_fake_frame_reset(frame, 1 /* needs_draining */);
+ return TSI_OK;
+}
+
+/* This method should not be called if frame->needs_framing is 0. */
+static tsi_result drain_frame_to_bytes(unsigned char* outgoing_bytes,
+ uint32_t* outgoing_bytes_size,
+ tsi_fake_frame* frame) {
+ uint32_t to_write_size = frame->size - frame->offset;
+ if (!frame->needs_draining) return TSI_INTERNAL_ERROR;
+ if (*outgoing_bytes_size < to_write_size) {
+ memcpy(outgoing_bytes, frame->data + frame->offset, *outgoing_bytes_size);
+ frame->offset += *outgoing_bytes_size;
+ return TSI_INCOMPLETE_DATA;
+ }
+ memcpy(outgoing_bytes, frame->data + frame->offset, to_write_size);
+ *outgoing_bytes_size = to_write_size;
+ tsi_fake_frame_reset(frame, 0 /* needs_draining */);
+ return TSI_OK;
+}
+
+static tsi_result bytes_to_frame(unsigned char* bytes, uint32_t bytes_size,
+ tsi_fake_frame* frame) {
+ frame->offset = 0;
+ frame->size = bytes_size + TSI_FAKE_FRAME_HEADER_SIZE;
+ if (!tsi_fake_frame_ensure_size(frame)) return TSI_OUT_OF_RESOURCES;
+ store32_little_endian(frame->size, frame->data);
+ memcpy(frame->data + TSI_FAKE_FRAME_HEADER_SIZE, bytes, bytes_size);
+ tsi_fake_frame_reset(frame, 1 /* needs draining */);
+ return TSI_OK;
+}
+
+static void tsi_fake_frame_destruct(tsi_fake_frame* frame) {
+ if (frame->data != NULL) free(frame->data);
+}
+
+/* --- tsi_frame_protector methods implementation. ---*/
+
+static tsi_result fake_protector_protect(
+ tsi_frame_protector* self, const unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size, unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size) {
+ tsi_result result = TSI_OK;
+ tsi_fake_frame_protector* impl = (tsi_fake_frame_protector*)self;
+ unsigned char frame_header[TSI_FAKE_FRAME_HEADER_SIZE];
+ tsi_fake_frame* frame = &impl->protect_frame;
+ uint32_t saved_output_size = *protected_output_frames_size;
+ uint32_t drained_size = 0;
+ uint32_t* num_bytes_written = protected_output_frames_size;
+ *num_bytes_written = 0;
+
+ /* Try to drain first. */
+ if (frame->needs_draining) {
+ drained_size = saved_output_size - *num_bytes_written;
+ result = drain_frame_to_bytes(protected_output_frames,
+ &drained_size, frame);
+ *num_bytes_written += drained_size;
+ protected_output_frames += drained_size;
+ if (result != TSI_OK) {
+ if (result == TSI_INCOMPLETE_DATA) {
+ *unprotected_bytes_size = 0;
+ result = TSI_OK;
+ }
+ return result;
+ }
+ }
+
+ /* Now process the unprotected_bytes. */
+ if (frame->needs_draining) return TSI_INTERNAL_ERROR;
+ if (frame->size == 0) {
+ /* New frame, create a header. */
+ uint32_t written_in_frame_size = 0;
+ store32_little_endian(impl->max_frame_size, frame_header);
+ written_in_frame_size = TSI_FAKE_FRAME_HEADER_SIZE;
+ result = fill_frame_from_bytes(frame_header, &written_in_frame_size, frame);
+ if (result != TSI_INCOMPLETE_DATA) {
+ gpr_log(GPR_ERROR, "fill_frame_from_bytes returned %s",
+ tsi_result_to_string(result));
+ return result;
+ }
+ }
+ result = fill_frame_from_bytes(unprotected_bytes, unprotected_bytes_size,
+ frame);
+ if (result != TSI_OK) {
+ if (result == TSI_INCOMPLETE_DATA) result = TSI_OK;
+ return result;
+ }
+
+ /* Try to drain again. */
+ if (!frame->needs_draining) return TSI_INTERNAL_ERROR;
+ if (frame->offset != 0) return TSI_INTERNAL_ERROR;
+ drained_size = saved_output_size - *num_bytes_written;
+ result = drain_frame_to_bytes(protected_output_frames, &drained_size, frame);
+ *num_bytes_written += drained_size;
+ if (result == TSI_INCOMPLETE_DATA) result = TSI_OK;
+ return result;
+}
+
+static tsi_result fake_protector_protect_flush(
+ tsi_frame_protector* self, unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size, uint32_t* still_pending_size) {
+ tsi_result result = TSI_OK;
+ tsi_fake_frame_protector* impl = (tsi_fake_frame_protector*)self;
+ tsi_fake_frame* frame = &impl->protect_frame;
+ if (!frame->needs_draining) {
+ /* Create a short frame. */
+ frame->size = frame->offset;
+ frame->offset = 0;
+ frame->needs_draining = 1;
+ store32_little_endian(frame->size, frame->data); /* Overwrite header. */
+ }
+ result = drain_frame_to_bytes(protected_output_frames,
+ protected_output_frames_size, frame);
+ if (result == TSI_INCOMPLETE_DATA) result = TSI_OK;
+ *still_pending_size = frame->size - frame->offset;
+ return result;
+}
+
+static tsi_result fake_protector_unprotect(
+ tsi_frame_protector* self, const unsigned char* protected_frames_bytes,
+ uint32_t* protected_frames_bytes_size, unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size) {
+ tsi_result result = TSI_OK;
+ tsi_fake_frame_protector* impl = (tsi_fake_frame_protector*)self;
+ tsi_fake_frame* frame = &impl->unprotect_frame;
+ uint32_t saved_output_size = *unprotected_bytes_size;
+ uint32_t drained_size = 0;
+ uint32_t* num_bytes_written = unprotected_bytes_size;
+ *num_bytes_written = 0;
+
+ /* Try to drain first. */
+ if (frame->needs_draining) {
+ /* Go past the header if needed. */
+ if (frame->offset == 0) frame->offset = TSI_FAKE_FRAME_HEADER_SIZE;
+ drained_size = saved_output_size - *num_bytes_written;
+ result = drain_frame_to_bytes(unprotected_bytes, &drained_size,
+ frame);
+ unprotected_bytes += drained_size;
+ *num_bytes_written += drained_size;
+ if (result != TSI_OK) {
+ if (result == TSI_INCOMPLETE_DATA) {
+ *protected_frames_bytes_size = 0;
+ result = TSI_OK;
+ }
+ return result;
+ }
+ }
+
+ /* Now process the protected_bytes. */
+ if (frame->needs_draining) return TSI_INTERNAL_ERROR;
+ result = fill_frame_from_bytes(protected_frames_bytes,
+ protected_frames_bytes_size, frame);
+ if (result != TSI_OK) {
+ if (result == TSI_INCOMPLETE_DATA) result = TSI_OK;
+ return result;
+ }
+
+ /* Try to drain again. */
+ if (!frame->needs_draining) return TSI_INTERNAL_ERROR;
+ if (frame->offset != 0) return TSI_INTERNAL_ERROR;
+ frame->offset = TSI_FAKE_FRAME_HEADER_SIZE; /* Go past the header. */
+ drained_size = saved_output_size - *num_bytes_written;
+ result = drain_frame_to_bytes(unprotected_bytes, &drained_size, frame);
+ *num_bytes_written += drained_size;
+ if (result == TSI_INCOMPLETE_DATA) result = TSI_OK;
+ return result;
+}
+
+static void fake_protector_destroy(tsi_frame_protector* self) {
+ tsi_fake_frame_protector* impl = (tsi_fake_frame_protector*)self;
+ tsi_fake_frame_destruct(&impl->protect_frame);
+ tsi_fake_frame_destruct(&impl->unprotect_frame);
+ free(self);
+}
+
+static const tsi_frame_protector_vtable frame_protector_vtable = {
+ fake_protector_protect, fake_protector_protect_flush,
+ fake_protector_unprotect, fake_protector_destroy,
+};
+
+/* --- tsi_handshaker methods implementation. ---*/
+
+static tsi_result fake_handshaker_get_bytes_to_send_to_peer(
+ tsi_handshaker* self, unsigned char* bytes, uint32_t* bytes_size) {
+ tsi_fake_handshaker* impl = (tsi_fake_handshaker*)self;
+ tsi_result result = TSI_OK;
+ if (impl->needs_incoming_message || impl->result == TSI_OK) {
+ *bytes_size = 0;
+ return TSI_OK;
+ }
+ if (!impl->outgoing.needs_draining) {
+ int next_message_to_send = impl->next_message_to_send + 2;
+ const char* msg_string =
+ tsi_fake_handshake_message_to_string(impl->next_message_to_send);
+ result = bytes_to_frame((unsigned char*)msg_string, strlen(msg_string),
+ &impl->outgoing);
+ if (result != TSI_OK) return result;
+ if (next_message_to_send > TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
+ next_message_to_send = TSI_FAKE_HANDSHAKE_MESSAGE_MAX;
+ }
+ gpr_log(GPR_INFO, "%s prepared %s.", impl->is_client ? "Client" : "Server",
+ tsi_fake_handshake_message_to_string(impl->next_message_to_send));
+ impl->next_message_to_send = next_message_to_send;
+ }
+ result = drain_frame_to_bytes(bytes, bytes_size, &impl->outgoing);
+ if (result != TSI_OK) return result;
+ if (!impl->is_client &&
+ impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
+ /* We're done. */
+ gpr_log(GPR_INFO, "Server is done.");
+ impl->result = TSI_OK;
+ } else {
+ impl->needs_incoming_message = 1;
+ }
+ return TSI_OK;
+}
+
+static tsi_result fake_handshaker_process_bytes_from_peer(
+ tsi_handshaker* self, const unsigned char* bytes, uint32_t* bytes_size) {
+ tsi_result result = TSI_OK;
+ tsi_fake_handshaker* impl = (tsi_fake_handshaker*)self;
+ int expected_msg = impl->next_message_to_send - 1;
+ tsi_fake_handshake_message received_msg;
+
+ if (!impl->needs_incoming_message || impl->result == TSI_OK) {
+ *bytes_size = 0;
+ return TSI_OK;
+ }
+ result = fill_frame_from_bytes(bytes, bytes_size, &impl->incoming);
+ if (result != TSI_OK) return result;
+
+ /* We now have a complete frame. */
+ result = tsi_fake_handshake_message_from_string(
+ (const char*)impl->incoming.data + TSI_FAKE_FRAME_HEADER_SIZE,
+ &received_msg);
+ if (result != TSI_OK) {
+ impl->result = result;
+ return result;
+ }
+ if (received_msg != expected_msg) {
+ gpr_log(GPR_ERROR, "Invalid received message (%s instead of %s)",
+ tsi_fake_handshake_message_to_string(received_msg),
+ tsi_fake_handshake_message_to_string(expected_msg));
+ }
+ gpr_log(GPR_INFO, "%s received %s.", impl->is_client ? "Client" : "Server",
+ tsi_fake_handshake_message_to_string(received_msg));
+ tsi_fake_frame_reset(&impl->incoming, 0 /* needs_draining */);
+ impl->needs_incoming_message = 0;
+ if (impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) {
+ /* We're done. */
+ gpr_log(GPR_INFO, "%s is done.", impl->is_client ? "Client" : "Server");
+ impl->result = TSI_OK;
+ }
+ return TSI_OK;
+}
+
+static tsi_result fake_handshaker_get_result(tsi_handshaker* self) {
+ tsi_fake_handshaker* impl = (tsi_fake_handshaker*)self;
+ return impl->result;
+}
+
+static tsi_result fake_handshaker_extract_peer(tsi_handshaker* self,
+ tsi_peer* peer) {
+ tsi_result result = tsi_construct_peer(1, peer);
+ if (result != TSI_OK) return result;
+ result = tsi_construct_string_peer_property_from_cstring(
+ TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_FAKE_CERTIFICATE_TYPE,
+ &peer->properties[0]);
+ if (result != TSI_OK) tsi_peer_destruct(peer);
+ return result;
+}
+
+static tsi_result fake_handshaker_create_frame_protector(
+ tsi_handshaker* self, uint32_t* max_protected_frame_size,
+ tsi_frame_protector** protector) {
+ *protector = tsi_create_fake_protector(max_protected_frame_size);
+ if (*protector == NULL) return TSI_OUT_OF_RESOURCES;
+ return TSI_OK;
+}
+
+static void fake_handshaker_destroy(tsi_handshaker* self) {
+ tsi_fake_handshaker* impl = (tsi_fake_handshaker*)self;
+ tsi_fake_frame_destruct(&impl->incoming);
+ tsi_fake_frame_destruct(&impl->outgoing);
+ free(self);
+}
+
+static const tsi_handshaker_vtable handshaker_vtable = {
+ fake_handshaker_get_bytes_to_send_to_peer,
+ fake_handshaker_process_bytes_from_peer,
+ fake_handshaker_get_result,
+ fake_handshaker_extract_peer,
+ fake_handshaker_create_frame_protector,
+ fake_handshaker_destroy,
+};
+
+tsi_handshaker* tsi_create_fake_handshaker(int is_client) {
+ tsi_fake_handshaker* impl = calloc(1, sizeof(tsi_fake_handshaker));
+ impl->base.vtable = &handshaker_vtable;
+ impl->is_client = is_client;
+ impl->result = TSI_HANDSHAKE_IN_PROGRESS;
+ if (is_client) {
+ impl->needs_incoming_message = 0;
+ impl->next_message_to_send = TSI_FAKE_CLIENT_INIT;
+ } else {
+ impl->needs_incoming_message = 1;
+ impl->next_message_to_send = TSI_FAKE_SERVER_INIT;
+ }
+ return &impl->base;
+}
+
+tsi_frame_protector* tsi_create_fake_protector(
+ uint32_t* max_protected_frame_size) {
+ tsi_fake_frame_protector* impl = calloc(1, sizeof(tsi_fake_frame_protector));
+ if (impl == NULL) return NULL;
+ impl->max_frame_size = (max_protected_frame_size == NULL)
+ ? TSI_FAKE_DEFAULT_FRAME_SIZE
+ : *max_protected_frame_size;
+ impl->base.vtable = &frame_protector_vtable;
+ return &impl->base;
+}
diff --git a/src/core/tsi/fake_transport_security.h b/src/core/tsi/fake_transport_security.h
new file mode 100644
index 0000000000..075d51871b
--- /dev/null
+++ b/src/core/tsi/fake_transport_security.h
@@ -0,0 +1,62 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __FAKE_TRANSPORT_SECURITY_H_
+#define __FAKE_TRANSPORT_SECURITY_H_
+
+#include "src/core/tsi/transport_security_interface.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Value for the TSI_CERTIFICATE_TYPE_PEER_PROPERTY property for FAKE certs. */
+#define TSI_FAKE_CERTIFICATE_TYPE "FAKE"
+
+/* Creates a fake handshaker that will create a fake frame protector.
+
+ No cryptography is performed in these objects. They just simulate handshake
+ messages going back and forth for the handshaker and do some framing on
+ cleartext data for the protector. */
+tsi_handshaker* tsi_create_fake_handshaker(int is_client);
+
+
+/* Creates a protector directly without going through the handshake phase. */
+tsi_frame_protector* tsi_create_fake_protector(
+ uint32_t* max_protected_frame_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __FAKE_TRANSPORT_SECURITY_H_ */
diff --git a/src/core/tsi/fake_transport_security_test.cc b/src/core/tsi/fake_transport_security_test.cc
new file mode 100644
index 0000000000..0ae88e0c9a
--- /dev/null
+++ b/src/core/tsi/fake_transport_security_test.cc
@@ -0,0 +1,151 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/tsi/fake_transport_security.h"
+
+#include "src/core/tsi/transport_security_test_lib.h"
+#include <gtest/gtest.h>
+#include "util/random/permute-inl.h"
+
+namespace {
+
+void CheckStringPeerProperty(const tsi_peer& peer, int property_index,
+ const char* expected_name,
+ const char* expected_value) {
+ EXPECT_LT(property_index, peer.property_count);
+ const tsi_peer_property* property = &peer.properties[property_index];
+ EXPECT_EQ(TSI_PEER_PROPERTY_TYPE_STRING, property->type);
+ EXPECT_EQ(string(expected_name), string(property->name));
+ EXPECT_EQ(string(expected_value),
+ string(property->value.string.data, property->value.string.length));
+}
+
+class FakeTransportSecurityTest : public tsi::test::TransportSecurityTest {
+ protected:
+ void SetupHandshakers() override {
+ client_handshaker_.reset(tsi_create_fake_handshaker(1));
+ server_handshaker_.reset(tsi_create_fake_handshaker(0));
+ }
+
+ void CheckPeer(tsi_handshaker* handshaker) {
+ tsi_peer peer;
+ EXPECT_EQ(TSI_OK, tsi_handshaker_extract_peer(handshaker, &peer));
+ EXPECT_EQ(1, peer.property_count);
+ CheckStringPeerProperty(peer, 0, TSI_CERTIFICATE_TYPE_PEER_PROPERTY,
+ TSI_FAKE_CERTIFICATE_TYPE);
+ tsi_peer_destruct(&peer);
+ }
+
+ void CheckHandshakeResults() override {
+ CheckPeer(client_handshaker_.get());
+ CheckPeer(server_handshaker_.get());
+ }
+
+ const tsi::test::TestConfig* config() {
+ return &config_;
+ }
+
+ tsi::test::TestConfig config_;
+};
+
+TEST_F(FakeTransportSecurityTest, Handshake) {
+ PerformHandshake();
+}
+
+TEST_F(FakeTransportSecurityTest, HandshakeSmallBuffer) {
+ config_.handshake_buffer_size = 3;
+ PerformHandshake();
+}
+TEST_F(FakeTransportSecurityTest, PingPong) {
+ PingPong();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTrip) {
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripSmallMessageBuffer) {
+ config_.message_buffer_allocated_size = 42;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripSmallProtectedBufferSize) {
+ config_.protected_buffer_size = 37;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripSmallReadBufferSize) {
+ config_.read_buffer_allocated_size = 41;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripSmallClientFrames) {
+ config_.set_client_max_output_protected_frame_size(39);
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripSmallServerFrames) {
+ config_.set_server_max_output_protected_frame_size(43);
+ config_.client_message = small_message_;
+ config_.server_message = big_message_;
+ DoRoundTrip();
+}
+
+TEST_F(FakeTransportSecurityTest, RoundTripOddBufferSizes) {
+ int odd_sizes[] = {33, 67, 135, 271, 523};
+ RandomPermutation<int> permute(odd_sizes, arraysize(odd_sizes),
+ random_.get());
+ permute.Permute();
+ LOG(ERROR) << odd_sizes[0] << "\t" << odd_sizes[1] << "\t" << odd_sizes[2]
+ << "\t" << odd_sizes[3] << "\t" << odd_sizes[4];
+ config_.message_buffer_allocated_size = odd_sizes[0];
+ config_.protected_buffer_size = odd_sizes[1];
+ config_.read_buffer_allocated_size = odd_sizes[2];
+ config_.set_client_max_output_protected_frame_size(odd_sizes[3]);
+ config_.set_server_max_output_protected_frame_size(odd_sizes[4]);
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+} // namespace
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
new file mode 100644
index 0000000000..7bd178b372
--- /dev/null
+++ b/src/core/tsi/ssl_transport_security.c
@@ -0,0 +1,1294 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/tsi/ssl_transport_security.h"
+
+#include <limits.h>
+#include <pthread.h>
+
+#include <grpc/support/log.h>
+#include "src/core/tsi/transport_security.h"
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+/* --- Constants. ---*/
+
+#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND 16384
+#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND 1024
+
+/* TODO(jboeuf): I have not found a way to get this number dynamically from the
+ * SSL structure. This is what we would ultimately want though... */
+#define TSI_SSL_MAX_PROTECTION_OVERHEAD 100
+
+
+/* --- Structure definitions. ---*/
+
+struct tsi_ssl_handshaker_factory {
+ tsi_result (*create_handshaker)(tsi_ssl_handshaker_factory* self,
+ const char* server_name_indication,
+ tsi_handshaker** handshaker);
+ void (*destroy)(tsi_ssl_handshaker_factory* self);
+};
+
+typedef struct {
+ tsi_ssl_handshaker_factory base;
+ SSL_CTX* ssl_context;
+} tsi_ssl_client_handshaker_factory;
+
+typedef struct {
+ tsi_ssl_handshaker_factory base;
+
+ /* Several contexts to support SNI.
+ The tsi_peer array contains the subject names of the server certificates
+ associated with the contexts at the same index. */
+ SSL_CTX** ssl_contexts;
+ tsi_peer* ssl_context_x509_subject_names;
+ uint32_t ssl_context_count;
+ unsigned char* alpn_protocol_list;
+ uint32_t alpn_protocol_list_length;
+} tsi_ssl_server_handshaker_factory;
+
+typedef struct {
+ tsi_handshaker base;
+ SSL* ssl;
+ BIO* into_ssl;
+ BIO* from_ssl;
+ tsi_result result;
+} tsi_ssl_handshaker;
+
+typedef struct {
+ tsi_frame_protector base;
+ SSL* ssl;
+ BIO* into_ssl;
+ BIO* from_ssl;
+ unsigned char* buffer;
+ uint32_t buffer_size;
+ uint32_t buffer_offset;
+} tsi_ssl_frame_protector;
+
+
+/* --- Library Initialization. ---*/
+
+static pthread_once_t init_openssl_once = PTHREAD_ONCE_INIT;
+
+static void init_openssl(void) {
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+}
+
+/* --- Ssl utils. ---*/
+
+static const char* ssl_error_string(int error) {
+ switch (error) {
+ case SSL_ERROR_NONE:
+ return "SSL_ERROR_NONE";
+ case SSL_ERROR_ZERO_RETURN:
+ return "SSL_ERROR_ZERO_RETURN";
+ case SSL_ERROR_WANT_READ:
+ return "SSL_ERROR_WANT_READ";
+ case SSL_ERROR_WANT_WRITE:
+ return "SSL_ERROR_WANT_WRITE";
+ case SSL_ERROR_WANT_CONNECT:
+ return "SSL_ERROR_WANT_CONNECT";
+ case SSL_ERROR_WANT_ACCEPT:
+ return "SSL_ERROR_WANT_ACCEPT";
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ return "SSL_ERROR_WANT_X509_LOOKUP";
+ case SSL_ERROR_SYSCALL:
+ return "SSL_ERROR_SYSCALL";
+ case SSL_ERROR_SSL:
+ return "SSL_ERROR_SSL";
+ default:
+ return "Unknown error";
+ }
+}
+
+/* TODO(jboeuf): Remove when we are past the debugging phase with this code. */
+static void ssl_log_where_info(const SSL* ssl, int where, int flag,
+ const char* msg) {
+ if (where & flag) {
+ gpr_log(GPR_INFO, "%20.20s - %30.30s - %5.10s", msg,
+ SSL_state_string_long(ssl), SSL_state_string(ssl));
+ }
+}
+
+/* Used for debugging. TODO(jboeuf): Remove when code is mature enough. */
+static void ssl_info_callback(const SSL* ssl, int where, int ret) {
+ if (ret == 0) {
+ gpr_log(GPR_ERROR, "ssl_info_callback: error occured.\n");
+ return;
+ }
+
+ ssl_log_where_info(ssl, where, SSL_CB_LOOP, "LOOP");
+ ssl_log_where_info(ssl, where, SSL_CB_HANDSHAKE_START, "HANDSHAKE START");
+ ssl_log_where_info(ssl, where, SSL_CB_HANDSHAKE_DONE, "HANDSHAKE DONE");
+}
+
+/* Gets the subject CN from an X509 cert. */
+static tsi_result ssl_get_x509_common_name(X509* cert, unsigned char** utf8,
+ uint32_t* utf8_size) {
+ int common_name_index = -1;
+ X509_NAME_ENTRY* common_name_entry = NULL;
+ ASN1_STRING* common_name_asn1 = NULL;
+ X509_NAME* subject_name = X509_get_subject_name(cert);
+ int utf8_returned_size = 0;
+ if (subject_name == NULL) {
+ gpr_log(GPR_ERROR, "Could not get subject name from certificate.");
+ return TSI_NOT_FOUND;
+ }
+ common_name_index =
+ X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
+ if (common_name_index == -1) {
+ gpr_log(GPR_ERROR,
+ "Could not get common name of subject from certificate.");
+ return TSI_NOT_FOUND;
+ }
+ common_name_entry = X509_NAME_get_entry(subject_name, common_name_index);
+ if (common_name_entry == NULL) {
+ gpr_log(GPR_ERROR, "Could not get common name entry from certificate.");
+ return TSI_INTERNAL_ERROR;
+ }
+ common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
+ if (common_name_asn1 == NULL) {
+ gpr_log(GPR_ERROR,
+ "Could not get common name entry asn1 from certificate.");
+ return TSI_INTERNAL_ERROR;
+ }
+ utf8_returned_size = ASN1_STRING_to_UTF8(utf8, common_name_asn1);
+ if (utf8_returned_size < 0) {
+ gpr_log(GPR_ERROR, "Could not extract utf8 from asn1 string.");
+ return TSI_OUT_OF_RESOURCES;
+ }
+ *utf8_size = utf8_returned_size;
+ return TSI_OK;
+}
+
+/* Gets the subject CN of an X509 cert as a tsi_peer_property. */
+static tsi_result peer_property_from_x509_common_name(
+ X509* cert, tsi_peer_property* property) {
+ unsigned char* common_name;
+ uint32_t common_name_size;
+ tsi_result result =
+ ssl_get_x509_common_name(cert, &common_name, &common_name_size);
+ if (result != TSI_OK) return result;
+ result = tsi_construct_string_peer_property(
+ TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY, (const char*)common_name,
+ common_name_size, property);
+ OPENSSL_free(common_name);
+ return result;
+}
+
+/* Gets the subject SANs from an X509 cert as a tsi_peer_property. */
+static tsi_result peer_property_from_x509_subject_alt_names(
+ X509* cert, tsi_peer_property* property) {
+ int i = 0;
+ int subject_alt_name_count = 0;
+ tsi_result result = TSI_OK;
+ GENERAL_NAMES* subject_alt_names =
+ X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+ if (subject_alt_names == NULL) {
+ /* Empty list. */
+ return tsi_construct_list_peer_property(
+ TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY, 0, property);
+ }
+
+ subject_alt_name_count = sk_GENERAL_NAME_num(subject_alt_names);
+ result = tsi_construct_list_peer_property(
+ TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY, subject_alt_name_count,
+ property);
+ if (result != TSI_OK) return result;
+
+ /* Reset for DNS entries filtering. */
+ subject_alt_name_count = property->value.list.child_count;
+ property->value.list.child_count = 0;
+
+ for (i = 0; i < subject_alt_name_count; i++) {
+ tsi_peer_property* child_property = NULL;
+ GENERAL_NAME* subject_alt_name =
+ sk_GENERAL_NAME_value(subject_alt_names, i);
+ /* Filter out the non-dns entries names. */
+ if (subject_alt_name->type == GEN_DNS) {
+ unsigned char* dns_name = NULL;
+ int dns_name_size =
+ ASN1_STRING_to_UTF8(&dns_name, subject_alt_name->d.dNSName);
+ if (dns_name_size < 0) {
+ gpr_log(GPR_ERROR, "Could not get utf8 from asn1 string.");
+ result = TSI_INTERNAL_ERROR;
+ break;
+ }
+ child_property =
+ &property->value.list.children[property->value.list.child_count++];
+ result = tsi_construct_string_peer_property(
+ NULL, (const char*)dns_name, dns_name_size, child_property);
+ OPENSSL_free(dns_name);
+ if (result != TSI_OK) break;
+ }
+ }
+ if (result != TSI_OK) tsi_peer_property_destruct(property);
+ sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
+ return TSI_OK;
+}
+
+/* Gets information about the peer's X509 cert as a tsi_peer object. */
+static tsi_result peer_from_x509(X509* cert, int include_certificate_type,
+ tsi_peer* peer) {
+ /* TODO(jboeuf): Maybe add more properties. */
+ uint32_t property_count = include_certificate_type ? 3 : 2;
+ tsi_result result = tsi_construct_peer(property_count, peer);
+ if (result != TSI_OK) return result;
+ do {
+ result = peer_property_from_x509_common_name(cert, &peer->properties[0]);
+ if (result != TSI_OK) break;
+ result =
+ peer_property_from_x509_subject_alt_names(cert, &peer->properties[1]);
+ if (result != TSI_OK) break;
+ if (include_certificate_type) {
+ result = tsi_construct_string_peer_property_from_cstring(
+ TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_X509_CERTIFICATE_TYPE,
+ &peer->properties[2]);
+ if (result != TSI_OK) break;
+ }
+ } while (0);
+
+ if (result != TSI_OK) tsi_peer_destruct(peer);
+ return result;
+}
+
+/* Performs an SSL_read and handle errors. */
+static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size) {
+ int read_from_ssl = SSL_read(ssl, unprotected_bytes,
+ *unprotected_bytes_size);
+ if (read_from_ssl == 0) {
+ gpr_log(GPR_ERROR, "SSL_read returned 0 unexpectedly.");
+ return TSI_INTERNAL_ERROR;
+ }
+ if (read_from_ssl < 0) {
+ read_from_ssl = SSL_get_error(ssl, read_from_ssl);
+ switch (read_from_ssl) {
+ case SSL_ERROR_WANT_READ:
+ /* We need more data to finish the frame. */
+ *unprotected_bytes_size = 0;
+ return TSI_OK;
+ case SSL_ERROR_WANT_WRITE:
+ gpr_log(
+ GPR_ERROR,
+ "Peer tried to renegotiate SSL connection. This is unsupported.");
+ return TSI_UNIMPLEMENTED;
+ case SSL_ERROR_SSL:
+ gpr_log(GPR_ERROR, "Corruption detected.");
+ return TSI_DATA_CORRUPTED;
+ default:
+ gpr_log(GPR_ERROR, "SSL_read failed with error %s.",
+ ssl_error_string(read_from_ssl));
+ return TSI_PROTOCOL_FAILURE;
+ }
+ }
+ *unprotected_bytes_size = read_from_ssl;
+ return TSI_OK;
+}
+
+/* Performs an SSL_write and handle errors. */
+static tsi_result do_ssl_write(SSL* ssl, unsigned char* unprotected_bytes,
+ uint32_t unprotected_bytes_size) {
+ int ssl_write_result =
+ SSL_write(ssl, unprotected_bytes, unprotected_bytes_size);
+ if (ssl_write_result < 0) {
+ ssl_write_result = SSL_get_error(ssl, ssl_write_result);
+ if (ssl_write_result == SSL_ERROR_WANT_READ) {
+ gpr_log(GPR_ERROR,
+ "Peer tried to renegotiate SSL connection. This is unsupported.");
+ return TSI_UNIMPLEMENTED;
+ } else {
+ gpr_log(GPR_ERROR, "SSL_write failed with error %s.",
+ ssl_error_string(ssl_write_result));
+ return TSI_INTERNAL_ERROR;
+ }
+ }
+ return TSI_OK;
+}
+
+/* Loads an in-memory PEM certificate chain into the SSL context. */
+static tsi_result ssl_ctx_use_certificate_chain(
+ SSL_CTX* context, const unsigned char* pem_cert_chain,
+ uint32_t pem_cert_chain_size) {
+ tsi_result result = TSI_OK;
+ X509* certificate = NULL;
+ BIO* pem = BIO_new_mem_buf((void*)pem_cert_chain, pem_cert_chain_size);
+ if (pem == NULL) return TSI_OUT_OF_RESOURCES;
+
+ do {
+ certificate = PEM_read_bio_X509_AUX(pem, NULL, NULL, "");
+ if (certificate == NULL) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ if (!SSL_CTX_use_certificate(context, certificate)) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ while (1) {
+ X509* certificate_authority = PEM_read_bio_X509(pem, NULL, NULL, "");
+ if (certificate_authority == NULL) break; /* Done reading. */
+ if (!SSL_CTX_add_extra_chain_cert(context, certificate_authority)) {
+ X509_free(certificate_authority);
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ /* We don't need to free certificate_authority as its ownership has been
+ transfered to the context. That is not the case for certificate though.
+ */
+ }
+ } while (0);
+
+ if (certificate != NULL) X509_free(certificate);
+ BIO_free(pem);
+ return result;
+}
+
+/* Loads an in-memory PEM private key into the SSL context. */
+static tsi_result ssl_ctx_use_private_key(SSL_CTX* context,
+ const unsigned char* pem_key,
+ uint32_t pem_key_size) {
+ tsi_result result = TSI_OK;
+ EVP_PKEY* private_key = NULL;
+ BIO* pem = BIO_new_mem_buf((void*)pem_key, pem_key_size);
+ if (pem == NULL) return TSI_OUT_OF_RESOURCES;
+ do {
+ private_key = PEM_read_bio_PrivateKey(pem, NULL, NULL, "");
+ if (private_key == NULL) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ if (!SSL_CTX_use_PrivateKey(context, private_key)) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ } while (0);
+ if (private_key != NULL) EVP_PKEY_free(private_key);
+ BIO_free(pem);
+ return result;
+}
+
+/* Loads in-memory PEM verification certs into the SSL context and optionally
+ returns the verification cert names (root_names can be NULL). */
+static tsi_result ssl_ctx_load_verification_certs(
+ SSL_CTX* context, const unsigned char* pem_roots,
+ uint32_t pem_roots_size, STACK_OF(X509_NAME)** root_names) {
+ tsi_result result = TSI_OK;
+ uint32_t num_roots = 0;
+ X509* root = NULL;
+ X509_NAME* root_name = NULL;
+ BIO* pem = BIO_new_mem_buf((void*)pem_roots, pem_roots_size);
+ X509_STORE* root_store = SSL_CTX_get_cert_store(context);
+ if (root_store == NULL) return TSI_INVALID_ARGUMENT;
+ if (pem == NULL) return TSI_OUT_OF_RESOURCES;
+ if (root_names != NULL) {
+ *root_names = sk_X509_NAME_new_null();
+ if (*root_names == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+
+ while (1) {
+ root = PEM_read_bio_X509_AUX(pem, NULL, NULL, "");
+ if (root == NULL) break; /* We're at the end of stream. */
+ if (root_names != NULL) {
+ root_name = X509_get_subject_name(root);
+ if (root_name == NULL) {
+ gpr_log(GPR_ERROR, "Could not get name from root certificate.");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ root_name = X509_NAME_dup(root_name);
+ if (root_name == NULL) {
+ result = TSI_OUT_OF_RESOURCES;
+ break;
+ }
+ sk_X509_NAME_push(*root_names, root_name);
+ root_name = NULL;
+ }
+ if (!X509_STORE_add_cert(root_store, root)) {
+ gpr_log(GPR_ERROR, "Could not add root certificate to ssl context.");
+ result = TSI_INTERNAL_ERROR;
+ break;
+ }
+ X509_free(root);
+ num_roots++;
+ }
+
+ if (num_roots == 0) {
+ gpr_log(GPR_ERROR, "Could not load any root certificate.");
+ result = TSI_INVALID_ARGUMENT;
+ }
+
+ if (result != TSI_OK) {
+ if (root != NULL) X509_free(root);
+ if (root_names != NULL) {
+ sk_X509_NAME_pop_free(*root_names, X509_NAME_free);
+ *root_names = NULL;
+ if (root_name != NULL) X509_NAME_free(root_name);
+ }
+ }
+ BIO_free(pem);
+ return result;
+}
+
+
+/* Populates the SSL context with a private key and a cert chain, and sets the
+ cipher list and the ephemeral ECDH key. */
+static tsi_result populate_ssl_context(
+ SSL_CTX* context, const unsigned char* pem_private_key,
+ uint32_t pem_private_key_size,
+ const unsigned char* pem_certificate_chain,
+ uint32_t pem_certificate_chain_size, const char* cipher_list) {
+ tsi_result result = TSI_OK;
+ if (pem_certificate_chain != NULL) {
+ result = ssl_ctx_use_certificate_chain(context, pem_certificate_chain,
+ pem_certificate_chain_size);
+ if (result != TSI_OK) {
+ gpr_log(GPR_ERROR, "Invalid cert chain file.");
+ return result;
+ }
+ }
+ if (pem_private_key != NULL) {
+ result =
+ ssl_ctx_use_private_key(context, pem_private_key, pem_private_key_size);
+ if (result != TSI_OK || !SSL_CTX_check_private_key(context)) {
+ gpr_log(GPR_ERROR, "Invalid private key.");
+ return result != TSI_OK ? result : TSI_INVALID_ARGUMENT;
+ }
+ }
+ if ((cipher_list != NULL) && !SSL_CTX_set_cipher_list(context, cipher_list)) {
+ gpr_log(GPR_ERROR, "Invalid cipher list: %s.", cipher_list);
+ return TSI_INVALID_ARGUMENT;
+ }
+ {
+ EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ if (!SSL_CTX_set_tmp_ecdh(context, ecdh)) {
+ gpr_log(GPR_ERROR, "Could not set ephemeral ECDH key.");
+ result = TSI_INTERNAL_ERROR;
+ }
+ SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
+ EC_KEY_free(ecdh);
+ }
+ return TSI_OK;
+}
+
+/* Extracts the CN and the SANs from an X509 cert as a peer object. */
+static tsi_result extract_x509_subject_names_from_pem_cert(
+ const unsigned char* pem_cert, uint32_t pem_cert_size, tsi_peer* peer) {
+ tsi_result result = TSI_OK;
+ X509* cert = NULL;
+ BIO* pem = BIO_new_mem_buf((void*)pem_cert, pem_cert_size);
+ if (pem == NULL) return TSI_OUT_OF_RESOURCES;
+
+ cert = PEM_read_bio_X509(pem, NULL, NULL, "");
+ if (cert == NULL) {
+ gpr_log(GPR_ERROR, "Invalid certificate");
+ result = TSI_INVALID_ARGUMENT;
+ } else {
+ result = peer_from_x509(cert, 0, peer);
+ }
+ if (cert != NULL) X509_free(cert);
+ BIO_free(pem);
+ return result;
+}
+
+/* Builds the alpn protocol name list according to rfc 7301. */
+static tsi_result build_alpn_protocol_name_list(
+ const unsigned char** alpn_protocols,
+ const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols,
+ unsigned char** protocol_name_list, uint32_t* protocol_name_list_length) {
+ uint16_t i;
+ unsigned char* current;
+ *protocol_name_list = NULL;
+ *protocol_name_list_length = 0;
+ for (i = 0; i < num_alpn_protocols; i++) {
+ if (alpn_protocols_lengths[i] == 0) {
+ gpr_log(GPR_ERROR, "Invalid 0-length protocol name.");
+ return TSI_INVALID_ARGUMENT;
+ }
+ *protocol_name_list_length += alpn_protocols_lengths[i] + 1;
+ }
+ *protocol_name_list = malloc(*protocol_name_list_length);
+ if (*protocol_name_list == NULL) return TSI_OUT_OF_RESOURCES;
+ current = *protocol_name_list;
+ for (i = 0; i < num_alpn_protocols; i++) {
+ *(current++) = alpn_protocols_lengths[i];
+ memcpy(current, alpn_protocols[i], alpn_protocols_lengths[i]);
+ current += alpn_protocols_lengths[i];
+ }
+ /* Safety check. */
+ if ((current - *protocol_name_list) != *protocol_name_list_length) {
+ return TSI_INTERNAL_ERROR;
+ }
+ return TSI_OK;
+}
+
+/* --- tsi_frame_protector methods implementation. ---*/
+
+static tsi_result ssl_protector_protect(
+ tsi_frame_protector* self, const unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size) {
+ tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self;
+ int read_from_ssl;
+ uint32_t available;
+ tsi_result result = TSI_OK;
+
+ /* First see if we have some pending data in the SSL BIO. */
+ uint32_t pending_in_ssl = BIO_ctrl_pending(impl->from_ssl);
+ if (pending_in_ssl > 0) {
+ *unprotected_bytes_size = 0;
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ *protected_output_frames_size);
+ if (read_from_ssl < 0) {
+ gpr_log(GPR_ERROR,
+ "Could not read from BIO even though some data is pending");
+ return TSI_INTERNAL_ERROR;
+ }
+ *protected_output_frames_size = read_from_ssl;
+ return TSI_OK;
+ }
+
+ /* Now see if we can send a complete frame. */
+ available = impl->buffer_size - impl->buffer_offset;
+ if (available > *unprotected_bytes_size) {
+ /* If we cannot, just copy the data in our internal buffer. */
+ memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes,
+ *unprotected_bytes_size);
+ impl->buffer_offset += *unprotected_bytes_size;
+ *protected_output_frames_size = 0;
+ return TSI_OK;
+ }
+
+ /* If we can, prepare the buffer, send it to SSL_write and read. */
+ memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes, available);
+ result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_size);
+ if (result != TSI_OK) return result;
+
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ *protected_output_frames_size);
+ if (read_from_ssl < 0) {
+ gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write.");
+ return TSI_INTERNAL_ERROR;
+ }
+ *protected_output_frames_size = read_from_ssl;
+ *unprotected_bytes_size = available;
+ impl->buffer_offset = 0;
+ return TSI_OK;
+}
+
+static tsi_result ssl_protector_protect_flush(
+ tsi_frame_protector* self, unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size,
+ uint32_t* still_pending_size) {
+ tsi_result result = TSI_OK;
+ tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self;
+ int read_from_ssl = 0;
+
+ if (impl->buffer_offset != 0) {
+ result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_offset);
+ if (result != TSI_OK) return result;
+ impl->buffer_offset = 0;
+ }
+
+ *still_pending_size = BIO_ctrl_pending(impl->from_ssl);
+ if (*still_pending_size == 0) return TSI_OK;
+
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ *protected_output_frames_size);
+ if (read_from_ssl <= 0) {
+ gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write.");
+ return TSI_INTERNAL_ERROR;
+ }
+ *protected_output_frames_size = read_from_ssl;
+ *still_pending_size = BIO_ctrl_pending(impl->from_ssl);
+ return TSI_OK;
+}
+
+static tsi_result ssl_protector_unprotect(
+ tsi_frame_protector* self, const unsigned char* protected_frames_bytes,
+ uint32_t* protected_frames_bytes_size,
+ unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size) {
+ tsi_result result = TSI_OK;
+ int written_into_ssl = 0;
+ uint32_t output_bytes_size = *unprotected_bytes_size;
+ uint32_t output_bytes_offset = 0;
+ tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self;
+
+ /* First, try to read remaining data from ssl. */
+ result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size);
+ if (result != TSI_OK) return result;
+ if (*unprotected_bytes_size == output_bytes_size) {
+ /* We have read everything we could and cannot process any more input. */
+ *protected_frames_bytes_size = 0;
+ return TSI_OK;
+ }
+ output_bytes_offset = *unprotected_bytes_size;
+ unprotected_bytes += output_bytes_offset;
+ *unprotected_bytes_size = output_bytes_size - output_bytes_offset;
+
+ /* Then, try to write some data to ssl. */
+ written_into_ssl = BIO_write(
+ impl->into_ssl, protected_frames_bytes, *protected_frames_bytes_size);
+ if (written_into_ssl < 0) {
+ gpr_log(GPR_ERROR, "Sending protected frame to ssl failed with %d",
+ written_into_ssl);
+ return TSI_INTERNAL_ERROR;
+ }
+ *protected_frames_bytes_size = written_into_ssl;
+
+ /* Now try to read some data again. */
+ result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size);
+ if (result == TSI_OK) {
+ /* Don't forget to output the total number of bytes read. */
+ *unprotected_bytes_size += output_bytes_offset;
+ }
+ return result;
+}
+
+static void ssl_protector_destroy(tsi_frame_protector* self) {
+ tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self;
+ if (impl->buffer != NULL) free(impl->buffer);
+ if (impl->ssl != NULL) SSL_free(impl->ssl);
+ free(self);
+}
+
+static const tsi_frame_protector_vtable frame_protector_vtable = {
+ ssl_protector_protect,
+ ssl_protector_protect_flush,
+ ssl_protector_unprotect,
+ ssl_protector_destroy,
+};
+
+
+/* --- tsi_handshaker methods implementation. ---*/
+
+static tsi_result ssl_handshaker_get_bytes_to_send_to_peer(
+ tsi_handshaker* self, unsigned char* bytes, uint32_t* bytes_size) {
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ int bytes_read_from_ssl = 0;
+ if (bytes == NULL || bytes_size == NULL || *bytes_size == 0 ||
+ *bytes_size > INT_MAX) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ bytes_read_from_ssl = BIO_read(impl->from_ssl, bytes, *bytes_size);
+ if (bytes_read_from_ssl < 0) {
+ *bytes_size = 0;
+ if (!BIO_should_retry(impl->from_ssl)) {
+ impl->result = TSI_INTERNAL_ERROR;
+ return impl->result;
+ } else {
+ return TSI_OK;
+ }
+ }
+ *bytes_size = (uint32_t)bytes_read_from_ssl;
+ return BIO_ctrl_pending(impl->from_ssl) == 0 ? TSI_OK : TSI_INCOMPLETE_DATA;
+}
+
+static tsi_result ssl_handshaker_get_result(tsi_handshaker* self) {
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ if ((impl->result == TSI_HANDSHAKE_IN_PROGRESS) &&
+ SSL_is_init_finished(impl->ssl)) {
+ impl->result = TSI_OK;
+ }
+ return impl->result;
+}
+
+static tsi_result ssl_handshaker_process_bytes_from_peer(
+ tsi_handshaker* self, const unsigned char* bytes,
+ uint32_t* bytes_size) {
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ int bytes_written_into_ssl_size = 0;
+ if (bytes == NULL || bytes_size == 0 || *bytes_size > INT_MAX) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ bytes_written_into_ssl_size = BIO_write(impl->into_ssl, bytes, *bytes_size);
+ if (bytes_written_into_ssl_size < 0) {
+ gpr_log(GPR_ERROR, "Could not write to memory BIO.");
+ impl->result = TSI_INTERNAL_ERROR;
+ return impl->result;
+ }
+ *bytes_size = bytes_written_into_ssl_size;
+
+ if (!tsi_handshaker_is_in_progress(self)) {
+ impl->result = TSI_OK;
+ return impl->result;
+ } else {
+ /* Get ready to get some bytes from SSL. */
+ int ssl_result = SSL_do_handshake(impl->ssl);
+ ssl_result = SSL_get_error(impl->ssl, ssl_result);
+ switch (ssl_result) {
+ case SSL_ERROR_WANT_READ:
+ if (BIO_ctrl_pending(impl->from_ssl) == 0) {
+ /* We need more data. */
+ return TSI_INCOMPLETE_DATA;
+ } else {
+ return TSI_OK;
+ }
+ case SSL_ERROR_NONE:
+ return TSI_OK;
+ default: {
+ char err_str[256];
+ ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
+ gpr_log(GPR_ERROR, "Handshake failed with fatal error %s: %s.",
+ ssl_error_string(ssl_result), err_str);
+ impl->result = TSI_PROTOCOL_FAILURE;
+ return impl->result;
+ }
+ }
+ }
+}
+
+static tsi_result ssl_handshaker_extract_peer(tsi_handshaker* self,
+ tsi_peer* peer) {
+ tsi_result result = TSI_OK;
+ const unsigned char* alpn_selected;
+ unsigned int alpn_selected_len;
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ X509* peer_cert = SSL_get_peer_certificate(impl->ssl);
+ if (peer_cert != NULL) {
+ result = peer_from_x509(peer_cert, 1, peer);
+ X509_free(peer_cert);
+ if (result != TSI_OK) return result;
+ }
+ SSL_get0_alpn_selected(impl->ssl, &alpn_selected, &alpn_selected_len);
+ if (alpn_selected != NULL) {
+ uint32_t i;
+ tsi_peer_property* new_properties =
+ calloc(1, sizeof(tsi_peer_property) * (peer->property_count + 1));
+ if (new_properties == NULL) return TSI_OUT_OF_RESOURCES;
+ for (i = 0; i < peer->property_count; i++) {
+ new_properties[i] = peer->properties[i];
+ }
+ result = tsi_construct_string_peer_property(
+ TSI_SSL_ALPN_SELECTED_PROTOCOL, (const char*)alpn_selected,
+ alpn_selected_len, &new_properties[peer->property_count]);
+ if (result != TSI_OK) {
+ free(new_properties);
+ return result;
+ }
+ if (peer->properties != NULL) free(peer->properties);
+ peer->property_count++;
+ peer->properties = new_properties;
+ }
+ return result;
+}
+
+static tsi_result ssl_handshaker_create_frame_protector(
+ tsi_handshaker* self, uint32_t* max_output_protected_frame_size,
+ tsi_frame_protector** protector) {
+ uint32_t actual_max_output_protected_frame_size =
+ TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND;
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ tsi_ssl_frame_protector* protector_impl =
+ calloc(1, sizeof(tsi_ssl_frame_protector));
+ if (protector_impl == NULL) {
+ return TSI_OUT_OF_RESOURCES;
+ }
+
+ if (max_output_protected_frame_size != NULL) {
+ if (*max_output_protected_frame_size >
+ TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND) {
+ *max_output_protected_frame_size =
+ TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND;
+ } else if (*max_output_protected_frame_size <
+ TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND) {
+ *max_output_protected_frame_size =
+ TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND;
+ }
+ actual_max_output_protected_frame_size = *max_output_protected_frame_size;
+ }
+ protector_impl->buffer_size =
+ actual_max_output_protected_frame_size - TSI_SSL_MAX_PROTECTION_OVERHEAD;
+ protector_impl->buffer = malloc(protector_impl->buffer_size);
+ if (protector_impl->buffer == NULL) {
+ gpr_log(GPR_ERROR,
+ "Could not allocated buffer for tsi_ssl_frame_protector.");
+ free(protector_impl);
+ return TSI_INTERNAL_ERROR;
+ }
+
+ /* Transfer ownership of ssl to the frame protector. It is OK as the caller
+ * cannot call anything else but destroy on the handshaker after this call. */
+ protector_impl->ssl = impl->ssl;
+ impl->ssl = NULL;
+ protector_impl->into_ssl = impl->into_ssl;
+ protector_impl->from_ssl = impl->from_ssl;
+
+ protector_impl->base.vtable = &frame_protector_vtable;
+ *protector = &protector_impl->base;
+ return TSI_OK;
+}
+
+static void ssl_handshaker_destroy(tsi_handshaker* self) {
+ tsi_ssl_handshaker* impl = (tsi_ssl_handshaker*)self;
+ SSL_free(impl->ssl); /* The BIO objects are owned by ssl */
+ free(impl);
+}
+
+static const tsi_handshaker_vtable handshaker_vtable = {
+ ssl_handshaker_get_bytes_to_send_to_peer,
+ ssl_handshaker_process_bytes_from_peer,
+ ssl_handshaker_get_result,
+ ssl_handshaker_extract_peer,
+ ssl_handshaker_create_frame_protector,
+ ssl_handshaker_destroy,
+};
+
+
+/* --- tsi_ssl_handshaker_factory common methods. --- */
+
+tsi_result tsi_ssl_handshaker_factory_create_handshaker(
+ tsi_ssl_handshaker_factory* self, const char* server_name_indication,
+ tsi_handshaker** handshaker) {
+ if (self == NULL || handshaker == NULL) return TSI_INVALID_ARGUMENT;
+ return self->create_handshaker(self, server_name_indication, handshaker);
+}
+
+void tsi_ssl_handshaker_factory_destroy(tsi_ssl_handshaker_factory* self) {
+ if (self == NULL) return;
+ self->destroy(self);
+}
+
+static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
+ const char* server_name_indication,
+ tsi_handshaker** handshaker) {
+ SSL* ssl = SSL_new(ctx);
+ BIO* into_ssl = NULL;
+ BIO* from_ssl = NULL;
+ tsi_ssl_handshaker* impl = NULL;
+ *handshaker = NULL;
+ if (ctx == NULL) {
+ gpr_log(GPR_ERROR, "SSL Context is null. Should never happen.");
+ return TSI_INTERNAL_ERROR;
+ }
+ if (ssl == NULL) {
+ return TSI_OUT_OF_RESOURCES;
+ }
+ SSL_set_info_callback(ssl, ssl_info_callback);
+
+ into_ssl = BIO_new(BIO_s_mem());
+ from_ssl = BIO_new(BIO_s_mem());
+ if (into_ssl == NULL || from_ssl == NULL) {
+ gpr_log(GPR_ERROR, "BIO_new failed.");
+ SSL_free(ssl);
+ if (into_ssl != NULL) BIO_free(into_ssl);
+ if (from_ssl != NULL) BIO_free(into_ssl);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ SSL_set_bio(ssl, into_ssl, from_ssl);
+ if (is_client) {
+ int ssl_result;
+ SSL_set_connect_state(ssl);
+ if (server_name_indication != NULL) {
+ if (!SSL_set_tlsext_host_name(ssl, server_name_indication)) {
+ gpr_log(GPR_ERROR, "Invalid server name indication %s.",
+ server_name_indication);
+ SSL_free(ssl);
+ return TSI_INTERNAL_ERROR;
+ }
+ }
+ ssl_result = SSL_do_handshake(ssl);
+ ssl_result = SSL_get_error(ssl, ssl_result);
+ if (ssl_result != SSL_ERROR_WANT_READ) {
+ gpr_log(GPR_ERROR,
+ "Unexpected error received from first SSL_do_handshake call: %s",
+ ssl_error_string(ssl_result));
+ SSL_free(ssl);
+ return TSI_INTERNAL_ERROR;
+ }
+ } else {
+ SSL_set_accept_state(ssl);
+ }
+
+ impl = calloc(1, sizeof(tsi_ssl_handshaker));
+ if (impl == NULL) {
+ SSL_free(ssl);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ impl->ssl = ssl;
+ impl->into_ssl = into_ssl;
+ impl->from_ssl = from_ssl;
+ impl->result = TSI_HANDSHAKE_IN_PROGRESS;
+ impl->base.vtable = &handshaker_vtable;
+ *handshaker = &impl->base;
+ return TSI_OK;
+}
+
+
+/* --- tsi_ssl__client_handshaker_factory methods implementation. --- */
+
+static tsi_result ssl_client_handshaker_factory_create_handshaker(
+ tsi_ssl_handshaker_factory* self, const char* server_name_indication,
+ tsi_handshaker** handshaker) {
+ tsi_ssl_client_handshaker_factory* impl =
+ (tsi_ssl_client_handshaker_factory*)self;
+ return create_tsi_ssl_handshaker(impl->ssl_context, 1, server_name_indication,
+ handshaker);
+}
+
+static void ssl_client_handshaker_factory_destroy(
+ tsi_ssl_handshaker_factory* self) {
+ tsi_ssl_client_handshaker_factory* impl =
+ (tsi_ssl_client_handshaker_factory*)self;
+ SSL_CTX_free(impl->ssl_context);
+ free(impl);
+}
+
+
+/* --- tsi_ssl_server_handshaker_factory methods implementation. --- */
+
+static tsi_result ssl_server_handshaker_factory_create_handshaker(
+ tsi_ssl_handshaker_factory* self, const char* server_name_indication,
+ tsi_handshaker** handshaker) {
+ tsi_ssl_server_handshaker_factory* impl =
+ (tsi_ssl_server_handshaker_factory*)self;
+ if (impl->ssl_context_count == 0 || server_name_indication != NULL) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ /* Create the handshaker with the first context. We will switch if needed
+ because of SNI in ssl_server_handshaker_factory_servername_callback. */
+ return create_tsi_ssl_handshaker(impl->ssl_contexts[0], 0, NULL, handshaker);
+}
+
+static void ssl_server_handshaker_factory_destroy(
+ tsi_ssl_handshaker_factory* self) {
+ tsi_ssl_server_handshaker_factory* impl =
+ (tsi_ssl_server_handshaker_factory*)self;
+ uint32_t i;
+ for (i = 0; i < impl->ssl_context_count; i++) {
+ if (impl->ssl_contexts[i] != NULL) {
+ SSL_CTX_free(impl->ssl_contexts[i]);
+ tsi_peer_destruct(&impl->ssl_context_x509_subject_names[i]);
+ }
+ }
+ if (impl->ssl_contexts != NULL) free(impl->ssl_contexts);
+ if (impl->ssl_context_x509_subject_names != NULL) {
+ free(impl->ssl_context_x509_subject_names);
+ }
+ if (impl->alpn_protocol_list != NULL) free(impl->alpn_protocol_list);
+ free(impl);
+}
+
+static int does_entry_match_name(const char* entry, uint32_t entry_length,
+ const char* name) {
+ const char* name_subdomain = NULL;
+ if (entry_length == 0) return 0;
+ if (!strncmp(name, entry, entry_length) && (strlen(name) == entry_length)) {
+ return 1; /* Perfect match. */
+ }
+ if (entry[0] != '*') return 0;
+
+ /* Wildchar subdomain matching. */
+ if (entry_length < 3 || entry[1] != '.') { /* At least *.x */
+ gpr_log(GPR_ERROR, "Invalid wildchar entry.");
+ return 0;
+ }
+ name_subdomain = strchr(name, '.');
+ if (name_subdomain == NULL || strlen(name_subdomain) < 2) return 0;
+ name_subdomain++; /* Starts after the dot. */
+ entry += 2; /* Remove *. */
+ entry_length -= 2;
+ return (!strncmp(entry, name_subdomain, entry_length) &&
+ (strlen(name_subdomain) == entry_length));
+}
+
+static int ssl_server_handshaker_factory_servername_callback(SSL* ssl, int* ap,
+ void* arg) {
+ tsi_ssl_server_handshaker_factory* impl =
+ (tsi_ssl_server_handshaker_factory*)arg;
+ uint32_t i = 0;
+ const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ if (servername == NULL || strlen(servername) == 0) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ for (i = 0; i < impl->ssl_context_count; i++) {
+ if (tsi_ssl_peer_matches_name(&impl->ssl_context_x509_subject_names[i],
+ servername)) {
+ SSL_set_SSL_CTX(ssl, impl->ssl_contexts[i]);
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+ gpr_log(GPR_ERROR, "No match found for server name: %s.", servername);
+ return SSL_TLSEXT_ERR_ALERT_WARNING;
+}
+
+static int server_handshaker_factory_alpn_callback(
+ SSL* ssl, const unsigned char** out, unsigned char* outlen,
+ const unsigned char* in, unsigned int inlen, void* arg) {
+ tsi_ssl_server_handshaker_factory* factory =
+ (tsi_ssl_server_handshaker_factory*)arg;
+ const unsigned char* client_current = in;
+ while ((client_current - in) < inlen) {
+ unsigned char client_current_len = *(client_current++);
+ const unsigned char* server_current = factory->alpn_protocol_list;
+ while ((server_current - factory->alpn_protocol_list) <
+ factory->alpn_protocol_list_length) {
+ unsigned char server_current_len = *(server_current++);
+ if ((client_current_len == server_current_len) &&
+ !memcmp(client_current, server_current, server_current_len)) {
+ *out = server_current;
+ *outlen = server_current_len;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ server_current += server_current_len;
+ }
+ client_current += client_current_len;
+ }
+ return SSL_TLSEXT_ERR_NOACK;
+}
+
+
+/* --- tsi_ssl_handshaker_factory constructors. --- */
+
+tsi_result tsi_create_ssl_client_handshaker_factory(
+ const unsigned char* pem_private_key, uint32_t pem_private_key_size,
+ const unsigned char* pem_cert_chain, uint32_t pem_cert_chain_size,
+ const unsigned char* pem_root_certs, uint32_t pem_root_certs_size,
+ const char* cipher_list, const unsigned char** alpn_protocols,
+ const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols,
+ tsi_ssl_handshaker_factory** factory) {
+ SSL_CTX* ssl_context = NULL;
+ tsi_ssl_client_handshaker_factory* impl = NULL;
+ tsi_result result = TSI_OK;
+
+ pthread_once(&init_openssl_once, init_openssl);
+
+ if (factory == NULL) return TSI_INVALID_ARGUMENT;
+ *factory = NULL;
+ if (pem_root_certs == NULL) return TSI_INVALID_ARGUMENT;
+
+ ssl_context = SSL_CTX_new(TLSv1_2_method());
+ if (ssl_context == NULL) {
+ gpr_log(GPR_ERROR, "Could not create ssl context.");
+ return TSI_INVALID_ARGUMENT;
+ }
+ do {
+ result =
+ populate_ssl_context(ssl_context, pem_private_key, pem_private_key_size,
+ pem_cert_chain, pem_cert_chain_size, cipher_list);
+ if (result != TSI_OK) break;
+ result = ssl_ctx_load_verification_certs(ssl_context, pem_root_certs,
+ pem_root_certs_size, NULL);
+ if (result != TSI_OK) {
+ gpr_log(GPR_ERROR, "Cannot load server root certificates.");
+ break;
+ }
+
+ if (num_alpn_protocols != 0) {
+ unsigned char* alpn_protocol_list = NULL;
+ uint32_t alpn_protocol_list_length = 0;
+ int ssl_failed;
+ result = build_alpn_protocol_name_list(
+ alpn_protocols, alpn_protocols_lengths, num_alpn_protocols,
+ &alpn_protocol_list, &alpn_protocol_list_length);
+ if (result != TSI_OK) {
+ gpr_log(GPR_ERROR, "Building alpn list failed with error %s.",
+ tsi_result_to_string(result));
+ break;
+ }
+ ssl_failed = SSL_CTX_set_alpn_protos(ssl_context, alpn_protocol_list,
+ alpn_protocol_list_length);
+ free(alpn_protocol_list);
+ if (ssl_failed) {
+ gpr_log(GPR_ERROR, "Could not set alpn protocol list to context.");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ }
+ } while (0);
+ if (result != TSI_OK) {
+ SSL_CTX_free(ssl_context);
+ return result;
+ }
+ SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL);
+ /* TODO(jboeuf): Add revocation verification. */
+
+ impl = calloc(1, sizeof(tsi_ssl_client_handshaker_factory));
+ if (impl == NULL) {
+ SSL_CTX_free(ssl_context);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ impl->ssl_context = ssl_context;
+ impl->base.create_handshaker =
+ ssl_client_handshaker_factory_create_handshaker;
+ impl->base.destroy = ssl_client_handshaker_factory_destroy;
+ *factory = &impl->base;
+ return TSI_OK;
+}
+
+tsi_result tsi_create_ssl_server_handshaker_factory(
+ const unsigned char** pem_private_keys,
+ const uint32_t* pem_private_keys_sizes,
+ const unsigned char** pem_cert_chains,
+ const uint32_t* pem_cert_chains_sizes, uint32_t key_cert_pair_count,
+ const unsigned char* pem_client_root_certs,
+ uint32_t pem_client_root_certs_size, const char* cipher_list,
+ const unsigned char** alpn_protocols,
+ const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols,
+ tsi_ssl_handshaker_factory** factory) {
+ tsi_ssl_server_handshaker_factory* impl = NULL;
+ tsi_result result = TSI_OK;
+ uint32_t i = 0;
+
+ pthread_once(&init_openssl_once, init_openssl);
+
+ if (factory == NULL) return TSI_INVALID_ARGUMENT;
+ *factory = NULL;
+ if (key_cert_pair_count == 0 || pem_private_keys == NULL ||
+ pem_cert_chains == NULL) {
+ return TSI_INVALID_ARGUMENT;
+ }
+
+ impl = calloc(1, sizeof(tsi_ssl_server_handshaker_factory));
+ if (impl == NULL) return TSI_OUT_OF_RESOURCES;
+ impl->base.create_handshaker =
+ ssl_server_handshaker_factory_create_handshaker;
+ impl->base.destroy = ssl_server_handshaker_factory_destroy;
+ impl->ssl_contexts = calloc(key_cert_pair_count, sizeof(SSL_CTX*));
+ impl->ssl_context_x509_subject_names =
+ calloc(key_cert_pair_count, sizeof(tsi_peer));
+ if (impl->ssl_contexts == NULL ||
+ impl->ssl_context_x509_subject_names == NULL) {
+ tsi_ssl_handshaker_factory_destroy(&impl->base);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ impl->ssl_context_count = key_cert_pair_count;
+
+ if (num_alpn_protocols > 0) {
+ result = build_alpn_protocol_name_list(
+ alpn_protocols, alpn_protocols_lengths, num_alpn_protocols,
+ &impl->alpn_protocol_list, &impl->alpn_protocol_list_length);
+ if (result != TSI_OK) {
+ tsi_ssl_handshaker_factory_destroy(&impl->base);
+ return result;
+ }
+ }
+
+ for (i = 0; i < key_cert_pair_count; i++) {
+ do {
+ impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method());
+ if (impl->ssl_contexts[i] == NULL) {
+ gpr_log(GPR_ERROR, "Could not create ssl context.");
+ result = TSI_OUT_OF_RESOURCES;
+ break;
+ }
+ result = populate_ssl_context(
+ impl->ssl_contexts[i], pem_private_keys[i], pem_private_keys_sizes[i],
+ pem_cert_chains[i], pem_cert_chains_sizes[i], cipher_list);
+ if (result != TSI_OK) break;
+
+ if (pem_client_root_certs != NULL) {
+ STACK_OF(X509_NAME)* root_names = NULL;
+ result = ssl_ctx_load_verification_certs(
+ impl->ssl_contexts[i], pem_client_root_certs,
+ pem_client_root_certs_size, &root_names);
+ if (result != TSI_OK) {
+ gpr_log(GPR_ERROR, "Invalid verification certs.");
+ break;
+ }
+ SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names);
+ SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_PEER, NULL);
+ /* TODO(jboeuf): Add revocation verification. */
+ }
+
+ result = extract_x509_subject_names_from_pem_cert(
+ pem_cert_chains[i], pem_cert_chains_sizes[i],
+ &impl->ssl_context_x509_subject_names[i]);
+ if (result != TSI_OK) break;
+
+ SSL_CTX_set_tlsext_servername_callback(
+ impl->ssl_contexts[i],
+ ssl_server_handshaker_factory_servername_callback);
+ SSL_CTX_set_tlsext_servername_arg(impl->ssl_contexts[i], impl);
+ SSL_CTX_set_alpn_select_cb(impl->ssl_contexts[i],
+ server_handshaker_factory_alpn_callback, impl);
+ } while (0);
+
+ if (result != TSI_OK) {
+ tsi_ssl_handshaker_factory_destroy(&impl->base);
+ return result;
+ }
+ }
+ *factory = &impl->base;
+ return TSI_OK;
+}
+
+/* --- tsi_ssl utils. --- */
+
+int tsi_ssl_peer_matches_name(const tsi_peer* peer, const char* name) {
+ uint32_t i = 0;
+ const tsi_peer_property* property = tsi_peer_get_property_by_name(
+ peer, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY);
+ if (property == NULL ||
+ property->type != TSI_PEER_PROPERTY_TYPE_STRING) {
+ gpr_log(GPR_ERROR,
+ "Invalid x509 subject common name property.");
+ return 0;
+ }
+ if (does_entry_match_name(property->value.string.data,
+ property->value.string.length, name)) {
+ return 1;
+ }
+
+ property = tsi_peer_get_property_by_name(
+ peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY);
+ if (property == NULL || property->type != TSI_PEER_PROPERTY_TYPE_LIST) {
+ gpr_log(GPR_ERROR,
+ "Invalid x509 subject alternative names property.");
+ return 0;
+ }
+
+ for (i = 0; i < property->value.list.child_count; i++) {
+ const tsi_peer_property* alt_name_property =
+ &property->value.list.children[i];
+ if (alt_name_property->type != TSI_PEER_PROPERTY_TYPE_STRING) {
+ gpr_log(GPR_ERROR, "Invalid x509 subject alternative name property.");
+ return 0;
+ }
+ if (does_entry_match_name(alt_name_property->value.string.data,
+ alt_name_property->value.string.length, name)) {
+ return 1;
+ }
+ }
+ return 0; /* Not found. */
+}
diff --git a/src/core/tsi/ssl_transport_security.h b/src/core/tsi/ssl_transport_security.h
new file mode 100644
index 0000000000..2ed3ed861b
--- /dev/null
+++ b/src/core/tsi/ssl_transport_security.h
@@ -0,0 +1,159 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __SSL_TRANSPORT_SECURITY_H_
+#define __SSL_TRANSPORT_SECURITY_H_
+
+#include "src/core/tsi/transport_security_interface.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Value for the TSI_CERTIFICATE_TYPE_PEER_PROPERTY property for X509 certs. */
+#define TSI_X509_CERTIFICATE_TYPE "X509"
+
+/* --- tsi_ssl_handshaker_factory object ---
+
+ This object creates tsi_handshaker objects implemented in terms of the
+ TLS 1.2 specificiation. */
+
+typedef struct tsi_ssl_handshaker_factory tsi_ssl_handshaker_factory;
+
+/* Creates a client handshaker factory.
+ - pem_private_key is the buffer containing the PEM encoding of the client's
+ private key. This parameter can be NULL if the client does not have a
+ private key.
+ - pem_private_key_size is the size of the associated buffer.
+ - pem_cert_chain is the buffer containing the PEM encoding of the client's
+ certificate chain. This parameter can be NULL if the client does not have
+ a certificate chain.
+ - pem_cert_chain_size is the size of the associated buffer.
+ - pem_roots_cert is the buffer containing the PEM encoding of the server
+ root certificates. This parameter cannot be NULL.
+ - pem_roots_cert_size is the size of the associated buffer.
+ - cipher_suites contains an optional list of the ciphers that the client
+ supports. The format of this string is described in:
+ https://www.openssl.org/docs/apps/ciphers.html.
+ This parameter can be set to NULL to use the default set of ciphers.
+ TODO(jboeuf): Revisit the format of this parameter.
+ - alpn_protocols is an array containing the protocol names that the
+ handshakers created with this factory support. This parameter can be NULL.
+ - alpn_protocols_lengths is an array containing the lengths of the alpn
+ protocols specified in alpn_protocols. This parameter can be NULL.
+ - num_alpn_protocols is the number of alpn protocols and associated lengths
+ specified. If this parameter is 0, the other alpn parameters must be NULL.
+ - factory is the address of the factory pointer to be created.
+
+ - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case
+ where a parameter is invalid. */
+tsi_result tsi_create_ssl_client_handshaker_factory(
+ const unsigned char* pem_private_key, uint32_t pem_private_key_size,
+ const unsigned char* pem_cert_chain, uint32_t pem_cert_chain_size,
+ const unsigned char* pem_root_certs, uint32_t pem_root_certs_size,
+ const char* cipher_suites, const unsigned char** alpn_protocols,
+ const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols,
+ tsi_ssl_handshaker_factory** factory);
+
+/* Creates a server handshaker factory.
+ - version indicates which version of the specification to use.
+ - pem_private_keys is an array containing the PEM encoding of the server's
+ private keys. This parameter cannot be NULL. The size of the array is
+ given by the key_cert_pair_count parameter.
+ - pem_private_keys_sizes is the array containing the sizes of the associated
+ buffers.
+ - pem_cert_chains is an array containing the PEM encoding of the server's
+ cert chains. This parameter cannot be NULL. The size of the array is
+ given by the key_cert_pair_count parameter.
+ - pem_cert_chains_sizes is the array containing the sizes of the associated
+ buffers.
+ - key_cert_pair_count indicates the number of items in the private_key_files
+ and cert_chain_files parameters.
+ - pem_client_roots is the buffer containing the PEM encoding of the client
+ root certificates. This parameter may be NULL in which case the server
+ will not ask the client to authenticate itself with a certificate (server-
+ only authentication mode).
+ - pem_client_roots_size is the size of the associated buffer.
+ - cipher_suites contains an optional list of the ciphers that the server
+ supports. The format of this string is described in:
+ https://www.openssl.org/docs/apps/ciphers.html.
+ This parameter can be set to NULL to use the default set of ciphers.
+ TODO(jboeuf): Revisit the format of this parameter.
+ - alpn_protocols is an array containing the protocol names that the
+ handshakers created with this factory support. This parameter can be NULL.
+ - alpn_protocols_lengths is an array containing the lengths of the alpn
+ protocols specified in alpn_protocols. This parameter can be NULL.
+ - num_alpn_protocols is the number of alpn protocols and associated lengths
+ specified. If this parameter is 0, the other alpn parameters must be NULL.
+ - factory is the address of the factory pointer to be created.
+
+ - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case
+ where a parameter is invalid. */
+tsi_result tsi_create_ssl_server_handshaker_factory(
+ const unsigned char** pem_private_keys,
+ const uint32_t* pem_private_keys_sizes,
+ const unsigned char** pem_cert_chains,
+ const uint32_t* pem_cert_chains_sizes, uint32_t key_cert_pair_count,
+ const unsigned char* pem_client_root_certs,
+ uint32_t pem_client_root_certs_size, const char* cipher_suites,
+ const unsigned char** alpn_protocols,
+ const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols,
+ tsi_ssl_handshaker_factory** factory);
+
+/* Creates a handshaker.
+ - self is the factory from which the handshaker will be created.
+ - server_name_indication indicates the name of the server the client is
+ trying to connect to which will be relayed to the server using the SNI
+ extension.
+ This parameter must be NULL for a server handshaker factory.
+ - handhshaker is the address of the handshaker pointer to be created.
+
+ - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case
+ where a parameter is invalid. */
+tsi_result tsi_ssl_handshaker_factory_create_handshaker(
+ tsi_ssl_handshaker_factory* self, const char* server_name_indication,
+ tsi_handshaker** handshaker);
+
+/* Destroys the handshaker factory. WARNING: it is unsafe to destroy a factory
+ while handshakers created with this factory are still in use. */
+void tsi_ssl_handshaker_factory_destroy(tsi_ssl_handshaker_factory* self);
+
+/* Util that checks that an ssl peer matches a specific name. */
+int tsi_ssl_peer_matches_name(const tsi_peer* peer, const char* name);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SSL_TRANSPORT_SECURITY_H_ */
diff --git a/src/core/tsi/ssl_transport_security_test.cc b/src/core/tsi/ssl_transport_security_test.cc
new file mode 100644
index 0000000000..a759403126
--- /dev/null
+++ b/src/core/tsi/ssl_transport_security_test.cc
@@ -0,0 +1,534 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <memory>
+
+#include "base/commandlineflags.h"
+#include "file/base/helpers.h"
+#include "file/base/options.pb.h"
+#include "file/base/path.h"
+#include "src/core/tsi/transport_security_test_lib.h"
+#include "src/core/tsi/ssl_transport_security.h"
+#include "util/random/permute-inl.h"
+
+namespace {
+
+const char kTestCredsDir[] =
+ "/internal/tsi/test_creds/";
+
+enum AlpnMode {
+ NO_ALPN,
+ ALPN_CLIENT_NO_SERVER,
+ ALPN_SERVER_NO_CLIENT,
+ ALPN_CLIENT_SERVER_OK,
+ ALPN_CLIENT_SERVER_MISMATCH
+};
+
+class SslTestConfig : public tsi::test::TestConfig {
+ public:
+ SslTestConfig()
+ : do_client_authentication(false),
+ subject_name_indication(nullptr),
+ use_bad_client_cert(false),
+ use_bad_server_cert(false),
+ alpn_mode(NO_ALPN) {}
+ bool do_client_authentication;
+ const char* subject_name_indication;
+ bool use_bad_client_cert;
+ bool use_bad_server_cert;
+ AlpnMode alpn_mode;
+};
+
+struct TsiSslHandshakerFactoryDeleter {
+ inline void operator()(tsi_ssl_handshaker_factory* ptr) {
+ tsi_ssl_handshaker_factory_destroy(ptr);
+ }
+};
+typedef std::unique_ptr<tsi_ssl_handshaker_factory,
+ TsiSslHandshakerFactoryDeleter>
+ TsiSslHandshakerFactoryUniquePtr;
+
+class SslTransportSecurityTest : public tsi::test::TransportSecurityTest {
+ protected:
+ void CheckSubjectAltName(const tsi_peer_property& property,
+ const string& expected_subject_alt_name) {
+ EXPECT_EQ(property.type, TSI_PEER_PROPERTY_TYPE_STRING);
+ EXPECT_EQ(property.name, nullptr);
+ EXPECT_EQ(
+ string(property.value.string.data, property.value.string.length),
+ expected_subject_alt_name);
+ }
+
+ const tsi_peer_property* CheckBasicAuthenticatedPeerAndGetCommonName(
+ const tsi_peer* peer) {
+ const tsi_peer_property* property =
+ tsi_peer_get_property_by_name(peer, TSI_CERTIFICATE_TYPE_PEER_PROPERTY);
+ EXPECT_NE(property, nullptr);
+ EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_STRING);
+ EXPECT_EQ(
+ string(property->value.string.data, property->value.string.length),
+ string(TSI_X509_CERTIFICATE_TYPE));
+ property = tsi_peer_get_property_by_name(
+ peer, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY);
+ EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_STRING);
+ return property;
+ }
+
+ void CheckServer0Peer(tsi_peer* peer) {
+ const tsi_peer_property* property =
+ CheckBasicAuthenticatedPeerAndGetCommonName(peer);
+ EXPECT_EQ(
+ string(property->value.string.data, property->value.string.length),
+ string("*.test.google.com.au"));
+ property = tsi_peer_get_property_by_name(
+ peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY);
+ EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_LIST);
+ EXPECT_EQ(property->value.list.child_count, 0);
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.google.com.au"));
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "bar.test.google.com.au"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "bar.test.google.blah"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "foo.bar.test.google.com.au"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "test.google.com.au"));
+ tsi_peer_destruct(peer);
+ }
+
+ void CheckServer1Peer(tsi_peer* peer) {
+ const tsi_peer_property* property =
+ CheckBasicAuthenticatedPeerAndGetCommonName(peer);
+ EXPECT_EQ(
+ string(property->value.string.data, property->value.string.length),
+ string("*.test.google.com"));
+ property = tsi_peer_get_property_by_name(
+ peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY);
+ EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_LIST);
+ EXPECT_EQ(property->value.list.child_count, 3);
+ CheckSubjectAltName(property->value.list.children[0], "*.test.google.fr");
+ CheckSubjectAltName(property->value.list.children[1],
+ "waterzooi.test.google.be");
+ CheckSubjectAltName(property->value.list.children[2], "*.test.youtube.com");
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.google.com"));
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "bar.test.google.fr"));
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "waterzooi.test.google.be"));
+ EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.youtube.com"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "bar.foo.test.google.com"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "test.google.fr"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "tartines.test.google.be"));
+ EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "tartines.youtube.com"));
+ tsi_peer_destruct(peer);
+ }
+
+ void CheckClientPeer(tsi_peer* peer, bool is_authenticated) {
+ if (!is_authenticated) {
+ EXPECT_EQ(peer->property_count,
+ config_.alpn_mode == ALPN_CLIENT_SERVER_OK ? 1 : 0);
+ } else {
+ const tsi_peer_property* property =
+ CheckBasicAuthenticatedPeerAndGetCommonName(peer);
+ EXPECT_EQ(
+ string(property->value.string.data, property->value.string.length),
+ string("testclient"));
+ }
+ tsi_peer_destruct(peer);
+ }
+
+ void SetupHandshakers() override {
+ tsi_ssl_handshaker_factory* client_handshaker_factory;
+ const unsigned char* client_cert = NULL;
+ unsigned int client_cert_size = 0;
+ const unsigned char* client_key = NULL;
+ unsigned int client_key_size = 0;
+ if (config_.do_client_authentication) {
+ if (config_.use_bad_client_cert) {
+ client_cert =
+ reinterpret_cast<const unsigned char*>(badclient_cert_.data());
+ client_cert_size = badclient_cert_.size();
+ client_key =
+ reinterpret_cast<const unsigned char*>(badclient_key_.data());
+ client_key_size = badclient_key_.size();
+ } else {
+ client_cert =
+ reinterpret_cast<const unsigned char*>(client_cert_.data());
+ client_cert_size = client_cert_.size();
+ client_key = reinterpret_cast<const unsigned char*>(client_key_.data());
+ client_key_size = client_key_.size();
+ }
+ }
+ const unsigned char** client_alpn_protocols(nullptr);
+ const unsigned char* client_alpn_protocols_lengths(nullptr);
+ uint16_t num_client_alpn_protocols = 0;
+ if (config_.alpn_mode == ALPN_CLIENT_NO_SERVER ||
+ config_.alpn_mode == ALPN_CLIENT_SERVER_OK ||
+ config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
+ client_alpn_protocols =
+ reinterpret_cast<const unsigned char**>(&client_alpn_protocols_[0]);
+ client_alpn_protocols_lengths = &client_alpn_protocols_lengths_[0];
+ num_client_alpn_protocols = client_alpn_protocols_.size();
+ }
+
+ EXPECT_EQ(tsi_create_ssl_client_handshaker_factory(
+ client_key, client_key_size, client_cert, client_cert_size,
+ reinterpret_cast<const unsigned char*>(root_certs_.data()),
+ root_certs_.size(), NULL, client_alpn_protocols,
+ client_alpn_protocols_lengths, num_client_alpn_protocols,
+ &client_handshaker_factory),
+ TSI_OK);
+ client_handshaker_factory_.reset(client_handshaker_factory);
+
+ const unsigned char** server_alpn_protocols(nullptr);
+ const unsigned char* server_alpn_protocols_lengths(nullptr);
+ uint16_t num_server_alpn_protocols = 0;
+ if (config_.alpn_mode == ALPN_SERVER_NO_CLIENT ||
+ config_.alpn_mode == ALPN_CLIENT_SERVER_OK ||
+ config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
+ server_alpn_protocols =
+ reinterpret_cast<const unsigned char**>(&server_alpn_protocols_[0]);
+ server_alpn_protocols_lengths = &server_alpn_protocols_lengths_[0];
+ num_server_alpn_protocols = server_alpn_protocols_.size();
+ if (config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
+ // Remove the last element that is common.
+ num_server_alpn_protocols--;
+ }
+ }
+ tsi_ssl_handshaker_factory* server_handshaker_factory;
+ EXPECT_EQ(
+ tsi_create_ssl_server_handshaker_factory(
+ config_.use_bad_server_cert ? &badserver_keys_[0]
+ : &server_keys_[0],
+ config_.use_bad_server_cert ? &badserver_keys_sizes_[0]
+ : &server_keys_sizes_[0],
+ config_.use_bad_server_cert ? &badserver_certs_[0]
+ : &server_certs_[0],
+ config_.use_bad_server_cert ? &badserver_certs_sizes_[0]
+ : &server_certs_sizes_[0],
+ config_.use_bad_server_cert ? badserver_keys_.size()
+ : server_keys_.size(),
+ config_.do_client_authentication
+ ? reinterpret_cast<const unsigned char*>(root_certs_.data())
+ : NULL,
+ config_.do_client_authentication ? root_certs_.size() : 0, NULL,
+ server_alpn_protocols, server_alpn_protocols_lengths,
+ num_server_alpn_protocols, &server_handshaker_factory),
+ TSI_OK);
+ server_handshaker_factory_.reset(server_handshaker_factory);
+
+ tsi_handshaker* client_handshaker;
+ EXPECT_EQ(tsi_ssl_handshaker_factory_create_handshaker(
+ client_handshaker_factory, config_.subject_name_indication,
+ &client_handshaker),
+ TSI_OK);
+ client_handshaker_.reset(client_handshaker);
+
+ tsi_handshaker* server_handshaker;
+ EXPECT_EQ(tsi_ssl_handshaker_factory_create_handshaker(
+ server_handshaker_factory, NULL, &server_handshaker),
+ TSI_OK);
+ server_handshaker_.reset(server_handshaker);
+ }
+
+ void CheckAlpn(const tsi_peer* peer) {
+ const tsi_peer_property* alpn_property =
+ tsi_peer_get_property_by_name(peer, TSI_SSL_ALPN_SELECTED_PROTOCOL);
+ if (config_.alpn_mode != ALPN_CLIENT_SERVER_OK) {
+ EXPECT_EQ(nullptr, alpn_property);
+ } else {
+ EXPECT_NE(nullptr, alpn_property);
+ EXPECT_EQ(TSI_PEER_PROPERTY_TYPE_STRING, alpn_property->type);
+ string expected_match("baz");
+ EXPECT_EQ(expected_match, string(alpn_property->value.string.data,
+ alpn_property->value.string.length));
+ }
+ }
+
+ void CheckHandshakeResults() override {
+ tsi_peer peer;
+
+ bool expect_success =
+ !(config_.use_bad_server_cert ||
+ (config_.use_bad_client_cert && config_.do_client_authentication));
+ tsi_result result = tsi_handshaker_get_result(client_handshaker_.get());
+ EXPECT_NE(result, TSI_HANDSHAKE_IN_PROGRESS);
+ if (expect_success) {
+ EXPECT_EQ(result, TSI_OK);
+ EXPECT_EQ(tsi_handshaker_extract_peer(client_handshaker_.get(), &peer),
+ TSI_OK);
+ CheckAlpn(&peer);
+ // TODO(jboeuf): This is a bit fragile. Maybe revisit.
+ if (config_.subject_name_indication != nullptr) {
+ CheckServer1Peer(&peer);
+ } else {
+ CheckServer0Peer(&peer);
+ }
+ } else {
+ EXPECT_NE(result, TSI_OK);
+ EXPECT_NE(tsi_handshaker_extract_peer(client_handshaker_.get(), &peer),
+ TSI_OK);
+ }
+
+ result = tsi_handshaker_get_result(server_handshaker_.get());
+ EXPECT_NE(result, TSI_HANDSHAKE_IN_PROGRESS);
+ if (expect_success) {
+ EXPECT_EQ(result, TSI_OK);
+ EXPECT_EQ(tsi_handshaker_extract_peer(server_handshaker_.get(), &peer),
+ TSI_OK);
+ CheckAlpn(&peer);
+ CheckClientPeer(&peer, config_.do_client_authentication);
+ } else {
+ EXPECT_NE(result, TSI_OK);
+ EXPECT_NE(tsi_handshaker_extract_peer(server_handshaker_.get(), &peer),
+ TSI_OK);
+ }
+ }
+
+ const tsi::test::TestConfig* config() override {
+ return &config_;
+ }
+
+ SslTransportSecurityTest()
+ : client_alpn_protocols_({"foo", "toto", "baz"}),
+ server_alpn_protocols_({"boooo", "far", "baz"}),
+ client_alpn_protocols_lengths_({3, 4, 3}),
+ server_alpn_protocols_lengths_({5, 3, 3}) {
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badserver.key"),
+ &badserver_key_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badserver.pem"),
+ &badserver_cert_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badclient.key"),
+ &badclient_key_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badclient.pem"),
+ &badclient_cert_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server0.key"),
+ &server0_key_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server0.pem"),
+ &server0_cert_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server1.key"),
+ &server1_key_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server1.pem"),
+ &server1_cert_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "client.key"),
+ &client_key_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "client.pem"),
+ &client_cert_, file::Options()));
+ CHECK_OK(file::GetContents(
+ file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "ca.pem"),
+ &root_certs_, file::Options()));
+ badserver_keys_.push_back(
+ reinterpret_cast<const unsigned char*>(badserver_key_.data()));
+ badserver_certs_.push_back(
+ reinterpret_cast<const unsigned char*>(badserver_cert_.data()));
+ server_keys_.push_back(
+ reinterpret_cast<const unsigned char*>(server0_key_.data()));
+ server_keys_.push_back(
+ reinterpret_cast<const unsigned char*>(server1_key_.data()));
+ server_certs_.push_back(
+ reinterpret_cast<const unsigned char*>(server0_cert_.data()));
+ server_certs_.push_back(
+ reinterpret_cast<const unsigned char*>(server1_cert_.data()));
+ badserver_keys_sizes_.push_back(badserver_key_.size());
+ badserver_certs_sizes_.push_back(badserver_cert_.size());
+ server_keys_sizes_.push_back(server0_key_.size());
+ server_keys_sizes_.push_back(server1_key_.size());
+ server_certs_sizes_.push_back(server0_cert_.size());
+ server_certs_sizes_.push_back(server1_cert_.size());
+ }
+
+ string badserver_key_;
+ string badserver_cert_;
+ string badclient_key_;
+ string badclient_cert_;
+ string server0_key_;
+ string server0_cert_;
+ string server1_key_;
+ string server1_cert_;
+ string client_key_;
+ string client_cert_;
+ string root_certs_;
+ std::vector<const unsigned char*> badserver_keys_;
+ std::vector<const unsigned char*> badserver_certs_;
+ std::vector<const unsigned char*> server_keys_;
+ std::vector<const unsigned char*> server_certs_;
+ std::vector<unsigned int> badserver_keys_sizes_;
+ std::vector<unsigned int> badserver_certs_sizes_;
+ std::vector<unsigned int> server_keys_sizes_;
+ std::vector<unsigned int> server_certs_sizes_;
+ TsiSslHandshakerFactoryUniquePtr client_handshaker_factory_;
+ TsiSslHandshakerFactoryUniquePtr server_handshaker_factory_;
+ std::vector<const char*> client_alpn_protocols_;
+ std::vector<const char*> server_alpn_protocols_;
+ std::vector<unsigned char> client_alpn_protocols_lengths_;
+ std::vector<unsigned char> server_alpn_protocols_lengths_;
+ string matched_alpn_;
+ SslTestConfig config_;
+};
+
+
+TEST_F(SslTransportSecurityTest, LoadInvalidRoots) {
+ tsi_ssl_handshaker_factory* client_handshaker_factory;
+ string invalid_roots("Invalid roots!");
+ EXPECT_EQ(
+ TSI_INVALID_ARGUMENT,
+ tsi_create_ssl_client_handshaker_factory(
+ NULL, 0, NULL, 0,
+ reinterpret_cast<const unsigned char*>(invalid_roots.data()),
+ invalid_roots.size(), NULL, NULL, 0, 0, &client_handshaker_factory));
+}
+
+TEST_F(SslTransportSecurityTest, Handshake) {
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, HandshakeClientAuthentication) {
+ config_.do_client_authentication = true;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, HandshakeSmallBuffer) {
+ config_.handshake_buffer_size = 128;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, HandshakeSNIExactDomain) {
+ // server1 cert contains waterzooi.test.google.be in SAN.
+ config_.subject_name_indication = "waterzooi.test.google.be";
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, HandshakeSNIWildstarDomain) {
+ // server1 cert contains *.test.google.fr in SAN.
+ config_.subject_name_indication = "juju.test.google.fr";
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, BadServerCertFailure) {
+ config_.use_bad_server_cert = true;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, BadClientCertFailure) {
+ config_.use_bad_client_cert = true;
+ config_.do_client_authentication = true;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, AlpnClientNoServer) {
+ config_.alpn_mode = ALPN_CLIENT_NO_SERVER;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, AlpnServerNoClient) {
+ config_.alpn_mode = ALPN_SERVER_NO_CLIENT;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, AlpnClientServeMismatch) {
+ config_.alpn_mode = ALPN_CLIENT_SERVER_MISMATCH;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, AlpnClientServerOk) {
+ config_.alpn_mode = ALPN_CLIENT_SERVER_OK;
+ PerformHandshake();
+}
+
+TEST_F(SslTransportSecurityTest, PingPong) {
+ PingPong();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTrip) {
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripSmallMessageBuffer) {
+ config_.message_buffer_allocated_size = 42;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripSmallProtectedBufferSize) {
+ config_.protected_buffer_size = 37;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripSmallReadBufferSize) {
+ config_.read_buffer_allocated_size = 41;
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripSmallClientFrames) {
+ config_.set_client_max_output_protected_frame_size(39);
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripSmallServerFrames) {
+ config_.set_server_max_output_protected_frame_size(43);
+ config_.client_message = small_message_;
+ config_.server_message = big_message_;
+ DoRoundTrip();
+}
+
+TEST_F(SslTransportSecurityTest, RoundTripOddBufferSizes) {
+ int odd_sizes[] = {33, 67, 135, 271, 523};
+ RandomPermutation<int> permute(odd_sizes, arraysize(odd_sizes),
+ random_.get());
+ permute.Permute();
+ LOG(ERROR) << odd_sizes[0] << "\t" << odd_sizes[1] << "\t" << odd_sizes[2]
+ << "\t" << odd_sizes[3] << "\t" << odd_sizes[4];
+ config_.message_buffer_allocated_size = odd_sizes[0];
+ config_.protected_buffer_size = odd_sizes[1];
+ config_.read_buffer_allocated_size = odd_sizes[2];
+ config_.set_client_max_output_protected_frame_size(odd_sizes[3]);
+ config_.set_server_max_output_protected_frame_size(odd_sizes[4]);
+ config_.client_message = big_message_;
+ config_.server_message = small_message_;
+ DoRoundTrip();
+}
+
+} // namespace
diff --git a/src/core/tsi/test_creds/README b/src/core/tsi/test_creds/README
new file mode 100644
index 0000000000..eb8482d648
--- /dev/null
+++ b/src/core/tsi/test_creds/README
@@ -0,0 +1,62 @@
+The test credentials (CONFIRMEDTESTKEY) have been generated with the following
+commands:
+
+Bad credentials (badclient.* / badserver.*):
+============================================
+
+These are self-signed certificates:
+
+$ openssl req -x509 -newkey rsa:1024 -keyout badserver.key -out badserver.pem \
+ -days 3650 -nodes
+
+When prompted for certificate information, everything is default except the
+common name which is set to badserver.test.google.com.
+
+
+Valid test credentials:
+=======================
+
+The ca is self-signed:
+----------------------
+
+$ openssl req -x509 -new -newkey rsa:1024 -nodes -out ca.pem -config ca-openssl.cnf -days 3650 -extensions v3_req
+When prompted for certificate information, everything is default.
+
+client is issued by CA:
+-----------------------
+
+$ openssl genrsa -out client.key.rsa 1024
+$ openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt
+$ rm client.key.rsa
+$ openssl req -new -key client.key -out client.csr
+
+When prompted for certificate information, everything is default except the
+common name which is set to testclient.
+
+$ openssl ca -in client.csr -out client.pem
+
+server0 is issued by CA:
+------------------------
+
+$ openssl genrsa -out server0.key.rsa 1024
+$ openssl pkcs8 -topk8 -in server0.key.rsa -out server0.key -nocrypt
+$ rm server0.key.rsa
+$ openssl req -new -key server0.key -out server0.csr
+
+When prompted for certificate information, everything is default except the
+common name which is set to *.test.google.com.au.
+
+$ openssl ca -in server0.csr -out server0.pem
+
+server1 is issued by CA with a special config for subject alternative names:
+----------------------------------------------------------------------------
+
+$ openssl genrsa -out server1.key.rsa 1024
+$ openssl pkcs8 -topk8 -in server1.key.rsa -out server1.key -nocrypt
+$ rm server1.key.rsa
+$ openssl req -new -key server1.key -out server1.csr -config server1-openssl.cnf
+
+When prompted for certificate information, everything is default except the
+common name which is set to *.test.google.com.
+
+$ openssl ca -in server1.csr -out server1.pem
diff --git a/src/core/tsi/test_creds/badclient.key b/src/core/tsi/test_creds/badclient.key
new file mode 100644
index 0000000000..5832685122
--- /dev/null
+++ b/src/core/tsi/test_creds/badclient.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALJfYnFn4nkj52WF
+E5W2qUxCfjsEFyuXYYKS/07UPWsv3gpZhtjXgdeGL+dpwEBC0IRDBfGnkMp6YY5S
+O7rnEz0X3r/fvgYy+dEl2jnaA6zgc7RzMGl9U11d56gP9FiDC2190mvP/hpq2xLZ
+CTbIximpmaoQyxuuH1bbYunesIG/AgMBAAECgYAdqJCEzMIyZE7oaW0tOpcB0BiP
+FYoIvH4BKRH8eHvR476mt+YdDhBP1scGUmYeCT4Ej+RgHv2LPTgVYwT9eciP2+E/
+CBCNRel0Sw9JepwW0r+jWJtDY1pp6YXAgNRGX2UflvUsT+o9lZvagf9moLTMyGvU
+uLFnsyfLim1B4vXvWQJBANouZllXGZoSrZLtR3VgV4tzRQvJxu84kLeIk64Ov47X
+pHVBMTRBfzPEhbBodjr1m5OLaVLqkFcXftzRCrbWoKsCQQDRSoLLXOiLrtJ3DLJC
+rX7Y8wrHZrqk5bMdZLGa/UX8RanhVw3+Xp+urd1711umeNJfzu/MCk4a1KkG/CU0
+rqs9AkA4cSx1DD1JSG+yxMNpsAS1xJomFIrsM9vsPt7FdndDwrF+y+CovhDkGYDk
+RAHh+svGfZg/pQK2JRPimAmHhzqFAkEAu6Ya70s2FUeB3Mu9aJs2CD6hg3dQEVkB
+53DI7TX48d9kGW58VX1xnqS02LyWqAPcW5qm1kLHFLdndaPNmBaj4QJBAJugl367
+9d9t/QLTSuULLaoYv2vJT3s1y9HN89EoaDDEkPVfQu6GVEXgIBtim1sI/VPSzI8H
+aXvaTUwblFWSM70=
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/badclient.pem b/src/core/tsi/test_creds/badclient.pem
new file mode 100644
index 0000000000..1785970221
--- /dev/null
+++ b/src/core/tsi/test_creds/badclient.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICoDCCAgmgAwIBAgIJANIz2/zoRiapMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZGNsaWVudC50ZXN0Lmdvb2dsZS5j
+b20wHhcNMTQwNzI4MjAwODI1WhcNMjQwNzI1MjAwODI1WjBpMQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRjbGllbnQudGVzdC5nb29nbGUuY29tMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyX2JxZ+J5I+dlhROVtqlMQn47BBcr
+l2GCkv9O1D1rL94KWYbY14HXhi/nacBAQtCEQwXxp5DKemGOUju65xM9F96/374G
+MvnRJdo52gOs4HO0czBpfVNdXeeoD/RYgwttfdJrz/4aatsS2Qk2yMYpqZmqEMsb
+rh9W22Lp3rCBvwIDAQABo1AwTjAdBgNVHQ4EFgQU523AJMR8Ds9V8fhf7gu1i0MM
+UqAwHwYDVR0jBBgwFoAU523AJMR8Ds9V8fhf7gu1i0MMUqAwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQUFAAOBgQCI/tvSBYH1iyfLaCTBKwpdj36+MkR9EeJJmImx
+X+bjhKWXwsBX4PDMWvdusr++QGUYtyoya+hfYMXRhXua39mD54xgloQNuu9REDwX
+Ffto+aOw3BcYducz6ofxicFK/Y2VeXDurSMpRv5TfGf2Qr6eOOdaRhj6ed7BibHk
+X1VGZA==
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/test_creds/badserver.key b/src/core/tsi/test_creds/badserver.key
new file mode 100644
index 0000000000..abfbde10ff
--- /dev/null
+++ b/src/core/tsi/test_creds/badserver.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKeZ1e1y29cmBKaW
+oIUwJ5neOJUjx+eD/3nRPe+dvLXEd9+db0fG5RYRR0S3mF1Ywuj4PIxlTW2YprUS
+oGSw+tcqWNIzxv94HjwYFkkvER3AblXcDBh0P2zAkzg+nf9AcAsMh0QpDTyrXtMl
+gqryjq1/vkhFofKMMbY+aXJdG6OBAgMBAAECgYAAgaB51S0A22aMMkxN2rVj6530
+JWWHN4jgD1fGj41wZyWNkWYyq1Ep3ed/N6bIMWp1VbqpGe0/9YQba/D8HOTFHGRt
+72YXnP1e/ds8cxU4x4j1vvqSPtXpMmkiXfXijOvCl9mrMH2xjghFAt6/1Nb9xo1m
+VdcOB8OdSuOIw6CI+QJBAN5FZUbS+bRXDWII/FaAih1DBpwCxhYEN+TXPJBxSen6
+kOzGt5g+mB6YqRMZ/qshshwPq7bsgFGfJ2lIdS2t3GsCQQDBCKifV5AAkOdOUrkK
+HvoX3qnVmyIA8CyvWLcIWpfZ76QAYh0q0StedKdOMXaB1jTeSJ2KU1nlss7UD1Yw
+VbrDAkAwjMHpbW3jiVw//Kx5jIwehiRscWKpLnSzBJyTBFvbwsJjJai2lX2OuVO8
++2GYKb0Iyhd81j3VFkl6grwtpRtPAkB7+n+yt555fpfRKjhGU9b09cHGu7h/OcK5
+bBVCfE0DYHLI/DsXgPiF1g6Onh4rDdUu3xyv9xDKAqnscV099hHZAkEAvcFBfXZs
+tk18N+bUcvXTdZjzZbfLCHlJmwPIspZ8G/6Pn63deg4GVYoCvTwGruah+8y734Ph
+7PskfPgUQlB7Ag==
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/badserver.pem b/src/core/tsi/test_creds/badserver.pem
new file mode 100644
index 0000000000..983c979f31
--- /dev/null
+++ b/src/core/tsi/test_creds/badserver.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICoDCCAgmgAwIBAgIJAPdqwqsKNy81MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZHNlcnZlci50ZXN0Lmdvb2dsZS5j
+b20wHhcNMTQwNzI4MjAwODU0WhcNMjQwNzI1MjAwODU0WjBpMQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRzZXJ2ZXIudGVzdC5nb29nbGUuY29tMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnmdXtctvXJgSmlqCFMCeZ3jiVI8fn
+g/950T3vnby1xHffnW9HxuUWEUdEt5hdWMLo+DyMZU1tmKa1EqBksPrXKljSM8b/
+eB48GBZJLxEdwG5V3AwYdD9swJM4Pp3/QHALDIdEKQ08q17TJYKq8o6tf75IRaHy
+jDG2PmlyXRujgQIDAQABo1AwTjAdBgNVHQ4EFgQU3u/qvHr9knMBeZyAD7mAA/ec
+8cUwHwYDVR0jBBgwFoAU3u/qvHr9knMBeZyAD7mAA/ec8cUwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQUFAAOBgQA/FmR1SGLguxCCfhp4CYCbrAePSyPWDi48gTwj
+vVZf/OMxdVu/H8sBYFf27BjbrEugAw16DElFtgTZ83pLb2BvkUgb6vBUK5sEkgmh
+z88zBsgDp8aCf4STDOLFZMBh/E9ZKkm1zogbEmlTjFp/ceSpa2gNv7OuN4WiorOh
+Wvw40g==
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/test_creds/ca-openssl.cnf b/src/core/tsi/test_creds/ca-openssl.cnf
new file mode 100644
index 0000000000..e97b945e4b
--- /dev/null
+++ b/src/core/tsi/test_creds/ca-openssl.cnf
@@ -0,0 +1,17 @@
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = Some-State
+organizationName = Organization Name (eg, company)
+organizationName_default = Internet Widgits Pty Ltd
+commonName = Common Name (eg, YOUR name)
+commonName_default = testca
+
+[v3_req]
+basicConstraints = CA:true
+keyUsage = critical, keyCertSign
diff --git a/src/core/tsi/test_creds/ca.key b/src/core/tsi/test_creds/ca.key
new file mode 100644
index 0000000000..03c4f950e3
--- /dev/null
+++ b/src/core/tsi/test_creds/ca.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMBA3wVeTGHZR1Ry
+e/i+J8a2cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBl
+LoNY0lSvxDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JA
+HUGodjx+QAi6yCAurUZGvYXGgZSBAgMBAAECgYAxRi8i9BlFlufGSBVoGmydbJOm
+bwLKl9dP3o33ODSP9hok5y6A0w5plWk3AJSF1hPLleK9VcSKYGYnt0clmPVHF35g
+bx2rVK8dOT0mn7rz9Zr70jcSz1ETA2QonHZ+Y+niLmcic9At6hRtWiewblUmyFQm
+GwggIzi7LOyEUHrEcQJBAOXxyQvnLvtKzXiqcsW/K6rExqVJVk+KF0fzzVyMzTJx
+HRBxUVgvGdEJT7j+7P2kcTyafve0BBzDSPIaDyiJ+Y0CQQDWCb7jASFSbu5M3Zcd
+Gkr4ZKN1XO3VLQX10b22bQYdF45hrTN2tnzRvVUR4q86VVnXmiGiTqmLkXcA2WWf
+pHfFAkAhv9olUBo6MeF0i3frBEMRfm41hk0PwZHnMqZ6pgPcGnQMnMU2rzsXzkkQ
+OwJnvAIOxhJKovZTjmofdqmw5odlAkBYVUdRWjsNUTjJwj3GRf6gyq/nFMYWz3EB
+RWFdM1ttkDYzu45ctO2IhfHg4sPceDMO1s6AtKQmNI9/azkUjITdAkApNa9yFRzc
+TBaDNPd5KVd58LVIzoPQ6i7uMHteLXJUWqSroji6S3s4gKMFJ/dO+ZXIlgQgfJJJ
+ZDL4cdrdkeoM
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/ca.pem b/src/core/tsi/test_creds/ca.pem
new file mode 100644
index 0000000000..6c8511a73c
--- /dev/null
+++ b/src/core/tsi/test_creds/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/test_creds/client.key b/src/core/tsi/test_creds/client.key
new file mode 100644
index 0000000000..f48d0735d9
--- /dev/null
+++ b/src/core/tsi/test_creds/client.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAOxUR9uhvhbeVUIM
+s5WbH0px0mehl2+6sZpNjzvE2KimZpHzMJHukVH0Ffkvhs0b8+S5Ut9VNUAqd3IM
+JCCAEGtRNoQhM1t9Yr2zAckSvbRacp+FL/Cj9eDmyo00KsVGaeefA4Dh4OW+ZhkT
+NKcldXqkSuj1sEf244JZYuqZp6/tAgMBAAECgYEAi2NSVqpZMafE5YYUTcMGe6QS
+k2jtpsqYgggI2RnLJ/2tNZwYI5pwP8QVSbnMaiF4gokD5hGdrNDfTnb2v+yIwYEH
+0w8+oG7Z81KodsiZSIDJfTGsAZhVNwOz9y0VD8BBZZ1/274Zh52AUKLjZS/ZwIbS
+W2ywya855dPnH/wj+0ECQQD9X8D920kByTNHhBG18biAEZ4pxs9f0OAG8333eVcI
+w2lJDLsYDZrCB2ocgA3lUdozlzPC7YDYw8reg0tkiRY5AkEA7sdNzOeQsQRn7++5
+0bP9DtT/iON1gbfxRzCfCfXdoOtfQWIzTePWtURt9X/5D9NofI0Rg5W2oGy/MLe5
+/sXHVQJBAIup5XrJDkQywNZyAUU2ecn2bCWBFjwtqd+LBmuMciI9fOKsZtEKZrz/
+U0lkeMRoSwvXE8wmGLjjrAbdfohrXFkCQQDZEx/LtIl6JINJQiswVe0tWr6k+ASP
+1WXoTm+HYpoF/XUvv9LccNF1IazFj34hwRQwhx7w/V52Ieb+p0jUMYGxAkEAjDhd
+9pBO1fKXWiXzi9ZKfoyTNcUq3eBSVKwPG2nItg5ycXengjT5sgcWDnciIzW7BIVI
+JiqOszq9GWESErAatg==
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/client.pem b/src/core/tsi/test_creds/client.pem
new file mode 100644
index 0000000000..e332091019
--- /dev/null
+++ b/src/core/tsi/test_creds/client.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCAQEwDQYJKoZIhvcNAQEFBQAwVjELMAkGA1UEBhMCQVUxEzARBgNV
+BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
+ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTE0MDcxNzIzNTYwMloXDTI0MDcxNDIzNTYw
+MlowWjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKdGVzdGNsaWVudDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7FRH26G+Ft5VQgyzlZsfSnHSZ6GX
+b7qxmk2PO8TYqKZmkfMwke6RUfQV+S+GzRvz5LlS31U1QCp3cgwkIIAQa1E2hCEz
+W31ivbMByRK9tFpyn4Uv8KP14ObKjTQqxUZp558DgOHg5b5mGRM0pyV1eqRK6PWw
+R/bjglli6pmnr+0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAStSm5PM7ubROiKK6/
+T2FkKlhiTOx+Ryenm3Eio59emq+jXl+1nhPySX5G2PQzSR5vd1dIhwgZSR4Gyttk
+tRZ57k/NI1brUW8joiEOMJA/Mr7H7asx7wIRYDE91Fs8GkKWd5LhoPAQj+qdG35C
+OO+svdkmqH0KZo320ZUqdl2ooQ==
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/test_creds/server0.key b/src/core/tsi/test_creds/server0.key
new file mode 100644
index 0000000000..add153c9ae
--- /dev/null
+++ b/src/core/tsi/test_creds/server0.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANOmffupIGC8YDau
+rOF4eKnHwPszgpkkhWzKsVxhNDBxCVYx4TEjG0XWIO0iyRXupZbUC+7N/8HnEVNa
+8F1jYhng14Iiq99cNQbbnuHHhIztmpocrJTxmnhGzoAnRa1Tb+GnAuRoIHRA/V2c
+VUE9tbikQugFx/SPgXAw6tfWB+YvAgMBAAECgYEAoEq9qzUBgoHoVEGiSPiWWe8g
+5p6yUA1qx2QTQyWTAwT4z0DjjfVKmG99bFsl8+hTnJFnoCp/gnjflEOROwkjp5kG
+m0drqOPx1jeipJjpXYTBu49h+WpZ1PF+KhVtxsIm3OOCvh67iWaKyyOVb5Og8aiR
+jl6dn/TdG/dlGD8AfUECQQDuNMle6p0oU8amC6O9wIMBroxx2nFstzE6O35PLEzG
+/tj0kxxn9Jp2TS9mGaLCzSuXmpjlF4+NOWiBPkrLC2TfAkEA43Xg7uEUkaJAz2/W
+m1lIBTLt+4rIQY/2emh33bDcA+rv8rwwrMMIv17/xPx7bs49YqGG5xufD+Rwl6TL
+qFXYsQJAPrOwagax1aKvwJeBw3oAQhoTKAkLIEXcdGqipe6QSzVcIIz0xjxxyEAr
+AOIwoLxnBCISqwMXq2H4K0UdZPMb2wJAdhdYLY1L6YRMk6XjzImg25oidisKZweA
+FvMv8DgHMj2CUAqmVrt3SivfLH1M9C09L3zfFhOAFHcsgX58gav4MQJBANSBnrHj
+tIq4l8z79CPUIuu3QyeEh+XwY8s5qE5CNTck0U59lzp9NvENHbkx3KO896TTerko
++8bXHMLkJkHPXms=
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/server0.pem b/src/core/tsi/test_creds/server0.pem
new file mode 100644
index 0000000000..ade75d8563
--- /dev/null
+++ b/src/core/tsi/test_creds/server0.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHDCCAYUCAQQwDQYJKoZIhvcNAQEFBQAwVjELMAkGA1UEBhMCQVUxEzARBgNV
+BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
+ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTE0MDcyMjE3NTk0OVoXDTI0MDcxOTE3NTk0
+OVowVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxFDASBgNVBAoM
+C0dvb2dsZSBJbmMuMR0wGwYDVQQDDBQqLnRlc3QuZ29vZ2xlLmNvbS5hdTCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA06Z9+6kgYLxgNq6s4Xh4qcfA+zOCmSSF
+bMqxXGE0MHEJVjHhMSMbRdYg7SLJFe6lltQL7s3/wecRU1rwXWNiGeDXgiKr31w1
+Btue4ceEjO2amhyslPGaeEbOgCdFrVNv4acC5GggdED9XZxVQT21uKRC6AXH9I+B
+cDDq19YH5i8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBtfR5qXG9TTI8YcYh7sA4V
+GeNoplp0x6p7OG0NLvbJqAkUnkvjIkk1m1R2AUHhbkxzx6G75JIOoNJcWrCzywBA
+BIsaTdmnNysf/s1hQJuD3IHiVb+7Ji0jhttnJlYcMid4o0tJO/a2E9YUxR+9cg0i
+obb+Ql3qsvKdWBC1dDLDLw==
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/test_creds/server1-openssl.cnf b/src/core/tsi/test_creds/server1-openssl.cnf
new file mode 100644
index 0000000000..8a02108289
--- /dev/null
+++ b/src/core/tsi/test_creds/server1-openssl.cnf
@@ -0,0 +1,26 @@
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName = Country Name (2 letter code)
+countryName_default = US
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = Illinois
+localityName = Locality Name (eg, city)
+localityName_default = Chicago
+organizationName = Organization Name (eg, company)
+organizationName_default = Example, Co.
+commonName = Common Name (eg, YOUR name)
+commonName_max = 64
+
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = *.test.google.fr
+DNS.2 = waterzooi.test.google.be
+DNS.3 = *.test.youtube.com
+IP.1 = "192.168.1.3"
diff --git a/src/core/tsi/test_creds/server1.key b/src/core/tsi/test_creds/server1.key
new file mode 100644
index 0000000000..143a5b8765
--- /dev/null
+++ b/src/core/tsi/test_creds/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/src/core/tsi/test_creds/server1.pem b/src/core/tsi/test_creds/server1.pem
new file mode 100644
index 0000000000..8e582e571f
--- /dev/null
+++ b/src/core/tsi/test_creds/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
+MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
+c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
+JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
+RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
+3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
+BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
+b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
+KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
+wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
+aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/transport_security.c b/src/core/tsi/transport_security.c
new file mode 100644
index 0000000000..94252e36d0
--- /dev/null
+++ b/src/core/tsi/transport_security.c
@@ -0,0 +1,372 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/tsi/transport_security.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Utils. --- */
+
+char* tsi_strdup(const char* src) {
+ char* dst;
+ uint32_t len;
+ if (!src) return NULL;
+ len = strlen(src) + 1;
+ dst = malloc(len);
+ if (!dst) return NULL;
+ memcpy(dst, src, len);
+ return dst;
+}
+
+/* --- tsi_result common implementation. --- */
+
+const char* tsi_result_to_string(tsi_result result) {
+ switch (result) {
+ case TSI_OK:
+ return "TSI_OK";
+ case TSI_UNKNOWN_ERROR:
+ return "TSI_UNKNOWN_ERROR";
+ case TSI_INVALID_ARGUMENT:
+ return "TSI_INVALID_ARGUMENT";
+ case TSI_PERMISSION_DENIED:
+ return "TSI_PERMISSION_DENIED";
+ case TSI_INCOMPLETE_DATA:
+ return "TSI_INCOMPLETE_DATA";
+ case TSI_FAILED_PRECONDITION:
+ return "TSI_FAILED_PRECONDITION";
+ case TSI_UNIMPLEMENTED:
+ return "TSI_UNIMPLEMENTED";
+ case TSI_INTERNAL_ERROR:
+ return "TSI_INTERNAL_ERROR";
+ case TSI_DATA_CORRUPTED:
+ return "TSI_DATA_CORRUPTED";
+ case TSI_NOT_FOUND:
+ return "TSI_NOT_FOUND";
+ case TSI_PROTOCOL_FAILURE:
+ return "TSI_PROTOCOL_FAILURE";
+ case TSI_HANDSHAKE_IN_PROGRESS:
+ return "TSI_HANDSHAKE_IN_PROGRESS";
+ case TSI_OUT_OF_RESOURCES:
+ return "TSI_OUT_OF_RESOURCES";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+
+/* --- tsi_frame_protector common implementation. ---
+
+ Calls specific implementation after state/input validation. */
+
+tsi_result tsi_frame_protector_protect(
+ tsi_frame_protector* self,
+ const unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size) {
+ if (self == NULL || unprotected_bytes == NULL ||
+ unprotected_bytes_size == NULL || protected_output_frames == NULL ||
+ protected_output_frames_size == NULL) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ return self->vtable->protect(self, unprotected_bytes, unprotected_bytes_size,
+ protected_output_frames,
+ protected_output_frames_size);
+}
+
+tsi_result tsi_frame_protector_protect_flush(
+ tsi_frame_protector* self,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size,
+ uint32_t* still_pending_size) {
+ if (self == NULL || protected_output_frames == NULL ||
+ protected_output_frames == NULL || still_pending_size == NULL) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ return self->vtable->protect_flush(self, protected_output_frames,
+ protected_output_frames_size,
+ still_pending_size);
+}
+
+tsi_result tsi_frame_protector_unprotect(
+ tsi_frame_protector* self,
+ const unsigned char* protected_frames_bytes,
+ uint32_t* protected_frames_bytes_size,
+ unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size) {
+ if (self == NULL || protected_frames_bytes == NULL ||
+ protected_frames_bytes_size == NULL || unprotected_bytes == NULL ||
+ unprotected_bytes_size == NULL) {
+ return TSI_INVALID_ARGUMENT;
+ }
+ return self->vtable->unprotect(self, protected_frames_bytes,
+ protected_frames_bytes_size, unprotected_bytes,
+ unprotected_bytes_size);
+}
+
+void tsi_frame_protector_destroy(tsi_frame_protector* self) {
+ if (self == NULL) return;
+ self->vtable->destroy(self);
+}
+
+
+/* --- tsi_handshaker common implementation. ---
+
+ Calls specific implementation after state/input validation. */
+
+tsi_result tsi_handshaker_get_bytes_to_send_to_peer(tsi_handshaker* self,
+ unsigned char* bytes,
+ uint32_t* bytes_size) {
+ if (self == NULL) return TSI_INVALID_ARGUMENT;
+ if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+ return self->vtable->get_bytes_to_send_to_peer(self, bytes, bytes_size);
+}
+
+
+tsi_result tsi_handshaker_process_bytes_from_peer(tsi_handshaker* self,
+ const unsigned char* bytes,
+ uint32_t* bytes_size) {
+ if (self == NULL) return TSI_INVALID_ARGUMENT;
+ if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+ return self->vtable->process_bytes_from_peer(self, bytes, bytes_size);
+}
+
+tsi_result tsi_handshaker_get_result(tsi_handshaker* self) {
+ if (self == NULL) return TSI_INVALID_ARGUMENT;
+ if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+ return self->vtable->get_result(self);
+}
+
+tsi_result tsi_handshaker_extract_peer(tsi_handshaker* self, tsi_peer* peer) {
+ if (self == NULL || peer == NULL) return TSI_INVALID_ARGUMENT;
+ memset(peer, 0, sizeof(tsi_peer));
+ if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+ if (tsi_handshaker_get_result(self) != TSI_OK) {
+ return TSI_FAILED_PRECONDITION;
+ }
+ return self->vtable->extract_peer(self, peer);
+}
+
+tsi_result tsi_handshaker_create_frame_protector(
+ tsi_handshaker* self,
+ uint32_t* max_protected_frame_size,
+ tsi_frame_protector** protector) {
+ tsi_result result;
+ if (self == NULL || protector == NULL) return TSI_INVALID_ARGUMENT;
+ if (self->frame_protector_created) return TSI_FAILED_PRECONDITION;
+ if (tsi_handshaker_get_result(self) != TSI_OK) {
+ return TSI_FAILED_PRECONDITION;
+ }
+ result = self->vtable->create_frame_protector(self, max_protected_frame_size,
+ protector);
+ if (result == TSI_OK) {
+ self->frame_protector_created = 1;
+ }
+ return result;
+}
+
+void tsi_handshaker_destroy(tsi_handshaker* self) {
+ if (self == NULL) return;
+ self->vtable->destroy(self);
+}
+
+
+/* --- tsi_peer implementation. --- */
+
+const tsi_peer_property* tsi_peer_get_property_by_name(const tsi_peer* self,
+ const char* name) {
+ uint32_t i;
+ if (self == NULL) return NULL;
+ for (i = 0; i < self->property_count; i++) {
+ const tsi_peer_property* property = &self->properties[i];
+ if (name == NULL && property->name == NULL) {
+ return property;
+ }
+ if (name != NULL && property->name != NULL &&
+ !strcmp(property->name, name)) {
+ return property;
+ }
+ }
+ return NULL;
+}
+
+tsi_peer_property tsi_init_peer_property(void) {
+ tsi_peer_property property;
+ memset(&property, 0, sizeof(tsi_peer_property));
+ return property;
+}
+
+
+static void tsi_peer_destroy_list_property(tsi_peer_property* children,
+ uint32_t child_count) {
+ uint32_t i;
+ for (i = 0; i < child_count; i++) {
+ tsi_peer_property_destruct(&children[i]);
+ }
+ free(children);
+}
+
+void tsi_peer_property_destruct(tsi_peer_property* property) {
+ if (property->name != NULL) {
+ free(property->name);
+ }
+ switch (property->type) {
+ case TSI_PEER_PROPERTY_TYPE_STRING:
+ if (property->value.string.data != NULL) {
+ free(property->value.string.data);
+ }
+ break;
+ case TSI_PEER_PROPERTY_TYPE_LIST:
+ tsi_peer_destroy_list_property(property->value.list.children,
+ property->value.list.child_count);
+ default:
+ /* Nothing to free. */
+ break;
+ }
+ *property = tsi_init_peer_property(); /* Reset everything to 0. */
+}
+
+void tsi_peer_destruct(tsi_peer* self) {
+ if (self == NULL) return;
+ if (self->properties != NULL) {
+ tsi_peer_destroy_list_property(self->properties, self->property_count);
+ self->properties = NULL;
+ }
+ self->property_count = 0;
+}
+
+tsi_result tsi_construct_signed_integer_peer_property(
+ const char* name, int64_t value, tsi_peer_property* property) {
+ *property = tsi_init_peer_property();
+ property->type = TSI_PEER_PROPERTY_TYPE_SIGNED_INTEGER;
+ if (name != NULL) {
+ property->name = tsi_strdup(name);
+ if (property->name == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+ property->value.signed_int = value;
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_unsigned_integer_peer_property(
+ const char* name, uint64_t value, tsi_peer_property* property) {
+ *property = tsi_init_peer_property();
+ property->type = TSI_PEER_PROPERTY_TYPE_UNSIGNED_INTEGER;
+ if (name != NULL) {
+ property->name = tsi_strdup(name);
+ if (property->name == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+ property->value.unsigned_int = value;
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_real_peer_property(const char* name, double value,
+ tsi_peer_property* property) {
+ *property = tsi_init_peer_property();
+ property->type = TSI_PEER_PROPERTY_TYPE_REAL;
+ if (name != NULL) {
+ property->name = tsi_strdup(name);
+ if (property->name == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+ property->value.real = value;
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_allocated_string_peer_property(
+ const char* name, uint32_t value_length, tsi_peer_property* property) {
+ *property = tsi_init_peer_property();
+ property->type = TSI_PEER_PROPERTY_TYPE_STRING;
+ if (name != NULL) {
+ property->name = tsi_strdup(name);
+ if (property->name == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+ if (value_length > 0) {
+ property->value.string.data = calloc(1, value_length);
+ if (property->value.string.data == NULL) {
+ tsi_peer_property_destruct(property);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ property->value.string.length = value_length;
+ }
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_string_peer_property_from_cstring(
+ const char* name, const char* value, tsi_peer_property* property) {
+ return tsi_construct_string_peer_property(name, value, strlen(value),
+ property);
+}
+
+tsi_result tsi_construct_string_peer_property(const char* name,
+ const char* value,
+ uint32_t value_length,
+ tsi_peer_property* property) {
+ tsi_result result = tsi_construct_allocated_string_peer_property(
+ name, value_length, property);
+ if (result != TSI_OK) return result;
+ if (value_length > 0) {
+ memcpy(property->value.string.data, value, value_length);
+ }
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_list_peer_property(const char* name,
+ uint32_t child_count,
+ tsi_peer_property* property) {
+ *property = tsi_init_peer_property();
+ property->type = TSI_PEER_PROPERTY_TYPE_LIST;
+ if (name != NULL) {
+ property->name = tsi_strdup(name);
+ if (property->name == NULL) return TSI_OUT_OF_RESOURCES;
+ }
+ if (child_count > 0) {
+ property->value.list.children =
+ calloc(child_count, sizeof(tsi_peer_property));
+ if (property->value.list.children == NULL) {
+ tsi_peer_property_destruct(property);
+ return TSI_OUT_OF_RESOURCES;
+ }
+ property->value.list.child_count = child_count;
+ }
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_peer(uint32_t property_count, tsi_peer* peer) {
+ memset(peer, 0, sizeof(tsi_peer));
+ if (property_count > 0) {
+ peer->properties = calloc(property_count, sizeof(tsi_peer_property));
+ if (peer->properties == NULL) return TSI_OUT_OF_RESOURCES;
+ peer->property_count = property_count;
+ }
+ return TSI_OK;
+}
diff --git a/src/core/tsi/transport_security.h b/src/core/tsi/transport_security.h
new file mode 100644
index 0000000000..cf9a2b0195
--- /dev/null
+++ b/src/core/tsi/transport_security.h
@@ -0,0 +1,118 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __TRANSPORT_SECURITY_H_
+#define __TRANSPORT_SECURITY_H_
+
+#include "src/core/tsi/transport_security_interface.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Base for tsi_frame_protector implementations.
+ See transport_security_interface.h for documentation. */
+typedef struct {
+ tsi_result (*protect)(tsi_frame_protector* self,
+ const unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size);
+ tsi_result (*protect_flush)(tsi_frame_protector* self,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size,
+ uint32_t* still_pending_size);
+ tsi_result (*unprotect)(tsi_frame_protector* self,
+ const unsigned char* protected_frames_bytes,
+ uint32_t* protected_frames_bytes_size,
+ unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size);
+ void (*destroy)(tsi_frame_protector* self);
+} tsi_frame_protector_vtable;
+
+struct tsi_frame_protector {
+ const tsi_frame_protector_vtable* vtable;
+};
+
+/* Base for tsi_handshaker implementations.
+ See transport_security_interface.h for documentation. */
+typedef struct {
+ tsi_result (*get_bytes_to_send_to_peer)(tsi_handshaker* self,
+ unsigned char* bytes,
+ uint32_t* bytes_size);
+ tsi_result (*process_bytes_from_peer)(tsi_handshaker* self,
+ const unsigned char* bytes,
+ uint32_t* bytes_size);
+ tsi_result (*get_result)(tsi_handshaker* self);
+ tsi_result (*extract_peer)(tsi_handshaker* self, tsi_peer* peer);
+ tsi_result (*create_frame_protector)(tsi_handshaker* self,
+ uint32_t* max_protected_frame_size,
+ tsi_frame_protector** protector);
+ void (*destroy)(tsi_handshaker* self);
+} tsi_handshaker_vtable;
+
+struct tsi_handshaker {
+ const tsi_handshaker_vtable* vtable;
+ int frame_protector_created;
+};
+
+/* Peer and property construction/destruction functions. */
+tsi_result tsi_construct_peer(uint32_t property_count, tsi_peer* peer);
+tsi_peer_property tsi_init_peer_property(void);
+void tsi_peer_property_destruct(tsi_peer_property* property);
+tsi_result tsi_construct_signed_integer_peer_property(
+ const char* name, int64_t value, tsi_peer_property* property);
+tsi_result tsi_construct_unsigned_integer_peer_property(
+ const char* name, uint64_t value, tsi_peer_property* property);
+tsi_result tsi_construct_real_peer_property(const char* name, double value,
+ tsi_peer_property* property);
+tsi_result tsi_construct_string_peer_property(const char* name,
+ const char* value,
+ uint32_t value_length,
+ tsi_peer_property* property);
+tsi_result tsi_construct_allocated_string_peer_property(
+ const char* name, uint32_t value_length, tsi_peer_property* property);
+tsi_result tsi_construct_string_peer_property_from_cstring(
+ const char* name, const char* value, tsi_peer_property* property);
+tsi_result tsi_construct_list_peer_property(const char* name,
+ uint32_t child_count,
+ tsi_peer_property* property);
+
+/* Utils. */
+char* tsi_strdup(const char* src); /* Sadly, no strdup in C89. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TRANSPORT_SECURITY_H_ */
diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h
new file mode 100644
index 0000000000..6be72c753a
--- /dev/null
+++ b/src/core/tsi/transport_security_interface.h
@@ -0,0 +1,389 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __TRANSPORT_SECURITY_INTERFACE_H_
+#define __TRANSPORT_SECURITY_INTERFACE_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* --- tsi result --- */
+
+typedef enum {
+ TSI_OK = 0,
+ TSI_UNKNOWN_ERROR = 1,
+ TSI_INVALID_ARGUMENT = 2,
+ TSI_PERMISSION_DENIED = 3,
+ TSI_INCOMPLETE_DATA = 4,
+ TSI_FAILED_PRECONDITION = 5,
+ TSI_UNIMPLEMENTED = 6,
+ TSI_INTERNAL_ERROR = 7,
+ TSI_DATA_CORRUPTED = 8,
+ TSI_NOT_FOUND = 9,
+ TSI_PROTOCOL_FAILURE = 10,
+ TSI_HANDSHAKE_IN_PROGRESS = 11,
+ TSI_OUT_OF_RESOURCES = 12
+} tsi_result;
+
+const char* tsi_result_to_string(tsi_result result);
+
+
+/* --- tsi_frame_protector object ---
+
+ This object protects and unprotects buffers once the handshake is done.
+ Implementations of this object must be thread compatible. */
+
+typedef struct tsi_frame_protector tsi_frame_protector;
+
+/* Outputs protected frames.
+ - unprotected_bytes is an input only parameter and points to the data
+ to be protected.
+ - unprotected_bytes_size is an input/output parameter used by the caller to
+ specify how many bytes are available in unprotected_bytes. The output
+ value is the number of bytes consumed during the call.
+ - protected_output_frames points to a buffer allocated by the caller that
+ will be written.
+ - protected_output_frames_size is an input/output parameter used by the
+ caller to specify how many bytes are available in protected_output_frames.
+ As an output, this value indicates the number of bytes written.
+ - This method returns TSI_OK in case of success or a specific error code in
+ case of failure. Note that even if all the input unprotected bytes are
+ consumed, they may not have been processed into the returned protected
+ output frames. The caller should call the protect_flush method
+ to make sure that there are no more protected bytes buffered in the
+ protector.
+
+ A typical way to call this method would be:
+
+ ------------------------------------------------------------------------
+ unsigned char protected_buffer[4096];
+ uint32_t protected_buffer_size = sizeof(protected_buffer);
+ tsi_result result = TSI_OK;
+ while (message_size > 0) {
+ uint32_t protected_buffer_size_to_send = protected_buffer_size;
+ uint32_t processed_message_size = message_size;
+ result = tsi_frame_protector_protect(protector,
+ message_bytes,
+ &processed_message_size,
+ protected_buffer,
+ &protected_buffer_size_to_send);
+ if (result != TSI_OK) break;
+ send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send);
+ message_bytes += processed_message_size;
+ message_size -= processed_message_size;
+
+ // Don't forget to flush.
+ if (message_size == 0) {
+ uint32_t still_pending_size;
+ do {
+ protected_buffer_size_to_send = protected_buffer_size;
+ result = tsi_frame_protector_protect_flush(
+ protector, protected_buffer,
+ &protected_buffer_size_to_send, &still_pending_size);
+ if (result != TSI_OK) break;
+ send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send);
+ } while (still_pending_size > 0);
+ }
+ }
+
+ if (result != TSI_OK) HandleError(result);
+ ------------------------------------------------------------------------ */
+tsi_result tsi_frame_protector_protect(
+ tsi_frame_protector* self,
+ const unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size);
+
+/* Indicates that we need to flush the bytes buffered in the protector and get
+ the resulting frame.
+ - protected_output_frames points to a buffer allocated by the caller that
+ will be written.
+ - protected_output_frames_size is an input/output parameter used by the
+ caller to specify how many bytes are available in protected_output_frames.
+ - still_pending_bytes is an output parameter indicating the number of bytes
+ that still need to be flushed from the protector.*/
+tsi_result tsi_frame_protector_protect_flush(
+ tsi_frame_protector* self,
+ unsigned char* protected_output_frames,
+ uint32_t* protected_output_frames_size,
+ uint32_t* still_pending_size);
+
+/* Outputs unprotected bytes.
+ - protected_frames_bytes is an input only parameter and points to the
+ protected frames to be unprotected.
+ - protected_frames_bytes_size is an input/output only parameter used by the
+ caller to specify how many bytes are available in protected_bytes. The
+ output value is the number of bytes consumed during the call.
+ Implementations will buffer up to a frame of protected data.
+ - unprotected_bytes points to a buffer allocated by the caller that will be
+ written.
+ - unprotected_bytes_size is an input/output parameter used by the caller to
+ specify how many bytes are available in unprotected_bytes. This
+ value is expected to be at most max_protected_frame_size minus overhead
+ which means that max_protected_frame_size is a safe bet. The output value
+ is the number of bytes actually written.
+
+ - This method returns TSI_OK in case of success. Success includes cases where
+ there is not enough data to output a frame in which case
+ unprotected_bytes_size will be set to 0 and cases where the internal buffer
+ needs to be read before new protected data can be processed in which case
+ protected_frames_size will be set to 0. */
+tsi_result tsi_frame_protector_unprotect(
+ tsi_frame_protector* self,
+ const unsigned char* protected_frames_bytes,
+ uint32_t* protected_frames_bytes_size,
+ unsigned char* unprotected_bytes,
+ uint32_t* unprotected_bytes_size);
+
+/* Destroys the tsi_frame_protector object. */
+void tsi_frame_protector_destroy(tsi_frame_protector* self);
+
+
+/* --- tsi_peer objects ---
+
+ tsi_peer objects are a set of properties. The peer owns the properties. */
+
+/* This property is of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_CERTIFICATE_TYPE_PEER_PROPERTY "certificate_type"
+
+/* This property is of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name"
+
+/* This property is of type TSI_PEER_PROPERTY_LIST and the children contain
+ unnamed (name == NULL) properties of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY \
+ "x509_subject_alternative_names"
+
+/* This property is of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol"
+
+/* This property is of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_MDB_USER_NAME_PEER_PROPERTY "mdb_user_name"
+
+/* This property is of type TSI_PEER_PROPERTY_SIGNED_INTEGER. */
+#define TSI_MDB_GAIA_ID_PEER_PROPERTY "mdb_gaia_id"
+
+/* Properties of type TSI_PEER_PROPERTY_TYPE_STRING may contain NULL characters
+ just like C++ strings. The length field gives the length of the string. */
+typedef enum {
+ TSI_PEER_PROPERTY_TYPE_SIGNED_INTEGER,
+ TSI_PEER_PROPERTY_TYPE_UNSIGNED_INTEGER,
+ TSI_PEER_PROPERTY_TYPE_REAL,
+ TSI_PEER_PROPERTY_TYPE_STRING,
+ TSI_PEER_PROPERTY_TYPE_LIST
+} tsi_peer_property_type;
+
+/* The relevant field in the union value is dictated by the type field.
+ name may be NULL in case of an unnamed property. */
+typedef struct tsi_peer_property {
+ char* name;
+ tsi_peer_property_type type;
+ union {
+ int64_t signed_int;
+ uint64_t unsigned_int;
+ double real;
+ struct {
+ char* data;
+ uint32_t length;
+ } string;
+ struct {
+ struct tsi_peer_property* children;
+ uint32_t child_count;
+ } list;
+ } value;
+} tsi_peer_property;
+
+typedef struct {
+ tsi_peer_property* properties;
+ uint32_t property_count;
+} tsi_peer;
+
+/* Gets the first property with the specified name. Iteration over the
+ properties of the peer should be used if the client of the API is expecting
+ several properties with the same name.
+ Returns NULL if there is no corresponding property. */
+const tsi_peer_property* tsi_peer_get_property_by_name(const tsi_peer* self,
+ const char* name);
+
+/* Destructs the tsi_peer object. */
+void tsi_peer_destruct(tsi_peer* self);
+
+/* --- tsi_handshaker objects ----
+
+ Implementations of this object must be thread compatible.
+
+ A typical usage of this object would be:
+
+ ------------------------------------------------------------------------
+ tsi_result result = TSI_OK;
+ unsigned char buf[4096];
+ uint32_t buf_offset;
+ uint32_t buf_size;
+ while (1) {
+ // See if we need to send some bytes to the peer.
+ do {
+ uint32_t buf_size_to_send = sizeof(buf);
+ result = tsi_handshaker_get_bytes_to_send_to_peer(handshaker, buf,
+ &buf_size_to_send);
+ if (buf_size_to_send > 0) send_bytes_to_peer(buf, buf_size_to_send);
+ } while (result == TSI_INCOMPLETE_DATA);
+ if (result != TSI_OK) return result;
+ if (!tsi_handshaker_is_in_progress(handshaker)) break;
+
+ do {
+ // Read bytes from the peer.
+ buf_size = sizeof(buf);
+ buf_offset = 0;
+ read_bytes_from_peer(buf, &buf_size);
+ if (buf_size == 0) break;
+
+ // Process the bytes from the peer. We have to be careful as these bytes
+ // may contain non-handshake data (protected data). If this is the case,
+ // we will exit from the loop with buf_size > 0.
+ uint32_t consumed_by_handshaker = buf_size;
+ result = tsi_handshaker_process_bytes_from_peer(
+ handshaker, buf, &consumed_by_handshaker);
+ buf_size -= consumed_by_handshaker;
+ buf_offset += consumed_by_handshaker;
+ } while (result == TSI_INCOMPLETE_DATA);
+
+ if (result != TSI_OK) return result;
+ if (!tsi_handshaker_is_in_progress(handshaker)) break;
+ }
+
+ // Check the Peer.
+ tsi_peer peer;
+ do {
+ result = tsi_handshaker_extract_peer(handshaker, &peer);
+ if (result != TSI_OK) break;
+ result = check_peer(&peer);
+ } while (0);
+ tsi_peer_destruct(&peer);
+ if (result != TSI_OK) return result;
+
+ // Create the protector.
+ tsi_frame_protector* protector = NULL;
+ result = tsi_handshaker_create_frame_protector(handshaker, NULL,
+ &protector);
+ if (result != TSI_OK) return result;
+
+ // Do not forget to unprotect outstanding data if any.
+ if (buf_size > 0) {
+ result = tsi_frame_protector_unprotect(protector, buf + buf_offset,
+ buf_size, ..., ...);
+ ....
+ }
+ ...
+ ------------------------------------------------------------------------ */
+typedef struct tsi_handshaker tsi_handshaker;
+
+/* Gets bytes that need to be sent to the peer.
+ - bytes is the buffer that will be written with the data to be sent to the
+ peer.
+ - bytes_size is an input/output parameter specifying the capacity of the
+ bytes parameter as input and the number of bytes written as output.
+ Returns TSI_OK if all the data to send to the peer has been written or if
+ nothing has to be sent to the peer (in which base bytes_size outputs to 0),
+ otherwise returns TSI_INCOMPLETE_DATA which indicates that this method
+ needs to be called again to get all the bytes to send to the peer (there
+ was more data to write than the specified bytes_size). In case of a fatal
+ error in the handshake, another specific error code is returned. */
+tsi_result tsi_handshaker_get_bytes_to_send_to_peer(tsi_handshaker* self,
+ unsigned char* bytes,
+ uint32_t* bytes_size);
+
+/* Processes bytes received from the peer.
+ - bytes is the buffer containing the data.
+ - bytes_size is an input/output parameter specifying the size of the data as
+ input and the number of bytes consumed as output.
+ Return TSI_OK if the handshake has all the data it needs to process,
+ otherwise return TSI_INCOMPLETE_DATA which indicates that this method
+ needs to be called again to complete the data needed for processing. In
+ case of a fatal error in the handshake, another specific error code is
+ returned. */
+tsi_result tsi_handshaker_process_bytes_from_peer(tsi_handshaker* self,
+ const unsigned char* bytes,
+ uint32_t* bytes_size);
+
+/* Gets the result of the handshaker.
+ Returns TSI_OK if the hanshake completed successfully and there has been no
+ errors. Returns TSI_HANDSHAKE_IN_PROGRESS if the handshaker is not done yet
+ but no error has been encountered so far. Otherwise the handshaker failed
+ with the returned error. */
+tsi_result tsi_handshaker_get_result(tsi_handshaker* self);
+
+/* Returns 1 if the handshake is in progress, 0 otherwise. */
+#define tsi_handshaker_is_in_progress(h) \
+ (tsi_handshaker_get_result((h)) == TSI_HANDSHAKE_IN_PROGRESS)
+
+
+/* This method may return TSI_FAILED_PRECONDITION if
+ tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise
+ assuming the handshaker is not in a fatal error state.
+ The caller is responsible for destructing the peer. */
+tsi_result tsi_handshaker_extract_peer(tsi_handshaker* self, tsi_peer* peer);
+
+/* This method creates a tsi_frame_protector object after the handshake phase
+ is done. After this method has been called successfully, the only method
+ that can be called on this object is Destroy.
+ - max_output_protected_frame_size is an input/output parameter specifying the
+ desired max output protected frame size as input and outputing the actual
+ max output frame size as the output. Passing NULL is OK and will result in
+ the implementation choosing the default maximum protected frame size. Note
+ that this size only applies to outgoing frames (generated with
+ tsi_frame_protector_protect) and not incoming frames (input of
+ tsi_frame_protector_unprotect).
+ - protector is an output parameter pointing to the newly created
+ tsi_frame_protector object.
+ This method may return TSI_FAILED_PRECONDITION if
+ tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise assuming
+ the handshaker is not in a fatal error state.
+ The caller is responsible for destroying the protector. */
+tsi_result tsi_handshaker_create_frame_protector(
+ tsi_handshaker* self,
+ uint32_t* max_output_protected_frame_size,
+ tsi_frame_protector** protector);
+
+/* This method releases the tsi_handshaker object. After this method is called,
+ no other method can be called on the object. */
+void tsi_handshaker_destroy(tsi_handshaker* self);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TRANSPORT_SECURITY_INTERFACE_H_ */
diff --git a/src/core/tsi/transport_security_test_lib.cc b/src/core/tsi/transport_security_test_lib.cc
new file mode 100644
index 0000000000..1b630c9578
--- /dev/null
+++ b/src/core/tsi/transport_security_test_lib.cc
@@ -0,0 +1,363 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/tsi/transport_security_test_lib.h"
+
+#include <memory>
+
+#include "base/commandlineflags.h"
+#include "src/core/tsi/transport_security_interface.h"
+#include "strings/escaping.h"
+#include "strings/strcat.h"
+#include <gtest/gtest.h>
+#include "util/random/mt_random.h"
+
+namespace {
+
+const char kPingRequest[] = "Ping";
+const char kPongResponse[] = "Pong";
+const int kBigMessageSize = 17000;
+
+} // namespace
+
+namespace tsi {
+namespace test {
+
+TransportSecurityTest::TransportSecurityTest() : random_(new MTRandom()) {
+ small_message_ = "Chapi Chapo";
+ big_message_ = RandomString(kBigMessageSize);
+}
+
+string TransportSecurityTest::RandomString(int size) {
+ std::unique_ptr<char[]> buffer(new char[size]);
+ for (int i = 0; i < size; i++) {
+ buffer[i] = random_->Rand8();
+ }
+ return string(buffer.get(), size);
+}
+
+void TransportSecurityTest::SendBytesToPeer(bool is_client, unsigned char* buf,
+ unsigned int buf_size) {
+ string& channel = is_client ? to_server_channel_ : to_client_channel_;
+ LOG(INFO) << (is_client ? "Client:" : "Server") << " sending " << buf_size
+ << " bytes to peer.";
+ channel.append(reinterpret_cast<const char*>(buf), buf_size);
+}
+
+void TransportSecurityTest::ReadBytesFromPeer(bool is_client,
+ unsigned char* buf,
+ unsigned int* buf_size) {
+ string& channel = is_client ? to_client_channel_ : to_server_channel_;
+ unsigned int to_read =
+ *buf_size < channel.size() ? *buf_size : channel.size();
+ memcpy(buf, channel.data(), to_read);
+ *buf_size = to_read;
+ channel.erase(0, to_read);
+ LOG(INFO) << (is_client ? "Client:" : "Server") << " read " << to_read
+ << " bytes from peer.";
+}
+
+void TransportSecurityTest::DoHandshakeStep(bool is_client,
+ unsigned int buf_allocated_size,
+ tsi_handshaker* handshaker,
+ string* remaining_bytes) {
+ tsi_result result = TSI_OK;
+ std::unique_ptr<unsigned char[]> buf(new unsigned char[buf_allocated_size]);
+ unsigned int buf_offset;
+ unsigned int buf_size;
+ // See if we need to send some bytes to the peer.
+ do {
+ unsigned int buf_size_to_send = buf_allocated_size;
+ result = tsi_handshaker_get_bytes_to_send_to_peer(handshaker, buf.get(),
+ &buf_size_to_send);
+ if (buf_size_to_send > 0) {
+ SendBytesToPeer(is_client, buf.get(), buf_size_to_send);
+ }
+ } while (result == TSI_INCOMPLETE_DATA);
+ if (!tsi_handshaker_is_in_progress(handshaker)) return;
+
+ do {
+ // Read bytes from the peer.
+ buf_size = buf_allocated_size;
+ buf_offset = 0;
+ ReadBytesFromPeer(is_client, buf.get(), &buf_size);
+ if (buf_size == 0) break;
+
+ // Process the bytes from the peer. We have to be careful as these bytes
+ // may contain non-handshake data (protected data). If this is the case,
+ // we will exit from the loop with buf_size > 0.
+ unsigned int consumed_by_handshaker = buf_size;
+ result = tsi_handshaker_process_bytes_from_peer(handshaker, buf.get(),
+ &consumed_by_handshaker);
+ buf_size -= consumed_by_handshaker;
+ buf_offset += consumed_by_handshaker;
+ } while (result == TSI_INCOMPLETE_DATA);
+
+ if (!tsi_handshaker_is_in_progress(handshaker)) {
+ remaining_bytes->assign(
+ reinterpret_cast<const char*>(buf.get()) + buf_offset, buf_size);
+ }
+}
+
+void TransportSecurityTest::PerformHandshake() {
+ SetupHandshakers();
+ string remaining_bytes;
+ do {
+ DoHandshakeStep(true, config()->handshake_buffer_size,
+ client_handshaker_.get(), &remaining_bytes);
+ EXPECT_EQ(0, remaining_bytes.size());
+ DoHandshakeStep(false, config()->handshake_buffer_size,
+ server_handshaker_.get(), &remaining_bytes);
+ EXPECT_EQ(0, remaining_bytes.size());
+ } while (tsi_handshaker_is_in_progress(client_handshaker_.get()) ||
+ tsi_handshaker_is_in_progress(server_handshaker_.get()));
+ CheckHandshakeResults();
+}
+
+void TransportSecurityTest::SendMessageToPeer(
+ bool is_client, tsi_frame_protector* protector, const string& message,
+ unsigned int protected_buffer_size) {
+ std::unique_ptr<unsigned char[]> protected_buffer(
+ new unsigned char[protected_buffer_size]);
+ unsigned int message_size = message.size();
+ const unsigned char* message_bytes =
+ reinterpret_cast<const unsigned char*>(message.data());
+ tsi_result result = TSI_OK;
+ while (message_size > 0 && result == TSI_OK) {
+ unsigned int protected_buffer_size_to_send = protected_buffer_size;
+ unsigned int processed_message_size = message_size;
+ result = tsi_frame_protector_protect(
+ protector, message_bytes, &processed_message_size,
+ protected_buffer.get(), &protected_buffer_size_to_send);
+ EXPECT_EQ(TSI_OK, result);
+ SendBytesToPeer(is_client, protected_buffer.get(),
+ protected_buffer_size_to_send);
+ message_bytes += processed_message_size;
+ message_size -= processed_message_size;
+
+ // Flush if we're done.
+ if (message_size == 0) {
+ unsigned int still_pending_size;
+ do {
+ protected_buffer_size_to_send = protected_buffer_size;
+ result = tsi_frame_protector_protect_flush(
+ protector, protected_buffer.get(), &protected_buffer_size_to_send,
+ &still_pending_size);
+ EXPECT_EQ(TSI_OK, result);
+ SendBytesToPeer(is_client, protected_buffer.get(),
+ protected_buffer_size_to_send);
+ } while (still_pending_size > 0 && result == TSI_OK);
+ EXPECT_EQ(TSI_OK, result);
+ }
+ }
+ EXPECT_EQ(TSI_OK, result);
+}
+
+void TransportSecurityTest::ReceiveMessageFromPeer(
+ bool is_client, tsi_frame_protector* protector,
+ unsigned int read_buf_allocated_size,
+ unsigned int message_buf_allocated_size, string* message) {
+ std::unique_ptr<unsigned char[]> read_buffer(
+ new unsigned char[read_buf_allocated_size]);
+ unsigned int read_offset = 0;
+ unsigned int read_from_peer_size = 0;
+ std::unique_ptr<unsigned char[]> message_buffer(
+ new unsigned char[message_buf_allocated_size]);
+ tsi_result result = TSI_OK;
+ bool done = false;
+ while (!done && result == TSI_OK) {
+ if (read_from_peer_size == 0) {
+ read_from_peer_size = read_buf_allocated_size;
+ ReadBytesFromPeer(is_client, read_buffer.get(), &read_from_peer_size);
+ read_offset = 0;
+ }
+ if (read_from_peer_size == 0) done = true;
+ unsigned int message_buffer_size;
+ do {
+ message_buffer_size = message_buf_allocated_size;
+ unsigned int processed_size = read_from_peer_size;
+ result = tsi_frame_protector_unprotect(
+ protector, read_buffer.get() + read_offset, &processed_size,
+ message_buffer.get(), &message_buffer_size);
+ EXPECT_EQ(TSI_OK, result);
+ if (message_buffer_size > 0) {
+ LOG(INFO) << "Wrote " << message_buffer_size << " bytes to message.";
+ message->append(reinterpret_cast<const char*>(message_buffer.get()),
+ message_buffer_size);
+ }
+ read_offset += processed_size;
+ read_from_peer_size -= processed_size;
+ } while ((read_from_peer_size > 0 || message_buffer_size > 0) &&
+ result == TSI_OK);
+ EXPECT_EQ(TSI_OK, result);
+ }
+ EXPECT_EQ(TSI_OK, result);
+}
+
+void TransportSecurityTest::DoRoundTrip(const string& request,
+ const string& response) {
+ PerformHandshake();
+
+ tsi_frame_protector* client_frame_protector;
+ tsi_frame_protector* server_frame_protector;
+ unsigned int client_max_output_protected_frame_size =
+ config()->client_max_output_protected_frame_size();
+ EXPECT_EQ(TSI_OK,
+ tsi_handshaker_create_frame_protector(
+ client_handshaker_.get(),
+ config()->use_client_default_max_output_protected_frame_size()
+ ? nullptr
+ : &client_max_output_protected_frame_size,
+ &client_frame_protector));
+
+ unsigned int server_max_output_protected_frame_size =
+ config()->server_max_output_protected_frame_size();
+ EXPECT_EQ(TSI_OK,
+ tsi_handshaker_create_frame_protector(
+ server_handshaker_.get(),
+ config()->use_server_default_max_output_protected_frame_size()
+ ? nullptr
+ : &server_max_output_protected_frame_size,
+ &server_frame_protector));
+
+ SendMessageToPeer(true, client_frame_protector, request,
+ config()->protected_buffer_size);
+ string retrieved_request;
+ ReceiveMessageFromPeer(
+ false, server_frame_protector, config()->read_buffer_allocated_size,
+ config()->message_buffer_allocated_size, &retrieved_request);
+ EXPECT_EQ(request.size(), retrieved_request.size());
+ EXPECT_EQ(strings::b2a_hex(request), strings::b2a_hex(retrieved_request));
+
+ SendMessageToPeer(false, server_frame_protector, response,
+ config()->protected_buffer_size);
+ string retrieved_response;
+ ReceiveMessageFromPeer(
+ true, client_frame_protector, config()->read_buffer_allocated_size,
+ config()->message_buffer_allocated_size, &retrieved_response);
+ EXPECT_EQ(response.size(), retrieved_response.size());
+ EXPECT_EQ(strings::b2a_hex(response), strings::b2a_hex(retrieved_response));
+
+ tsi_frame_protector_destroy(client_frame_protector);
+ tsi_frame_protector_destroy(server_frame_protector);
+}
+
+void TransportSecurityTest::DoRoundTrip() {
+ DoRoundTrip(config()->client_message, config()->server_message);
+}
+void TransportSecurityTest::PingPong() {
+ PerformHandshake();
+
+ unsigned char to_server[4096];
+ unsigned char to_client[4096];
+ unsigned int max_frame_size = sizeof(to_client);
+ tsi_frame_protector* client_frame_protector;
+ tsi_frame_protector* server_frame_protector;
+ EXPECT_EQ(
+ tsi_handshaker_create_frame_protector(
+ client_handshaker_.get(), &max_frame_size, &client_frame_protector),
+ TSI_OK);
+ EXPECT_EQ(max_frame_size, sizeof(to_client));
+ EXPECT_EQ(
+ tsi_handshaker_create_frame_protector(
+ server_handshaker_.get(), &max_frame_size, &server_frame_protector),
+ TSI_OK);
+ EXPECT_EQ(max_frame_size, sizeof(to_client));
+
+ // Send Ping.
+ unsigned int ping_length = strlen(kPingRequest);
+ unsigned int protected_size = sizeof(to_server);
+ EXPECT_EQ(tsi_frame_protector_protect(
+ client_frame_protector,
+ reinterpret_cast<const unsigned char*>(kPingRequest),
+ &ping_length, to_server, &protected_size),
+ TSI_OK);
+ EXPECT_EQ(ping_length, strlen(kPingRequest));
+ EXPECT_EQ(protected_size, 0);
+ protected_size = sizeof(to_server);
+ unsigned int still_pending_size;
+ EXPECT_EQ(
+ tsi_frame_protector_protect_flush(client_frame_protector, to_server,
+ &protected_size, &still_pending_size),
+ TSI_OK);
+ EXPECT_EQ(still_pending_size, 0);
+ EXPECT_GT(protected_size, strlen(kPingRequest));
+
+ // Receive Ping.
+ unsigned int unprotected_size = sizeof(to_server);
+ unsigned int saved_protected_size = protected_size;
+ EXPECT_EQ(tsi_frame_protector_unprotect(server_frame_protector, to_server,
+ &protected_size, to_server,
+ &unprotected_size),
+ TSI_OK);
+ EXPECT_EQ(saved_protected_size, protected_size);
+ EXPECT_EQ(ping_length, unprotected_size);
+ EXPECT_EQ(string(kPingRequest),
+ string(reinterpret_cast<const char*>(to_server), unprotected_size));
+
+ // Send back Pong.
+ unsigned int pong_length = strlen(kPongResponse);
+ protected_size = sizeof(to_client);
+ EXPECT_EQ(tsi_frame_protector_protect(
+ server_frame_protector,
+ reinterpret_cast<const unsigned char*>(kPongResponse),
+ &pong_length, to_client, &protected_size),
+ TSI_OK);
+ EXPECT_EQ(pong_length, strlen(kPongResponse));
+ EXPECT_EQ(protected_size, 0);
+ protected_size = sizeof(to_client);
+ EXPECT_EQ(
+ tsi_frame_protector_protect_flush(server_frame_protector, to_client,
+ &protected_size, &still_pending_size),
+ TSI_OK);
+ EXPECT_EQ(still_pending_size, 0);
+ EXPECT_GT(protected_size, strlen(kPongResponse));
+
+ // Receive Pong.
+ unprotected_size = sizeof(to_server);
+ saved_protected_size = protected_size;
+ EXPECT_EQ(tsi_frame_protector_unprotect(client_frame_protector, to_client,
+ &protected_size, to_client,
+ &unprotected_size),
+ TSI_OK);
+ EXPECT_EQ(saved_protected_size, protected_size);
+ EXPECT_EQ(pong_length, unprotected_size);
+ EXPECT_EQ(string(kPongResponse),
+ string(reinterpret_cast<const char*>(to_client), unprotected_size));
+
+ tsi_frame_protector_destroy(client_frame_protector);
+ tsi_frame_protector_destroy(server_frame_protector);
+}
+
+} // namespace test
+} // namespace tsi
diff --git a/src/core/tsi/transport_security_test_lib.h b/src/core/tsi/transport_security_test_lib.h
new file mode 100644
index 0000000000..8c9c764c91
--- /dev/null
+++ b/src/core/tsi/transport_security_test_lib.h
@@ -0,0 +1,154 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __TRANSPORT_SECURITY_TEST_LIB_H_
+#define __TRANSPORT_SECURITY_TEST_LIB_H_
+
+#include <memory>
+
+#include "base/commandlineflags.h"
+#include "src/core/tsi/transport_security_interface.h"
+#include "strings/strcat.h"
+#include <gtest/gtest.h>
+#include "util/random/mt_random.h"
+
+namespace tsi {
+namespace test {
+
+class TestConfig {
+ public:
+ TestConfig()
+ : client_message("Chapi Chapo"),
+ server_message("Chapi Chapo"),
+ handshake_buffer_size(4096),
+ read_buffer_allocated_size(4096),
+ message_buffer_allocated_size(4096),
+ protected_buffer_size(16384),
+ use_client_default_max_output_protected_frame_size_(true),
+ use_server_default_max_output_protected_frame_size_(true),
+ client_max_output_protected_frame_size_(0),
+ server_max_output_protected_frame_size_(0) {}
+
+ void set_client_max_output_protected_frame_size(unsigned int size) {
+ use_client_default_max_output_protected_frame_size_ = false;
+ client_max_output_protected_frame_size_ = size;
+ }
+ void set_server_max_output_protected_frame_size(unsigned int size) {
+ use_server_default_max_output_protected_frame_size_ = false;
+ server_max_output_protected_frame_size_ = size;
+ }
+ bool use_client_default_max_output_protected_frame_size() const {
+ return use_client_default_max_output_protected_frame_size_;
+ }
+ bool use_server_default_max_output_protected_frame_size() const {
+ return use_server_default_max_output_protected_frame_size_;
+ }
+ unsigned int client_max_output_protected_frame_size() const {
+ return client_max_output_protected_frame_size_;
+ }
+ unsigned int server_max_output_protected_frame_size() const {
+ return server_max_output_protected_frame_size_;
+ }
+
+ string client_message;
+ string server_message;
+ unsigned int handshake_buffer_size;
+ unsigned int read_buffer_allocated_size;
+ unsigned int message_buffer_allocated_size;
+ unsigned int protected_buffer_size;
+
+ private:
+ bool use_client_default_max_output_protected_frame_size_;
+ bool use_server_default_max_output_protected_frame_size_;
+ unsigned int client_max_output_protected_frame_size_;
+ unsigned int server_max_output_protected_frame_size_;
+};
+
+
+struct TsiHandshakerDeleter {
+ inline void operator()(tsi_handshaker* ptr) { tsi_handshaker_destroy(ptr); }
+};
+typedef std::unique_ptr<tsi_handshaker, TsiHandshakerDeleter>
+ TsiHandshakerUniquePtr;
+
+class TransportSecurityTest : public ::testing::Test {
+ protected:
+ TransportSecurityTest();
+ virtual ~TransportSecurityTest() {}
+ virtual const TestConfig* config() = 0;
+ string RandomString(int size);
+ virtual void SetupHandshakers() = 0;
+ // An implementation-specific verification of the validity of the handshake.
+ virtual void CheckHandshakeResults() = 0;
+ // Do a full handshake.
+ void PerformHandshake();
+ // Send a protected message between the client and server.
+ void SendMessageToPeer(bool is_client, tsi_frame_protector* protector,
+ const string& message,
+ unsigned int protected_buffer_size);
+ void ReceiveMessageFromPeer(bool is_client, tsi_frame_protector* protector,
+ unsigned int read_buf_allocated_size,
+ unsigned int message_buf_allocated_size,
+ string* message);
+
+ // A simple test that does a handshake and sends a message back and forth
+ void PingPong();
+ // A complicated test that can be configured by modifying config().
+ void DoRoundTrip();
+
+ TsiHandshakerUniquePtr client_handshaker_;
+ TsiHandshakerUniquePtr server_handshaker_;
+
+ string small_message_;
+ string big_message_;
+ std::unique_ptr<RandomBase> random_;
+
+ private:
+ // Functions to send raw bytes between the client and server.
+ void SendBytesToPeer(bool is_client, unsigned char* buf,
+ unsigned int buf_size);
+ void ReadBytesFromPeer(bool is_client, unsigned char* buf,
+ unsigned int* buf_size);
+ // Do a single step of the handshake.
+ void DoHandshakeStep(bool is_client, unsigned int buf_allocated_size,
+ tsi_handshaker* handshaker, string* remaining_bytes);
+ void DoRoundTrip(const string& request, const string& response);
+
+ string to_server_channel_;
+ string to_client_channel_;
+};
+
+} // namespace test
+} // namespace tsi
+
+#endif // __TRANSPORT_SECURITY_TEST_LIB_H_