• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 PRCallOnceType lockOnce;
39 
40 /* FreeSessionCacheLocks is a callback from NSS_RegisterShutdown which destroys
41  * the session cache locks on shutdown and resets them to their initial
42  * state. */
43 static SECStatus
FreeSessionCacheLocks(void * appData,void * nssData)44 FreeSessionCacheLocks(void* appData, void* nssData)
45 {
46     static const PRCallOnceType pristineCallOnce;
47     SECStatus rv;
48 
49     if (!cacheLock) {
50         PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
51         return SECFailure;
52     }
53 
54     PZ_DestroyLock(cacheLock);
55     cacheLock = NULL;
56 
57     rv = ssl_FreeSymWrapKeysLock();
58     if (rv != SECSuccess) {
59         return rv;
60     }
61 
62     lockOnce = pristineCallOnce;
63     return SECSuccess;
64 }
65 
66 /* InitSessionCacheLocks is called, protected by lockOnce, to create the
67  * session cache locks. */
68 static PRStatus
InitSessionCacheLocks(void)69 InitSessionCacheLocks(void)
70 {
71     SECStatus rv;
72 
73     cacheLock = PZ_NewLock(nssILockCache);
74     if (cacheLock == NULL) {
75         return PR_FAILURE;
76     }
77     rv = ssl_InitSymWrapKeysLock();
78     if (rv != SECSuccess) {
79         PRErrorCode error = PORT_GetError();
80         PZ_DestroyLock(cacheLock);
81         cacheLock = NULL;
82         PORT_SetError(error);
83         return PR_FAILURE;
84     }
85 
86     rv = NSS_RegisterShutdown(FreeSessionCacheLocks, NULL);
87     PORT_Assert(SECSuccess == rv);
88     if (SECSuccess != rv) {
89         return PR_FAILURE;
90     }
91     return PR_SUCCESS;
92 }
93 
94 SECStatus
ssl_InitSessionCacheLocks(void)95 ssl_InitSessionCacheLocks(void)
96 {
97     return (PR_SUCCESS ==
98             PR_CallOnce(&lockOnce, InitSessionCacheLocks)) ?
99            SECSuccess : SECFailure;
100 }
101 
102 static void
lock_cache(void)103 lock_cache(void)
104 {
105     ssl_InitSessionCacheLocks();
106     PZ_Lock(cacheLock);
107 }
108 
109 /* BEWARE: This function gets called for both client and server SIDs !!
110  * If the unreferenced sid is not in the cache, Free sid and its contents.
111  */
112 static void
ssl_DestroySID(sslSessionID * sid)113 ssl_DestroySID(sslSessionID *sid)
114 {
115     int i;
116     SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
117     PORT_Assert(sid->references == 0);
118     PORT_Assert(sid->cached != in_client_cache);
119 
120     if (sid->version < SSL_LIBRARY_VERSION_3_0) {
121 	SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE);
122 	SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE);
123     } else {
124         if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
125             SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
126                              PR_FALSE);
127         }
128         if (sid->u.ssl3.srvName.data) {
129             SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
130         }
131         if (sid->u.ssl3.originalHandshakeHash.data) {
132             SECITEM_FreeItem(&sid->u.ssl3.originalHandshakeHash, PR_FALSE);
133         }
134         if (sid->u.ssl3.signedCertTimestamps.data) {
135             SECITEM_FreeItem(&sid->u.ssl3.signedCertTimestamps, PR_FALSE);
136         }
137 
138         if (sid->u.ssl3.lock) {
139             NSSRWLock_Destroy(sid->u.ssl3.lock);
140         }
141     }
142 
143     if (sid->peerID != NULL)
144 	PORT_Free((void *)sid->peerID);		/* CONST */
145 
146     if (sid->urlSvrName != NULL)
147 	PORT_Free((void *)sid->urlSvrName);	/* CONST */
148 
149     if ( sid->peerCert ) {
150 	CERT_DestroyCertificate(sid->peerCert);
151     }
152     for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
153 	CERT_DestroyCertificate(sid->peerCertChain[i]);
154     }
155     if (sid->peerCertStatus.items) {
156         SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
157     }
158 
159     if ( sid->localCert ) {
160 	CERT_DestroyCertificate(sid->localCert);
161     }
162 
163     PORT_ZFree(sid, sizeof(sslSessionID));
164 }
165 
166 /* BEWARE: This function gets called for both client and server SIDs !!
167  * Decrement reference count, and
168  *    free sid if ref count is zero, and sid is not in the cache.
169  * Does NOT remove from the cache first.
170  * If the sid is still in the cache, it is left there until next time
171  * the cache list is traversed.
172  */
173 static void
ssl_FreeLockedSID(sslSessionID * sid)174 ssl_FreeLockedSID(sslSessionID *sid)
175 {
176     PORT_Assert(sid->references >= 1);
177     if (--sid->references == 0) {
178 	ssl_DestroySID(sid);
179     }
180 }
181 
182 /* BEWARE: This function gets called for both client and server SIDs !!
183  * Decrement reference count, and
184  *    free sid if ref count is zero, and sid is not in the cache.
185  * Does NOT remove from the cache first.
186  * These locks are necessary because the sid _might_ be in the cache list.
187  */
188 void
ssl_FreeSID(sslSessionID * sid)189 ssl_FreeSID(sslSessionID *sid)
190 {
191     LOCK_CACHE;
192     ssl_FreeLockedSID(sid);
193     UNLOCK_CACHE;
194 }
195 
196 /************************************************************************/
197 
198 /*
199 **  Lookup sid entry in cache by Address, port, and peerID string.
200 **  If found, Increment reference count, and return pointer to caller.
201 **  If it has timed out or ref count is zero, remove from list and free it.
202 */
203 
204 sslSessionID *
ssl_LookupSID(const PRIPv6Addr * addr,PRUint16 port,const char * peerID,const char * urlSvrName)205 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
206               const char * urlSvrName)
207 {
208     sslSessionID **sidp;
209     sslSessionID * sid;
210     PRUint32       now;
211 
212     if (!urlSvrName)
213     	return NULL;
214     now = ssl_Time();
215     LOCK_CACHE;
216     sidp = &cache;
217     while ((sid = *sidp) != 0) {
218 	PORT_Assert(sid->cached == in_client_cache);
219 	PORT_Assert(sid->references >= 1);
220 
221 	SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid));
222 
223 	if (sid->expirationTime < now) {
224 	    /*
225 	    ** This session-id timed out.
226 	    ** Don't even care who it belongs to, blow it out of our cache.
227 	    */
228 	    SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
229 			now - sid->creationTime, sid->references));
230 
231 	    *sidp = sid->next; 			/* delink it from the list. */
232 	    sid->cached = invalid_cache;	/* mark not on list. */
233 	    ssl_FreeLockedSID(sid);		/* drop ref count, free. */
234 	} else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
235 	           (sid->port == port) && /* server port matches */
236 		   /* proxy (peerID) matches */
237 		   (((peerID == NULL) && (sid->peerID == NULL)) ||
238 		    ((peerID != NULL) && (sid->peerID != NULL) &&
239 		     PORT_Strcmp(sid->peerID, peerID) == 0)) &&
240 		   /* is cacheable */
241 		   (sid->version < SSL_LIBRARY_VERSION_3_0 ||
242 		    sid->u.ssl3.keys.resumable) &&
243 		   /* server hostname matches. */
244 	           (sid->urlSvrName != NULL) &&
245 		   ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) ||
246 		    ((sid->peerCert != NULL) && (SECSuccess ==
247 		      CERT_VerifyCertName(sid->peerCert, urlSvrName))) )
248 		  ) {
249 	    /* Hit */
250 	    sid->lastAccessTime = now;
251 	    sid->references++;
252 	    break;
253 	} else {
254 	    sidp = &sid->next;
255 	}
256     }
257     UNLOCK_CACHE;
258     return sid;
259 }
260 
261 /*
262 ** Add an sid to the cache or return a previously cached entry to the cache.
263 ** Although this is static, it is called via ss->sec.cache().
264 */
265 static void
CacheSID(sslSessionID * sid)266 CacheSID(sslSessionID *sid)
267 {
268     PRUint32  expirationPeriod;
269 
270     PORT_Assert(sid->cached == never_cached);
271 
272     SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
273 		"time=%x cached=%d",
274 		sid, sid->cached, sid->addr.pr_s6_addr32[0],
275 		sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2],
276 		sid->addr.pr_s6_addr32[3],  sid->port, sid->creationTime,
277 		sid->cached));
278 
279     if (!sid->urlSvrName) {
280         /* don't cache this SID because it can never be matched */
281         return;
282     }
283 
284     /* XXX should be different trace for version 2 vs. version 3 */
285     if (sid->version < SSL_LIBRARY_VERSION_3_0) {
286 	expirationPeriod = ssl_sid_timeout;
287 	PRINT_BUF(8, (0, "sessionID:",
288 		  sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID)));
289 	PRINT_BUF(8, (0, "masterKey:",
290 		  sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len));
291 	PRINT_BUF(8, (0, "cipherArg:",
292 		  sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len));
293     } else {
294 	if (sid->u.ssl3.sessionIDLength == 0 &&
295 	    sid->u.ssl3.locked.sessionTicket.ticket.data == NULL)
296 	    return;
297 
298 	/* Client generates the SessionID if this was a stateless resume. */
299 	if (sid->u.ssl3.sessionIDLength == 0) {
300 	    SECStatus rv;
301 	    rv = PK11_GenerateRandom(sid->u.ssl3.sessionID,
302 		SSL3_SESSIONID_BYTES);
303 	    if (rv != SECSuccess)
304 		return;
305 	    sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
306 	}
307 	expirationPeriod = ssl3_sid_timeout;
308 	PRINT_BUF(8, (0, "sessionID:",
309 		      sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength));
310 
311 	sid->u.ssl3.lock = NSSRWLock_New(NSS_RWLOCK_RANK_NONE, NULL);
312 	if (!sid->u.ssl3.lock) {
313 	    return;
314 	}
315     }
316     PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0);
317     if (!sid->creationTime)
318 	sid->lastAccessTime = sid->creationTime = ssl_Time();
319     if (!sid->expirationTime)
320 	sid->expirationTime = sid->creationTime + expirationPeriod;
321 
322     /*
323      * Put sid into the cache.  Bump reference count to indicate that
324      * cache is holding a reference. Uncache will reduce the cache
325      * reference.
326      */
327     LOCK_CACHE;
328     sid->references++;
329     sid->cached = in_client_cache;
330     sid->next   = cache;
331     cache       = sid;
332     UNLOCK_CACHE;
333 }
334 
335 /*
336  * If sid "zap" is in the cache,
337  *    removes sid from cache, and decrements reference count.
338  * Caller must hold cache lock.
339  */
340 static void
UncacheSID(sslSessionID * zap)341 UncacheSID(sslSessionID *zap)
342 {
343     sslSessionID **sidp = &cache;
344     sslSessionID *sid;
345 
346     if (zap->cached != in_client_cache) {
347 	return;
348     }
349 
350     SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
351 	       "time=%x cipher=%d",
352 	       zap, zap->cached, zap->addr.pr_s6_addr32[0],
353 	       zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2],
354 	       zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime,
355 	       zap->u.ssl2.cipherType));
356     if (zap->version < SSL_LIBRARY_VERSION_3_0) {
357 	PRINT_BUF(8, (0, "sessionID:",
358 		      zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID)));
359 	PRINT_BUF(8, (0, "masterKey:",
360 		      zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len));
361 	PRINT_BUF(8, (0, "cipherArg:",
362 		      zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len));
363     }
364 
365     /* See if it's in the cache, if so nuke it */
366     while ((sid = *sidp) != 0) {
367 	if (sid == zap) {
368 	    /*
369 	    ** Bingo. Reduce reference count by one so that when
370 	    ** everyone is done with the sid we can free it up.
371 	    */
372 	    *sidp = zap->next;
373 	    zap->cached = invalid_cache;
374 	    ssl_FreeLockedSID(zap);
375 	    return;
376 	}
377 	sidp = &sid->next;
378     }
379 }
380 
381 /* If sid "zap" is in the cache,
382  *    removes sid from cache, and decrements reference count.
383  * Although this function is static, it is called externally via
384  *    ss->sec.uncache().
385  */
386 static void
LockAndUncacheSID(sslSessionID * zap)387 LockAndUncacheSID(sslSessionID *zap)
388 {
389     LOCK_CACHE;
390     UncacheSID(zap);
391     UNLOCK_CACHE;
392 
393 }
394 
395 /* choose client or server cache functions for this sslsocket. */
396 void
ssl_ChooseSessionIDProcs(sslSecurityInfo * sec)397 ssl_ChooseSessionIDProcs(sslSecurityInfo *sec)
398 {
399     if (sec->isServer) {
400 	sec->cache   = ssl_sid_cache;
401 	sec->uncache = ssl_sid_uncache;
402     } else {
403 	sec->cache   = CacheSID;
404 	sec->uncache = LockAndUncacheSID;
405     }
406 }
407 
408 /* wipe out the entire client session cache. */
409 void
SSL_ClearSessionCache(void)410 SSL_ClearSessionCache(void)
411 {
412     LOCK_CACHE;
413     while(cache != NULL)
414 	UncacheSID(cache);
415     UNLOCK_CACHE;
416 }
417 
418 /* returns an unsigned int containing the number of seconds in PR_Now() */
419 PRUint32
ssl_Time(void)420 ssl_Time(void)
421 {
422     PRUint32 myTime;
423 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
424     myTime = time(NULL);	/* accurate until the year 2038. */
425 #else
426     /* portable, but possibly slower */
427     PRTime now;
428     PRInt64 ll;
429 
430     now = PR_Now();
431     LL_I2L(ll, 1000000L);
432     LL_DIV(now, now, ll);
433     LL_L2UI(myTime, now);
434 #endif
435     return myTime;
436 }
437 
438 void
ssl3_SetSIDSessionTicket(sslSessionID * sid,NewSessionTicket * newSessionTicket)439 ssl3_SetSIDSessionTicket(sslSessionID *sid,
440                          /*in/out*/ NewSessionTicket *newSessionTicket)
441 {
442     PORT_Assert(sid);
443     PORT_Assert(newSessionTicket);
444 
445     /* if sid->u.ssl3.lock, we are updating an existing entry that is already
446      * cached or was once cached, so we need to acquire and release the write
447      * lock. Otherwise, this is a new session that isn't shared with anything
448      * yet, so no locking is needed.
449      */
450     if (sid->u.ssl3.lock) {
451 	NSSRWLock_LockWrite(sid->u.ssl3.lock);
452 
453 	/* A server might have sent us an empty ticket, which has the
454 	 * effect of clearing the previously known ticket.
455 	 */
456 	if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
457 	    SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
458 			     PR_FALSE);
459 	}
460     }
461 
462     PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data);
463 
464     /* Do a shallow copy, moving the ticket data. */
465     sid->u.ssl3.locked.sessionTicket = *newSessionTicket;
466     newSessionTicket->ticket.data = NULL;
467     newSessionTicket->ticket.len = 0;
468 
469     if (sid->u.ssl3.lock) {
470 	NSSRWLock_UnlockWrite(sid->u.ssl3.lock);
471     }
472 }
473