1 /*
2 * This file implements the CLIENT Session ID cache.
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 #include "cert.h"
9 #include "pk11pub.h"
10 #include "secitem.h"
11 #include "ssl.h"
12 #include "nss.h"
13
14 #include "sslimpl.h"
15 #include "sslproto.h"
16 #include "nssilock.h"
17 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
18 #include <time.h>
19 #endif
20
21 PRUint32 ssl_sid_timeout = 100;
22 PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */
23
24 static sslSessionID *cache = NULL;
25 static PZLock *cacheLock = NULL;
26
27 /* sids can be in one of 4 states:
28 *
29 * never_cached, created, but not yet put into cache.
30 * in_client_cache, in the client cache's linked list.
31 * in_server_cache, entry came from the server's cache file.
32 * invalid_cache has been removed from the cache.
33 */
34
35 #define LOCK_CACHE lock_cache()
36 #define UNLOCK_CACHE PZ_Unlock(cacheLock)
37
38 static SECStatus
ssl_InitClientSessionCacheLock(void)39 ssl_InitClientSessionCacheLock(void)
40 {
41 cacheLock = PZ_NewLock(nssILockCache);
42 return cacheLock ? SECSuccess : SECFailure;
43 }
44
45 static SECStatus
ssl_FreeClientSessionCacheLock(void)46 ssl_FreeClientSessionCacheLock(void)
47 {
48 if (cacheLock) {
49 PZ_DestroyLock(cacheLock);
50 cacheLock = NULL;
51 return SECSuccess;
52 }
53 PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
54 return SECFailure;
55 }
56
57 static PRBool LocksInitializedEarly = PR_FALSE;
58
59 static SECStatus
FreeSessionCacheLocks()60 FreeSessionCacheLocks()
61 {
62 SECStatus rv1, rv2;
63 rv1 = ssl_FreeSymWrapKeysLock();
64 rv2 = ssl_FreeClientSessionCacheLock();
65 if ((SECSuccess == rv1) && (SECSuccess == rv2)) {
66 return SECSuccess;
67 }
68 return SECFailure;
69 }
70
71 static SECStatus
InitSessionCacheLocks(void)72 InitSessionCacheLocks(void)
73 {
74 SECStatus rv1, rv2;
75 PRErrorCode rc;
76 rv1 = ssl_InitSymWrapKeysLock();
77 rv2 = ssl_InitClientSessionCacheLock();
78 if ((SECSuccess == rv1) && (SECSuccess == rv2)) {
79 return SECSuccess;
80 }
81 rc = PORT_GetError();
82 FreeSessionCacheLocks();
83 PORT_SetError(rc);
84 return SECFailure;
85 }
86
87 /* free the session cache locks if they were initialized early */
88 SECStatus
ssl_FreeSessionCacheLocks()89 ssl_FreeSessionCacheLocks()
90 {
91 PORT_Assert(PR_TRUE == LocksInitializedEarly);
92 if (!LocksInitializedEarly) {
93 PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
94 return SECFailure;
95 }
96 FreeSessionCacheLocks();
97 LocksInitializedEarly = PR_FALSE;
98 return SECSuccess;
99 }
100
101 static PRCallOnceType lockOnce;
102
103 /* free the session cache locks if they were initialized lazily */
104 static SECStatus
ssl_ShutdownLocks(void * appData,void * nssData)105 ssl_ShutdownLocks(void *appData, void *nssData)
106 {
107 PORT_Assert(PR_FALSE == LocksInitializedEarly);
108 if (LocksInitializedEarly) {
109 PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
110 return SECFailure;
111 }
112 FreeSessionCacheLocks();
113 memset(&lockOnce, 0, sizeof(lockOnce));
114 return SECSuccess;
115 }
116
117 static PRStatus
initSessionCacheLocksLazily(void)118 initSessionCacheLocksLazily(void)
119 {
120 SECStatus rv = InitSessionCacheLocks();
121 if (SECSuccess != rv) {
122 return PR_FAILURE;
123 }
124 rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL);
125 PORT_Assert(SECSuccess == rv);
126 if (SECSuccess != rv) {
127 return PR_FAILURE;
128 }
129 return PR_SUCCESS;
130 }
131
132 /* lazyInit means that the call is not happening during a 1-time
133 * initialization function, but rather during dynamic, lazy initialization
134 */
135 SECStatus
ssl_InitSessionCacheLocks(PRBool lazyInit)136 ssl_InitSessionCacheLocks(PRBool lazyInit)
137 {
138 if (LocksInitializedEarly) {
139 return SECSuccess;
140 }
141
142 if (lazyInit) {
143 return (PR_SUCCESS ==
144 PR_CallOnce(&lockOnce, initSessionCacheLocksLazily))
145 ? SECSuccess
146 : SECFailure;
147 }
148
149 if (SECSuccess == InitSessionCacheLocks()) {
150 LocksInitializedEarly = PR_TRUE;
151 return SECSuccess;
152 }
153
154 return SECFailure;
155 }
156
157 static void
lock_cache(void)158 lock_cache(void)
159 {
160 ssl_InitSessionCacheLocks(PR_TRUE);
161 PZ_Lock(cacheLock);
162 }
163
164 /* BEWARE: This function gets called for both client and server SIDs !!
165 * If the unreferenced sid is not in the cache, Free sid and its contents.
166 */
167 static void
ssl_DestroySID(sslSessionID * sid)168 ssl_DestroySID(sslSessionID *sid)
169 {
170 SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
171 PORT_Assert(sid->references == 0);
172 PORT_Assert(sid->cached != in_client_cache);
173
174 if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
175 SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
176 PR_FALSE);
177 }
178 if (sid->u.ssl3.srvName.data) {
179 SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
180 }
181 if (sid->u.ssl3.signedCertTimestamps.data) {
182 SECITEM_FreeItem(&sid->u.ssl3.signedCertTimestamps, PR_FALSE);
183 }
184
185 if (sid->u.ssl3.lock) {
186 PR_DestroyRWLock(sid->u.ssl3.lock);
187 }
188
189 if (sid->peerID != NULL)
190 PORT_Free((void *)sid->peerID); /* CONST */
191
192 if (sid->urlSvrName != NULL)
193 PORT_Free((void *)sid->urlSvrName); /* CONST */
194
195 if (sid->peerCert) {
196 CERT_DestroyCertificate(sid->peerCert);
197 }
198 if (sid->peerCertStatus.items) {
199 SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
200 }
201
202 if (sid->localCert) {
203 CERT_DestroyCertificate(sid->localCert);
204 }
205
206 SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE);
207
208 PORT_ZFree(sid, sizeof(sslSessionID));
209 }
210
211 /* BEWARE: This function gets called for both client and server SIDs !!
212 * Decrement reference count, and
213 * free sid if ref count is zero, and sid is not in the cache.
214 * Does NOT remove from the cache first.
215 * If the sid is still in the cache, it is left there until next time
216 * the cache list is traversed.
217 */
218 static void
ssl_FreeLockedSID(sslSessionID * sid)219 ssl_FreeLockedSID(sslSessionID *sid)
220 {
221 PORT_Assert(sid->references >= 1);
222 if (--sid->references == 0) {
223 ssl_DestroySID(sid);
224 }
225 }
226
227 /* BEWARE: This function gets called for both client and server SIDs !!
228 * Decrement reference count, and
229 * free sid if ref count is zero, and sid is not in the cache.
230 * Does NOT remove from the cache first.
231 * These locks are necessary because the sid _might_ be in the cache list.
232 */
233 void
ssl_FreeSID(sslSessionID * sid)234 ssl_FreeSID(sslSessionID *sid)
235 {
236 LOCK_CACHE;
237 ssl_FreeLockedSID(sid);
238 UNLOCK_CACHE;
239 }
240
241 /************************************************************************/
242
243 /*
244 ** Lookup sid entry in cache by Address, port, and peerID string.
245 ** If found, Increment reference count, and return pointer to caller.
246 ** If it has timed out or ref count is zero, remove from list and free it.
247 */
248
249 sslSessionID *
ssl_LookupSID(const PRIPv6Addr * addr,PRUint16 port,const char * peerID,const char * urlSvrName)250 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
251 const char *urlSvrName)
252 {
253 sslSessionID **sidp;
254 sslSessionID *sid;
255 PRUint32 now;
256
257 if (!urlSvrName)
258 return NULL;
259 now = ssl_Time();
260 LOCK_CACHE;
261 sidp = &cache;
262 while ((sid = *sidp) != 0) {
263 PORT_Assert(sid->cached == in_client_cache);
264 PORT_Assert(sid->references >= 1);
265
266 SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid));
267
268 if (sid->expirationTime < now) {
269 /*
270 ** This session-id timed out.
271 ** Don't even care who it belongs to, blow it out of our cache.
272 */
273 SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
274 now - sid->creationTime, sid->references));
275
276 *sidp = sid->next; /* delink it from the list. */
277 sid->cached = invalid_cache; /* mark not on list. */
278 ssl_FreeLockedSID(sid); /* drop ref count, free. */
279 } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
280 (sid->port == port) && /* server port matches */
281 /* proxy (peerID) matches */
282 (((peerID == NULL) && (sid->peerID == NULL)) ||
283 ((peerID != NULL) && (sid->peerID != NULL) &&
284 PORT_Strcmp(sid->peerID, peerID) == 0)) &&
285 /* is cacheable */
286 (sid->u.ssl3.keys.resumable) &&
287 /* server hostname matches. */
288 (sid->urlSvrName != NULL) &&
289 (0 == PORT_Strcmp(urlSvrName, sid->urlSvrName))) {
290 /* Hit */
291 sid->lastAccessTime = now;
292 sid->references++;
293 break;
294 } else {
295 sidp = &sid->next;
296 }
297 }
298 UNLOCK_CACHE;
299 return sid;
300 }
301
302 /*
303 ** Add an sid to the cache or return a previously cached entry to the cache.
304 ** Although this is static, it is called via ss->sec.cache().
305 */
306 static void
CacheSID(sslSessionID * sid)307 CacheSID(sslSessionID *sid)
308 {
309 PRUint32 expirationPeriod;
310
311 PORT_Assert(sid->cached == never_cached);
312
313 SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
314 "time=%x cached=%d",
315 sid, sid->cached, sid->addr.pr_s6_addr32[0],
316 sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2],
317 sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime,
318 sid->cached));
319
320 if (!sid->urlSvrName) {
321 /* don't cache this SID because it can never be matched */
322 return;
323 }
324
325 if (sid->u.ssl3.sessionIDLength == 0 &&
326 sid->u.ssl3.locked.sessionTicket.ticket.data == NULL)
327 return;
328
329 /* Client generates the SessionID if this was a stateless resume. */
330 if (sid->u.ssl3.sessionIDLength == 0) {
331 SECStatus rv;
332 rv = PK11_GenerateRandom(sid->u.ssl3.sessionID,
333 SSL3_SESSIONID_BYTES);
334 if (rv != SECSuccess)
335 return;
336 sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
337 }
338 expirationPeriod = ssl3_sid_timeout;
339 PRINT_BUF(8, (0, "sessionID:",
340 sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength));
341
342 sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL);
343 if (!sid->u.ssl3.lock) {
344 return;
345 }
346 PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0);
347 if (!sid->creationTime)
348 sid->lastAccessTime = sid->creationTime = ssl_Time();
349 if (!sid->expirationTime)
350 sid->expirationTime = sid->creationTime + expirationPeriod;
351
352 /*
353 * Put sid into the cache. Bump reference count to indicate that
354 * cache is holding a reference. Uncache will reduce the cache
355 * reference.
356 */
357 LOCK_CACHE;
358 sid->references++;
359 sid->cached = in_client_cache;
360 sid->next = cache;
361 cache = sid;
362 UNLOCK_CACHE;
363 }
364
365 /*
366 * If sid "zap" is in the cache,
367 * removes sid from cache, and decrements reference count.
368 * Caller must hold cache lock.
369 */
370 static void
UncacheSID(sslSessionID * zap)371 UncacheSID(sslSessionID *zap)
372 {
373 sslSessionID **sidp = &cache;
374 sslSessionID *sid;
375
376 if (zap->cached != in_client_cache) {
377 return;
378 }
379
380 SSL_TRC(8, ("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
381 "time=%x cipherSuite=%d",
382 zap, zap->cached, zap->addr.pr_s6_addr32[0],
383 zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2],
384 zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime,
385 zap->u.ssl3.cipherSuite));
386
387 /* See if it's in the cache, if so nuke it */
388 while ((sid = *sidp) != 0) {
389 if (sid == zap) {
390 /*
391 ** Bingo. Reduce reference count by one so that when
392 ** everyone is done with the sid we can free it up.
393 */
394 *sidp = zap->next;
395 zap->cached = invalid_cache;
396 ssl_FreeLockedSID(zap);
397 return;
398 }
399 sidp = &sid->next;
400 }
401 }
402
403 /* If sid "zap" is in the cache,
404 * removes sid from cache, and decrements reference count.
405 * Although this function is static, it is called externally via
406 * ss->sec.uncache().
407 */
408 static void
LockAndUncacheSID(sslSessionID * zap)409 LockAndUncacheSID(sslSessionID *zap)
410 {
411 LOCK_CACHE;
412 UncacheSID(zap);
413 UNLOCK_CACHE;
414 }
415
416 /* choose client or server cache functions for this sslsocket. */
417 void
ssl_ChooseSessionIDProcs(sslSecurityInfo * sec)418 ssl_ChooseSessionIDProcs(sslSecurityInfo *sec)
419 {
420 if (sec->isServer) {
421 sec->cache = ssl_sid_cache;
422 sec->uncache = ssl_sid_uncache;
423 } else {
424 sec->cache = CacheSID;
425 sec->uncache = LockAndUncacheSID;
426 }
427 }
428
429 /* wipe out the entire client session cache. */
430 void
SSL_ClearSessionCache(void)431 SSL_ClearSessionCache(void)
432 {
433 LOCK_CACHE;
434 while (cache != NULL)
435 UncacheSID(cache);
436 UNLOCK_CACHE;
437 }
438
439 /* returns an unsigned int containing the number of seconds in PR_Now() */
440 PRUint32
ssl_Time(void)441 ssl_Time(void)
442 {
443 #ifdef UNSAFE_FUZZER_MODE
444 return 1234;
445 #endif
446
447 PRUint32 myTime;
448 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
449 myTime = time(NULL); /* accurate until the year 2038. */
450 #else
451 /* portable, but possibly slower */
452 PRTime now;
453 PRInt64 ll;
454
455 now = PR_Now();
456 LL_I2L(ll, 1000000L);
457 LL_DIV(now, now, ll);
458 LL_L2UI(myTime, now);
459 #endif
460 return myTime;
461 }
462
463 void
ssl3_SetSIDSessionTicket(sslSessionID * sid,NewSessionTicket * newSessionTicket)464 ssl3_SetSIDSessionTicket(sslSessionID *sid,
465 /*in/out*/ NewSessionTicket *newSessionTicket)
466 {
467 PORT_Assert(sid);
468 PORT_Assert(newSessionTicket);
469 PORT_Assert(newSessionTicket->ticket.data);
470 PORT_Assert(newSessionTicket->ticket.len != 0);
471
472 /* if sid->u.ssl3.lock, we are updating an existing entry that is already
473 * cached or was once cached, so we need to acquire and release the write
474 * lock. Otherwise, this is a new session that isn't shared with anything
475 * yet, so no locking is needed.
476 */
477 if (sid->u.ssl3.lock) {
478 PR_RWLock_Wlock(sid->u.ssl3.lock);
479 if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
480 SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
481 PR_FALSE);
482 }
483 }
484
485 PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data);
486
487 /* Do a shallow copy, moving the ticket data. */
488 sid->u.ssl3.locked.sessionTicket = *newSessionTicket;
489 newSessionTicket->ticket.data = NULL;
490 newSessionTicket->ticket.len = 0;
491
492 if (sid->u.ssl3.lock) {
493 PR_RWLock_Unlock(sid->u.ssl3.lock);
494 }
495 }
496