Home | History | Annotate | Download | only in ssl
      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
     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
     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
     95 ssl_InitSessionCacheLocks(void)
     96 {
     97     return (PR_SUCCESS ==
     98             PR_CallOnce(&lockOnce, InitSessionCacheLocks)) ?
     99            SECSuccess : SECFailure;
    100 }
    101 
    102 static 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
    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
    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
    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 *
    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
    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
    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
    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
    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
    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
    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
    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