[grpc] Add SSL session client cache support

pull/14483/head
Ruslan Nigmatullin 7 years ago
parent c4223da3ef
commit 7ae3733cab
  1. 5
      BUILD
  2. 21
      build.yaml
  3. 19
      include/grpc/grpc_security.h
  4. 1
      include/grpc/grpc_security_constants.h
  5. 4
      include/grpc/impl/codegen/grpc_types.h
  6. 7
      src/core/lib/http/httpcli_security_connector.cc
  7. 11
      src/core/lib/security/credentials/ssl/ssl_credentials.cc
  8. 76
      src/core/lib/security/security_connector/security_connector.cc
  9. 4
      src/core/lib/security/security_connector/security_connector.h
  10. 73
      src/core/tsi/ssl/session_cache/ssl_session.h
  11. 58
      src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
  12. 211
      src/core/tsi/ssl/session_cache/ssl_session_cache.cc
  13. 93
      src/core/tsi/ssl/session_cache/ssl_session_cache.h
  14. 76
      src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
  15. 240
      src/core/tsi/ssl_transport_security.cc
  16. 98
      src/core/tsi/ssl_transport_security.h
  17. 3
      src/python/grpcio_tests/tests/unit/_auth_context_test.py
  18. 17
      test/core/end2end/BUILD
  19. 280
      test/core/end2end/h2_ssl_session_reuse_test.cc
  20. 14
      test/core/tsi/BUILD
  21. 154
      test/core/tsi/ssl_session_cache_test.cc
  22. 159
      test/core/tsi/ssl_transport_security_test.cc

@ -1704,6 +1704,9 @@ grpc_cc_library(
"src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc",
"src/core/tsi/alts/handshaker/alts_tsi_utils.cc",
"src/core/tsi/fake_transport_security.cc",
"src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc",
"src/core/tsi/ssl/session_cache/ssl_session_cache.cc",
"src/core/tsi/ssl/session_cache/ssl_session_openssl.cc",
"src/core/tsi/ssl_transport_security.cc",
"src/core/tsi/transport_security_grpc.cc",
],
@ -1715,6 +1718,8 @@ grpc_cc_library(
"src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h",
"src/core/tsi/alts/handshaker/alts_tsi_utils.h",
"src/core/tsi/fake_transport_security.h",
"src/core/tsi/ssl/session_cache/ssl_session.h",
"src/core/tsi/ssl/session_cache/ssl_session_cache.h",
"src/core/tsi/ssl_transport_security.h",
"src/core/tsi/ssl_types.h",
"src/core/tsi/transport_security_grpc.h",

@ -1037,12 +1037,17 @@ filegroups:
headers:
- src/core/tsi/alts_transport_security.h
- src/core/tsi/fake_transport_security.h
- src/core/tsi/ssl/session_cache/ssl_session.h
- src/core/tsi/ssl/session_cache/ssl_session_cache.h
- src/core/tsi/ssl_transport_security.h
- src/core/tsi/ssl_types.h
- src/core/tsi/transport_security_grpc.h
src:
- src/core/tsi/alts_transport_security.cc
- src/core/tsi/fake_transport_security.cc
- src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
- src/core/tsi/ssl/session_cache/ssl_session_cache.cc
- src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
- src/core/tsi/ssl_transport_security.cc
- src/core/tsi/transport_security_grpc.cc
deps:
@ -4532,6 +4537,22 @@ targets:
- gpr
uses:
- grpc++_test
- name: h2_ssl_session_reuse_test
gtest: true
build: test
language: c++
headers:
- test/core/end2end/end2end_tests.h
src:
- test/core/end2end/h2_ssl_session_reuse_test.cc
deps:
- grpc_test_util
- grpc++
- grpc
- gpr_test_util
- gpr
uses:
- grpc++_test
- name: health_service_end2end_test
gtest: true
build: test

@ -100,6 +100,25 @@ GRPCAPI void grpc_auth_context_add_cstring_property(grpc_auth_context* ctx,
GRPCAPI int grpc_auth_context_set_peer_identity_property_name(
grpc_auth_context* ctx, const char* name);
/** --- SSL Session Cache. ---
A SSL session cache object represents a way to cache client sessions
between connections. Only ticket-based resumption is supported. */
typedef struct grpc_ssl_session_cache grpc_ssl_session_cache;
/** Create LRU cache for client-side SSL sessions with the given capacity.
If capacity is < 1, a default capacity is used instead. */
GRPCAPI grpc_ssl_session_cache* grpc_ssl_session_cache_create_lru(
size_t capacity);
/** Destroy SSL session cache. */
GRPCAPI void grpc_ssl_session_cache_destroy(grpc_ssl_session_cache* cache);
/** Create a channel arg with the given cache object. */
GRPCAPI grpc_arg
grpc_ssl_session_cache_create_channel_arg(grpc_ssl_session_cache* cache);
/** --- grpc_channel_credentials object. ---
A channel credentials object represents a way to authenticate a client on a

@ -29,6 +29,7 @@ extern "C" {
#define GRPC_X509_CN_PROPERTY_NAME "x509_common_name"
#define GRPC_X509_SAN_PROPERTY_NAME "x509_subject_alternative_name"
#define GRPC_X509_PEM_CERT_PROPERTY_NAME "x509_pem_cert"
#define GRPC_SSL_SESSION_REUSED_PROPERTY "ssl_session_reused"
/** Environment variable that points to the default SSL roots file. This file
must be a PEM encoded file with all the roots such as the one that can be

@ -258,6 +258,10 @@ typedef struct {
secure channel is an SSL channel). If this parameter is specified and the
underlying is not an SSL channel, it will just be ignored. */
#define GRPC_SSL_TARGET_NAME_OVERRIDE_ARG "grpc.ssl_target_name_override"
/** If non-zero, a pointer to a session cache (a pointer of type
grpc_ssl_session_cache*). (use grpc_ssl_session_cache_arg_vtable() to fetch
an appropriate pointer arg vtable) */
#define GRPC_SSL_SESSION_CACHE_ARG "grpc.ssl_session_cache"
/** Maximum metadata size, in bytes. Note this limit applies to the max sum of
all metadata key-value entries in a batch of headers. */
#define GRPC_ARG_MAX_METADATA_SIZE "grpc.max_metadata_size"

@ -121,8 +121,11 @@ static grpc_security_status httpcli_ssl_channel_security_connector_create(
if (secure_peer_name != nullptr) {
c->secure_peer_name = gpr_strdup(secure_peer_name);
}
result = tsi_create_ssl_client_handshaker_factory(
nullptr, pem_root_certs, nullptr, nullptr, 0, &c->handshaker_factory);
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_root_certs = pem_root_certs;
result = tsi_create_ssl_client_handshaker_factory_with_options(
&options, &c->handshaker_factory);
if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.",
tsi_result_to_string(result));

@ -24,6 +24,7 @@
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/surface/api_trace.h"
#include "src/core/tsi/ssl_transport_security.h"
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
@ -56,16 +57,22 @@ static grpc_security_status ssl_create_security_connector(
grpc_ssl_credentials* c = reinterpret_cast<grpc_ssl_credentials*>(creds);
grpc_security_status status = GRPC_SECURITY_OK;
const char* overridden_target_name = nullptr;
tsi_ssl_session_cache* ssl_session_cache = nullptr;
for (size_t i = 0; args && i < args->num_args; i++) {
grpc_arg* arg = &args->args[i];
if (strcmp(arg->key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG) == 0 &&
arg->type == GRPC_ARG_STRING) {
overridden_target_name = arg->value.string;
break;
}
if (strcmp(arg->key, GRPC_SSL_SESSION_CACHE_ARG) == 0 &&
arg->type == GRPC_ARG_POINTER) {
ssl_session_cache =
static_cast<tsi_ssl_session_cache*>(arg->value.pointer.p);
}
}
status = grpc_ssl_channel_security_connector_create(
creds, call_creds, &c->config, target, overridden_target_name, sc);
creds, call_creds, &c->config, target, overridden_target_name,
ssl_session_cache, sc);
if (status != GRPC_SECURITY_OK) {
return status;
}

@ -542,6 +542,46 @@ grpc_server_security_connector* grpc_fake_server_security_connector_create(
/* --- Ssl implementation. --- */
grpc_ssl_session_cache* grpc_ssl_session_cache_create_lru(size_t capacity) {
tsi_ssl_session_cache* cache = tsi_ssl_session_cache_create_lru(capacity);
return reinterpret_cast<grpc_ssl_session_cache*>(cache);
}
void grpc_ssl_session_cache_destroy(grpc_ssl_session_cache* cache) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(cache);
tsi_ssl_session_cache_unref(tsi_cache);
}
static void* grpc_ssl_session_cache_arg_copy(void* p) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(p);
// destroy call below will unref the pointer.
tsi_ssl_session_cache_ref(tsi_cache);
return p;
}
static void grpc_ssl_session_cache_arg_destroy(void* p) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(p);
tsi_ssl_session_cache_unref(tsi_cache);
}
static int grpc_ssl_session_cache_arg_cmp(void* p, void* q) {
return GPR_ICMP(p, q);
}
grpc_arg grpc_ssl_session_cache_create_channel_arg(
grpc_ssl_session_cache* cache) {
static const grpc_arg_pointer_vtable vtable = {
grpc_ssl_session_cache_arg_copy,
grpc_ssl_session_cache_arg_destroy,
grpc_ssl_session_cache_arg_cmp,
};
return grpc_channel_arg_pointer_create(
const_cast<char*>(GRPC_SSL_SESSION_CACHE_ARG), cache, &vtable);
}
typedef struct {
grpc_channel_security_connector base;
tsi_ssl_client_handshaker_factory* client_handshaker_factory;
@ -760,6 +800,9 @@ grpc_auth_context* tsi_ssl_peer_to_auth_context(const tsi_peer* peer) {
} else if (strcmp(prop->name, TSI_X509_PEM_CERT_PROPERTY) == 0) {
grpc_auth_context_add_property(ctx, GRPC_X509_PEM_CERT_PROPERTY_NAME,
prop->value.data, prop->value.length);
} else if (strcmp(prop->name, TSI_SSL_SESSION_REUSED_PEER_PROPERTY) == 0) {
grpc_auth_context_add_property(ctx, GRPC_SSL_SESSION_REUSED_PROPERTY,
prop->value.data, prop->value.length);
}
}
if (peer_identity_property_name != nullptr) {
@ -983,28 +1026,30 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
grpc_channel_credentials* channel_creds,
grpc_call_credentials* request_metadata_creds,
const grpc_ssl_config* config, const char* target_name,
const char* overridden_target_name, grpc_channel_security_connector** sc) {
size_t num_alpn_protocols = 0;
const char** alpn_protocol_strings =
fill_alpn_protocol_strings(&num_alpn_protocols);
const char* overridden_target_name,
tsi_ssl_session_cache* ssl_session_cache,
grpc_channel_security_connector** sc) {
tsi_result result = TSI_OK;
grpc_ssl_channel_security_connector* c;
const char* pem_root_certs;
char* port;
bool has_key_cert_pair;
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.alpn_protocols =
fill_alpn_protocol_strings(&options.num_alpn_protocols);
if (config == nullptr || target_name == nullptr) {
gpr_log(GPR_ERROR, "An ssl channel needs a config and a target name.");
goto error;
}
if (config->pem_root_certs == nullptr) {
pem_root_certs = grpc_get_default_ssl_roots();
if (pem_root_certs == nullptr) {
options.pem_root_certs = grpc_get_default_ssl_roots();
if (options.pem_root_certs == nullptr) {
gpr_log(GPR_ERROR, "Could not get default pem root certs.");
goto error;
}
} else {
pem_root_certs = config->pem_root_certs;
options.pem_root_certs = config->pem_root_certs;
}
c = static_cast<grpc_ssl_channel_security_connector*>(
@ -1028,10 +1073,13 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
has_key_cert_pair = config->pem_key_cert_pair != nullptr &&
config->pem_key_cert_pair->private_key != nullptr &&
config->pem_key_cert_pair->cert_chain != nullptr;
result = tsi_create_ssl_client_handshaker_factory(
has_key_cert_pair ? config->pem_key_cert_pair : nullptr, pem_root_certs,
ssl_cipher_suites(), alpn_protocol_strings,
static_cast<uint16_t>(num_alpn_protocols), &c->client_handshaker_factory);
if (has_key_cert_pair) {
options.pem_key_cert_pair = config->pem_key_cert_pair;
}
options.cipher_suites = ssl_cipher_suites();
options.session_cache = ssl_session_cache;
result = tsi_create_ssl_client_handshaker_factory_with_options(
&options, &c->client_handshaker_factory);
if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.",
tsi_result_to_string(result));
@ -1040,11 +1088,11 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
goto error;
}
*sc = &c->base;
gpr_free((void*)alpn_protocol_strings);
gpr_free((void*)options.alpn_protocols);
return GRPC_SECURITY_OK;
error:
gpr_free((void*)alpn_protocol_strings);
gpr_free((void*)options.alpn_protocols);
return GRPC_SECURITY_ERROR;
}

