michael@0: /* michael@0: * This file implements the CLIENT Session ID cache. michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "cert.h" michael@0: #include "pk11pub.h" michael@0: #include "secitem.h" michael@0: #include "ssl.h" michael@0: #include "nss.h" michael@0: michael@0: #include "sslimpl.h" michael@0: #include "sslproto.h" michael@0: #include "nssilock.h" michael@0: #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) michael@0: #include michael@0: #endif michael@0: michael@0: PRUint32 ssl_sid_timeout = 100; michael@0: PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ michael@0: michael@0: static sslSessionID *cache = NULL; michael@0: static PZLock * cacheLock = NULL; michael@0: michael@0: /* sids can be in one of 4 states: michael@0: * michael@0: * never_cached, created, but not yet put into cache. michael@0: * in_client_cache, in the client cache's linked list. michael@0: * in_server_cache, entry came from the server's cache file. michael@0: * invalid_cache has been removed from the cache. michael@0: */ michael@0: michael@0: #define LOCK_CACHE lock_cache() michael@0: #define UNLOCK_CACHE PZ_Unlock(cacheLock) michael@0: michael@0: static SECStatus michael@0: ssl_InitClientSessionCacheLock(void) michael@0: { michael@0: cacheLock = PZ_NewLock(nssILockCache); michael@0: return cacheLock ? SECSuccess : SECFailure; michael@0: } michael@0: michael@0: static SECStatus michael@0: ssl_FreeClientSessionCacheLock(void) michael@0: { michael@0: if (cacheLock) { michael@0: PZ_DestroyLock(cacheLock); michael@0: cacheLock = NULL; michael@0: return SECSuccess; michael@0: } michael@0: PORT_SetError(SEC_ERROR_NOT_INITIALIZED); michael@0: return SECFailure; michael@0: } michael@0: michael@0: static PRBool LocksInitializedEarly = PR_FALSE; michael@0: michael@0: static SECStatus michael@0: FreeSessionCacheLocks() michael@0: { michael@0: SECStatus rv1, rv2; michael@0: rv1 = ssl_FreeSymWrapKeysLock(); michael@0: rv2 = ssl_FreeClientSessionCacheLock(); michael@0: if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { michael@0: return SECSuccess; michael@0: } michael@0: return SECFailure; michael@0: } michael@0: michael@0: static SECStatus michael@0: InitSessionCacheLocks(void) michael@0: { michael@0: SECStatus rv1, rv2; michael@0: PRErrorCode rc; michael@0: rv1 = ssl_InitSymWrapKeysLock(); michael@0: rv2 = ssl_InitClientSessionCacheLock(); michael@0: if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { michael@0: return SECSuccess; michael@0: } michael@0: rc = PORT_GetError(); michael@0: FreeSessionCacheLocks(); michael@0: PORT_SetError(rc); michael@0: return SECFailure; michael@0: } michael@0: michael@0: /* free the session cache locks if they were initialized early */ michael@0: SECStatus michael@0: ssl_FreeSessionCacheLocks() michael@0: { michael@0: PORT_Assert(PR_TRUE == LocksInitializedEarly); michael@0: if (!LocksInitializedEarly) { michael@0: PORT_SetError(SEC_ERROR_NOT_INITIALIZED); michael@0: return SECFailure; michael@0: } michael@0: FreeSessionCacheLocks(); michael@0: LocksInitializedEarly = PR_FALSE; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: static PRCallOnceType lockOnce; michael@0: michael@0: /* free the session cache locks if they were initialized lazily */ michael@0: static SECStatus ssl_ShutdownLocks(void* appData, void* nssData) michael@0: { michael@0: PORT_Assert(PR_FALSE == LocksInitializedEarly); michael@0: if (LocksInitializedEarly) { michael@0: PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); michael@0: return SECFailure; michael@0: } michael@0: FreeSessionCacheLocks(); michael@0: memset(&lockOnce, 0, sizeof(lockOnce)); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: static PRStatus initSessionCacheLocksLazily(void) michael@0: { michael@0: SECStatus rv = InitSessionCacheLocks(); michael@0: if (SECSuccess != rv) { michael@0: return PR_FAILURE; michael@0: } michael@0: rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL); michael@0: PORT_Assert(SECSuccess == rv); michael@0: if (SECSuccess != rv) { michael@0: return PR_FAILURE; michael@0: } michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: /* lazyInit means that the call is not happening during a 1-time michael@0: * initialization function, but rather during dynamic, lazy initialization michael@0: */ michael@0: SECStatus michael@0: ssl_InitSessionCacheLocks(PRBool lazyInit) michael@0: { michael@0: if (LocksInitializedEarly) { michael@0: return SECSuccess; michael@0: } michael@0: michael@0: if (lazyInit) { michael@0: return (PR_SUCCESS == michael@0: PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ? michael@0: SECSuccess : SECFailure; michael@0: } michael@0: michael@0: if (SECSuccess == InitSessionCacheLocks()) { michael@0: LocksInitializedEarly = PR_TRUE; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: return SECFailure; michael@0: } michael@0: michael@0: static void michael@0: lock_cache(void) michael@0: { michael@0: ssl_InitSessionCacheLocks(PR_TRUE); michael@0: PZ_Lock(cacheLock); michael@0: } michael@0: michael@0: /* BEWARE: This function gets called for both client and server SIDs !! michael@0: * If the unreferenced sid is not in the cache, Free sid and its contents. michael@0: */ michael@0: static void michael@0: ssl_DestroySID(sslSessionID *sid) michael@0: { michael@0: SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); michael@0: PORT_Assert(sid->references == 0); michael@0: PORT_Assert(sid->cached != in_client_cache); michael@0: michael@0: if (sid->version < SSL_LIBRARY_VERSION_3_0) { michael@0: SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE); michael@0: SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE); michael@0: } else { michael@0: if (sid->u.ssl3.locked.sessionTicket.ticket.data) { michael@0: SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, michael@0: PR_FALSE); michael@0: } michael@0: if (sid->u.ssl3.srvName.data) { michael@0: SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE); michael@0: } michael@0: michael@0: if (sid->u.ssl3.lock) { michael@0: PR_DestroyRWLock(sid->u.ssl3.lock); michael@0: } michael@0: } michael@0: michael@0: if (sid->peerID != NULL) michael@0: PORT_Free((void *)sid->peerID); /* CONST */ michael@0: michael@0: if (sid->urlSvrName != NULL) michael@0: PORT_Free((void *)sid->urlSvrName); /* CONST */ michael@0: michael@0: if ( sid->peerCert ) { michael@0: CERT_DestroyCertificate(sid->peerCert); michael@0: } michael@0: if (sid->peerCertStatus.items) { michael@0: SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE); michael@0: } michael@0: michael@0: if ( sid->localCert ) { michael@0: CERT_DestroyCertificate(sid->localCert); michael@0: } michael@0: michael@0: PORT_ZFree(sid, sizeof(sslSessionID)); michael@0: } michael@0: michael@0: /* BEWARE: This function gets called for both client and server SIDs !! michael@0: * Decrement reference count, and michael@0: * free sid if ref count is zero, and sid is not in the cache. michael@0: * Does NOT remove from the cache first. michael@0: * If the sid is still in the cache, it is left there until next time michael@0: * the cache list is traversed. michael@0: */ michael@0: static void michael@0: ssl_FreeLockedSID(sslSessionID *sid) michael@0: { michael@0: PORT_Assert(sid->references >= 1); michael@0: if (--sid->references == 0) { michael@0: ssl_DestroySID(sid); michael@0: } michael@0: } michael@0: michael@0: /* BEWARE: This function gets called for both client and server SIDs !! michael@0: * Decrement reference count, and michael@0: * free sid if ref count is zero, and sid is not in the cache. michael@0: * Does NOT remove from the cache first. michael@0: * These locks are necessary because the sid _might_ be in the cache list. michael@0: */ michael@0: void michael@0: ssl_FreeSID(sslSessionID *sid) michael@0: { michael@0: LOCK_CACHE; michael@0: ssl_FreeLockedSID(sid); michael@0: UNLOCK_CACHE; michael@0: } michael@0: michael@0: /************************************************************************/ michael@0: michael@0: /* michael@0: ** Lookup sid entry in cache by Address, port, and peerID string. michael@0: ** If found, Increment reference count, and return pointer to caller. michael@0: ** If it has timed out or ref count is zero, remove from list and free it. michael@0: */ michael@0: michael@0: sslSessionID * michael@0: ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, michael@0: const char * urlSvrName) michael@0: { michael@0: sslSessionID **sidp; michael@0: sslSessionID * sid; michael@0: PRUint32 now; michael@0: michael@0: if (!urlSvrName) michael@0: return NULL; michael@0: now = ssl_Time(); michael@0: LOCK_CACHE; michael@0: sidp = &cache; michael@0: while ((sid = *sidp) != 0) { michael@0: PORT_Assert(sid->cached == in_client_cache); michael@0: PORT_Assert(sid->references >= 1); michael@0: michael@0: SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid)); michael@0: michael@0: if (sid->expirationTime < now) { michael@0: /* michael@0: ** This session-id timed out. michael@0: ** Don't even care who it belongs to, blow it out of our cache. michael@0: */ michael@0: SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d", michael@0: now - sid->creationTime, sid->references)); michael@0: michael@0: *sidp = sid->next; /* delink it from the list. */ michael@0: sid->cached = invalid_cache; /* mark not on list. */ michael@0: ssl_FreeLockedSID(sid); /* drop ref count, free. */ michael@0: } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */ michael@0: (sid->port == port) && /* server port matches */ michael@0: /* proxy (peerID) matches */ michael@0: (((peerID == NULL) && (sid->peerID == NULL)) || michael@0: ((peerID != NULL) && (sid->peerID != NULL) && michael@0: PORT_Strcmp(sid->peerID, peerID) == 0)) && michael@0: /* is cacheable */ michael@0: (sid->version < SSL_LIBRARY_VERSION_3_0 || michael@0: sid->u.ssl3.keys.resumable) && michael@0: /* server hostname matches. */ michael@0: (sid->urlSvrName != NULL) && michael@0: ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) || michael@0: ((sid->peerCert != NULL) && (SECSuccess == michael@0: CERT_VerifyCertName(sid->peerCert, urlSvrName))) ) michael@0: ) { michael@0: /* Hit */ michael@0: sid->lastAccessTime = now; michael@0: sid->references++; michael@0: break; michael@0: } else { michael@0: sidp = &sid->next; michael@0: } michael@0: } michael@0: UNLOCK_CACHE; michael@0: return sid; michael@0: } michael@0: michael@0: /* michael@0: ** Add an sid to the cache or return a previously cached entry to the cache. michael@0: ** Although this is static, it is called via ss->sec.cache(). michael@0: */ michael@0: static void michael@0: CacheSID(sslSessionID *sid) michael@0: { michael@0: PRUint32 expirationPeriod; michael@0: michael@0: PORT_Assert(sid->cached == never_cached); michael@0: michael@0: SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " michael@0: "time=%x cached=%d", michael@0: sid, sid->cached, sid->addr.pr_s6_addr32[0], michael@0: sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], michael@0: sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, michael@0: sid->cached)); michael@0: michael@0: if (!sid->urlSvrName) { michael@0: /* don't cache this SID because it can never be matched */ michael@0: return; michael@0: } michael@0: michael@0: /* XXX should be different trace for version 2 vs. version 3 */ michael@0: if (sid->version < SSL_LIBRARY_VERSION_3_0) { michael@0: expirationPeriod = ssl_sid_timeout; michael@0: PRINT_BUF(8, (0, "sessionID:", michael@0: sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID))); michael@0: PRINT_BUF(8, (0, "masterKey:", michael@0: sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len)); michael@0: PRINT_BUF(8, (0, "cipherArg:", michael@0: sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len)); michael@0: } else { michael@0: if (sid->u.ssl3.sessionIDLength == 0 && michael@0: sid->u.ssl3.locked.sessionTicket.ticket.data == NULL) michael@0: return; michael@0: michael@0: /* Client generates the SessionID if this was a stateless resume. */ michael@0: if (sid->u.ssl3.sessionIDLength == 0) { michael@0: SECStatus rv; michael@0: rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, michael@0: SSL3_SESSIONID_BYTES); michael@0: if (rv != SECSuccess) michael@0: return; michael@0: sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; michael@0: } michael@0: expirationPeriod = ssl3_sid_timeout; michael@0: PRINT_BUF(8, (0, "sessionID:", michael@0: sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); michael@0: michael@0: sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL); michael@0: if (!sid->u.ssl3.lock) { michael@0: return; michael@0: } michael@0: } michael@0: PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0); michael@0: if (!sid->creationTime) michael@0: sid->lastAccessTime = sid->creationTime = ssl_Time(); michael@0: if (!sid->expirationTime) michael@0: sid->expirationTime = sid->creationTime + expirationPeriod; michael@0: michael@0: /* michael@0: * Put sid into the cache. Bump reference count to indicate that michael@0: * cache is holding a reference. Uncache will reduce the cache michael@0: * reference. michael@0: */ michael@0: LOCK_CACHE; michael@0: sid->references++; michael@0: sid->cached = in_client_cache; michael@0: sid->next = cache; michael@0: cache = sid; michael@0: UNLOCK_CACHE; michael@0: } michael@0: michael@0: /* michael@0: * If sid "zap" is in the cache, michael@0: * removes sid from cache, and decrements reference count. michael@0: * Caller must hold cache lock. michael@0: */ michael@0: static void michael@0: UncacheSID(sslSessionID *zap) michael@0: { michael@0: sslSessionID **sidp = &cache; michael@0: sslSessionID *sid; michael@0: michael@0: if (zap->cached != in_client_cache) { michael@0: return; michael@0: } michael@0: michael@0: SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " michael@0: "time=%x cipher=%d", michael@0: zap, zap->cached, zap->addr.pr_s6_addr32[0], michael@0: zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2], michael@0: zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime, michael@0: zap->u.ssl2.cipherType)); michael@0: if (zap->version < SSL_LIBRARY_VERSION_3_0) { michael@0: PRINT_BUF(8, (0, "sessionID:", michael@0: zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID))); michael@0: PRINT_BUF(8, (0, "masterKey:", michael@0: zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len)); michael@0: PRINT_BUF(8, (0, "cipherArg:", michael@0: zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len)); michael@0: } michael@0: michael@0: /* See if it's in the cache, if so nuke it */ michael@0: while ((sid = *sidp) != 0) { michael@0: if (sid == zap) { michael@0: /* michael@0: ** Bingo. Reduce reference count by one so that when michael@0: ** everyone is done with the sid we can free it up. michael@0: */ michael@0: *sidp = zap->next; michael@0: zap->cached = invalid_cache; michael@0: ssl_FreeLockedSID(zap); michael@0: return; michael@0: } michael@0: sidp = &sid->next; michael@0: } michael@0: } michael@0: michael@0: /* If sid "zap" is in the cache, michael@0: * removes sid from cache, and decrements reference count. michael@0: * Although this function is static, it is called externally via michael@0: * ss->sec.uncache(). michael@0: */ michael@0: static void michael@0: LockAndUncacheSID(sslSessionID *zap) michael@0: { michael@0: LOCK_CACHE; michael@0: UncacheSID(zap); michael@0: UNLOCK_CACHE; michael@0: michael@0: } michael@0: michael@0: /* choose client or server cache functions for this sslsocket. */ michael@0: void michael@0: ssl_ChooseSessionIDProcs(sslSecurityInfo *sec) michael@0: { michael@0: if (sec->isServer) { michael@0: sec->cache = ssl_sid_cache; michael@0: sec->uncache = ssl_sid_uncache; michael@0: } else { michael@0: sec->cache = CacheSID; michael@0: sec->uncache = LockAndUncacheSID; michael@0: } michael@0: } michael@0: michael@0: /* wipe out the entire client session cache. */ michael@0: void michael@0: SSL_ClearSessionCache(void) michael@0: { michael@0: LOCK_CACHE; michael@0: while(cache != NULL) michael@0: UncacheSID(cache); michael@0: UNLOCK_CACHE; michael@0: } michael@0: michael@0: /* returns an unsigned int containing the number of seconds in PR_Now() */ michael@0: PRUint32 michael@0: ssl_Time(void) michael@0: { michael@0: PRUint32 myTime; michael@0: #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) michael@0: myTime = time(NULL); /* accurate until the year 2038. */ michael@0: #else michael@0: /* portable, but possibly slower */ michael@0: PRTime now; michael@0: PRInt64 ll; michael@0: michael@0: now = PR_Now(); michael@0: LL_I2L(ll, 1000000L); michael@0: LL_DIV(now, now, ll); michael@0: LL_L2UI(myTime, now); michael@0: #endif michael@0: return myTime; michael@0: } michael@0: michael@0: void michael@0: ssl3_SetSIDSessionTicket(sslSessionID *sid, michael@0: /*in/out*/ NewSessionTicket *newSessionTicket) michael@0: { michael@0: PORT_Assert(sid); michael@0: PORT_Assert(newSessionTicket); michael@0: PORT_Assert(newSessionTicket->ticket.data); michael@0: PORT_Assert(newSessionTicket->ticket.len != 0); michael@0: michael@0: /* if sid->u.ssl3.lock, we are updating an existing entry that is already michael@0: * cached or was once cached, so we need to acquire and release the write michael@0: * lock. Otherwise, this is a new session that isn't shared with anything michael@0: * yet, so no locking is needed. michael@0: */ michael@0: if (sid->u.ssl3.lock) { michael@0: PR_RWLock_Wlock(sid->u.ssl3.lock); michael@0: if (sid->u.ssl3.locked.sessionTicket.ticket.data) { michael@0: SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, michael@0: PR_FALSE); michael@0: } michael@0: } michael@0: michael@0: PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data); michael@0: michael@0: /* Do a shallow copy, moving the ticket data. */ michael@0: sid->u.ssl3.locked.sessionTicket = *newSessionTicket; michael@0: newSessionTicket->ticket.data = NULL; michael@0: newSessionTicket->ticket.len = 0; michael@0: michael@0: if (sid->u.ssl3.lock) { michael@0: PR_RWLock_Unlock(sid->u.ssl3.lock); michael@0: } michael@0: }