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