@ -212,7 +212,9 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
grpc_channel_credentials* channel_creds,
grpc_call_credentials* request_metadata_creds,
const grpc_ssl_config* config, const char* target_name,
const char* overridden_target_name, grpc_channel_security_connector** sc);
const char* overridden_target_name,
tsi_ssl_session_cache* ssl_session_cache,
grpc_channel_security_connector** sc);
/* Gets the default ssl roots. Returns NULL if not found. */
const char* grpc_get_default_ssl_roots(void);

@ -0,0 +1,73 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H
#include <grpc/support/port_platform.h>
#include <grpc/slice.h>
extern "C" {
#include <openssl/ssl.h>
}
#include "src/core/lib/gprpp/ref_counted.h"
// The main purpose of code here is to provide means to cache SSL sessions
// in a way that they can be shared between connections.
//
// SSL_SESSION stands for single instance of session and is not generally safe
// to share between SSL contexts with different lifetimes. It happens because
// not all SSL implementations guarantee immutability of SSL_SESSION object.
// See SSL_SESSION documentation in BoringSSL and OpenSSL for more details.
namespace tsi {
struct SslSessionDeleter {
void operator()(SSL_SESSION* session) { SSL_SESSION_free(session); }
};
typedef std::unique_ptr<SSL_SESSION, SslSessionDeleter> SslSessionPtr;
/// SslCachedSession is an immutable thread-safe storage for single session
/// representation. It provides means to share SSL session data (e.g. TLS
/// ticket) between encrypted connections regardless of SSL context lifetime.
class SslCachedSession {
public:
// Not copyable nor movable.
SslCachedSession(const SslCachedSession&) = delete;
SslCachedSession& operator=(const SslCachedSession&) = delete;
/// Create single cached instance of \a session.
static grpc_core::UniquePtr<SslCachedSession> Create(SslSessionPtr session);
virtual ~SslCachedSession() = default;
/// Returns a copy of previously cached session.
virtual SslSessionPtr CopySession() const GRPC_ABSTRACT;
GRPC_ABSTRACT_BASE_CLASS
protected:
SslCachedSession() = default;
};
} // namespace tsi
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H */

@ -0,0 +1,58 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#ifdef OPENSSL_IS_BORINGSSL
// BoringSSL allows SSL_SESSION to outlive SSL and SSL_CTX objects which are
// re-created by gRPC on every certificate rotation or subchannel creation.
// BoringSSL guarantees that SSL_SESSION is immutable so it's safe to share
// the same original session object between different threads and connections.
namespace tsi {
namespace {
class BoringSslCachedSession : public SslCachedSession {
public:
BoringSslCachedSession(SslSessionPtr session)
: session_(std::move(session)) {}
SslSessionPtr CopySession() const override {
// SslSessionPtr will dereference on destruction.
SSL_SESSION_up_ref(session_.get());
return SslSessionPtr(session_.get());
}
private:
SslSessionPtr session_;
};
} // namespace
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create(
SslSessionPtr session) {
return grpc_core::UniquePtr<SslCachedSession>(
grpc_core::New<BoringSslCachedSession>(std::move(session)));
}
} // namespace tsi
#endif /* OPENSSL_IS_BORINGSSL */

