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