1 /** @file
2 
3   @section license License
4 
5   Licensed to the Apache Software Foundation (ASF) under one
6   or more contributor license agreements.  See the NOTICE file
7   distributed with this work for additional information
8   regarding copyright ownership.  The ASF licenses this file
9   to you under the Apache License, Version 2.0 (the
10   "License"); you may not use this file except in compliance
11   with the License.  You may obtain a copy of the License at
12 
13       http://www.apache.org/licenses/LICENSE-2.0
14 
15   Unless required by applicable law or agreed to in writing, software
16   distributed under the License is distributed on an "AS IS" BASIS,
17   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   See the License for the specific language governing permissions and
19   limitations under the License.
20  */
21 
22 #include "P_SSLConfig.h"
23 #include "SSLSessionCache.h"
24 #include "SSLStats.h"
25 
26 #include <cstring>
27 
28 #define SSLSESSIONCACHE_STRINGIFY0(x) #x
29 #define SSLSESSIONCACHE_STRINGIFY(x) SSLSESSIONCACHE_STRINGIFY0(x)
30 #define SSLSESSIONCACHE_LINENO SSLSESSIONCACHE_STRINGIFY(__LINE__)
31 
32 #ifdef DEBUG
33 #define PRINT_BUCKET(x) this->print(x " at " __FILE__ ":" SSLSESSIONCACHE_LINENO);
34 #else
35 #define PRINT_BUCKET(x)
36 #endif
37 
38 /* Session Cache */
SSLSessionCache()39 SSLSessionCache::SSLSessionCache() : nbuckets(SSLConfigParams::session_cache_number_buckets)
40 {
41   Debug("ssl.session_cache", "Created new ssl session cache %p with %zu buckets each with size max size %zu", this, nbuckets,
42         SSLConfigParams::session_cache_max_bucket_size);
43 
44   session_bucket = new SSLSessionBucket[nbuckets];
45 }
46 
~SSLSessionCache()47 SSLSessionCache::~SSLSessionCache()
48 {
49   delete[] session_bucket;
50 }
51 
52 int
getSessionBuffer(const SSLSessionID & sid,char * buffer,int & len) const53 SSLSessionCache::getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const
54 {
55   uint64_t hash            = sid.hash();
56   uint64_t target_bucket   = hash % nbuckets;
57   SSLSessionBucket *bucket = &session_bucket[target_bucket];
58 
59   return bucket->getSessionBuffer(sid, buffer, len);
60 }
61 
62 bool
getSession(const SSLSessionID & sid,SSL_SESSION ** sess,ssl_session_cache_exdata ** data) const63 SSLSessionCache::getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const
64 {
65   uint64_t hash            = sid.hash();
66   uint64_t target_bucket   = hash % nbuckets;
67   SSLSessionBucket *bucket = &session_bucket[target_bucket];
68 
69   if (is_debug_tag_set("ssl.session_cache")) {
70     char buf[sid.len * 2 + 1];
71     sid.toString(buf, sizeof(buf));
72     Debug("ssl.session_cache.get", "SessionCache looking in bucket %" PRId64 " (%p) for session '%s' (hash: %" PRIX64 ").",
73           target_bucket, bucket, buf, hash);
74   }
75 
76   return bucket->getSession(sid, sess, data);
77 }
78 
79 void
removeSession(const SSLSessionID & sid)80 SSLSessionCache::removeSession(const SSLSessionID &sid)
81 {
82   uint64_t hash            = sid.hash();
83   uint64_t target_bucket   = hash % nbuckets;
84   SSLSessionBucket *bucket = &session_bucket[target_bucket];
85 
86   if (is_debug_tag_set("ssl.session_cache")) {
87     char buf[sid.len * 2 + 1];
88     sid.toString(buf, sizeof(buf));
89     Debug("ssl.session_cache.remove", "SessionCache using bucket %" PRId64 " (%p): Removing session '%s' (hash: %" PRIX64 ").",
90           target_bucket, bucket, buf, hash);
91   }
92 
93   if (ssl_rsb) {
94     SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction);
95   }
96   bucket->removeSession(sid);
97 }
98 
99 void
insertSession(const SSLSessionID & sid,SSL_SESSION * sess,SSL * ssl)100 SSLSessionCache::insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl)
101 {
102   uint64_t hash            = sid.hash();
103   uint64_t target_bucket   = hash % nbuckets;
104   SSLSessionBucket *bucket = &session_bucket[target_bucket];
105 
106   if (is_debug_tag_set("ssl.session_cache")) {
107     char buf[sid.len * 2 + 1];
108     sid.toString(buf, sizeof(buf));
109     Debug("ssl.session_cache.insert", "SessionCache using bucket %" PRId64 " (%p): Inserting session '%s' (hash: %" PRIX64 ").",
110           target_bucket, bucket, buf, hash);
111   }
112 
113   bucket->insertSession(sid, sess, ssl);
114 }
115 
116 void
insertSession(const SSLSessionID & id,SSL_SESSION * sess,SSL * ssl)117 SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess, SSL *ssl)
118 {
119   size_t len = i2d_SSL_SESSION(sess, nullptr); // make sure we're not going to need more than SSL_MAX_SESSION_SIZE bytes
120   /* do not cache a session that's too big. */
121   if (len > static_cast<size_t>(SSL_MAX_SESSION_SIZE)) {
122     Debug("ssl.session_cache", "Unable to save SSL session because size of %zd exceeds the max of %d", len, SSL_MAX_SESSION_SIZE);
123     return;
124   }
125 
126   if (is_debug_tag_set("ssl.session_cache")) {
127     char buf[id.len * 2 + 1];
128     id.toString(buf, sizeof(buf));
129     Debug("ssl.session_cache", "Inserting session '%s' to bucket %p.", buf, this);
130   }
131 
132   MUTEX_TRY_LOCK(lock, mutex, this_ethread());
133   if (!lock.is_locked()) {
134     if (ssl_rsb) {
135       SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
136     }
137     if (SSLConfigParams::session_cache_skip_on_lock_contention) {
138       return;
139     }
140     lock.acquire(this_ethread());
141   }
142 
143   PRINT_BUCKET("insertSession before")
144   if (queue.size >= static_cast<int>(SSLConfigParams::session_cache_max_bucket_size)) {
145     if (ssl_rsb) {
146       SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction);
147     }
148     removeOldestSession();
149   }
150 
151   // Don't insert if it is already there
152   SSLSession *node = queue.tail;
153   while (node) {
154     if (node->session_id == id) {
155       return;
156     }
157     node = node->link.prev;
158   }
159 
160   Ptr<IOBufferData> buf;
161   Ptr<IOBufferData> buf_exdata;
162   size_t len_exdata = sizeof(ssl_session_cache_exdata);
163   buf               = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
164   ink_release_assert(static_cast<size_t>(buf->block_size()) >= len);
165   unsigned char *loc = reinterpret_cast<unsigned char *>(buf->data());
166   i2d_SSL_SESSION(sess, &loc);
167   buf_exdata = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
168   ink_release_assert(static_cast<size_t>(buf_exdata->block_size()) >= len_exdata);
169   ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(buf_exdata->data());
170   // This could be moved to a function in charge of populating exdata
171   exdata->curve = (ssl == nullptr) ? 0 : SSLGetCurveNID(ssl);
172 
173   ats_scoped_obj<SSLSession> ssl_session(new SSLSession(id, buf, len, buf_exdata));
174 
175   /* do the actual insert */
176   queue.enqueue(ssl_session.release());
177 
178   PRINT_BUCKET("insertSession after")
179 }
180 
181 int
getSessionBuffer(const SSLSessionID & id,char * buffer,int & len)182 SSLSessionBucket::getSessionBuffer(const SSLSessionID &id, char *buffer, int &len)
183 {
184   int true_len = 0;
185   MUTEX_TRY_LOCK(lock, mutex, this_ethread());
186   if (!lock.is_locked()) {
187     if (ssl_rsb) {
188       SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
189     }
190     if (SSLConfigParams::session_cache_skip_on_lock_contention) {
191       return true_len;
192     }
193 
194     lock.acquire(this_ethread());
195   }
196 
197   // We work backwards because that's the most likely place we'll find our session...
198   SSLSession *node = queue.tail;
199   while (node) {
200     if (node->session_id == id) {
201       true_len = node->len_asn1_data;
202       if (buffer) {
203         const unsigned char *loc = reinterpret_cast<const unsigned char *>(node->asn1_data->data());
204         if (true_len < len) {
205           len = true_len;
206         }
207         memcpy(buffer, loc, len);
208         return true_len;
209       }
210     }
211     node = node->link.prev;
212   }
213   return 0;
214 }
215 
216 bool
getSession(const SSLSessionID & id,SSL_SESSION ** sess,ssl_session_cache_exdata ** data)217 SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess, ssl_session_cache_exdata **data)
218 {
219   char buf[id.len * 2 + 1];
220   buf[0] = '\0'; // just to be safe.
221   if (is_debug_tag_set("ssl.session_cache")) {
222     id.toString(buf, sizeof(buf));
223   }
224 
225   Debug("ssl.session_cache", "Looking for session with id '%s' in bucket %p", buf, this);
226 
227   MUTEX_TRY_LOCK(lock, mutex, this_ethread());
228   if (!lock.is_locked()) {
229     if (ssl_rsb) {
230       SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention);
231     }
232     if (SSLConfigParams::session_cache_skip_on_lock_contention) {
233       return false;
234     }
235 
236     lock.acquire(this_ethread());
237   }
238 
239   PRINT_BUCKET("getSession")
240 
241   // We work backwards because that's the most likely place we'll find our session...
242   SSLSession *node = queue.tail;
243   while (node) {
244     if (node->session_id == id) {
245       const unsigned char *loc = reinterpret_cast<const unsigned char *>(node->asn1_data->data());
246       *sess                    = d2i_SSL_SESSION(nullptr, &loc, node->len_asn1_data);
247       if (data != nullptr) {
248         ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(node->extra_data->data());
249         *data                            = exdata;
250       }
251 
252       return true;
253     }
254     node = node->link.prev;
255   }
256 
257   Debug("ssl.session_cache", "Session with id '%s' not found in bucket %p.", buf, this);
258   return false;
259 }
260 
print(const char * ref_str) const261 void inline SSLSessionBucket::print(const char *ref_str) const
262 {
263   /* NOTE: This method assumes you're already holding the bucket lock */
264   if (!is_debug_tag_set("ssl.session_cache.bucket")) {
265     return;
266   }
267 
268   fprintf(stderr, "-------------- BUCKET %p (%s) ----------------\n", this, ref_str);
269   fprintf(stderr, "Current Size: %d, Max Size: %zd\n", queue.size, SSLConfigParams::session_cache_max_bucket_size);
270   fprintf(stderr, "Queue: \n");
271 
272   SSLSession *node = queue.head;
273   while (node) {
274     char s_buf[2 * node->session_id.len + 1];
275     node->session_id.toString(s_buf, sizeof(s_buf));
276     fprintf(stderr, "  %s\n", s_buf);
277     node = node->link.next;
278   }
279 }
280 
removeOldestSession()281 void inline SSLSessionBucket::removeOldestSession()
282 {
283   // Caller must hold the bucket lock.
284   ink_assert(this_ethread() == mutex->thread_holding);
285 
286   PRINT_BUCKET("removeOldestSession before")
287   while (queue.head && queue.size >= static_cast<int>(SSLConfigParams::session_cache_max_bucket_size)) {
288     SSLSession *old_head = queue.pop();
289     if (is_debug_tag_set("ssl.session_cache")) {
290       char buf[old_head->session_id.len * 2 + 1];
291       old_head->session_id.toString(buf, sizeof(buf));
292       Debug("ssl.session_cache", "Removing session '%s' from bucket %p because the bucket has size %d and max %zd", buf, this,
293             (queue.size + 1), SSLConfigParams::session_cache_max_bucket_size);
294     }
295     delete old_head;
296   }
297   PRINT_BUCKET("removeOldestSession after")
298 }
299 
300 void
removeSession(const SSLSessionID & id)301 SSLSessionBucket::removeSession(const SSLSessionID &id)
302 {
303   SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); // We can't bail on contention here because this session MUST be removed.
304   SSLSession *node = queue.head;
305   while (node) {
306     if (node->session_id == id) {
307       queue.remove(node);
308       delete node;
309       return;
310     }
311     node = node->link.next;
312   }
313 }
314 
315 /* Session Bucket */
SSLSessionBucket()316 SSLSessionBucket::SSLSessionBucket() : mutex(new_ProxyMutex()) {}
317 
~SSLSessionBucket()318 SSLSessionBucket::~SSLSessionBucket() {}
319