@ -0,0 +1,211 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
namespace tsi {
static void cache_key_avl_destroy(void* key, void* unused) {}
static void* cache_key_avl_copy(void* key, void* unused) { return key; }
static long cache_key_avl_compare(void* key1, void* key2, void* unused) {
return grpc_slice_cmp(*static_cast<grpc_slice*>(key1),
*static_cast<grpc_slice*>(key2));
}
static void cache_value_avl_destroy(void* value, void* unused) {}
static void* cache_value_avl_copy(void* value, void* unused) { return value; }
// AVL only stores pointers, ownership belonges to the linked list.
static const grpc_avl_vtable cache_avl_vtable = {
cache_key_avl_destroy, cache_key_avl_copy, cache_key_avl_compare,
cache_value_avl_destroy, cache_value_avl_copy,
};
/// Node for single cached session.
class SslSessionLRUCache::Node {
public:
Node(const grpc_slice& key, SslSessionPtr session) : key_(key) {
SetSession(std::move(session));
}
~Node() { grpc_slice_unref(key_); }
// Not copyable nor movable.
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
void* AvlKey() { return &key_; }
/// Returns a copy of the node's cache session.
SslSessionPtr CopySession() const { return session_->CopySession(); }
/// Set the \a session (which is moved) for the node.
void SetSession(SslSessionPtr session) {
session_ = SslCachedSession::Create(std::move(session));
}
private:
friend class SslSessionLRUCache;
grpc_slice key_;
grpc_core::UniquePtr<SslCachedSession> session_;
Node* next_ = nullptr;
Node* prev_ = nullptr;
};
SslSessionLRUCache::SslSessionLRUCache(size_t capacity) : capacity_(capacity) {
GPR_ASSERT(capacity > 0);
gpr_mu_init(&lock_);
entry_by_key_ = grpc_avl_create(&cache_avl_vtable);
}
SslSessionLRUCache::~SslSessionLRUCache() {
Node* node = use_order_list_head_;
while (node) {
Node* next = node->next_;
grpc_core::Delete(node);
node = next;
}
grpc_avl_unref(entry_by_key_, nullptr);
gpr_mu_destroy(&lock_);
}
size_t SslSessionLRUCache::Size() {
grpc_core::mu_guard guard(&lock_);
return use_order_list_size_;
}
SslSessionLRUCache::Node* SslSessionLRUCache::FindLocked(
const grpc_slice& key) {
void* value =
grpc_avl_get(entry_by_key_, const_cast<grpc_slice*>(&key), nullptr);
if (value == nullptr) {
return nullptr;
}
Node* node = static_cast<Node*>(value);
// Move to the beginning.
Remove(node);
PushFront(node);
AssertInvariants();
return node;
}
void SslSessionLRUCache::Put(const char* key, SslSessionPtr session) {
grpc_core::mu_guard guard(&lock_);
Node* node = FindLocked(grpc_slice_from_static_string(key));
if (node != nullptr) {
node->SetSession(std::move(session));
return;
}
grpc_slice key_slice = grpc_slice_from_copied_string(key);
node = grpc_core::New<Node>(key_slice, std::move(session));
PushFront(node);
entry_by_key_ = grpc_avl_add(entry_by_key_, node->AvlKey(), node, nullptr);
AssertInvariants();
if (use_order_list_size_ > capacity_) {
GPR_ASSERT(use_order_list_tail_);
node = use_order_list_tail_;
Remove(node);
// Order matters, key is destroyed after deleting node.
entry_by_key_ = grpc_avl_remove(entry_by_key_, node->AvlKey(), nullptr);
grpc_core::Delete(node);
AssertInvariants();
}
}
SslSessionPtr SslSessionLRUCache::Get(const char* key) {
grpc_core::mu_guard guard(&lock_);
// Key is only used for lookups.
grpc_slice key_slice = grpc_slice_from_static_string(key);
Node* node = FindLocked(key_slice);
if (node == nullptr) {
return nullptr;
}
return node->CopySession();
}
void SslSessionLRUCache::Remove(SslSessionLRUCache::Node* node) {
if (node->prev_ == nullptr) {
use_order_list_head_ = node->next_;
} else {
node->prev_->next_ = node->next_;
}
if (node->next_ == nullptr) {
use_order_list_tail_ = node->prev_;
} else {
node->next_->prev_ = node->prev_;
}
GPR_ASSERT(use_order_list_size_ >= 1);
use_order_list_size_--;
}
void SslSessionLRUCache::PushFront(SslSessionLRUCache::Node* node) {
if (use_order_list_head_ == nullptr) {
use_order_list_head_ = node;
use_order_list_tail_ = node;
node->next_ = nullptr;
node->prev_ = nullptr;
} else {
node->next_ = use_order_list_head_;
node->next_->prev_ = node;
use_order_list_head_ = node;
node->prev_ = nullptr;
}
use_order_list_size_++;
}
#ifndef NDEBUG
static size_t calculate_tree_size(grpc_avl_node* node) {
if (node == nullptr) {
return 0;
}
return 1 + calculate_tree_size(node->left) + calculate_tree_size(node->right);
}
void SslSessionLRUCache::AssertInvariants() {
size_t size = 0;
Node* prev = nullptr;
Node* current = use_order_list_head_;
while (current != nullptr) {
size++;
GPR_ASSERT(current->prev_ == prev);
void* node = grpc_avl_get(entry_by_key_, current->AvlKey(), nullptr);
GPR_ASSERT(node == current);
prev = current;
current = current->next_;
}
GPR_ASSERT(prev == use_order_list_tail_);
GPR_ASSERT(size == use_order_list_size_);
GPR_ASSERT(calculate_tree_size(entry_by_key_.root) == use_order_list_size_);
}
#else
void SslSessionLRUCache::AssertInvariants() {}
#endif
} // namespace tsi

@ -0,0 +1,93 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H
#include <grpc/support/port_platform.h>
#include <grpc/slice.h>
#include <grpc/support/sync.h>
extern "C" {
#include <openssl/ssl.h>
}
#include "src/core/lib/avl/avl.h"
#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
/// Cache for SSL sessions for sessions resumption.
///
/// Older sessions may be evicted from the cache using LRU policy if capacity
/// limit is hit. All sessions are associated with some key, usually server
/// name. Note that servers are required to share session ticket encryption keys
/// in order for cache to be effective.
///
/// This class is thread safe.
namespace tsi {
class SslSessionLRUCache : public grpc_core::RefCounted<SslSessionLRUCache> {
public:
/// Create new LRU cache with the given capacity.
static grpc_core::RefCountedPtr<SslSessionLRUCache> Create(size_t capacity) {
return grpc_core::MakeRefCounted<SslSessionLRUCache>(capacity);
}
// Not copyable nor movable.
SslSessionLRUCache(const SslSessionLRUCache&) = delete;
SslSessionLRUCache& operator=(const SslSessionLRUCache&) = delete;
/// Returns current number of sessions in the cache.
size_t Size();
/// Add \a session in the cache using \a key. This operation may discard older
/// sessions.
void Put(const char* key, SslSessionPtr session);
/// Returns the session from the cache associated with \a key or null if not
/// found.
SslSessionPtr Get(const char* key);
private:
// So New() can call our private ctor.
template <typename T, typename... Args>
friend T* grpc_core::New(Args&&... args);
class Node;
explicit SslSessionLRUCache(size_t capacity);
~SslSessionLRUCache();
Node* FindLocked(const grpc_slice& key);
void Remove(Node* node);
void PushFront(Node* node);
void AssertInvariants();
gpr_mu lock_;
size_t capacity_;
Node* use_order_list_head_ = nullptr;
Node* use_order_list_tail_ = nullptr;
size_t use_order_list_size_ = 0;
grpc_avl entry_by_key_;
};
} // namespace tsi
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H */

@ -0,0 +1,76 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#include <grpc/support/log.h>
#ifndef OPENSSL_IS_BORINGSSL
// OpenSSL invalidates SSL_SESSION on SSL destruction making it pointless
// to cache sessions. The workaround is to serialize (relatively expensive)
// session into binary blob and re-create it from blob on every handshake.
// Note that it's safe to keep serialized session outside of SSL lifetime
// as openssl performs all necessary validation while attempting to use a
// session and creates a new one if something is wrong (e.g. server changed
// set of allowed codecs).
namespace tsi {
namespace {
class OpenSslCachedSession : public SslCachedSession {
public:
OpenSslCachedSession(SslSessionPtr session) {
int size = i2d_SSL_SESSION(session.get(), nullptr);
GPR_ASSERT(size > 0);
grpc_slice slice = grpc_slice_malloc(size_t(size));
unsigned char* start = GRPC_SLICE_START_PTR(slice);
int second_size = i2d_SSL_SESSION(session.get(), &start);
GPR_ASSERT(size == second_size);
serialized_session_ = slice;
}
virtual ~OpenSslCachedSession() { grpc_slice_unref(serialized_session_); }
SslSessionPtr CopySession() const override {
const unsigned char* data = GRPC_SLICE_START_PTR(serialized_session_);
size_t length = GRPC_SLICE_LENGTH(serialized_session_);
SSL_SESSION* session = d2i_SSL_SESSION(nullptr, &data, length);
if (session == nullptr) {
return SslSessionPtr();
}
return SslSessionPtr(session);
}
private:
grpc_slice serialized_session_;
};
} // namespace
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create(
SslSessionPtr session) {
return grpc_core::UniquePtr<SslCachedSession>(
grpc_core::New<OpenSslCachedSession>(std::move(session)));
}
} // namespace tsi
#endif /* OPENSSL_IS_BORINGSSL */

