/* * This file implements the CLIENT Session ID cache. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* $Id: sslnonce.c,v 1.25 2008/03/10 00:01:28 wtc%google.com Exp $ */ #include "cert.h" #include "pk11pub.h" #include "secitem.h" #include "ssl.h" #include "nss.h" #include "sslimpl.h" #include "sslproto.h" #include "nssilock.h" #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE) #include <time.h> #endif PRUint32 ssl_sid_timeout = 100; PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ static sslSessionID *cache = NULL; static PZLock * cacheLock = NULL; /* sids can be in one of 4 states: * * never_cached, created, but not yet put into cache. * in_client_cache, in the client cache's linked list. * in_server_cache, entry came from the server's cache file. * invalid_cache has been removed from the cache. */ #define LOCK_CACHE lock_cache() #define UNLOCK_CACHE PZ_Unlock(cacheLock) static SECStatus ssl_InitClientSessionCacheLock(void) { cacheLock = PZ_NewLock(nssILockCache); return cacheLock ? SECSuccess : SECFailure; } static SECStatus ssl_FreeClientSessionCacheLock(void) { if (cacheLock) { PZ_DestroyLock(cacheLock); cacheLock = NULL; return SECSuccess; } PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } static PRBool LocksInitializedEarly = PR_FALSE; static SECStatus FreeSessionCacheLocks() { SECStatus rv1, rv2; rv1 = ssl_FreeSymWrapKeysLock(); rv2 = ssl_FreeClientSessionCacheLock(); if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { return SECSuccess; } return SECFailure; } static SECStatus InitSessionCacheLocks(void) { SECStatus rv1, rv2; PRErrorCode rc; rv1 = ssl_InitSymWrapKeysLock(); rv2 = ssl_InitClientSessionCacheLock(); if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { return SECSuccess; } rc = PORT_GetError(); FreeSessionCacheLocks(); PORT_SetError(rc); return SECFailure; } /* free the session cache locks if they were initialized early */ SECStatus ssl_FreeSessionCacheLocks() { PORT_Assert(PR_TRUE == LocksInitializedEarly); if (!LocksInitializedEarly) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } FreeSessionCacheLocks(); LocksInitializedEarly = PR_FALSE; return SECSuccess; } static PRCallOnceType lockOnce; /* free the session cache locks if they were initialized lazily */ static SECStatus ssl_ShutdownLocks(void* appData, void* nssData) { PORT_Assert(PR_FALSE == LocksInitializedEarly); if (LocksInitializedEarly) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } FreeSessionCacheLocks(); memset(&lockOnce, 0, sizeof(lockOnce)); return SECSuccess; } static PRStatus initSessionCacheLocksLazily(void) { SECStatus rv = InitSessionCacheLocks(); if (SECSuccess != rv) { return PR_FAILURE; } rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL); PORT_Assert(SECSuccess == rv); if (SECSuccess != rv) { return PR_FAILURE; } return PR_SUCCESS; } /* lazyInit means that the call is not happening during a 1-time * initialization function, but rather during dynamic, lazy initialization */ SECStatus ssl_InitSessionCacheLocks(PRBool lazyInit) { if (LocksInitializedEarly) { return SECSuccess; } if (lazyInit) { return (PR_SUCCESS == PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ? SECSuccess : SECFailure; } if (SECSuccess == InitSessionCacheLocks()) { LocksInitializedEarly = PR_TRUE; return SECSuccess; } return SECFailure; } static void lock_cache(void) { ssl_InitSessionCacheLocks(PR_TRUE); PZ_Lock(cacheLock); } /* BEWARE: This function gets called for both client and server SIDs !! * If the unreferenced sid is not in the cache, Free sid and its contents. */ static void ssl_DestroySID(sslSessionID *sid) { SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); PORT_Assert((sid->references == 0)); if (sid->cached == in_client_cache) return; /* it will get taken care of next time cache is traversed. */ if (sid->version < SSL_LIBRARY_VERSION_3_0) { SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE); SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE); } if (sid->peerID != NULL) PORT_Free((void *)sid->peerID); /* CONST */ if (sid->urlSvrName != NULL) PORT_Free((void *)sid->urlSvrName); /* CONST */ if ( sid->peerCert ) { CERT_DestroyCertificate(sid->peerCert); } if ( sid->localCert ) { CERT_DestroyCertificate(sid->localCert); } if (sid->u.ssl3.sessionTicket.ticket.data) { SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); } PORT_ZFree(sid, sizeof(sslSessionID)); } /* BEWARE: This function gets called for both client and server SIDs !! * Decrement reference count, and * free sid if ref count is zero, and sid is not in the cache. * Does NOT remove from the cache first. * If the sid is still in the cache, it is left there until next time * the cache list is traversed. */ static void ssl_FreeLockedSID(sslSessionID *sid) { PORT_Assert(sid->references >= 1); if (--sid->references == 0) { ssl_DestroySID(sid); } } /* BEWARE: This function gets called for both client and server SIDs !! * Decrement reference count, and * free sid if ref count is zero, and sid is not in the cache. * Does NOT remove from the cache first. * These locks are necessary because the sid _might_ be in the cache list. */ void ssl_FreeSID(sslSessionID *sid) { LOCK_CACHE; ssl_FreeLockedSID(sid); UNLOCK_CACHE; } /************************************************************************/ /* ** Lookup sid entry in cache by Address, port, and peerID string. ** If found, Increment reference count, and return pointer to caller. ** If it has timed out or ref count is zero, remove from list and free it. */ sslSessionID * ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, const char * urlSvrName) { sslSessionID **sidp; sslSessionID * sid; PRUint32 now; if (!urlSvrName) return NULL; now = ssl_Time(); LOCK_CACHE; sidp = &cache; while ((sid = *sidp) != 0) { PORT_Assert(sid->cached == in_client_cache); PORT_Assert(sid->references >= 1); SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid)); if (sid->expirationTime < now || !sid->references) { /* ** This session-id timed out, or was orphaned. ** Don't even care who it belongs to, blow it out of our cache. */ SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d", now - sid->creationTime, sid->references)); *sidp = sid->next; /* delink it from the list. */ sid->cached = invalid_cache; /* mark not on list. */ if (!sid->references) ssl_DestroySID(sid); else ssl_FreeLockedSID(sid); /* drop ref count, free. */ } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */ (sid->port == port) && /* server port matches */ /* proxy (peerID) matches */ (((peerID == NULL) && (sid->peerID == NULL)) || ((peerID != NULL) && (sid->peerID != NULL) && PORT_Strcmp(sid->peerID, peerID) == 0)) && /* is cacheable */ (sid->version < SSL_LIBRARY_VERSION_3_0 || sid->u.ssl3.keys.resumable) && /* server hostname matches. */ (sid->urlSvrName != NULL) && ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) || ((sid->peerCert != NULL) && (SECSuccess == CERT_VerifyCertName(sid->peerCert, urlSvrName))) ) ) { /* Hit */ sid->lastAccessTime = now; sid->references++; break; } else { sidp = &sid->next; } } UNLOCK_CACHE; return sid; } /* ** Add an sid to the cache or return a previously cached entry to the cache. ** Although this is static, it is called via ss->sec.cache(). */ static void CacheSID(sslSessionID *sid) { PRUint32 expirationPeriod; SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " "time=%x cached=%d", sid, sid->cached, sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, sid->cached)); if (sid->cached == in_client_cache) return; if (!sid->urlSvrName) { /* don't cache this SID because it can never be matched */ return; } /* XXX should be different trace for version 2 vs. version 3 */ if (sid->version < SSL_LIBRARY_VERSION_3_0) { expirationPeriod = ssl_sid_timeout; PRINT_BUF(8, (0, "sessionID:", sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID))); PRINT_BUF(8, (0, "masterKey:", sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len)); PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len)); } else { if (sid->u.ssl3.sessionIDLength == 0 && sid->u.ssl3.sessionTicket.ticket.data == NULL) return; /* Client generates the SessionID if this was a stateless resume. */ if (sid->u.ssl3.sessionIDLength == 0) { SECStatus rv; rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, SSL3_SESSIONID_BYTES); if (rv != SECSuccess) return; sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; } expirationPeriod = ssl3_sid_timeout; PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); } PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0); if (!sid->creationTime) sid->lastAccessTime = sid->creationTime = ssl_Time(); if (!sid->expirationTime) sid->expirationTime = sid->creationTime + expirationPeriod; /* * Put sid into the cache. Bump reference count to indicate that * cache is holding a reference. Uncache will reduce the cache * reference. */ LOCK_CACHE; sid->references++; sid->cached = in_client_cache; sid->next = cache; cache = sid; UNLOCK_CACHE; } /* * If sid "zap" is in the cache, * removes sid from cache, and decrements reference count. * Caller must hold cache lock. */ static void UncacheSID(sslSessionID *zap) { sslSessionID **sidp = &cache; sslSessionID *sid; if (zap->cached != in_client_cache) { return; } SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " "time=%x cipher=%d", zap, zap->cached, zap->addr.pr_s6_addr32[0], zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2], zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime, zap->u.ssl2.cipherType)); if (zap->version < SSL_LIBRARY_VERSION_3_0) { PRINT_BUF(8, (0, "sessionID:", zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID))); PRINT_BUF(8, (0, "masterKey:", zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len)); PRINT_BUF(8, (0, "cipherArg:", zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len)); } /* See if it's in the cache, if so nuke it */ while ((sid = *sidp) != 0) { if (sid == zap) { /* ** Bingo. Reduce reference count by one so that when ** everyone is done with the sid we can free it up. */ *sidp = zap->next; zap->cached = invalid_cache; ssl_FreeLockedSID(zap); return; } sidp = &sid->next; } } /* If sid "zap" is in the cache, * removes sid from cache, and decrements reference count. * Although this function is static, it is called externally via * ss->sec.uncache(). */ static void LockAndUncacheSID(sslSessionID *zap) { LOCK_CACHE; UncacheSID(zap); UNLOCK_CACHE; } /* choose client or server cache functions for this sslsocket. */ void ssl_ChooseSessionIDProcs(sslSecurityInfo *sec) { if (sec->isServer) { sec->cache = ssl_sid_cache; sec->uncache = ssl_sid_uncache; } else { sec->cache = CacheSID; sec->uncache = LockAndUncacheSID; } } /* wipe out the entire client session cache. */ void SSL_ClearSessionCache(void) { LOCK_CACHE; while(cache != NULL) UncacheSID(cache); UNLOCK_CACHE; } /* returns an unsigned int containing the number of seconds in PR_Now() */ PRUint32 ssl_Time(void) { PRUint32 myTime; #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE) myTime = time(NULL); /* accurate until the year 2038. */ #else /* portable, but possibly slower */ PRTime now; PRInt64 ll; now = PR_Now(); LL_I2L(ll, 1000000L); LL_DIV(now, now, ll); LL_L2UI(myTime, now); #endif return myTime; } SECStatus ssl3_SetSIDSessionTicket(sslSessionID *sid, NewSessionTicket *session_ticket) { SECStatus rv; /* We need to lock the cache, as this sid might already be in the cache. */ LOCK_CACHE; /* A server might have sent us an empty ticket, which has the * effect of clearing the previously known ticket. */ if (sid->u.ssl3.sessionTicket.ticket.data) SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); if (session_ticket->ticket.len > 0) { rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.sessionTicket.ticket, &session_ticket->ticket); if (rv != SECSuccess) { UNLOCK_CACHE; return rv; } } else { sid->u.ssl3.sessionTicket.ticket.data = NULL; sid->u.ssl3.sessionTicket.ticket.len = 0; } sid->u.ssl3.sessionTicket.received_timestamp = session_ticket->received_timestamp; sid->u.ssl3.sessionTicket.ticket_lifetime_hint = session_ticket->ticket_lifetime_hint; UNLOCK_CACHE; return SECSuccess; }