mirror of https://github.com/grpc/grpc.git
Merge pull request #14483 from euroelessar/ticket-client
[grpc] Add SSL session client cache supportpull/14774/head
commit
356d491245
39 changed files with 1727 additions and 111 deletions
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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; |
||||||
|
} |
Loading…
Reference in new issue