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