aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/tsi
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/tsi')
-rw-r--r--src/core/tsi/README.md2
-rw-r--r--src/core/tsi/fake_transport_security.c527
-rw-r--r--src/core/tsi/fake_transport_security.h61
-rw-r--r--src/core/tsi/ssl_transport_security.c1572
-rw-r--r--src/core/tsi/ssl_transport_security.h191
-rw-r--r--src/core/tsi/ssl_types.h55
-rw-r--r--src/core/tsi/test_creds/BUILD36
-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.c266
-rw-r--r--src/core/tsi/transport_security.h111
-rw-r--r--src/core/tsi/transport_security_interface.h353
25 files changed, 3468 insertions, 0 deletions
diff --git a/src/core/tsi/README.md b/src/core/tsi/README.md
new file mode 100644
index 0000000000..3ca3c1ef38
--- /dev/null
+++ b/src/core/tsi/README.md
@@ -0,0 +1,2 @@
+# Transport Security Interface
+An abstraction library over crypto and auth modules (typically OpenSSL)
diff --git a/src/core/tsi/fake_transport_security.c b/src/core/tsi/fake_transport_security.c
new file mode 100644
index 0000000000..bbe323df3b
--- /dev/null
+++ b/src/core/tsi/fake_transport_security.c
@@ -0,0 +1,527 @@
+/*
+ *
+ * Copyright 2015, 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/lib/tsi/fake_transport_security.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/port_platform.h>
+#include <grpc/support/useful.h>
+#include "src/core/lib/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;
+ size_t size;
+ size_t allocated_size;
+ size_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;
+ size_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) {
+ tsi_fake_handshake_message 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])) == 0) {
+ *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 = gpr_malloc(frame->allocated_size);
+ if (frame->data == NULL) return 0;
+ } else if (frame->size > frame->allocated_size) {
+ unsigned char *new_data = gpr_realloc(frame->data, frame->size);
+ if (new_data == NULL) {
+ gpr_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,
+ size_t *incoming_bytes_size,
+ tsi_fake_frame *frame) {
+ size_t available_size = *incoming_bytes_size;
+ size_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 = gpr_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 = (size_t)(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 = (size_t)(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 = (size_t)(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,
+ size_t *outgoing_bytes_size,
+ tsi_fake_frame *frame) {
+ size_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, size_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((uint32_t)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) gpr_free(frame->data);
+}
+
+/* --- tsi_frame_protector methods implementation. ---*/
+
+static tsi_result fake_protector_protect(tsi_frame_protector *self,
+ const unsigned char *unprotected_bytes,
+ size_t *unprotected_bytes_size,
+ unsigned char *protected_output_frames,
+ size_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;
+ size_t saved_output_size = *protected_output_frames_size;
+ size_t drained_size = 0;
+ size_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. */
+ size_t written_in_frame_size = 0;
+ store32_little_endian((uint32_t)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,
+ size_t *protected_output_frames_size, size_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((uint32_t)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,
+ size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes,
+ size_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;
+ size_t saved_output_size = *unprotected_bytes_size;
+ size_t drained_size = 0;
+ size_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);
+ gpr_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, size_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) {
+ tsi_fake_handshake_message 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;
+ }
+ if (tsi_tracing_enabled) {
+ 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. */
+ if (tsi_tracing_enabled) {
+ 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, size_t *bytes_size) {
+ tsi_result result = TSI_OK;
+ tsi_fake_handshaker *impl = (tsi_fake_handshaker *)self;
+ tsi_fake_handshake_message 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));
+ }
+ if (tsi_tracing_enabled) {
+ 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. */
+ if (tsi_tracing_enabled) {
+ 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, size_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);
+ gpr_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 = gpr_zalloc(sizeof(*impl));
+ 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(
+ size_t *max_protected_frame_size) {
+ tsi_fake_frame_protector *impl = gpr_zalloc(sizeof(*impl));
+ 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..54a9469b58
--- /dev/null
+++ b/src/core/tsi/fake_transport_security.h
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright 2015, 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 GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H
+#define GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H
+
+#include "src/core/lib/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(
+ size_t *max_protected_frame_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H */
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
new file mode 100644
index 0000000000..53aabdb926
--- /dev/null
+++ b/src/core/tsi/ssl_transport_security.c
@@ -0,0 +1,1572 @@
+/*
+ *
+ * Copyright 2015, 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/lib/tsi/ssl_transport_security.h"
+
+#include <grpc/support/port_platform.h>
+
+#include <limits.h>
+#include <string.h>
+
+/* TODO(jboeuf): refactor inet_ntop into a portability header. */
+/* Note: for whomever reads this and tries to refactor this, this
+ can't be in grpc, it has to be in gpr. */
+#ifdef GPR_WINDOWS
+#include <ws2tcpip.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+#include <grpc/support/thd.h>
+#include <grpc/support/useful.h>
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h> /* For OPENSSL_free */
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "src/core/lib/tsi/ssl_types.h"
+#include "src/core/lib/tsi/transport_security.h"
+
+/* --- Constants. ---*/
+
+#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND 16384
+#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND 1024
+
+/* Putting a macro like this and littering the source file with #if is really
+ bad practice.
+ TODO(jboeuf): refactor all the #if / #endif in a separate module. */
+#ifndef TSI_OPENSSL_ALPN_SUPPORT
+#define TSI_OPENSSL_ALPN_SUPPORT 1
+#endif
+
+/* 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;
+ unsigned char *alpn_protocol_list;
+ size_t alpn_protocol_list_length;
+} 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;
+ size_t ssl_context_count;
+ unsigned char *alpn_protocol_list;
+ size_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;
+ size_t buffer_size;
+ size_t buffer_offset;
+} tsi_ssl_frame_protector;
+
+/* --- Library Initialization. ---*/
+
+static gpr_once init_openssl_once = GPR_ONCE_INIT;
+static gpr_mu *openssl_mutexes = NULL;
+
+static void openssl_locking_cb(int mode, int type, const char *file, int line) {
+ if (mode & CRYPTO_LOCK) {
+ gpr_mu_lock(&openssl_mutexes[type]);
+ } else {
+ gpr_mu_unlock(&openssl_mutexes[type]);
+ }
+}
+
+static unsigned long openssl_thread_id_cb(void) {
+ return (unsigned long)gpr_thd_currentid();
+}
+
+static void init_openssl(void) {
+ int i;
+ int num_locks;
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+ num_locks = CRYPTO_num_locks();
+ GPR_ASSERT(num_locks > 0);
+ openssl_mutexes = gpr_malloc((size_t)num_locks * sizeof(gpr_mu));
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ gpr_mu_init(&openssl_mutexes[i]);
+ }
+ CRYPTO_set_locking_callback(openssl_locking_cb);
+ CRYPTO_set_id_callback(openssl_thread_id_cb);
+}
+
+/* --- 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) && tsi_tracing_enabled) {
+ 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");
+}
+
+/* Returns 1 if name looks like an IP address, 0 otherwise.
+ This is a very rough heuristic, and only handles IPv6 in hexadecimal form. */
+static int looks_like_ip_address(const char *name) {
+ size_t i;
+ size_t dot_count = 0;
+ size_t num_size = 0;
+ for (i = 0; i < strlen(name); i++) {
+ if (name[i] == ':') {
+ /* IPv6 Address in hexadecimal form, : is not allowed in DNS names. */
+ return 1;
+ }
+ if (name[i] >= '0' && name[i] <= '9') {
+ if (num_size > 3) return 0;
+ num_size++;
+ } else if (name[i] == '.') {
+ if (dot_count > 3 || num_size == 0) return 0;
+ dot_count++;
+ num_size = 0;
+ } else {
+ return 0;
+ }
+ }
+ if (dot_count < 3 || num_size == 0) return 0;
+ return 1;
+}
+
+/* Gets the subject CN from an X509 cert. */
+static tsi_result ssl_get_x509_common_name(X509 *cert, unsigned char **utf8,
+ size_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 = (size_t)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;
+ size_t common_name_size;
+ tsi_result result =
+ ssl_get_x509_common_name(cert, &common_name, &common_name_size);
+ if (result != TSI_OK) {
+ if (result == TSI_NOT_FOUND) {
+ common_name = NULL;
+ common_name_size = 0;
+ } else {
+ return result;
+ }
+ }
+ result = tsi_construct_string_peer_property(
+ TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY,
+ common_name == NULL ? "" : (const char *)common_name, common_name_size,
+ property);
+ OPENSSL_free(common_name);
+ return result;
+}
+
+/* Gets the X509 cert in PEM format as a tsi_peer_property. */
+static tsi_result add_pem_certificate(X509 *cert, tsi_peer_property *property) {
+ BIO *bio = BIO_new(BIO_s_mem());
+ if (!PEM_write_bio_X509(bio, cert)) {
+ BIO_free(bio);
+ return TSI_INTERNAL_ERROR;
+ }
+ char *contents;
+ long len = BIO_get_mem_data(bio, &contents);
+ if (len <= 0) {
+ BIO_free(bio);
+ return TSI_INTERNAL_ERROR;
+ }
+ tsi_result result = tsi_construct_string_peer_property(
+ TSI_X509_PEM_CERT_PROPERTY, (const char *)contents, (size_t)len,
+ property);
+ BIO_free(bio);
+ return result;
+}
+
+/* Gets the subject SANs from an X509 cert as a tsi_peer_property. */
+static tsi_result add_subject_alt_names_properties_to_peer(
+ tsi_peer *peer, GENERAL_NAMES *subject_alt_names,
+ size_t subject_alt_name_count) {
+ size_t i;
+ tsi_result result = TSI_OK;
+
+ /* Reset for DNS entries filtering. */
+ peer->property_count -= subject_alt_name_count;
+
+ for (i = 0; i < subject_alt_name_count; i++) {
+ GENERAL_NAME *subject_alt_name =
+ sk_GENERAL_NAME_value(subject_alt_names, TSI_SIZE_AS_SIZE(i));
+ /* Filter out the non-dns entries names. */
+ if (subject_alt_name->type == GEN_DNS) {
+ unsigned char *name = NULL;
+ int name_size;
+ name_size = ASN1_STRING_to_UTF8(&name, subject_alt_name->d.dNSName);
+ if (name_size < 0) {
+ gpr_log(GPR_ERROR, "Could not get utf8 from asn1 string.");
+ result = TSI_INTERNAL_ERROR;
+ break;
+ }
+ result = tsi_construct_string_peer_property(
+ TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, (const char *)name,
+ (size_t)name_size, &peer->properties[peer->property_count++]);
+ OPENSSL_free(name);
+ } else if (subject_alt_name->type == GEN_IPADD) {
+ char ntop_buf[INET6_ADDRSTRLEN];
+ int af;
+
+ if (subject_alt_name->d.iPAddress->length == 4) {
+ af = AF_INET;
+ } else if (subject_alt_name->d.iPAddress->length == 16) {
+ af = AF_INET6;
+ } else {
+ gpr_log(GPR_ERROR, "SAN IP Address contained invalid IP");
+ result = TSI_INTERNAL_ERROR;
+ break;
+ }
+ const char *name = inet_ntop(af, subject_alt_name->d.iPAddress->data,
+ ntop_buf, INET6_ADDRSTRLEN);
+ if (name == NULL) {
+ gpr_log(GPR_ERROR, "Could not get IP string from asn1 octet.");
+ result = TSI_INTERNAL_ERROR;
+ break;
+ }
+
+ result = tsi_construct_string_peer_property_from_cstring(
+ TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, name,
+ &peer->properties[peer->property_count++]);
+ }
+ if (result != TSI_OK) break;
+ }
+ return result;
+}
+
+/* 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. */
+ GENERAL_NAMES *subject_alt_names =
+ X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+ int subject_alt_name_count = (subject_alt_names != NULL)
+ ? (int)sk_GENERAL_NAME_num(subject_alt_names)
+ : 0;
+ size_t property_count;
+ tsi_result result;
+ GPR_ASSERT(subject_alt_name_count >= 0);
+ property_count = (include_certificate_type ? (size_t)1 : 0) +
+ 2 /* common name, certificate */ +
+ (size_t)subject_alt_name_count;
+ result = tsi_construct_peer(property_count, peer);
+ if (result != TSI_OK) return result;
+ do {
+ if (include_certificate_type) {
+ result = tsi_construct_string_peer_property_from_cstring(
+ TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_X509_CERTIFICATE_TYPE,
+ &peer->properties[0]);
+ if (result != TSI_OK) break;
+ }
+ result = peer_property_from_x509_common_name(
+ cert, &peer->properties[include_certificate_type ? 1 : 0]);
+ if (result != TSI_OK) break;
+
+ result = add_pem_certificate(
+ cert, &peer->properties[include_certificate_type ? 2 : 1]);
+ if (result != TSI_OK) break;
+
+ if (subject_alt_name_count != 0) {
+ result = add_subject_alt_names_properties_to_peer(
+ peer, subject_alt_names, (size_t)subject_alt_name_count);
+ if (result != TSI_OK) break;
+ }
+ } while (0);
+
+ if (subject_alt_names != NULL) {
+ sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
+ }
+ if (result != TSI_OK) tsi_peer_destruct(peer);
+ return result;
+}
+
+/* Logs the SSL error stack. */
+static void log_ssl_error_stack(void) {
+ unsigned long err;
+ while ((err = ERR_get_error()) != 0) {
+ char details[256];
+ ERR_error_string_n((uint32_t)err, details, sizeof(details));
+ gpr_log(GPR_ERROR, "%s", details);
+ }
+}
+
+/* Performs an SSL_read and handle errors. */
+static tsi_result do_ssl_read(SSL *ssl, unsigned char *unprotected_bytes,
+ size_t *unprotected_bytes_size) {
+ int read_from_ssl;
+ GPR_ASSERT(*unprotected_bytes_size <= INT_MAX);
+ read_from_ssl =
+ SSL_read(ssl, unprotected_bytes, (int)*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.");
+ log_ssl_error_stack();
+ 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 = (size_t)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,
+ size_t unprotected_bytes_size) {
+ int ssl_write_result;
+ GPR_ASSERT(unprotected_bytes_size <= INT_MAX);
+ ssl_write_result =
+ SSL_write(ssl, unprotected_bytes, (int)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,
+ size_t pem_cert_chain_size) {
+ tsi_result result = TSI_OK;
+ X509 *certificate = NULL;
+ BIO *pem;
+ GPR_ASSERT(pem_cert_chain_size <= INT_MAX);
+ pem = BIO_new_mem_buf((void *)pem_cert_chain, (int)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) {
+ ERR_clear_error();
+ 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,
+ size_t pem_key_size) {
+ tsi_result result = TSI_OK;
+ EVP_PKEY *private_key = NULL;
+ BIO *pem;
+ GPR_ASSERT(pem_key_size <= INT_MAX);
+ pem = BIO_new_mem_buf((void *)pem_key, (int)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, size_t pem_roots_size,
+ STACK_OF(X509_NAME) * *root_names) {
+ tsi_result result = TSI_OK;
+ size_t num_roots = 0;
+ X509 *root = NULL;
+ X509_NAME *root_name = NULL;
+ BIO *pem;
+ X509_STORE *root_store;
+ GPR_ASSERT(pem_roots_size <= INT_MAX);
+ pem = BIO_new_mem_buf((void *)pem_roots, (int)pem_roots_size);
+ 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) {
+ ERR_clear_error();
+ 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,
+ size_t pem_private_key_size, const unsigned char *pem_certificate_chain,
+ size_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.");
+ EC_KEY_free(ecdh);
+ return 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, size_t pem_cert_size, tsi_peer *peer) {
+ tsi_result result = TSI_OK;
+ X509 *cert = NULL;
+ BIO *pem;
+ GPR_ASSERT(pem_cert_size <= INT_MAX);
+ pem = BIO_new_mem_buf((void *)pem_cert, (int)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, size_t *protocol_name_list_length) {
+ uint16_t i;
+ unsigned char *current;
+ *protocol_name_list = NULL;
+ *protocol_name_list_length = 0;
+ if (num_alpn_protocols == 0) return TSI_INVALID_ARGUMENT;
+ 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 += (size_t)alpn_protocols_lengths[i] + 1;
+ }
+ *protocol_name_list = gpr_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) ||
+ ((uintptr_t)(current - *protocol_name_list) !=
+ *protocol_name_list_length)) {
+ return TSI_INTERNAL_ERROR;
+ }
+ return TSI_OK;
+}
+
+// The verification callback is used for clients that don't really care about
+// the server's certificate, but we need to pull it anyway, in case a higher
+// layer wants to look at it. In this case the verification may fail, but
+// we don't really care.
+static int NullVerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) {
+ return 1;
+}
+
+/* --- tsi_frame_protector methods implementation. ---*/
+
+static tsi_result ssl_protector_protect(tsi_frame_protector *self,
+ const unsigned char *unprotected_bytes,
+ size_t *unprotected_bytes_size,
+ unsigned char *protected_output_frames,
+ size_t *protected_output_frames_size) {
+ tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self;
+ int read_from_ssl;
+ size_t available;
+ tsi_result result = TSI_OK;
+
+ /* First see if we have some pending data in the SSL BIO. */
+ int pending_in_ssl = (int)BIO_pending(impl->from_ssl);
+ if (pending_in_ssl > 0) {
+ *unprotected_bytes_size = 0;
+ GPR_ASSERT(*protected_output_frames_size <= INT_MAX);
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ (int)*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 = (size_t)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;
+
+ GPR_ASSERT(*protected_output_frames_size <= INT_MAX);
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ (int)*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 = (size_t)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,
+ size_t *protected_output_frames_size, size_t *still_pending_size) {
+ tsi_result result = TSI_OK;
+ tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self;
+ int read_from_ssl = 0;
+ int pending;
+
+ 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;
+ }
+
+ pending = (int)BIO_pending(impl->from_ssl);
+ GPR_ASSERT(pending >= 0);
+ *still_pending_size = (size_t)pending;
+ if (*still_pending_size == 0) return TSI_OK;
+
+ GPR_ASSERT(*protected_output_frames_size <= INT_MAX);
+ read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames,
+ (int)*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 = (size_t)read_from_ssl;
+ pending = (int)BIO_pending(impl->from_ssl);
+ GPR_ASSERT(pending >= 0);
+ *still_pending_size = (size_t)pending;
+ return TSI_OK;
+}
+
+static tsi_result ssl_protector_unprotect(
+ tsi_frame_protector *self, const unsigned char *protected_frames_bytes,
+ size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes,
+ size_t *unprotected_bytes_size) {
+ tsi_result result = TSI_OK;
+ int written_into_ssl = 0;
+ size_t output_bytes_size = *unprotected_bytes_size;
+ size_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. */
+ GPR_ASSERT(*protected_frames_bytes_size <= INT_MAX);
+ written_into_ssl = BIO_write(impl->into_ssl, protected_frames_bytes,
+ (int)*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 = (size_t)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) gpr_free(impl->buffer);
+ if (impl->ssl != NULL) SSL_free(impl->ssl);
+ gpr_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,
+ size_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;
+ }
+ GPR_ASSERT(*bytes_size <= INT_MAX);
+ bytes_read_from_ssl = BIO_read(impl->from_ssl, bytes, (int)*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 = (size_t)bytes_read_from_ssl;
+ return BIO_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, size_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;
+ }
+ GPR_ASSERT(*bytes_size <= INT_MAX);
+ bytes_written_into_ssl_size =
+ BIO_write(impl->into_ssl, bytes, (int)*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 = (size_t)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_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 = NULL;
+ 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;
+ }
+#if TSI_OPENSSL_ALPN_SUPPORT
+ SSL_get0_alpn_selected(impl->ssl, &alpn_selected, &alpn_selected_len);
+#endif /* TSI_OPENSSL_ALPN_SUPPORT */
+ if (alpn_selected == NULL) {
+ /* Try npn. */
+ SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected,
+ &alpn_selected_len);
+ }
+ if (alpn_selected != NULL) {
+ size_t i;
+ tsi_peer_property *new_properties =
+ gpr_zalloc(sizeof(*new_properties) * (peer->property_count + 1));
+ 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) {
+ gpr_free(new_properties);
+ return result;
+ }
+ if (peer->properties != NULL) gpr_free(peer->properties);
+ peer->property_count++;
+ peer->properties = new_properties;
+ }
+ return result;
+}
+
+static tsi_result ssl_handshaker_create_frame_protector(
+ tsi_handshaker *self, size_t *max_output_protected_frame_size,
+ tsi_frame_protector **protector) {
+ size_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 = gpr_zalloc(sizeof(*protector_impl));
+
+ 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 = gpr_malloc(protector_impl->buffer_size);
+ if (protector_impl->buffer == NULL) {
+ gpr_log(GPR_ERROR,
+ "Could not allocated buffer for tsi_ssl_frame_protector.");
+ gpr_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 */
+ gpr_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 = gpr_zalloc(sizeof(*impl));
+ 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;
+}
+
+static int select_protocol_list(const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *client_list,
+ size_t client_list_len,
+ const unsigned char *server_list,
+ size_t server_list_len) {
+ const unsigned char *client_current = client_list;
+ while ((unsigned int)(client_current - client_list) < client_list_len) {
+ unsigned char client_current_len = *(client_current++);
+ const unsigned char *server_current = server_list;
+ while ((server_current >= server_list) &&
+ (uintptr_t)(server_current - server_list) < server_list_len) {
+ 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__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;
+ if (impl->ssl_context != NULL) SSL_CTX_free(impl->ssl_context);
+ if (impl->alpn_protocol_list != NULL) gpr_free(impl->alpn_protocol_list);
+ gpr_free(impl);
+}
+
+static int client_handshaker_factory_npn_callback(SSL *ssl, unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg) {
+ tsi_ssl_client_handshaker_factory *factory =
+ (tsi_ssl_client_handshaker_factory *)arg;
+ return select_protocol_list((const unsigned char **)out, outlen,
+ factory->alpn_protocol_list,
+ factory->alpn_protocol_list_length, in, inlen);
+}
+
+/* --- 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;
+ size_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) gpr_free(impl->ssl_contexts);
+ if (impl->ssl_context_x509_subject_names != NULL) {
+ gpr_free(impl->ssl_context_x509_subject_names);
+ }
+ if (impl->alpn_protocol_list != NULL) gpr_free(impl->alpn_protocol_list);
+ gpr_free(impl);
+}
+
+static int does_entry_match_name(const char *entry, size_t entry_length,
+ const char *name) {
+ const char *dot;
+ const char *name_subdomain = NULL;
+ size_t name_length = strlen(name);
+ size_t name_subdomain_length;
+ if (entry_length == 0) return 0;
+
+ /* Take care of '.' terminations. */
+ if (name[name_length - 1] == '.') {
+ name_length--;
+ }
+ if (entry[entry_length - 1] == '.') {
+ entry_length--;
+ if (entry_length == 0) return 0;
+ }
+
+ if ((name_length == entry_length) &&
+ strncmp(name, entry, entry_length) == 0) {
+ 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) return 0;
+ name_subdomain_length = strlen(name_subdomain);
+ if (name_subdomain_length < 2) return 0;
+ name_subdomain++; /* Starts after the dot. */
+ name_subdomain_length--;
+ entry += 2; /* Remove *. */
+ entry_length -= 2;
+ dot = strchr(name_subdomain, '.');
+ if ((dot == NULL) || (dot == &name_subdomain[name_subdomain_length - 1])) {
+ gpr_log(GPR_ERROR, "Invalid toplevel subdomain: %s", name_subdomain);
+ return 0;
+ }
+ if (name_subdomain[name_subdomain_length - 1] == '.') {
+ name_subdomain_length--;
+ }
+ return ((entry_length > 0) && (name_subdomain_length == entry_length) &&
+ strncmp(entry, name_subdomain, entry_length) == 0);
+}
+
+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;
+ size_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;
+}
+
+#if TSI_OPENSSL_ALPN_SUPPORT
+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;
+ return select_protocol_list(out, outlen, in, inlen,
+ factory->alpn_protocol_list,
+ factory->alpn_protocol_list_length);
+}
+#endif /* TSI_OPENSSL_ALPN_SUPPORT */
+
+static int server_handshaker_factory_npn_advertised_callback(
+ SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) {
+ tsi_ssl_server_handshaker_factory *factory =
+ (tsi_ssl_server_handshaker_factory *)arg;
+ *out = factory->alpn_protocol_list;
+ GPR_ASSERT(factory->alpn_protocol_list_length <= UINT_MAX);
+ *outlen = (unsigned int)factory->alpn_protocol_list_length;
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/* --- tsi_ssl_handshaker_factory constructors. --- */
+
+tsi_result tsi_create_ssl_client_handshaker_factory(
+ const unsigned char *pem_private_key, size_t pem_private_key_size,
+ const unsigned char *pem_cert_chain, size_t pem_cert_chain_size,
+ const unsigned char *pem_root_certs, size_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;
+
+ gpr_once_init(&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;
+ }
+
+ impl = gpr_zalloc(sizeof(*impl));
+ impl->ssl_context = ssl_context;
+
+ 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) {
+ 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) {
+ gpr_log(GPR_ERROR, "Building alpn list failed with error %s.",
+ tsi_result_to_string(result));
+ break;
+ }
+#if TSI_OPENSSL_ALPN_SUPPORT
+ GPR_ASSERT(impl->alpn_protocol_list_length < UINT_MAX);
+ if (SSL_CTX_set_alpn_protos(
+ ssl_context, impl->alpn_protocol_list,
+ (unsigned int)impl->alpn_protocol_list_length)) {
+ gpr_log(GPR_ERROR, "Could not set alpn protocol list to context.");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+#endif /* TSI_OPENSSL_ALPN_SUPPORT */
+ SSL_CTX_set_next_proto_select_cb(
+ ssl_context, client_handshaker_factory_npn_callback, impl);
+ }
+ } while (0);
+ if (result != TSI_OK) {
+ ssl_client_handshaker_factory_destroy(&impl->base);
+ return result;
+ }
+ SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL);
+ /* TODO(jboeuf): Add revocation verification. */
+
+ 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 size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains,
+ const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count,
+ const unsigned char *pem_client_root_certs,
+ size_t pem_client_root_certs_size, int force_client_auth,
+ 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) {
+ return tsi_create_ssl_server_handshaker_factory_ex(
+ pem_private_keys, pem_private_keys_sizes, pem_cert_chains,
+ pem_cert_chains_sizes, key_cert_pair_count, pem_client_root_certs,
+ pem_client_root_certs_size,
+ force_client_auth ? TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
+ : TSI_DONT_REQUEST_CLIENT_CERTIFICATE,
+ cipher_list, alpn_protocols, alpn_protocols_lengths, num_alpn_protocols,
+ factory);
+}
+
+tsi_result tsi_create_ssl_server_handshaker_factory_ex(
+ const unsigned char **pem_private_keys,
+ const size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains,
+ const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count,
+ const unsigned char *pem_client_root_certs,
+ size_t pem_client_root_certs_size,
+ tsi_client_certificate_request_type client_certificate_request,
+ 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;
+ size_t i = 0;
+
+ gpr_once_init(&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 = gpr_zalloc(sizeof(*impl));
+ impl->base.create_handshaker =
+ ssl_server_handshaker_factory_create_handshaker;
+ impl->base.destroy = ssl_server_handshaker_factory_destroy;
+ impl->ssl_contexts = gpr_zalloc(key_cert_pair_count * sizeof(SSL_CTX *));
+ impl->ssl_context_x509_subject_names =
+ gpr_zalloc(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);
+ switch (client_certificate_request) {
+ case TSI_DONT_REQUEST_CLIENT_CERTIFICATE:
+ SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_NONE, NULL);
+ break;
+ case TSI_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY:
+ SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_PEER,
+ NullVerifyCallback);
+ break;
+ case TSI_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY:
+ SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_PEER, NULL);
+ break;
+ case TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY:
+ SSL_CTX_set_verify(
+ impl->ssl_contexts[i],
+ SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ NullVerifyCallback);
+ break;
+ case TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY:
+ SSL_CTX_set_verify(
+ impl->ssl_contexts[i],
+ SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+ break;
+ }
+ /* 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);
+#if TSI_OPENSSL_ALPN_SUPPORT
+ SSL_CTX_set_alpn_select_cb(impl->ssl_contexts[i],
+ server_handshaker_factory_alpn_callback, impl);
+#endif /* TSI_OPENSSL_ALPN_SUPPORT */
+ SSL_CTX_set_next_protos_advertised_cb(
+ impl->ssl_contexts[i],
+ server_handshaker_factory_npn_advertised_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) {
+ size_t i = 0;
+ size_t san_count = 0;
+ const tsi_peer_property *cn_property = NULL;
+ int like_ip = looks_like_ip_address(name);
+
+ /* Check the SAN first. */
+ for (i = 0; i < peer->property_count; i++) {
+ const tsi_peer_property *property = &peer->properties[i];
+ if (property->name == NULL) continue;
+ if (strcmp(property->name,
+ TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY) == 0) {
+ san_count++;
+
+ if (!like_ip && does_entry_match_name(property->value.data,
+ property->value.length, name)) {
+ return 1;
+ } else if (like_ip &&
+ strncmp(name, property->value.data, property->value.length) ==
+ 0 &&
+ strlen(name) == property->value.length) {
+ /* IP Addresses are exact matches only. */
+ return 1;
+ }
+ } else if (strcmp(property->name,
+ TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY) == 0) {
+ cn_property = property;
+ }
+ }
+
+ /* If there's no SAN, try the CN, but only if its not like an IP Address */
+ if (san_count == 0 && cn_property != NULL && !like_ip) {
+ if (does_entry_match_name(cn_property->value.data,
+ cn_property->value.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..7407246118
--- /dev/null
+++ b/src/core/tsi/ssl_transport_security.h
@@ -0,0 +1,191 @@
+/*
+ *
+ * Copyright 2015, 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 GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H
+#define GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H
+
+#include "src/core/lib/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"
+
+/* This property is of type TSI_PEER_PROPERTY_STRING. */
+#define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name"
+#define TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY \
+ "x509_subject_alternative_name"
+
+#define TSI_X509_PEM_CERT_PROPERTY "x509_pem_cert"
+
+#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol"
+
+/* --- 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, size_t pem_private_key_size,
+ const unsigned char *pem_cert_chain, size_t pem_cert_chain_size,
+ const unsigned char *pem_root_certs, size_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 authenticate the client. If not NULL, the force_client_auth parameter
+ specifies if the server will accept only authenticated clients or both
+ authenticated and non-authenticated clients.
+ - pem_client_root_certs_size is the size of the associated buffer.
+ - force_client_auth, if set to non-zero will force the client to authenticate
+ with an SSL cert. Note that this option is ignored if pem_client_root_certs
+ is NULL or pem_client_roots_certs_size is 0
+ - 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 size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains,
+ const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count,
+ const unsigned char *pem_client_root_certs,
+ size_t pem_client_root_certs_size, int force_client_auth,
+ 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);
+
+/* Same as tsi_create_ssl_server_handshaker_factory method except uses
+ tsi_client_certificate_request_type to support more ways to handle client
+ certificate authentication.
+ - client_certificate_request, if set to non-zero will force the client to
+ authenticate with an SSL cert. Note that this option is ignored if
+ pem_client_root_certs is NULL or pem_client_roots_certs_size is 0 */
+tsi_result tsi_create_ssl_server_handshaker_factory_ex(
+ const unsigned char **pem_private_keys,
+ const size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains,
+ const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count,
+ const unsigned char *pem_client_root_certs,
+ size_t pem_client_root_certs_size,
+ tsi_client_certificate_request_type client_certificate_request,
+ 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.
+ Still TODO(jboeuf):
+ - handle mixed case.
+ - handle %encoded chars.
+ - handle public suffix wildchar more strictly (e.g. *.co.uk) */
+int tsi_ssl_peer_matches_name(const tsi_peer *peer, const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H */
diff --git a/src/core/tsi/ssl_types.h b/src/core/tsi/ssl_types.h
new file mode 100644
index 0000000000..0a988effd0
--- /dev/null
+++ b/src/core/tsi/ssl_types.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright 2015, 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 GRPC_CORE_LIB_TSI_SSL_TYPES_H
+#define GRPC_CORE_LIB_TSI_SSL_TYPES_H
+
+/* A collection of macros to cast between various integer types that are
+ * used differently between BoringSSL and OpenSSL:
+ * TSI_INT_AS_SIZE(x): convert 'int x' to a length parameter for an OpenSSL
+ * function
+ * TSI_SIZE_AS_SIZE(x): convert 'size_t x' to a length parameter for an OpenSSL
+ * function
+ */
+
+#include <openssl/ssl.h>
+
+#ifdef OPENSSL_IS_BORINGSSL
+#define TSI_INT_AS_SIZE(x) ((size_t)(x))
+#define TSI_SIZE_AS_SIZE(x) (x)
+#else
+#define TSI_INT_AS_SIZE(x) (x)
+#define TSI_SIZE_AS_SIZE(x) ((int)(x))
+#endif
+
+#endif /* GRPC_CORE_LIB_TSI_SSL_TYPES_H */
diff --git a/src/core/tsi/test_creds/BUILD b/src/core/tsi/test_creds/BUILD
new file mode 100644
index 0000000000..5cf04caf17
--- /dev/null
+++ b/src/core/tsi/test_creds/BUILD
@@ -0,0 +1,36 @@
+# Copyright 2017, 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.
+
+licenses(["notice"]) # 3-clause BSD
+
+exports_files([
+ "ca.pem",
+ "server1.key",
+ "server1.pem",
+])
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..f3d43fcc5b
--- /dev/null
+++ b/src/core/tsi/test_creds/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx
+MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50
+ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco
+LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg
+zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd
+9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw
+CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy
+em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G
+CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6
+hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh
+y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8
+-----END CERTIFICATE-----
diff --git a/src/core/tsi/transport_security.c b/src/core/tsi/transport_security.c
new file mode 100644
index 0000000000..2cbf381c88
--- /dev/null
+++ b/src/core/tsi/transport_security.c
@@ -0,0 +1,266 @@
+/*
+ *
+ * Copyright 2015, 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/lib/tsi/transport_security.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+/* --- Tracing. --- */
+
+int tsi_tracing_enabled = 0;
+
+/* --- 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,
+ size_t *unprotected_bytes_size,
+ unsigned char *protected_output_frames,
+ size_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,
+ size_t *protected_output_frames_size, size_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,
+ size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes,
+ size_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,
+ size_t *bytes_size) {
+ if (self == NULL || bytes == NULL || bytes_size == 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,
+ size_t *bytes_size) {
+ if (self == NULL || bytes == NULL || bytes_size == 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, size_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. --- */
+
+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,
+ size_t child_count) {
+ size_t i;
+ for (i = 0; i < child_count; i++) {
+ tsi_peer_property_destruct(&children[i]);
+ }
+ gpr_free(children);
+}
+
+void tsi_peer_property_destruct(tsi_peer_property *property) {
+ if (property->name != NULL) {
+ gpr_free(property->name);
+ }
+ if (property->value.data != NULL) {
+ gpr_free(property->value.data);
+ }
+ *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_allocated_string_peer_property(
+ const char *name, size_t value_length, tsi_peer_property *property) {
+ *property = tsi_init_peer_property();
+ if (name != NULL) property->name = gpr_strdup(name);
+ if (value_length > 0) {
+ property->value.data = gpr_zalloc(value_length);
+ property->value.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,
+ size_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.data, value, value_length);
+ }
+ return TSI_OK;
+}
+
+tsi_result tsi_construct_peer(size_t property_count, tsi_peer *peer) {
+ memset(peer, 0, sizeof(tsi_peer));
+ if (property_count > 0) {
+ peer->properties = gpr_zalloc(property_count * sizeof(tsi_peer_property));
+ 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..aaf110ee05
--- /dev/null
+++ b/src/core/tsi/transport_security.h
@@ -0,0 +1,111 @@
+/*
+ *
+ * Copyright 2015, 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 GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_H
+#define GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_H
+
+#include "src/core/lib/tsi/transport_security_interface.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int tsi_tracing_enabled;
+
+/* 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,
+ size_t *unprotected_bytes_size,
+ unsigned char *protected_output_frames,
+ size_t *protected_output_frames_size);
+ tsi_result (*protect_flush)(tsi_frame_protector *self,
+ unsigned char *protected_output_frames,
+ size_t *protected_output_frames_size,
+ size_t *still_pending_size);
+ tsi_result (*unprotect)(tsi_frame_protector *self,
+ const unsigned char *protected_frames_bytes,
+ size_t *protected_frames_bytes_size,
+ unsigned char *unprotected_bytes,
+ size_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,
+ size_t *bytes_size);
+ tsi_result (*process_bytes_from_peer)(tsi_handshaker *self,
+ const unsigned char *bytes,
+ size_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,
+ size_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(size_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_string_peer_property(const char *name,
+ const char *value,
+ size_t value_length,
+ tsi_peer_property *property);
+tsi_result tsi_construct_allocated_string_peer_property(
+ const char *name, size_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);
+
+/* Utils. */
+char *tsi_strdup(const char *src); /* Sadly, no strdup in C89. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRPC_CORE_LIB_TSI_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..3e8c9d7ffe
--- /dev/null
+++ b/src/core/tsi/transport_security_interface.h
@@ -0,0 +1,353 @@
+/*
+ *
+ * Copyright 2015, 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 GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H
+#define GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H
+
+#include <stdint.h>
+#include <stdlib.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;
+
+typedef enum {
+ // Default option
+ TSI_DONT_REQUEST_CLIENT_CERTIFICATE,
+ TSI_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
+ TSI_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY,
+ TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
+ TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY,
+} tsi_client_certificate_request_type;
+
+const char *tsi_result_to_string(tsi_result result);
+
+/* --- tsi tracing --- */
+
+/* Set this early to avoid races */
+extern int tsi_tracing_enabled;
+
+/* --- 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];
+ size_t protected_buffer_size = sizeof(protected_buffer);
+ tsi_result result = TSI_OK;
+ while (message_size > 0) {
+ size_t protected_buffer_size_to_send = protected_buffer_size;
+ size_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) {
+ size_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,
+ size_t *unprotected_bytes_size,
+ unsigned char *protected_output_frames,
+ size_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,
+ size_t *protected_output_frames_size, size_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.
+ If *unprotected_bytes_size is unchanged, there may be more data remaining
+ to unprotect, and the caller should call this function again.
+
+ - 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,
+ size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes,
+ size_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"
+
+/* Property values may contain NULL characters just like C++ strings.
+ The length field gives the length of the string. */
+typedef struct tsi_peer_property {
+ char *name;
+ struct {
+ char *data;
+ size_t length;
+ } value;
+} tsi_peer_property;
+
+typedef struct {
+ tsi_peer_property *properties;
+ size_t property_count;
+} tsi_peer;
+
+/* 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];
+ size_t buf_offset;
+ size_t buf_size;
+ while (1) {
+ // See if we need to send some bytes to the peer.
+ do {
+ size_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.
+ size_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,
+ size_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,
+ size_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, size_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 /* GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H */