@ -35,6 +35,7 @@
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/sync.h>
#include <grpc/support/thd_id.h>
@ -47,6 +48,8 @@ extern "C" {
#include <openssl/x509v3.h>
}
#include "src/core/lib/gpr/useful.h"
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "src/core/tsi/ssl_types.h"
#include "src/core/tsi/transport_security.h"
@ -78,6 +81,7 @@ struct tsi_ssl_client_handshaker_factory {
SSL_CTX* ssl_context;
unsigned char* alpn_protocol_list;
size_t alpn_protocol_list_length;
grpc_core::RefCountedPtr<tsi::SslSessionLRUCache> session_cache;
};
struct tsi_ssl_server_handshaker_factory {
@ -111,17 +115,19 @@ typedef struct {
/* --- Library Initialization. ---*/
static gpr_once init_openssl_once = GPR_ONCE_INIT;
static gpr_mu* openssl_mutexes = nullptr;
static gpr_once g_init_openssl_once = GPR_ONCE_INIT;
static gpr_mu* g_openssl_mutexes = nullptr;
static int g_ssl_ctx_ex_factory_index = -1;
static void openssl_locking_cb(int mode, int type, const char* file,
int line) GRPC_UNUSED;
static unsigned long openssl_thread_id_cb(void) GRPC_UNUSED;
static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
static void openssl_locking_cb(int mode, int type, const char* file, int line) {
if (mode & CRYPTO_LOCK) {
gpr_mu_lock(&openssl_mutexes[type]);
gpr_mu_lock(&g_openssl_mutexes[type]);
} else {
gpr_mu_unlock(&openssl_mutexes[type]);
gpr_mu_unlock(&g_openssl_mutexes[type]);
}
}
@ -137,13 +143,16 @@ static void init_openssl(void) {
OpenSSL_add_all_algorithms();
num_locks = CRYPTO_num_locks();
GPR_ASSERT(num_locks > 0);
openssl_mutexes = static_cast<gpr_mu*>(
g_openssl_mutexes = static_cast<gpr_mu*>(
gpr_malloc(static_cast<size_t>(num_locks) * sizeof(gpr_mu)));
for (i = 0; i < CRYPTO_num_locks(); i++) {
gpr_mu_init(&openssl_mutexes[i]);
gpr_mu_init(&g_openssl_mutexes[i]);
}
CRYPTO_set_locking_callback(openssl_locking_cb);
CRYPTO_set_id_callback(openssl_thread_id_cb);
g_ssl_ctx_ex_factory_index =
SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
GPR_ASSERT(g_ssl_ctx_ex_factory_index != -1);
}
/* --- Ssl utils. ---*/
@ -721,6 +730,23 @@ static int NullVerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
return 1;
}
/* --- tsi_ssl_session_cache methods implementation. ---*/
tsi_ssl_session_cache* tsi_ssl_session_cache_create_lru(size_t capacity) {
/* Pointer will be dereferenced by unref call. */
return reinterpret_cast<tsi_ssl_session_cache*>(
tsi::SslSessionLRUCache::Create(capacity).release());
}
void tsi_ssl_session_cache_ref(tsi_ssl_session_cache* cache) {
/* Pointer will be dereferenced by unref call. */
reinterpret_cast<tsi::SslSessionLRUCache*>(cache)->Ref().release();
}
void tsi_ssl_session_cache_unref(tsi_ssl_session_cache* cache) {
reinterpret_cast<tsi::SslSessionLRUCache*>(cache)->Unref();
}
/* --- tsi_frame_protector methods implementation. ---*/
static tsi_result ssl_protector_protect(tsi_frame_protector* self,
@ -1015,25 +1041,34 @@ static tsi_result ssl_handshaker_extract_peer(tsi_handshaker* self,
SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected,
&alpn_selected_len);
}
// 1 is for session reused property.
size_t new_property_count = peer->property_count + 1;
if (alpn_selected != nullptr) new_property_count++;
tsi_peer_property* new_properties = static_cast<tsi_peer_property*>(
gpr_zalloc(sizeof(*new_properties) * new_property_count));
for (size_t i = 0; i < peer->property_count; i++) {
new_properties[i] = peer->properties[i];
}
if (peer->properties != nullptr) gpr_free(peer->properties);
peer->properties = new_properties;
if (alpn_selected != nullptr) {
size_t i;
tsi_peer_property* new_properties = static_cast<tsi_peer_property*>(
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,
reinterpret_cast<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 != nullptr) gpr_free(peer->properties);
&peer->properties[peer->property_count]);
if (result != TSI_OK) return result;
peer->property_count++;
peer->properties = new_properties;
}
const char* session_reused = SSL_session_reused(impl->ssl) ? "true" : "false";
result = tsi_construct_string_peer_property(
TSI_SSL_SESSION_REUSED_PEER_PROPERTY, session_reused,
strlen(session_reused) + 1, &peer->properties[peer->property_count]);
if (result != TSI_OK) return result;
peer->property_count++;
return result;
}
@ -1103,6 +1138,19 @@ static const tsi_handshaker_vtable handshaker_vtable = {
/* --- tsi_ssl_handshaker_factory common methods. --- */
static void tsi_ssl_handshaker_resume_session(
SSL* ssl, tsi::SslSessionLRUCache* session_cache) {
const char* server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr) {
return;
}
tsi::SslSessionPtr session = session_cache->Get(server_name);
if (session != nullptr) {
// SSL_set_session internally increments reference counter.
SSL_set_session(ssl, session.get());
}
}
static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
const char* server_name_indication,
tsi_ssl_handshaker_factory* factory,
@ -1139,6 +1187,12 @@ static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
return TSI_INTERNAL_ERROR;
}
}
tsi_ssl_client_handshaker_factory* client_factory =
reinterpret_cast<tsi_ssl_client_handshaker_factory*>(factory);
if (client_factory->session_cache != nullptr) {
tsi_ssl_handshaker_resume_session(ssl,
client_factory->session_cache.get());
}
ssl_result = SSL_do_handshake(ssl);
ssl_result = SSL_get_error(ssl, ssl_result);
if (ssl_result != SSL_ERROR_WANT_READ) {
@ -1214,6 +1268,7 @@ static void tsi_ssl_client_handshaker_factory_destroy(
reinterpret_cast<tsi_ssl_client_handshaker_factory*>(factory);
if (self->ssl_context != nullptr) SSL_CTX_free(self->ssl_context);
if (self->alpn_protocol_list != nullptr) gpr_free(self->alpn_protocol_list);
self->session_cache.reset();
gpr_free(self);
}
@ -1357,6 +1412,30 @@ static int server_handshaker_factory_npn_advertised_callback(
return SSL_TLSEXT_ERR_OK;
}
/// This callback is called when new \a session is established and ready to
/// be cached. This session can be reused for new connections to similar
/// servers at later point of time.
/// It's intended to be used with SSL_CTX_sess_set_new_cb function.
///
/// It returns 1 if callback takes ownership over \a session and 0 otherwise.
static int server_handshaker_factory_new_session_callback(
SSL* ssl, SSL_SESSION* session) {
SSL_CTX* ssl_context = SSL_get_SSL_CTX(ssl);
if (ssl_context == nullptr) {
return 0;
}
void* arg = SSL_CTX_get_ex_data(ssl_context, g_ssl_ctx_ex_factory_index);
tsi_ssl_client_handshaker_factory* factory =
static_cast<tsi_ssl_client_handshaker_factory*>(arg);
const char* server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr) {
return 0;
}
factory->session_cache->Put(server_name, tsi::SslSessionPtr(session));
// Return 1 to indicate transfered ownership over the given session.
return 1;
}
/* --- tsi_ssl_handshaker_factory constructors. --- */
static tsi_ssl_handshaker_factory_vtable client_handshaker_factory_vtable = {
@ -1367,15 +1446,29 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
const char* pem_root_certs, const char* cipher_suites,
const char** alpn_protocols, uint16_t num_alpn_protocols,
tsi_ssl_client_handshaker_factory** factory) {
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_key_cert_pair = pem_key_cert_pair;
options.pem_root_certs = pem_root_certs;
options.cipher_suites = cipher_suites;
options.alpn_protocols = alpn_protocols;
options.num_alpn_protocols = num_alpn_protocols;
return tsi_create_ssl_client_handshaker_factory_with_options(&options,
factory);
}
tsi_result tsi_create_ssl_client_handshaker_factory_with_options(
const tsi_ssl_client_handshaker_options* options,
tsi_ssl_client_handshaker_factory** factory) {
SSL_CTX* ssl_context = nullptr;
tsi_ssl_client_handshaker_factory* impl = nullptr;
tsi_result result = TSI_OK;
gpr_once_init(&init_openssl_once, init_openssl);
gpr_once_init(&g_init_openssl_once, init_openssl);
if (factory == nullptr) return TSI_INVALID_ARGUMENT;
*factory = nullptr;
if (pem_root_certs == nullptr) return TSI_INVALID_ARGUMENT;
if (options->pem_root_certs == nullptr) return TSI_INVALID_ARGUMENT;
ssl_context = SSL_CTX_new(TLSv1_2_method());
if (ssl_context == nullptr) {
@ -1390,21 +1483,33 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
impl->ssl_context = ssl_context;
if (options->session_cache != nullptr) {
// Unref is called manually on factory destruction.
impl->session_cache =
reinterpret_cast<tsi::SslSessionLRUCache*>(options->session_cache)
->Ref();
SSL_CTX_set_ex_data(ssl_context, g_ssl_ctx_ex_factory_index, impl);
SSL_CTX_sess_set_new_cb(ssl_context,
server_handshaker_factory_new_session_callback);
SSL_CTX_set_session_cache_mode(ssl_context, SSL_SESS_CACHE_CLIENT);
}
do {
result =
populate_ssl_context(ssl_context, pem_key_cert_pair, cipher_suites);
result = populate_ssl_context(ssl_context, options->pem_key_cert_pair,
options->cipher_suites);
if (result != TSI_OK) break;
result = ssl_ctx_load_verification_certs(ssl_context, pem_root_certs,
strlen(pem_root_certs), nullptr);
result = ssl_ctx_load_verification_certs(
ssl_context, options->pem_root_certs, strlen(options->pem_root_certs),
nullptr);
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, num_alpn_protocols,
&impl->alpn_protocol_list,
&impl->alpn_protocol_list_length);
if (options->num_alpn_protocols != 0) {
result = build_alpn_protocol_name_list(
options->alpn_protocols, options->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));
@ -1457,15 +1562,32 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
tsi_client_certificate_request_type client_certificate_request,
const char* cipher_suites, const char** alpn_protocols,
uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory) {
tsi_ssl_server_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_key_cert_pairs = pem_key_cert_pairs;
options.num_key_cert_pairs = num_key_cert_pairs;
options.pem_client_root_certs = pem_client_root_certs;
options.client_certificate_request = client_certificate_request;
options.cipher_suites = cipher_suites;
options.alpn_protocols = alpn_protocols;
options.num_alpn_protocols = num_alpn_protocols;
return tsi_create_ssl_server_handshaker_factory_with_options(&options,
factory);
}
tsi_result tsi_create_ssl_server_handshaker_factory_with_options(
const tsi_ssl_server_handshaker_options* options,
tsi_ssl_server_handshaker_factory** factory) {
tsi_ssl_server_handshaker_factory* impl = nullptr;
tsi_result result = TSI_OK;
size_t i = 0;
gpr_once_init(&init_openssl_once, init_openssl);
gpr_once_init(&g_init_openssl_once, init_openssl);
if (factory == nullptr) return TSI_INVALID_ARGUMENT;
*factory = nullptr;
if (num_key_cert_pairs == 0 || pem_key_cert_pairs == nullptr) {
if (options->num_key_cert_pairs == 0 ||
options->pem_key_cert_pairs == nullptr) {
return TSI_INVALID_ARGUMENT;
}
@ -1474,28 +1596,28 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
tsi_ssl_handshaker_factory_init(&impl->base);
impl->base.vtable = &server_handshaker_factory_vtable;
impl->ssl_contexts =
static_cast<SSL_CTX**>(gpr_zalloc(num_key_cert_pairs * sizeof(SSL_CTX*)));
impl->ssl_context_x509_subject_names =
static_cast<tsi_peer*>(gpr_zalloc(num_key_cert_pairs * sizeof(tsi_peer)));
impl->ssl_contexts = static_cast<SSL_CTX**>(
gpr_zalloc(options->num_key_cert_pairs * sizeof(SSL_CTX*)));
impl->ssl_context_x509_subject_names = static_cast<tsi_peer*>(
gpr_zalloc(options->num_key_cert_pairs * sizeof(tsi_peer)));
if (impl->ssl_contexts == nullptr ||
impl->ssl_context_x509_subject_names == nullptr) {
tsi_ssl_handshaker_factory_unref(&impl->base);
return TSI_OUT_OF_RESOURCES;
}
impl->ssl_context_count = num_key_cert_pairs;
impl->ssl_context_count = options->num_key_cert_pairs;
if (num_alpn_protocols > 0) {
result = build_alpn_protocol_name_list(alpn_protocols, num_alpn_protocols,
&impl->alpn_protocol_list,
&impl->alpn_protocol_list_length);
if (options->num_alpn_protocols > 0) {
result = build_alpn_protocol_name_list(
options->alpn_protocols, options->num_alpn_protocols,
&impl->alpn_protocol_list, &impl->alpn_protocol_list_length);
if (result != TSI_OK) {
tsi_ssl_handshaker_factory_unref(&impl->base);
return result;
}
}
for (i = 0; i < num_key_cert_pairs; i++) {
for (i = 0; i < options->num_key_cert_pairs; i++) {
do {
impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method());
if (impl->ssl_contexts[i] == nullptr) {
@ -1504,20 +1626,44 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
break;
}
result = populate_ssl_context(impl->ssl_contexts[i],
&pem_key_cert_pairs[i], cipher_suites);
&options->pem_key_cert_pairs[i],
options->cipher_suites);
if (result != TSI_OK) break;
if (pem_client_root_certs != nullptr) {
// TODO(elessar): Provide ability to disable session ticket keys.
// Allow client cache sessions (it's needed for OpenSSL only).
int set_sid_ctx_result = SSL_CTX_set_session_id_context(
impl->ssl_contexts[i], kSslSessionIdContext,
GPR_ARRAY_SIZE(kSslSessionIdContext));
if (set_sid_ctx_result == 0) {
gpr_log(GPR_ERROR, "Failed to set session id context.");
result = TSI_INTERNAL_ERROR;
break;
}
if (options->session_ticket_key != nullptr) {
if (SSL_CTX_set_tlsext_ticket_keys(
impl->ssl_contexts[i],
const_cast<char*>(options->session_ticket_key),
options->session_ticket_key_size) == 0) {
gpr_log(GPR_ERROR, "Invalid STEK size.");
result = TSI_INVALID_ARGUMENT;
break;
}
}
if (options->pem_client_root_certs != nullptr) {
STACK_OF(X509_NAME)* root_names = nullptr;
result = ssl_ctx_load_verification_certs(
impl->ssl_contexts[i], pem_client_root_certs,
strlen(pem_client_root_certs), &root_names);
impl->ssl_contexts[i], options->pem_client_root_certs,
strlen(options->pem_client_root_certs), &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) {
switch (options->client_certificate_request) {
case TSI_DONT_REQUEST_CLIENT_CERTIFICATE:
SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_NONE, nullptr);
break;
@ -1544,7 +1690,7 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
}
result = extract_x509_subject_names_from_pem_cert(
pem_key_cert_pairs[i].cert_chain,
options->pem_key_cert_pairs[i].cert_chain,
&impl->ssl_context_x509_subject_names[i]);
if (result != TSI_OK) break;

@ -30,11 +30,27 @@
#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_SSL_SESSION_REUSED_PEER_PROPERTY "ssl_session_reused"
#define TSI_X509_PEM_CERT_PROPERTY "x509_pem_cert"
#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol"
/* --- tsi_ssl_session_cache object ---
Cache for SSL sessions for sessions resumption. */
typedef struct tsi_ssl_session_cache tsi_ssl_session_cache;
/* Create LRU cache for SSL sessions with \a capacity. */
tsi_ssl_session_cache* tsi_ssl_session_cache_create_lru(size_t capacity);
/* Increment reference counter of \a cache. */
void tsi_ssl_session_cache_ref(tsi_ssl_session_cache* cache);
/* Decrement reference counter of \a cache. */
void tsi_ssl_session_cache_unref(tsi_ssl_session_cache* cache);
/* --- tsi_ssl_client_handshaker_factory object ---
This object creates a client tsi_handshaker objects implemented in terms of
@ -81,6 +97,43 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
const char** alpn_protocols, uint16_t num_alpn_protocols,
tsi_ssl_client_handshaker_factory** factory);
typedef struct {
/* pem_key_cert_pair is a pointer to the object containing client's private
key and certificate chain. This parameter can be NULL if the client does
not have such a key/cert pair. */
const tsi_ssl_pem_key_cert_pair* pem_key_cert_pair;
/* pem_roots_cert is the NULL-terminated string containing the PEM encoding of
the client root certificates. This parameter may be NULL if the server does
not want the client to be authenticated with SSL. */
const char* pem_root_certs;
/* 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. */
const char* cipher_suites;
/* alpn_protocols is an array containing the NULL terminated protocol names
that the handshakers created with this factory support. This parameter can
be NULL. */
const char** alpn_protocols;
/* 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. */
size_t num_alpn_protocols;
/* ssl_session_cache is a cache for reusable client-side sessions. */
tsi_ssl_session_cache* session_cache;
} tsi_ssl_client_handshaker_options;
/* Creates a client handshaker factory.
- options is the options used to create a factory.
- 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_with_options(
const tsi_ssl_client_handshaker_options* options,
tsi_ssl_client_handshaker_factory** factory);
/* Creates a client handshaker.
- self is the factory from which the handshaker will be created.
- server_name_indication indicates the name of the server the client is
@ -147,6 +200,51 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
const char* cipher_suites, const char** alpn_protocols,
uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory);
typedef struct {
/* pem_key_cert_pairs is an array private key / certificate chains of the
server. */
const tsi_ssl_pem_key_cert_pair* pem_key_cert_pairs;
/* num_key_cert_pairs is the number of items in the pem_key_cert_pairs
array. */
size_t num_key_cert_pairs;
/* pem_root_certs is the NULL-terminated string containing the PEM encoding
of the server root certificates. */
const char* pem_client_root_certs;
/* 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_client_certificate_request_type client_certificate_request;
/* 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. */
const char* cipher_suites;
/* alpn_protocols is an array containing the NULL terminated protocol names
that the handshakers created with this factory support. This parameter can
be NULL. */
const char** alpn_protocols;
/* 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. */
uint16_t num_alpn_protocols;
/* session_ticket_key is optional key for encrypting session keys. If paramter
is not specified it must be NULL. */
const char* session_ticket_key;
/* session_ticket_key_size is a size of session ticket encryption key. */
size_t session_ticket_key_size;
} tsi_ssl_server_handshaker_options;
/* Creates a server handshaker factory.
- options is the options used to create a factory.
- 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_with_options(
const tsi_ssl_server_handshaker_options* options,
tsi_ssl_server_handshaker_factory** factory);
/* Creates a server handshaker.
- self is the factory from which the handshaker will be created.
- handshaker is the address of the handshaker pointer to be created.

@ -102,7 +102,8 @@ class AuthContextTest(unittest.TestCase):
self.assertIsNone(auth_data[_ID])
self.assertIsNone(auth_data[_ID_KEY])
self.assertDictEqual({
'transport_security_type': [b'ssl']
'transport_security_type': [b'ssl'],
'ssl_session_reused': [b'false'],
}, auth_data[_AUTH_CTX])
def testSecureClientCert(self):

@ -163,3 +163,20 @@ grpc_cc_test(
)
grpc_end2end_tests()
grpc_cc_test(
name = "h2_ssl_session_reuse_test",
srcs = ["h2_ssl_session_reuse_test.cc"],
external_deps = [
"gtest",
],
language = "C++",
deps = [
':end2end_tests',
'//:gpr',
'//:grpc',
'//:tsi',
'//test/core/util:gpr_test_util',
'//test/core/util:grpc_test_util',
],
)

@ -0,0 +1,280 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "test/core/end2end/end2end_tests.h"
#include <stdio.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/host_port.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/tmpfile.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "test/core/end2end/cq_verifier.h"
#include "test/core/end2end/data/ssl_test_data.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
#include <gtest/gtest.h>
namespace grpc {
namespace testing {
namespace {
void* tag(intptr_t t) { return (void*)t; }
gpr_timespec five_seconds_time() { return grpc_timeout_seconds_to_deadline(5); }
grpc_server* server_create(grpc_completion_queue* cq, char* server_addr) {
grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key,
test_server1_cert};
grpc_server_credentials* server_creds = grpc_ssl_server_credentials_create_ex(
test_root_cert, &pem_cert_key_pair, 1,
GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY, nullptr);
grpc_server* server = grpc_server_create(nullptr, nullptr);
grpc_server_register_completion_queue(server, cq, nullptr);
GPR_ASSERT(
grpc_server_add_secure_http2_port(server, server_addr, server_creds));
grpc_server_credentials_release(server_creds);
grpc_server_start(server);
return server;
}
grpc_channel* client_create(char* server_addr, grpc_ssl_session_cache* cache) {
grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = {
test_signed_client_key, test_signed_client_cert};
grpc_channel_credentials* client_creds = grpc_ssl_credentials_create(
test_root_cert, &signed_client_key_cert_pair, nullptr);
grpc_arg args[] = {
grpc_channel_arg_string_create(
const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
const_cast<char*>("waterzooi.test.google.be")),
grpc_ssl_session_cache_create_channel_arg(cache),
};
grpc_channel_args* client_args =
grpc_channel_args_copy_and_add(nullptr, args, GPR_ARRAY_SIZE(args));
grpc_channel* client = grpc_secure_channel_create(client_creds, server_addr,
client_args, nullptr);
GPR_ASSERT(client != nullptr);
grpc_channel_credentials_release(client_creds);
{
grpc_core::ExecCtx exec_ctx;
grpc_channel_args_destroy(client_args);
}
return client;
}
void do_round_trip(grpc_completion_queue* cq, grpc_server* server,
char* server_addr, grpc_ssl_session_cache* cache,
bool expect_session_reuse) {
grpc_channel* client = client_create(server_addr, cache);
cq_verifier* cqv = cq_verifier_create(cq);
grpc_op ops[6];
grpc_op* op;
grpc_metadata_array initial_metadata_recv;
grpc_metadata_array trailing_metadata_recv;
grpc_metadata_array request_metadata_recv;
grpc_call_details call_details;
grpc_status_code status;
grpc_call_error error;
grpc_slice details;
int was_cancelled = 2;
gpr_timespec deadline = grpc_timeout_seconds_to_deadline(60);
grpc_call* c = grpc_channel_create_call(
client, nullptr, GRPC_PROPAGATE_DEFAULTS, cq,
grpc_slice_from_static_string("/foo"), nullptr, deadline, nullptr);
GPR_ASSERT(c);
grpc_metadata_array_init(&initial_metadata_recv);
grpc_metadata_array_init(&trailing_metadata_recv);
grpc_metadata_array_init(&request_metadata_recv);
grpc_call_details_init(&call_details);
memset(ops, 0, sizeof(ops));
op = ops;
op->op = GRPC_OP_SEND_INITIAL_METADATA;
op->data.send_initial_metadata.count = 0;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_INITIAL_METADATA;
op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
op->data.recv_status_on_client.status = &status;
op->data.recv_status_on_client.status_details = &details;
op->flags = 0;
op->reserved = nullptr;
op++;
error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
nullptr);
GPR_ASSERT(GRPC_CALL_OK == error);
grpc_call* s;
error = grpc_server_request_call(server, &s, &call_details,
&request_metadata_recv, cq, cq, tag(101));
GPR_ASSERT(GRPC_CALL_OK == error);
CQ_EXPECT_COMPLETION(cqv, tag(101), 1);
cq_verify(cqv);
grpc_auth_context* auth = grpc_call_auth_context(s);
grpc_auth_property_iterator it = grpc_auth_context_find_properties_by_name(
auth, GRPC_SSL_SESSION_REUSED_PROPERTY);
const grpc_auth_property* property = grpc_auth_property_iterator_next(&it);
GPR_ASSERT(property != nullptr);
if (expect_session_reuse) {
GPR_ASSERT(strcmp(property->value, "true") == 0);
} else {
GPR_ASSERT(strcmp(property->value, "false") == 0);
}
grpc_auth_context_release(auth);
memset(ops, 0, sizeof(ops));
op = ops;
op->op = GRPC_OP_SEND_INITIAL_METADATA;
op->data.send_initial_metadata.count = 0;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
op->data.recv_close_on_server.cancelled = &was_cancelled;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
op->data.send_status_from_server.trailing_metadata_count = 0;
op->data.send_status_from_server.status = GRPC_STATUS_OK;
op->flags = 0;
op->reserved = nullptr;
op++;
error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
nullptr);
GPR_ASSERT(GRPC_CALL_OK == error);
CQ_EXPECT_COMPLETION(cqv, tag(103), 1);
CQ_EXPECT_COMPLETION(cqv, tag(1), 1);
cq_verify(cqv);
grpc_metadata_array_destroy(&initial_metadata_recv);
grpc_metadata_array_destroy(&trailing_metadata_recv);
grpc_metadata_array_destroy(&request_metadata_recv);
grpc_call_details_destroy(&call_details);
grpc_call_unref(c);
grpc_call_unref(s);
cq_verifier_destroy(cqv);
grpc_channel_destroy(client);
}
void drain_cq(grpc_completion_queue* cq) {
grpc_event ev;
do {
ev = grpc_completion_queue_next(cq, five_seconds_time(), nullptr);
} while (ev.type != GRPC_QUEUE_SHUTDOWN);
}
TEST(H2SessionReuseTest, SingleReuse) {
int port = grpc_pick_unused_port_or_die();
char* server_addr;
gpr_join_host_port(&server_addr, "localhost", port);
grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr);
grpc_ssl_session_cache* cache = grpc_ssl_session_cache_create_lru(16);
grpc_server* server = server_create(cq, server_addr);
do_round_trip(cq, server, server_addr, cache, false);
do_round_trip(cq, server, server_addr, cache, true);
do_round_trip(cq, server, server_addr, cache, true);
gpr_free(server_addr);
grpc_ssl_session_cache_destroy(cache);
GPR_ASSERT(grpc_completion_queue_next(
cq, grpc_timeout_milliseconds_to_deadline(100), nullptr)
.type == GRPC_QUEUE_TIMEOUT);
grpc_completion_queue* shutdown_cq =
grpc_completion_queue_create_for_pluck(nullptr);
grpc_server_shutdown_and_notify(server, shutdown_cq, tag(1000));
GPR_ASSERT(grpc_completion_queue_pluck(shutdown_cq, tag(1000),
grpc_timeout_seconds_to_deadline(5),
nullptr)
.type == GRPC_OP_COMPLETE);
grpc_server_destroy(server);
grpc_completion_queue_destroy(shutdown_cq);
grpc_completion_queue_shutdown(cq);
drain_cq(cq);
grpc_completion_queue_destroy(cq);
}
} // namespace
} // namespace testing
} // namespace grpc
int main(int argc, char** argv) {
FILE* roots_file;
size_t roots_size = strlen(test_root_cert);
char* roots_filename;
grpc_test_init(argc, argv);
/* Set the SSL roots env var. */
roots_file = gpr_tmpfile("chttp2_ssl_session_reuse_test", &roots_filename);
GPR_ASSERT(roots_filename != nullptr);
GPR_ASSERT(roots_file != nullptr);
GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
fclose(roots_file);
gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
grpc_init();
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
grpc_shutdown();
/* Cleanup. */
remove(roots_filename);
gpr_free(roots_filename);
return ret;
}

@ -41,6 +41,20 @@ grpc_cc_test(
],
)
grpc_cc_test(
name = "ssl_session_cache_test",
srcs = ["ssl_session_cache_test.cc"],
language = "C++",
external_deps = [
"gtest",
],
deps = [
"//:grpc",
"//:gpr",
"//:tsi",
"//test/core/util:gpr_test_util",
],
)
grpc_cc_test(
name = "ssl_transport_security_test",

@ -0,0 +1,154 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <string>
#include <unordered_set>
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "test/core/util/test_config.h"
#include <grpc/grpc.h>
#include <grpc/support/log.h>
#include <gtest/gtest.h>
namespace grpc_core {
namespace {
class SessionTracker;
struct SessionExDataId {
SessionTracker* tracker;
long id;
};
class SessionTracker {
public:
SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); }
~SessionTracker() { SSL_CTX_free(ssl_context_); }
tsi::SslSessionPtr NewSession(long id) {
static int ex_data_id = SSL_SESSION_get_ex_new_index(
0, nullptr, nullptr, nullptr, DestroyExData);
GPR_ASSERT(ex_data_id != -1);
// OpenSSL and different version of BoringSSL don't agree on API
// so try both.
tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new);
SessionExDataId* data = new SessionExDataId{this, id};
int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data);
EXPECT_EQ(result, 1);
alive_sessions_.insert(id);
return session;
}
bool IsAlive(long id) const {
return alive_sessions_.find(id) != alive_sessions_.end();
}
size_t AliveCount() const { return alive_sessions_.size(); }
private:
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) {
return tsi::SslSessionPtr(cb());
}
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) {
return tsi::SslSessionPtr(cb(ssl_context_));
}
static void DestroyExData(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
int index, long argl, void* argp) {
SessionExDataId* data = static_cast<SessionExDataId*>(ptr);
data->tracker->alive_sessions_.erase(data->id);
delete data;
}
SSL_CTX* ssl_context_;
std::unordered_set<long> alive_sessions_;
};
TEST(SslSessionCacheTest, InitialState) {
SessionTracker tracker;
// Verify session initial state.
{
tsi::SslSessionPtr tmp_sess = tracker.NewSession(1);
EXPECT_EQ(tmp_sess->references, 1);
EXPECT_TRUE(tracker.IsAlive(1));
EXPECT_EQ(tracker.AliveCount(), 1);
}
EXPECT_FALSE(tracker.IsAlive(1));
EXPECT_EQ(tracker.AliveCount(), 0);
}
TEST(SslSessionCacheTest, LruCache) {
SessionTracker tracker;
{
RefCountedPtr<tsi::SslSessionLRUCache> cache =
tsi::SslSessionLRUCache::Create(3);
tsi::SslSessionPtr sess2 = tracker.NewSession(2);
SSL_SESSION* sess2_ptr = sess2.get();
cache->Put("first.dropbox.com", std::move(sess2));
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr);
EXPECT_TRUE(tracker.IsAlive(2));
EXPECT_EQ(tracker.AliveCount(), 1);
// Putting element with the same key destroys old session.
tsi::SslSessionPtr sess3 = tracker.NewSession(3);
SSL_SESSION* sess3_ptr = sess3.get();
cache->Put("first.dropbox.com", std::move(sess3));
EXPECT_FALSE(tracker.IsAlive(2));
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr);
EXPECT_TRUE(tracker.IsAlive(3));
EXPECT_EQ(tracker.AliveCount(), 1);
// Putting three more elements discards current one.
for (long id = 4; id < 7; id++) {
EXPECT_TRUE(tracker.IsAlive(3));
std::string domain = std::to_string(id) + ".random.domain";
cache->Put(domain.c_str(), tracker.NewSession(id));
}
EXPECT_EQ(cache->Size(), 3);
EXPECT_FALSE(tracker.IsAlive(3));
EXPECT_EQ(tracker.AliveCount(), 3);
// Accessing element moves it into front of the queue.
EXPECT_TRUE(cache->Get("4.random.domain"));
EXPECT_TRUE(tracker.IsAlive(4));
EXPECT_TRUE(tracker.IsAlive(5));
EXPECT_TRUE(tracker.IsAlive(6));
// One element has to be evicted from cache->
cache->Put("7.random.domain", tracker.NewSession(7));
EXPECT_TRUE(tracker.IsAlive(4));
EXPECT_FALSE(tracker.IsAlive(5));
EXPECT_TRUE(tracker.IsAlive(6));
EXPECT_TRUE(tracker.IsAlive(7));
EXPECT_EQ(tracker.AliveCount(), 3);
}
// Cache destructor destroys all sessions.
EXPECT_EQ(tracker.AliveCount(), 0);
}
} // namespace
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
grpc_test_init(argc, argv);
grpc_init();
int ret = RUN_ALL_TESTS();
grpc_shutdown();
return ret;
}

