1 //
2 //
3 // Copyright 2018 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
20
21 #include <string>
22 #include <unordered_set>
23
24 #include <gtest/gtest.h>
25
26 #include <grpc/grpc.h>
27 #include <grpc/support/log.h>
28
29 #include "src/core/lib/gprpp/crash.h"
30 #include "test/core/util/test_config.h"
31
32 namespace grpc_core {
33
34 namespace {
35
36 class SessionTracker;
37
38 struct SessionExDataId {
39 SessionTracker* tracker;
40 long id;
41 };
42
43 class SessionTracker {
44 public:
SessionTracker()45 SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); }
46
~SessionTracker()47 ~SessionTracker() { SSL_CTX_free(ssl_context_); }
48
NewSession(long id)49 tsi::SslSessionPtr NewSession(long id) {
50 static int ex_data_id = SSL_SESSION_get_ex_new_index(
51 0, nullptr, nullptr, nullptr, DestroyExData);
52 GPR_ASSERT(ex_data_id != -1);
53 // OpenSSL and different version of BoringSSL don't agree on API
54 // so try both.
55 tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new);
56 SessionExDataId* data = new SessionExDataId{this, id};
57 int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data);
58 EXPECT_EQ(result, 1);
59 alive_sessions_.insert(id);
60 return session;
61 }
62
IsAlive(long id) const63 bool IsAlive(long id) const {
64 return alive_sessions_.find(id) != alive_sessions_.end();
65 }
66
AliveCount() const67 size_t AliveCount() const { return alive_sessions_.size(); }
68
69 private:
NewSessionInternal(SSL_SESSION * (* cb)())70 tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) {
71 return tsi::SslSessionPtr(cb());
72 }
73
NewSessionInternal(SSL_SESSION * (* cb)(const SSL_CTX *))74 tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) {
75 return tsi::SslSessionPtr(cb(ssl_context_));
76 }
77
DestroyExData(void *,void * ptr,CRYPTO_EX_DATA *,int,long,void *)78 static void DestroyExData(void* /*parent*/, void* ptr, CRYPTO_EX_DATA* /*ad*/,
79 int /*index*/, long /*argl*/, void* /*argp*/) {
80 SessionExDataId* data = static_cast<SessionExDataId*>(ptr);
81 data->tracker->alive_sessions_.erase(data->id);
82 delete data;
83 }
84
85 SSL_CTX* ssl_context_;
86 std::unordered_set<long> alive_sessions_;
87 };
88
TEST(SslSessionCacheTest,InitialState)89 TEST(SslSessionCacheTest, InitialState) {
90 SessionTracker tracker;
91 // Verify session initial state.
92 {
93 tsi::SslSessionPtr tmp_sess = tracker.NewSession(1);
94 EXPECT_TRUE(tracker.IsAlive(1));
95 EXPECT_EQ(tracker.AliveCount(), 1);
96 }
97 EXPECT_FALSE(tracker.IsAlive(1));
98 EXPECT_EQ(tracker.AliveCount(), 0);
99 }
100
TEST(SslSessionCacheTest,LruCache)101 TEST(SslSessionCacheTest, LruCache) {
102 SessionTracker tracker;
103 {
104 RefCountedPtr<tsi::SslSessionLRUCache> cache =
105 tsi::SslSessionLRUCache::Create(3);
106 EXPECT_EQ(cache->Size(), 0);
107 tsi::SslSessionPtr sess2 = tracker.NewSession(2);
108 SSL_SESSION* sess2_ptr = sess2.get();
109 cache->Put("first.dropbox.com", std::move(sess2));
110 EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr);
111 EXPECT_TRUE(tracker.IsAlive(2));
112 EXPECT_EQ(tracker.AliveCount(), 1);
113 // Putting element with the same key destroys old session.
114 tsi::SslSessionPtr sess3 = tracker.NewSession(3);
115 SSL_SESSION* sess3_ptr = sess3.get();
116 cache->Put("first.dropbox.com", std::move(sess3));
117 EXPECT_FALSE(tracker.IsAlive(2));
118 EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr);
119 EXPECT_TRUE(tracker.IsAlive(3));
120 EXPECT_EQ(tracker.AliveCount(), 1);
121 // Putting three more elements discards current one.
122 for (long id = 4; id < 7; id++) {
123 EXPECT_TRUE(tracker.IsAlive(3));
124 std::string domain = std::to_string(id) + ".random.domain";
125 cache->Put(domain.c_str(), tracker.NewSession(id));
126 }
127 EXPECT_EQ(cache->Size(), 3);
128 EXPECT_FALSE(tracker.IsAlive(3));
129 EXPECT_EQ(tracker.AliveCount(), 3);
130 // Accessing element moves it into front of the queue.
131 EXPECT_TRUE(cache->Get("4.random.domain"));
132 EXPECT_TRUE(tracker.IsAlive(4));
133 EXPECT_TRUE(tracker.IsAlive(5));
134 EXPECT_TRUE(tracker.IsAlive(6));
135 // One element has to be evicted from cache->
136 cache->Put("7.random.domain", tracker.NewSession(7));
137 EXPECT_TRUE(tracker.IsAlive(4));
138 EXPECT_FALSE(tracker.IsAlive(5));
139 EXPECT_TRUE(tracker.IsAlive(6));
140 EXPECT_TRUE(tracker.IsAlive(7));
141 EXPECT_EQ(tracker.AliveCount(), 3);
142 }
143 // Cache destructor destroys all sessions.
144 EXPECT_EQ(tracker.AliveCount(), 0);
145 }
146
TEST(SslSessionCacheTest,PutAndGet)147 TEST(SslSessionCacheTest, PutAndGet) {
148 // Set up an empty cache and an SSL session.
149 SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_method());
150 tsi::SslSessionPtr ssl_session_ptr(SSL_SESSION_new(ssl_ctx));
151 RefCountedPtr<tsi::SslSessionLRUCache> cache =
152 tsi::SslSessionLRUCache::Create(1);
153 EXPECT_EQ(cache->Size(), 0);
154 // Put the SSL session in the cache.
155 cache->Put("foo.domain", std::move(ssl_session_ptr));
156 EXPECT_EQ(cache->Size(), 1);
157 // Get a copy of the SSL session from the cache.
158 EXPECT_EQ(cache->Size(), 1);
159 EXPECT_NE(cache->Get("foo.domain"), nullptr);
160 // Try to put a null SSL session in the cache and check that it was not
161 // successful.
162 cache->Put("foo.domain.2", /*session=*/nullptr);
163 EXPECT_EQ(cache->Size(), 1);
164 EXPECT_NE(cache->Get("foo.domain"), nullptr);
165 EXPECT_EQ(cache->Get("foo.domain.2"), nullptr);
166 // Cleanup.
167 SSL_CTX_free(ssl_ctx);
168 }
169
TEST(SslSessionCacheTest,CapacityZeroCache)170 TEST(SslSessionCacheTest, CapacityZeroCache) {
171 // Set up an empty cache and an SSL session.
172 SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_method());
173 tsi::SslSessionPtr ssl_session_ptr(SSL_SESSION_new(ssl_ctx));
174 RefCountedPtr<tsi::SslSessionLRUCache> cache =
175 tsi::SslSessionLRUCache::Create(0);
176 EXPECT_EQ(cache->Size(), 0);
177 // Try to put the SSL session in the cache and check that it was not
178 // successful.
179 cache->Put("foo.domain", std::move(ssl_session_ptr));
180 EXPECT_EQ(cache->Size(), 0);
181 EXPECT_EQ(cache->Get("foo.domain"), nullptr);
182 // Cleanup.
183 SSL_CTX_free(ssl_ctx);
184 }
185
186 } // namespace
187 } // namespace grpc_core
188
main(int argc,char ** argv)189 int main(int argc, char** argv) {
190 ::testing::InitGoogleTest(&argc, argv);
191 grpc::testing::TestEnvironment env(&argc, argv);
192 grpc_init();
193 int ret = RUN_ALL_TESTS();
194 grpc_shutdown();
195 return ret;
196 }
197