]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 1999-2001,2005-2008,2010-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
d8f41ccd | 5 | * |
b1ab9ed8 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
d8f41ccd | 12 | * |
b1ab9ed8 A |
13 | * The Original Code and all software distributed under the License are |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
d8f41ccd | 20 | * |
b1ab9ed8 A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
24 | /* | |
25 | * appleSession.c - Session storage module, Apple CDSA version. | |
26 | */ | |
27 | ||
28 | /* | |
29 | * The current implementation stores sessions in a linked list, a member of a | |
30 | * SessionCache object for which we keep a single global instance. It is | |
31 | * expected that at a given time, only a small number of sessions will be | |
32 | * cached, so the random insertion access provided by a map<> is unnecessary. | |
33 | * New entries are placed in the head of the list, assuming a LIFO usage | |
34 | * tendency. | |
35 | * | |
36 | * Entries in this cache have a time to live of SESSION_CACHE_TTL, currently | |
37 | * ten minutes. Entries are tested for being stale upon lookup; also, the global | |
38 | * sslCleanupSession() tests all entries in the cache, deleting entries which | |
39 | * are stale. This function is currently called whenever an SSLContext is deleted. | |
40 | * The current design does not provide any asynchronous timed callouts to perform | |
41 | * further cache cleanup; it was decided that the thread overhead of this would | |
42 | * outweight the benefits (again assuming a small number of entries in the | |
43 | * cache). | |
44 | * | |
45 | * When a session is added via sslAddSession, and a cache entry already | |
46 | * exists for the specifed key (sessionID), the sessionData for the existing | |
47 | * cache entry is updated with the new sessionData. The entry's expiration | |
48 | * time is unchanged (thus a given session entry can only be used for a finite | |
49 | * time no mattter how often it is re-used), | |
50 | */ | |
51 | ||
52 | #include "ssl.h" | |
53 | #include "sslMemory.h" | |
54 | #include "sslDebug.h" | |
55 | #include "appleSession.h" | |
56 | ||
57 | #include <CoreFoundation/CFDate.h> | |
58 | #include <pthread.h> | |
59 | #include <string.h> | |
60 | ||
427c49bc A |
61 | #include <utilities/SecIOFormat.h> |
62 | ||
b1ab9ed8 A |
63 | /* default time-to-live in cache, in seconds */ |
64 | #define QUICK_CACHE_TEST 0 | |
65 | #if QUICK_CACHE_TEST | |
66 | #define SESSION_CACHE_TTL ((CFTimeInterval)5) | |
67 | #else | |
68 | #define SESSION_CACHE_TTL ((CFTimeInterval)(10 * 60)) | |
69 | #endif /* QUICK_CACHE_TEST */ | |
70 | ||
71 | #define CACHE_PRINT 0 | |
72 | #if CACHE_PRINT | |
73 | #define DUMP_ALL_CACHE 0 | |
74 | ||
75 | static void cachePrint( | |
76 | const void *entry, | |
77 | const SSLBuffer *key, | |
78 | const SSLBuffer *data) | |
79 | { | |
80 | printf("entry: %p ", entry); | |
81 | unsigned char *kd = key->data; | |
82 | if(data != NULL) { | |
83 | unsigned char *dd = data->data; | |
84 | printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X" | |
85 | " data: %02X%02X%02X%02X... (len %d)\n", | |
86 | kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7], | |
87 | dd[0],dd[1],dd[2],dd[3], (unsigned)data->length); | |
88 | } | |
89 | else { | |
90 | /* just print key */ | |
91 | printf(" key: %02X%02X%02X%02X%02X%02X%02X%02X\n", | |
92 | kd[0],kd[1],kd[2],kd[3], kd[4],kd[5],kd[6],kd[7]); | |
93 | } | |
94 | } | |
95 | #else /* !CACHE_PRINT */ | |
96 | #define cachePrint(e, k, d) | |
97 | #define DUMP_ALL_CACHE 0 | |
98 | #endif /* CACHE_PRINT */ | |
99 | ||
100 | #if DUMP_ALL_CACHE | |
101 | static void dumpAllCache(void); | |
102 | #else | |
103 | #define dumpAllCache() | |
104 | #endif | |
105 | ||
106 | /* | |
107 | * One entry (value) in SessionCache. | |
108 | */ | |
109 | typedef struct SessionCacheEntry SessionCacheEntry; | |
110 | struct SessionCacheEntry { | |
111 | /* Linked list of SessionCacheEntries. */ | |
112 | SessionCacheEntry *next; | |
113 | ||
114 | SSLBuffer mKey; | |
115 | SSLBuffer mSessionData; | |
116 | ||
117 | /* this entry to be removed from session map at this time */ | |
118 | CFAbsoluteTime mExpiration; | |
119 | }; | |
120 | ||
121 | /* | |
122 | * Note: the caller passes in the expiration time solely to accomodate the | |
123 | * instantiation of a single const Time::Interval for use in calculating | |
124 | * TTL. This const, SessionCache.mTimeToLive, is in the singleton gSession Cache. | |
125 | */ | |
126 | /* | |
127 | * This constructor, the only one, allocs copies of the key and value | |
128 | * SSLBuffers. | |
129 | */ | |
130 | static SessionCacheEntry *SessionCacheEntryCreate( | |
131 | const SSLBuffer *key, | |
132 | const SSLBuffer *sessionData, | |
133 | CFAbsoluteTime expirationTime) | |
134 | { | |
135 | OSStatus serr; | |
136 | ||
137 | SessionCacheEntry *entry = sslMalloc(sizeof(SessionCacheEntry)); | |
138 | if (entry == NULL) | |
139 | return NULL; | |
140 | ||
141 | serr = SSLCopyBuffer(key, &entry->mKey); | |
142 | if(serr) { | |
143 | sslFree (entry); | |
144 | return NULL; | |
145 | } | |
146 | serr = SSLCopyBuffer(sessionData, &entry->mSessionData); | |
147 | if(serr) { | |
427c49bc | 148 | SSLFreeBuffer(&entry->mKey); |
b1ab9ed8 A |
149 | sslFree (entry); |
150 | return NULL; | |
151 | } | |
152 | ||
153 | sslLogSessCacheDebug("SessionCacheEntryCreate(buf,buf) %p", entry); | |
154 | entry->mExpiration = expirationTime; | |
155 | ||
156 | return entry; | |
157 | } | |
158 | ||
159 | static void SessionCacheEntryDelete(SessionCacheEntry *entry) | |
160 | { | |
161 | sslLogSessCacheDebug("~SessionCacheEntryDelete() %p", entry); | |
427c49bc A |
162 | SSLFreeBuffer(&entry->mKey); // no SSLContext |
163 | SSLFreeBuffer(&entry->mSessionData); | |
b1ab9ed8 A |
164 | sslFree(entry); |
165 | } | |
166 | ||
167 | /* basic lookup/match function */ | |
168 | static bool SessionCacheEntryMatchKey(SessionCacheEntry *entry, | |
169 | const SSLBuffer *key) | |
170 | { | |
171 | if(key->length != entry->mKey.length) { | |
172 | return false; | |
173 | } | |
174 | if((key->data == NULL) || (entry->mKey.data == NULL)) { | |
175 | return false; | |
176 | } | |
177 | return (memcmp(key->data, entry->mKey.data, entry->mKey.length) == 0); | |
178 | } | |
179 | ||
180 | static bool SessionCacheEntryIsStale(SessionCacheEntry *entry, | |
181 | CFAbsoluteTime now) | |
182 | { | |
183 | return now > entry->mExpiration; | |
184 | } | |
185 | ||
186 | /* has this expired? */ | |
187 | static bool SessionCacheEntryIsStaleNow(SessionCacheEntry *entry) | |
188 | { | |
189 | return SessionCacheEntryIsStale(entry, CFAbsoluteTimeGetCurrent()); | |
190 | } | |
191 | ||
192 | /* replace existing mSessionData */ | |
193 | static OSStatus SessionCacheEntrySetSessionData(SessionCacheEntry *entry, | |
194 | const SSLBuffer *data) | |
195 | { | |
427c49bc | 196 | SSLFreeBuffer(&entry->mSessionData); |
b1ab9ed8 A |
197 | return SSLCopyBuffer(data, &entry->mSessionData); |
198 | } | |
199 | ||
200 | /* | |
201 | * Global list of sessions and associated state. We maintain a singleton of | |
202 | * this. | |
203 | */ | |
204 | typedef struct SessionCache { | |
205 | SessionCacheEntry *head; | |
206 | CFTimeInterval mTimeToLive; /* default time-to-live in seconds */ | |
207 | } SessionCache; | |
208 | ||
209 | static pthread_mutex_t gSessionCacheLock = PTHREAD_MUTEX_INITIALIZER; | |
210 | static SessionCache *gSessionCache = NULL; | |
211 | ||
212 | static void SessionCacheInit(void) { | |
213 | gSessionCache = sslMalloc(sizeof(SessionCache)); | |
214 | gSessionCache->head = NULL; | |
215 | gSessionCache->mTimeToLive = SESSION_CACHE_TTL; | |
216 | } | |
217 | ||
218 | static SessionCache *SessionCacheGetLockedInstance(void) { | |
219 | pthread_mutex_lock(&gSessionCacheLock); | |
220 | if (!gSessionCache) { | |
221 | /* We could use pthread_once, but we already have a mutex for other | |
222 | reasons. */ | |
223 | SessionCacheInit(); | |
224 | } | |
225 | ||
226 | return gSessionCache; | |
227 | } | |
228 | ||
229 | /* these three correspond to the C functions exported by this file */ | |
230 | static OSStatus SessionCacheAddEntry( | |
231 | SessionCache *cache, | |
232 | const SSLBuffer *sessionKey, | |
233 | const SSLBuffer *sessionData, | |
234 | uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */ | |
235 | { | |
236 | SessionCacheEntry *entry = NULL; | |
237 | SessionCacheEntry **current; | |
238 | CFTimeInterval expireTime; | |
239 | ||
240 | for (current = &(cache->head); *current; current = &((*current)->next)) { | |
241 | entry = *current; | |
242 | if (SessionCacheEntryMatchKey(entry, sessionKey)) { | |
243 | /* cache hit - just update this entry's sessionData if necessary */ | |
244 | /* Note we leave expiration time and position in queue unchanged | |
245 | - OK? */ | |
246 | /* What if the entry has already expired? */ | |
247 | if((entry->mSessionData.length == sessionData->length) && | |
248 | (memcmp(entry->mSessionData.data, sessionData->data, | |
249 | sessionData->length) == 0)) { | |
250 | /* | |
251 | * These usually match, and a memcmp is a lot cheaper than | |
252 | * a malloc and a free, hence this quick optimization..... | |
253 | */ | |
254 | sslLogSessCacheDebug("SessionCache::addEntry CACHE HIT " | |
255 | "entry = %p", entry); | |
427c49bc | 256 | return errSecSuccess; |
b1ab9ed8 A |
257 | } |
258 | else { | |
259 | sslLogSessCacheDebug("SessionCache::addEntry CACHE REPLACE " | |
260 | "entry = %p", entry); | |
261 | return SessionCacheEntrySetSessionData(entry, sessionData); | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | expireTime = CFAbsoluteTimeGetCurrent(); | |
267 | if(timeToLive) { | |
268 | /* caller-specified */ | |
269 | expireTime += (CFTimeInterval)timeToLive; | |
270 | } | |
271 | else { | |
272 | /* default */ | |
273 | expireTime += cache->mTimeToLive; | |
274 | } | |
275 | /* this allocs new copy of incoming sessionKey and sessionData */ | |
276 | entry = SessionCacheEntryCreate(sessionKey, sessionData, expireTime); | |
277 | ||
278 | sslLogSessCacheDebug("SessionCache::addEntry %p", entry); | |
279 | cachePrint(entry, sessionKey, sessionData); | |
280 | dumpAllCache(); | |
281 | ||
282 | /* add to head of queue for LIFO caching */ | |
283 | entry->next = cache->head; | |
284 | cache->head = entry; | |
285 | ||
427c49bc | 286 | return errSecSuccess; |
b1ab9ed8 A |
287 | } |
288 | ||
289 | static OSStatus SessionCacheLookupEntry( | |
290 | SessionCache *cache, | |
291 | const SSLBuffer *sessionKey, | |
292 | SSLBuffer *sessionData) | |
293 | { | |
294 | SessionCacheEntry *entry = NULL; | |
295 | SessionCacheEntry **current; | |
296 | for (current = &(cache->head); *current; current = &((*current)->next)) { | |
297 | entry = *current; | |
298 | if (SessionCacheEntryMatchKey(entry, sessionKey)) | |
299 | break; | |
300 | } | |
301 | ||
302 | if (*current == NULL) | |
303 | return errSSLSessionNotFound; | |
304 | ||
305 | if (SessionCacheEntryIsStaleNow(entry)) { | |
306 | sslLogSessCacheDebug("SessionCache::lookupEntry %p: STALE " | |
307 | "entry, deleting; current %p, entry->next %p", | |
308 | entry, current, entry->next); | |
309 | cachePrint(entry, sessionKey, &entry->mSessionData); | |
310 | *current = entry->next; | |
311 | SessionCacheEntryDelete(entry); | |
312 | return errSSLSessionNotFound; | |
313 | } | |
314 | ||
d8f41ccd A |
315 | /* alloc/copy sessionData from existing entry (caller must free) */ |
316 | return SSLCopyBuffer(&entry->mSessionData, sessionData); | |
b1ab9ed8 A |
317 | } |
318 | ||
319 | static OSStatus SessionCacheDeleteEntry( | |
320 | SessionCache *cache, | |
321 | const SSLBuffer *sessionKey) | |
322 | { | |
323 | SessionCacheEntry **current; | |
324 | ||
325 | for (current = &(cache->head); *current; current = &((*current)->next)) { | |
326 | SessionCacheEntry *entry = *current; | |
327 | if (SessionCacheEntryMatchKey(entry, sessionKey)) { | |
328 | #ifndef DEBUG | |
329 | sslLogSessCacheDebug("...SessionCacheDeleteEntry: deleting " | |
330 | "cached session (%p)", entry); | |
331 | cachePrint(entry, &entry->mKey, &entry->mSessionData); | |
332 | #endif | |
333 | *current = entry->next; | |
334 | SessionCacheEntryDelete(entry); | |
427c49bc | 335 | return errSecSuccess; |
b1ab9ed8 A |
336 | } |
337 | } | |
338 | ||
427c49bc | 339 | return errSecSuccess; |
b1ab9ed8 A |
340 | } |
341 | ||
342 | /* cleanup, delete stale entries */ | |
343 | static bool SessionCacheCleanup(SessionCache *cache) | |
344 | { | |
345 | bool brtn = false; | |
346 | CFAbsoluteTime rightNow = CFAbsoluteTimeGetCurrent(); | |
347 | SessionCacheEntry **current; | |
348 | ||
349 | for (current = &(cache->head); *current;) { | |
350 | SessionCacheEntry *entry = *current; | |
351 | if(SessionCacheEntryIsStale(entry, rightNow)) { | |
352 | #ifndef DEBUG | |
353 | sslLogSessCacheDebug("...SessionCacheCleanup: deleting " | |
354 | "cached session (%p)", entry); | |
355 | cachePrint(entry, &entry->mKey, &entry->mSessionData); | |
356 | #endif | |
357 | *current = entry->next; | |
358 | SessionCacheEntryDelete(entry); | |
359 | } | |
360 | else { | |
361 | current = &((*current)->next); | |
362 | /* we're leaving one in the map */ | |
363 | brtn = true; | |
364 | } | |
365 | } | |
366 | return brtn; | |
367 | } | |
368 | ||
369 | #if DUMP_ALL_CACHE | |
370 | static void dumpAllCache(void) | |
371 | { | |
372 | SessionCache *cache = gSessionCache; | |
373 | SessionCacheEntry *entry; | |
374 | ||
375 | printf("Contents of sessionCache:\n"); | |
376 | for(entry = cache->head; entry; entry = entry->next) { | |
377 | cachePrint(entry, &entry->mKey, &entry->mSessionData); | |
378 | } | |
379 | } | |
380 | #endif /* DUMP_ALL_CACHE */ | |
381 | ||
382 | /* | |
383 | * Store opaque sessionData, associated with opaque sessionKey. | |
384 | */ | |
385 | OSStatus sslAddSession ( | |
386 | const SSLBuffer sessionKey, | |
387 | const SSLBuffer sessionData, | |
388 | uint32_t timeToLive) /* optional time-to-live in seconds; 0 ==> default */ | |
389 | { | |
390 | SessionCache *cache = SessionCacheGetLockedInstance(); | |
391 | OSStatus serr; | |
392 | if (!cache) | |
393 | serr = errSSLSessionNotFound; | |
394 | else | |
395 | { | |
396 | serr = SessionCacheAddEntry(cache, &sessionKey, &sessionData, timeToLive); | |
397 | ||
398 | dumpAllCache(); | |
399 | } | |
400 | ||
401 | pthread_mutex_unlock(&gSessionCacheLock); | |
402 | return serr; | |
403 | } | |
404 | ||
405 | /* | |
406 | * Given an opaque sessionKey, alloc & retrieve associated sessionData. | |
407 | */ | |
d8f41ccd | 408 | OSStatus sslCopySession ( |
b1ab9ed8 A |
409 | const SSLBuffer sessionKey, |
410 | SSLBuffer *sessionData) | |
411 | { | |
412 | SessionCache *cache = SessionCacheGetLockedInstance(); | |
413 | OSStatus serr; | |
414 | if (!cache) | |
415 | serr = errSSLSessionNotFound; | |
416 | else | |
417 | { | |
418 | serr = SessionCacheLookupEntry(cache, &sessionKey, sessionData); | |
419 | ||
427c49bc | 420 | sslLogSessCacheDebug("sslGetSession(%d, %p): %d", |
b1ab9ed8 | 421 | (int)sessionKey.length, sessionKey.data, |
427c49bc | 422 | (int)serr); |
b1ab9ed8 A |
423 | if(!serr) { |
424 | cachePrint(NULL, &sessionKey, sessionData); | |
425 | } | |
426 | else { | |
427 | cachePrint(NULL, &sessionKey, NULL); | |
428 | } | |
429 | dumpAllCache(); | |
430 | } | |
431 | ||
432 | pthread_mutex_unlock(&gSessionCacheLock); | |
433 | ||
434 | return serr; | |
435 | } | |
436 | ||
437 | OSStatus sslDeleteSession ( | |
438 | const SSLBuffer sessionKey) | |
439 | { | |
440 | SessionCache *cache = SessionCacheGetLockedInstance(); | |
441 | OSStatus serr; | |
442 | if (!cache) | |
443 | serr = errSSLSessionNotFound; | |
444 | else | |
445 | { | |
446 | serr = SessionCacheDeleteEntry(cache, &sessionKey); | |
447 | } | |
448 | ||
449 | pthread_mutex_unlock(&gSessionCacheLock); | |
450 | return serr; | |
451 | } | |
452 | ||
453 | /* cleanup up session cache, deleting stale entries. */ | |
454 | OSStatus sslCleanupSession(void) | |
455 | { | |
456 | SessionCache *cache = SessionCacheGetLockedInstance(); | |
427c49bc | 457 | OSStatus serr = errSecSuccess; |
b1ab9ed8 A |
458 | bool moreToGo = false; |
459 | ||
460 | if (!cache) | |
461 | serr = errSSLSessionNotFound; | |
462 | else | |
463 | { | |
464 | moreToGo = SessionCacheCleanup(cache); | |
465 | } | |
466 | /* Possible TBD: if moreToGo, schedule a timed callback to this function */ | |
467 | ||
468 | pthread_mutex_unlock(&gSessionCacheLock); | |
469 | return serr; | |
470 | } |