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