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 sslSessionID *cache = NULL;
     25 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 
    119     if (sid->cached == in_client_cache)
    120     	return;	/* it will get taken care of next time cache is traversed. */
    121 
    122     if (sid->version < SSL_LIBRARY_VERSION_3_0) {
    123 	SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE);
    124 	SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE);
    125     } else {
    126 	if (sid->u.ssl3.sessionTicket.ticket.data) {
    127 	    SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE);
    128 	}
    129 	if (sid->u.ssl3.srvName.data) {
    130 	    SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
    131 	}
    132 	if (sid->u.ssl3.signedCertTimestamps.data) {
    133 	    SECITEM_FreeItem(&sid->u.ssl3.signedCertTimestamps, PR_FALSE);
    134 	}
    135 	if (sid->u.ssl3.originalHandshakeHash.data) {
    136 	  SECITEM_FreeItem(&sid->u.ssl3.originalHandshakeHash, PR_FALSE);
    137 	}
    138     }
    139 
    140     if (sid->peerID != NULL)
    141 	PORT_Free((void *)sid->peerID);		/* CONST */
    142 
    143     if (sid->urlSvrName != NULL)
    144 	PORT_Free((void *)sid->urlSvrName);	/* CONST */
    145 
    146     if ( sid->peerCert ) {
    147 	CERT_DestroyCertificate(sid->peerCert);
    148     }
    149     for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
    150 	CERT_DestroyCertificate(sid->peerCertChain[i]);
    151     }
    152     if (sid->peerCertStatus.items) {
    153         SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
    154     }
    155 
    156     if ( sid->localCert ) {
    157 	CERT_DestroyCertificate(sid->localCert);
    158     }
    159 
    160     PORT_ZFree(sid, sizeof(sslSessionID));
    161 }
    162 
    163 /* BEWARE: This function gets called for both client and server SIDs !!
    164  * Decrement reference count, and
    165  *    free sid if ref count is zero, and sid is not in the cache.
    166  * Does NOT remove from the cache first.
    167  * If the sid is still in the cache, it is left there until next time
    168  * the cache list is traversed.
    169  */
    170 static void
    171 ssl_FreeLockedSID(sslSessionID *sid)
    172 {
    173     PORT_Assert(sid->references >= 1);
    174     if (--sid->references == 0) {
    175 	ssl_DestroySID(sid);
    176     }
    177 }
    178 
    179 /* BEWARE: This function gets called for both client and server SIDs !!
    180  * Decrement reference count, and
    181  *    free sid if ref count is zero, and sid is not in the cache.
    182  * Does NOT remove from the cache first.
    183  * These locks are necessary because the sid _might_ be in the cache list.
    184  */
    185 void
    186 ssl_FreeSID(sslSessionID *sid)
    187 {
    188     LOCK_CACHE;
    189     ssl_FreeLockedSID(sid);
    190     UNLOCK_CACHE;
    191 }
    192 
    193 /************************************************************************/
    194 
    195 /*
    196 **  Lookup sid entry in cache by Address, port, and peerID string.
    197 **  If found, Increment reference count, and return pointer to caller.
    198 **  If it has timed out or ref count is zero, remove from list and free it.
    199 */
    200 
    201 sslSessionID *
    202 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
    203               const char * urlSvrName)
    204 {
    205     sslSessionID **sidp;
    206     sslSessionID * sid;
    207     PRUint32       now;
    208 
    209     if (!urlSvrName)
    210     	return NULL;
    211     now = ssl_Time();
    212     LOCK_CACHE;
    213     sidp = &cache;
    214     while ((sid = *sidp) != 0) {
    215 	PORT_Assert(sid->cached == in_client_cache);
    216 	PORT_Assert(sid->references >= 1);
    217 
    218 	SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid));
    219 
    220 	if (sid->expirationTime < now || !sid->references) {
    221 	    /*
    222 	    ** This session-id timed out, or was orphaned.
    223 	    ** Don't even care who it belongs to, blow it out of our cache.
    224 	    */
    225 	    SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
    226 			now - sid->creationTime, sid->references));
    227 
    228 	    *sidp = sid->next; 			/* delink it from the list. */
    229 	    sid->cached = invalid_cache;	/* mark not on list. */
    230 	    if (!sid->references)
    231 	    	ssl_DestroySID(sid);
    232 	    else
    233 		ssl_FreeLockedSID(sid);		/* drop ref count, free. */
    234 
    235 	} else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
    236 	           (sid->port == port) && /* server port matches */
    237 		   /* proxy (peerID) matches */
    238 		   (((peerID == NULL) && (sid->peerID == NULL)) ||
    239 		    ((peerID != NULL) && (sid->peerID != NULL) &&
    240 		     PORT_Strcmp(sid->peerID, peerID) == 0)) &&
    241 		   /* is cacheable */
    242 		   (sid->version < SSL_LIBRARY_VERSION_3_0 ||
    243 		    sid->u.ssl3.keys.resumable) &&
    244 		   /* server hostname matches. */
    245 	           (sid->urlSvrName != NULL) &&
    246 		   ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) ||
    247 		    ((sid->peerCert != NULL) && (SECSuccess ==
    248 		      CERT_VerifyCertName(sid->peerCert, urlSvrName))) )
    249 		  ) {
    250 	    /* Hit */
    251 	    sid->lastAccessTime = now;
    252 	    sid->references++;
    253 	    break;
    254 	} else {
    255 	    sidp = &sid->next;
    256 	}
    257     }
    258     UNLOCK_CACHE;
    259     return sid;
    260 }
    261 
    262 /*
    263 ** Add an sid to the cache or return a previously cached entry to the cache.
    264 ** Although this is static, it is called via ss->sec.cache().
    265 */
    266 static void
    267 CacheSID(sslSessionID *sid)
    268 {
    269     PRUint32  expirationPeriod;
    270     SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
    271 		"time=%x cached=%d",
    272 		sid, sid->cached, sid->addr.pr_s6_addr32[0],
    273 		sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2],
    274 		sid->addr.pr_s6_addr32[3],  sid->port, sid->creationTime,
    275 		sid->cached));
    276 
    277     if (sid->cached == in_client_cache)
    278 	return;
    279 
    280     if (!sid->urlSvrName) {
    281         /* don't cache this SID because it can never be matched */
    282         return;
    283     }
    284 
    285     /* XXX should be different trace for version 2 vs. version 3 */
    286     if (sid->version < SSL_LIBRARY_VERSION_3_0) {
    287 	expirationPeriod = ssl_sid_timeout;
    288 	PRINT_BUF(8, (0, "sessionID:",
    289 		  sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID)));
    290 	PRINT_BUF(8, (0, "masterKey:",
    291 		  sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len));
    292 	PRINT_BUF(8, (0, "cipherArg:",
    293 		  sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len));
    294     } else {
    295 	if (sid->u.ssl3.sessionIDLength == 0 &&
    296 	    sid->u.ssl3.sessionTicket.ticket.data == NULL)
    297 	    return;
    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     PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0);
    312     if (!sid->creationTime)
    313 	sid->lastAccessTime = sid->creationTime = ssl_Time();
    314     if (!sid->expirationTime)
    315 	sid->expirationTime = sid->creationTime + expirationPeriod;
    316 
    317     /*
    318      * Put sid into the cache.  Bump reference count to indicate that
    319      * cache is holding a reference. Uncache will reduce the cache
    320      * reference.
    321      */
    322     LOCK_CACHE;
    323     sid->references++;
    324     sid->cached = in_client_cache;
    325     sid->next   = cache;
    326     cache       = sid;
    327     UNLOCK_CACHE;
    328 }
    329 
    330 /*
    331  * If sid "zap" is in the cache,
    332  *    removes sid from cache, and decrements reference count.
    333  * Caller must hold cache lock.
    334  */
    335 static void
    336 UncacheSID(sslSessionID *zap)
    337 {
    338     sslSessionID **sidp = &cache;
    339     sslSessionID *sid;
    340 
    341     if (zap->cached != in_client_cache) {
    342 	return;
    343     }
    344 
    345     SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
    346 	       "time=%x cipher=%d",
    347 	       zap, zap->cached, zap->addr.pr_s6_addr32[0],
    348 	       zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2],
    349 	       zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime,
    350 	       zap->u.ssl2.cipherType));
    351     if (zap->version < SSL_LIBRARY_VERSION_3_0) {
    352 	PRINT_BUF(8, (0, "sessionID:",
    353 		      zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID)));
    354 	PRINT_BUF(8, (0, "masterKey:",
    355 		      zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len));
    356 	PRINT_BUF(8, (0, "cipherArg:",
    357 		      zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len));
    358     }
    359 
    360     /* See if it's in the cache, if so nuke it */
    361     while ((sid = *sidp) != 0) {
    362 	if (sid == zap) {
    363 	    /*
    364 	    ** Bingo. Reduce reference count by one so that when
    365 	    ** everyone is done with the sid we can free it up.
    366 	    */
    367 	    *sidp = zap->next;
    368 	    zap->cached = invalid_cache;
    369 	    ssl_FreeLockedSID(zap);
    370 	    return;
    371 	}
    372 	sidp = &sid->next;
    373     }
    374 }
    375 
    376 /* If sid "zap" is in the cache,
    377  *    removes sid from cache, and decrements reference count.
    378  * Although this function is static, it is called externally via
    379  *    ss->sec.uncache().
    380  */
    381 static void
    382 LockAndUncacheSID(sslSessionID *zap)
    383 {
    384     LOCK_CACHE;
    385     UncacheSID(zap);
    386     UNLOCK_CACHE;
    387 
    388 }
    389 
    390 /* choose client or server cache functions for this sslsocket. */
    391 void
    392 ssl_ChooseSessionIDProcs(sslSecurityInfo *sec)
    393 {
    394     if (sec->isServer) {
    395 	sec->cache   = ssl_sid_cache;
    396 	sec->uncache = ssl_sid_uncache;
    397     } else {
    398 	sec->cache   = CacheSID;
    399 	sec->uncache = LockAndUncacheSID;
    400     }
    401 }
    402 
    403 /* wipe out the entire client session cache. */
    404 void
    405 SSL_ClearSessionCache(void)
    406 {
    407     LOCK_CACHE;
    408     while(cache != NULL)
    409 	UncacheSID(cache);
    410     UNLOCK_CACHE;
    411 }
    412 
    413 /* returns an unsigned int containing the number of seconds in PR_Now() */
    414 PRUint32
    415 ssl_Time(void)
    416 {
    417     PRUint32 myTime;
    418 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
    419     myTime = time(NULL);	/* accurate until the year 2038. */
    420 #else
    421     /* portable, but possibly slower */
    422     PRTime now;
    423     PRInt64 ll;
    424 
    425     now = PR_Now();
    426     LL_I2L(ll, 1000000L);
    427     LL_DIV(now, now, ll);
    428     LL_L2UI(myTime, now);
    429 #endif
    430     return myTime;
    431 }
    432 
    433 SECStatus
    434 ssl3_SetSIDSessionTicket(sslSessionID *sid, NewSessionTicket *session_ticket)
    435 {
    436     SECStatus rv;
    437 
    438     /* We need to lock the cache, as this sid might already be in the cache. */
    439     LOCK_CACHE;
    440 
    441     /* Don't modify sid if it has ever been cached. */
    442     if (sid->cached != never_cached) {
    443 	UNLOCK_CACHE;
    444 	return SECSuccess;
    445     }
    446 
    447     /* A server might have sent us an empty ticket, which has the
    448      * effect of clearing the previously known ticket.
    449      */
    450     if (sid->u.ssl3.sessionTicket.ticket.data)
    451 	SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE);
    452     if (session_ticket->ticket.len > 0) {
    453 	rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.sessionTicket.ticket,
    454 	    &session_ticket->ticket);
    455 	if (rv != SECSuccess) {
    456 	    UNLOCK_CACHE;
    457 	    return rv;
    458 	}
    459     } else {
    460 	sid->u.ssl3.sessionTicket.ticket.data = NULL;
    461 	sid->u.ssl3.sessionTicket.ticket.len = 0;
    462     }
    463     sid->u.ssl3.sessionTicket.received_timestamp =
    464 	session_ticket->received_timestamp;
    465     sid->u.ssl3.sessionTicket.ticket_lifetime_hint =
    466 	session_ticket->ticket_lifetime_hint;
    467 
    468     UNLOCK_CACHE;
    469     return SECSuccess;
    470 }
    471