@ -52,8 +52,8 @@ typedef enum AlpnMode {
typedef struct ssl_alpn_lib {
AlpnMode alpn_mode;
char** server_alpn_protocols;
char** client_alpn_protocols;
const char** server_alpn_protocols;
const char** client_alpn_protocols;
uint16_t num_server_alpn_protocols;
uint16_t num_client_alpn_protocols;
} ssl_alpn_lib;
@ -76,6 +76,10 @@ typedef struct ssl_tsi_test_fixture {
ssl_alpn_lib* alpn_lib;
bool force_client_auth;
char* server_name_indication;
tsi_ssl_session_cache* session_cache;
bool session_reused;
const char* session_ticket_key;
size_t session_ticket_key_size;
tsi_ssl_server_handshaker_factory* server_handshaker_factory;
tsi_ssl_client_handshaker_factory* client_handshaker_factory;
} ssl_tsi_test_fixture;
@ -89,47 +93,60 @@ static void ssl_test_setup_handshakers(tsi_test_fixture* fixture) {
ssl_key_cert_lib* key_cert_lib = ssl_fixture->key_cert_lib;
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
/* Create client handshaker factory. */
tsi_ssl_pem_key_cert_pair* client_key_cert_pair = nullptr;
tsi_ssl_client_handshaker_options client_options;
memset(&client_options, 0, sizeof(client_options));
client_options.pem_root_certs = key_cert_lib->root_cert;
if (ssl_fixture->force_client_auth) {
client_key_cert_pair = key_cert_lib->use_bad_client_cert
? &key_cert_lib->bad_client_pem_key_cert_pair
: &key_cert_lib->client_pem_key_cert_pair;
client_options.pem_key_cert_pair =
key_cert_lib->use_bad_client_cert
? &key_cert_lib->bad_client_pem_key_cert_pair
: &key_cert_lib->client_pem_key_cert_pair;
}
char** client_alpn_protocols = nullptr;
uint16_t num_client_alpn_protocols = 0;
if (alpn_lib->alpn_mode == ALPN_CLIENT_NO_SERVER ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
client_alpn_protocols = alpn_lib->client_alpn_protocols;
num_client_alpn_protocols = alpn_lib->num_client_alpn_protocols;
client_options.alpn_protocols = alpn_lib->client_alpn_protocols;
client_options.num_alpn_protocols = alpn_lib->num_client_alpn_protocols;
}
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory(
client_key_cert_pair, key_cert_lib->root_cert, nullptr,
(const char**)client_alpn_protocols, num_client_alpn_protocols,
&ssl_fixture->client_handshaker_factory) == TSI_OK);
if (ssl_fixture->session_cache != nullptr) {
client_options.session_cache = ssl_fixture->session_cache;
}
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory_with_options(
&client_options, &ssl_fixture->client_handshaker_factory) ==
TSI_OK);
/* Create server handshaker factory. */
char** server_alpn_protocols = nullptr;
uint16_t num_server_alpn_protocols = 0;
tsi_ssl_server_handshaker_options server_options;
memset(&server_options, 0, sizeof(server_options));
if (alpn_lib->alpn_mode == ALPN_SERVER_NO_CLIENT ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
server_alpn_protocols = alpn_lib->server_alpn_protocols;
num_server_alpn_protocols = alpn_lib->num_server_alpn_protocols;
server_options.alpn_protocols = alpn_lib->server_alpn_protocols;
server_options.num_alpn_protocols = alpn_lib->num_server_alpn_protocols;
if (alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
num_server_alpn_protocols--;
server_options.num_alpn_protocols--;
}
}
GPR_ASSERT(tsi_create_ssl_server_handshaker_factory(
key_cert_lib->use_bad_server_cert
? key_cert_lib->bad_server_pem_key_cert_pairs
: key_cert_lib->server_pem_key_cert_pairs,
key_cert_lib->use_bad_server_cert
? key_cert_lib->bad_server_num_key_cert_pairs
: key_cert_lib->server_num_key_cert_pairs,
key_cert_lib->root_cert, ssl_fixture->force_client_auth,
nullptr, (const char**)server_alpn_protocols,
num_server_alpn_protocols,
&ssl_fixture->server_handshaker_factory) == TSI_OK);
server_options.pem_key_cert_pairs =
key_cert_lib->use_bad_server_cert
? key_cert_lib->bad_server_pem_key_cert_pairs
: key_cert_lib->server_pem_key_cert_pairs;
server_options.num_key_cert_pairs =
key_cert_lib->use_bad_server_cert
? key_cert_lib->bad_server_num_key_cert_pairs
: key_cert_lib->server_num_key_cert_pairs;
server_options.pem_client_root_certs = key_cert_lib->root_cert;
if (ssl_fixture->force_client_auth) {
server_options.client_certificate_request =
TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;
} else {
server_options.client_certificate_request =
TSI_DONT_REQUEST_CLIENT_CERTIFICATE;
}
server_options.session_ticket_key = ssl_fixture->session_ticket_key;
server_options.session_ticket_key_size = ssl_fixture->session_ticket_key_size;
GPR_ASSERT(tsi_create_ssl_server_handshaker_factory_with_options(
&server_options, &ssl_fixture->server_handshaker_factory) ==
TSI_OK);
/* Create server and client handshakers. */
tsi_handshaker* client_handshaker = nullptr;
GPR_ASSERT(tsi_ssl_client_handshaker_factory_create_handshaker(
@ -176,6 +193,18 @@ check_basic_authenticated_peer_and_get_common_name(const tsi_peer* peer) {
return property;
}
static void check_session_reusage(ssl_tsi_test_fixture* ssl_fixture,
tsi_peer* peer) {
const tsi_peer_property* session_reused =
tsi_peer_get_property_by_name(peer, TSI_SSL_SESSION_REUSED_PEER_PROPERTY);
GPR_ASSERT(session_reused != nullptr);
if (ssl_fixture->session_reused) {
GPR_ASSERT(strcmp(session_reused->value.data, "true") == 0);
} else {
GPR_ASSERT(strcmp(session_reused->value.data, "false") == 0);
}
}
void check_server0_peer(tsi_peer* peer) {
const tsi_peer_property* property =
check_basic_authenticated_peer_and_get_common_name(peer);
@ -233,7 +262,7 @@ static void check_client_peer(ssl_tsi_test_fixture* ssl_fixture,
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
if (!ssl_fixture->force_client_auth) {
GPR_ASSERT(peer->property_count ==
(alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ? 1 : 0));
(alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ? 2 : 1));
} else {
const tsi_peer_property* property =
check_basic_authenticated_peer_and_get_common_name(peer);
@ -257,8 +286,8 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) {
if (expect_success) {
GPR_ASSERT(tsi_handshaker_result_extract_peer(
ssl_fixture->base.client_result, &peer) == TSI_OK);
check_session_reusage(ssl_fixture, &peer);
check_alpn(ssl_fixture, &peer);
if (ssl_fixture->server_name_indication != nullptr) {
check_server1_peer(&peer);
} else {
@ -270,6 +299,7 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) {
if (expect_success) {
GPR_ASSERT(tsi_handshaker_result_extract_peer(
ssl_fixture->base.server_result, &peer) == TSI_OK);
check_session_reusage(ssl_fixture, &peer);
check_alpn(ssl_fixture, &peer);
check_client_peer(ssl_fixture, &peer);
} else {
@ -291,11 +321,11 @@ static void ssl_test_destruct(tsi_test_fixture* fixture) {
/* Destroy ssl_alpn_lib. */
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
for (size_t i = 0; i < alpn_lib->num_server_alpn_protocols; i++) {
gpr_free(alpn_lib->server_alpn_protocols[i]);
gpr_free(const_cast<char*>(alpn_lib->server_alpn_protocols[i]));
}
gpr_free(alpn_lib->server_alpn_protocols);
for (size_t i = 0; i < alpn_lib->num_client_alpn_protocols; i++) {
gpr_free(alpn_lib->client_alpn_protocols[i]);
gpr_free(const_cast<char*>(alpn_lib->client_alpn_protocols[i]));
}
gpr_free(alpn_lib->client_alpn_protocols);
gpr_free(alpn_lib);
@ -316,6 +346,9 @@ static void ssl_test_destruct(tsi_test_fixture* fixture) {
key_cert_lib->bad_client_pem_key_cert_pair);
gpr_free(key_cert_lib->root_cert);
gpr_free(key_cert_lib);
if (ssl_fixture->session_cache != nullptr) {
tsi_ssl_session_cache_unref(ssl_fixture->session_cache);
}
/* Unreference others. */
tsi_ssl_server_handshaker_factory_unref(
ssl_fixture->server_handshaker_factory);
@ -388,10 +421,10 @@ static tsi_test_fixture* ssl_tsi_test_fixture_create() {
/* Create ssl_alpn_lib. */
ssl_alpn_lib* alpn_lib =
static_cast<ssl_alpn_lib*>(gpr_zalloc(sizeof(*alpn_lib)));
alpn_lib->server_alpn_protocols =
static_cast<char**>(gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->client_alpn_protocols =
static_cast<char**>(gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->server_alpn_protocols = static_cast<const char**>(
gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->client_alpn_protocols = static_cast<const char**>(
gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->server_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN1);
alpn_lib->server_alpn_protocols[1] = gpr_strdup(SSL_TSI_TEST_ALPN3);
alpn_lib->client_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN2);
@ -402,6 +435,9 @@ static tsi_test_fixture* ssl_tsi_test_fixture_create() {
ssl_fixture->alpn_lib = alpn_lib;
ssl_fixture->base.vtable = &vtable;
ssl_fixture->server_name_indication = nullptr;
ssl_fixture->session_reused = false;
ssl_fixture->session_ticket_key = nullptr;
ssl_fixture->session_ticket_key_size = 0;
ssl_fixture->force_client_auth = false;
return &ssl_fixture->base;
}
@ -558,6 +594,38 @@ void ssl_tsi_test_do_round_trip_odd_buffer_size() {
}
}
void ssl_tsi_test_do_handshake_session_cache() {
tsi_ssl_session_cache* session_cache = tsi_ssl_session_cache_create_lru(16);
char session_ticket_key[48];
auto do_handshake = [&session_ticket_key,
&session_cache](bool session_reused) {
tsi_test_fixture* fixture = ssl_tsi_test_fixture_create();
ssl_tsi_test_fixture* ssl_fixture =
reinterpret_cast<ssl_tsi_test_fixture*>(fixture);
ssl_fixture->server_name_indication =
const_cast<char*>("waterzooi.test.google.be");
ssl_fixture->session_ticket_key = session_ticket_key;
ssl_fixture->session_ticket_key_size = 48;
tsi_ssl_session_cache_ref(session_cache);
ssl_fixture->session_cache = session_cache;
ssl_fixture->session_reused = session_reused;
tsi_test_do_round_trip(&ssl_fixture->base);
tsi_test_fixture_destroy(fixture);
};
memset(session_ticket_key, 'a', 48);
do_handshake(false);
do_handshake(true);
do_handshake(true);
// Changing session_ticket_key on server invalidates ticket.
memset(session_ticket_key, 'b', 48);
do_handshake(false);
do_handshake(true);
memset(session_ticket_key, 'c', 48);
do_handshake(false);
do_handshake(true);
tsi_ssl_session_cache_unref(session_cache);
}
static const tsi_ssl_handshaker_factory_vtable* original_vtable;
static bool handshaker_factory_destructor_called;
@ -575,13 +643,14 @@ static tsi_ssl_handshaker_factory_vtable test_handshaker_factory_vtable = {
void test_tsi_ssl_client_handshaker_factory_refcounting() {
int i;
const char* cert_chain =
load_file(SSL_TSI_TEST_CREDENTIALS_DIR, "client.pem");
char* cert_chain = load_file(SSL_TSI_TEST_CREDENTIALS_DIR, "client.pem");
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_root_certs = cert_chain;
tsi_ssl_client_handshaker_factory* client_handshaker_factory;
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory(
nullptr, cert_chain, nullptr, nullptr, 0,
&client_handshaker_factory) == TSI_OK);
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory_with_options(
&options, &client_handshaker_factory) == TSI_OK);
handshaker_factory_destructor_called = false;
original_vtable = tsi_ssl_handshaker_factory_swap_vtable(
@ -608,7 +677,7 @@ void test_tsi_ssl_client_handshaker_factory_refcounting() {
tsi_handshaker_destroy(handshaker[2]);
GPR_ASSERT(handshaker_factory_destructor_called);
gpr_free((void*)cert_chain);
gpr_free(cert_chain);
}
void test_tsi_ssl_server_handshaker_factory_refcounting() {
@ -673,6 +742,7 @@ void ssl_tsi_test_handshaker_factory_internals() {
int main(int argc, char** argv) {
grpc_test_init(argc, argv);
grpc_init();
ssl_tsi_test_do_handshake_tiny_handshake_buffer();
ssl_tsi_test_do_handshake_small_handshake_buffer();
ssl_tsi_test_do_handshake();
@ -688,6 +758,7 @@ int main(int argc, char** argv) {
#endif
ssl_tsi_test_do_handshake_alpn_server_no_client();
ssl_tsi_test_do_handshake_alpn_client_server_ok();
ssl_tsi_test_do_handshake_session_cache();
ssl_tsi_test_do_round_trip_for_all_configs();
ssl_tsi_test_do_round_trip_odd_buffer_size();
ssl_tsi_test_handshaker_factory_internals();

Loading…
Cancel
Save