1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/nss/lib/ssl/sslnonce.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,512 @@ 1.4 +/* 1.5 + * This file implements the CLIENT Session ID cache. 1.6 + * 1.7 + * This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "cert.h" 1.12 +#include "pk11pub.h" 1.13 +#include "secitem.h" 1.14 +#include "ssl.h" 1.15 +#include "nss.h" 1.16 + 1.17 +#include "sslimpl.h" 1.18 +#include "sslproto.h" 1.19 +#include "nssilock.h" 1.20 +#if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) 1.21 +#include <time.h> 1.22 +#endif 1.23 + 1.24 +PRUint32 ssl_sid_timeout = 100; 1.25 +PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ 1.26 + 1.27 +static sslSessionID *cache = NULL; 1.28 +static PZLock * cacheLock = NULL; 1.29 + 1.30 +/* sids can be in one of 4 states: 1.31 + * 1.32 + * never_cached, created, but not yet put into cache. 1.33 + * in_client_cache, in the client cache's linked list. 1.34 + * in_server_cache, entry came from the server's cache file. 1.35 + * invalid_cache has been removed from the cache. 1.36 + */ 1.37 + 1.38 +#define LOCK_CACHE lock_cache() 1.39 +#define UNLOCK_CACHE PZ_Unlock(cacheLock) 1.40 + 1.41 +static SECStatus 1.42 +ssl_InitClientSessionCacheLock(void) 1.43 +{ 1.44 + cacheLock = PZ_NewLock(nssILockCache); 1.45 + return cacheLock ? SECSuccess : SECFailure; 1.46 +} 1.47 + 1.48 +static SECStatus 1.49 +ssl_FreeClientSessionCacheLock(void) 1.50 +{ 1.51 + if (cacheLock) { 1.52 + PZ_DestroyLock(cacheLock); 1.53 + cacheLock = NULL; 1.54 + return SECSuccess; 1.55 + } 1.56 + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); 1.57 + return SECFailure; 1.58 +} 1.59 + 1.60 +static PRBool LocksInitializedEarly = PR_FALSE; 1.61 + 1.62 +static SECStatus 1.63 +FreeSessionCacheLocks() 1.64 +{ 1.65 + SECStatus rv1, rv2; 1.66 + rv1 = ssl_FreeSymWrapKeysLock(); 1.67 + rv2 = ssl_FreeClientSessionCacheLock(); 1.68 + if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { 1.69 + return SECSuccess; 1.70 + } 1.71 + return SECFailure; 1.72 +} 1.73 + 1.74 +static SECStatus 1.75 +InitSessionCacheLocks(void) 1.76 +{ 1.77 + SECStatus rv1, rv2; 1.78 + PRErrorCode rc; 1.79 + rv1 = ssl_InitSymWrapKeysLock(); 1.80 + rv2 = ssl_InitClientSessionCacheLock(); 1.81 + if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { 1.82 + return SECSuccess; 1.83 + } 1.84 + rc = PORT_GetError(); 1.85 + FreeSessionCacheLocks(); 1.86 + PORT_SetError(rc); 1.87 + return SECFailure; 1.88 +} 1.89 + 1.90 +/* free the session cache locks if they were initialized early */ 1.91 +SECStatus 1.92 +ssl_FreeSessionCacheLocks() 1.93 +{ 1.94 + PORT_Assert(PR_TRUE == LocksInitializedEarly); 1.95 + if (!LocksInitializedEarly) { 1.96 + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); 1.97 + return SECFailure; 1.98 + } 1.99 + FreeSessionCacheLocks(); 1.100 + LocksInitializedEarly = PR_FALSE; 1.101 + return SECSuccess; 1.102 +} 1.103 + 1.104 +static PRCallOnceType lockOnce; 1.105 + 1.106 +/* free the session cache locks if they were initialized lazily */ 1.107 +static SECStatus ssl_ShutdownLocks(void* appData, void* nssData) 1.108 +{ 1.109 + PORT_Assert(PR_FALSE == LocksInitializedEarly); 1.110 + if (LocksInitializedEarly) { 1.111 + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); 1.112 + return SECFailure; 1.113 + } 1.114 + FreeSessionCacheLocks(); 1.115 + memset(&lockOnce, 0, sizeof(lockOnce)); 1.116 + return SECSuccess; 1.117 +} 1.118 + 1.119 +static PRStatus initSessionCacheLocksLazily(void) 1.120 +{ 1.121 + SECStatus rv = InitSessionCacheLocks(); 1.122 + if (SECSuccess != rv) { 1.123 + return PR_FAILURE; 1.124 + } 1.125 + rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL); 1.126 + PORT_Assert(SECSuccess == rv); 1.127 + if (SECSuccess != rv) { 1.128 + return PR_FAILURE; 1.129 + } 1.130 + return PR_SUCCESS; 1.131 +} 1.132 + 1.133 +/* lazyInit means that the call is not happening during a 1-time 1.134 + * initialization function, but rather during dynamic, lazy initialization 1.135 + */ 1.136 +SECStatus 1.137 +ssl_InitSessionCacheLocks(PRBool lazyInit) 1.138 +{ 1.139 + if (LocksInitializedEarly) { 1.140 + return SECSuccess; 1.141 + } 1.142 + 1.143 + if (lazyInit) { 1.144 + return (PR_SUCCESS == 1.145 + PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ? 1.146 + SECSuccess : SECFailure; 1.147 + } 1.148 + 1.149 + if (SECSuccess == InitSessionCacheLocks()) { 1.150 + LocksInitializedEarly = PR_TRUE; 1.151 + return SECSuccess; 1.152 + } 1.153 + 1.154 + return SECFailure; 1.155 +} 1.156 + 1.157 +static void 1.158 +lock_cache(void) 1.159 +{ 1.160 + ssl_InitSessionCacheLocks(PR_TRUE); 1.161 + PZ_Lock(cacheLock); 1.162 +} 1.163 + 1.164 +/* BEWARE: This function gets called for both client and server SIDs !! 1.165 + * If the unreferenced sid is not in the cache, Free sid and its contents. 1.166 + */ 1.167 +static void 1.168 +ssl_DestroySID(sslSessionID *sid) 1.169 +{ 1.170 + SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); 1.171 + PORT_Assert(sid->references == 0); 1.172 + PORT_Assert(sid->cached != in_client_cache); 1.173 + 1.174 + if (sid->version < SSL_LIBRARY_VERSION_3_0) { 1.175 + SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE); 1.176 + SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE); 1.177 + } else { 1.178 + if (sid->u.ssl3.locked.sessionTicket.ticket.data) { 1.179 + SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, 1.180 + PR_FALSE); 1.181 + } 1.182 + if (sid->u.ssl3.srvName.data) { 1.183 + SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE); 1.184 + } 1.185 + 1.186 + if (sid->u.ssl3.lock) { 1.187 + PR_DestroyRWLock(sid->u.ssl3.lock); 1.188 + } 1.189 + } 1.190 + 1.191 + if (sid->peerID != NULL) 1.192 + PORT_Free((void *)sid->peerID); /* CONST */ 1.193 + 1.194 + if (sid->urlSvrName != NULL) 1.195 + PORT_Free((void *)sid->urlSvrName); /* CONST */ 1.196 + 1.197 + if ( sid->peerCert ) { 1.198 + CERT_DestroyCertificate(sid->peerCert); 1.199 + } 1.200 + if (sid->peerCertStatus.items) { 1.201 + SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE); 1.202 + } 1.203 + 1.204 + if ( sid->localCert ) { 1.205 + CERT_DestroyCertificate(sid->localCert); 1.206 + } 1.207 + 1.208 + PORT_ZFree(sid, sizeof(sslSessionID)); 1.209 +} 1.210 + 1.211 +/* BEWARE: This function gets called for both client and server SIDs !! 1.212 + * Decrement reference count, and 1.213 + * free sid if ref count is zero, and sid is not in the cache. 1.214 + * Does NOT remove from the cache first. 1.215 + * If the sid is still in the cache, it is left there until next time 1.216 + * the cache list is traversed. 1.217 + */ 1.218 +static void 1.219 +ssl_FreeLockedSID(sslSessionID *sid) 1.220 +{ 1.221 + PORT_Assert(sid->references >= 1); 1.222 + if (--sid->references == 0) { 1.223 + ssl_DestroySID(sid); 1.224 + } 1.225 +} 1.226 + 1.227 +/* BEWARE: This function gets called for both client and server SIDs !! 1.228 + * Decrement reference count, and 1.229 + * free sid if ref count is zero, and sid is not in the cache. 1.230 + * Does NOT remove from the cache first. 1.231 + * These locks are necessary because the sid _might_ be in the cache list. 1.232 + */ 1.233 +void 1.234 +ssl_FreeSID(sslSessionID *sid) 1.235 +{ 1.236 + LOCK_CACHE; 1.237 + ssl_FreeLockedSID(sid); 1.238 + UNLOCK_CACHE; 1.239 +} 1.240 + 1.241 +/************************************************************************/ 1.242 + 1.243 +/* 1.244 +** Lookup sid entry in cache by Address, port, and peerID string. 1.245 +** If found, Increment reference count, and return pointer to caller. 1.246 +** If it has timed out or ref count is zero, remove from list and free it. 1.247 +*/ 1.248 + 1.249 +sslSessionID * 1.250 +ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, 1.251 + const char * urlSvrName) 1.252 +{ 1.253 + sslSessionID **sidp; 1.254 + sslSessionID * sid; 1.255 + PRUint32 now; 1.256 + 1.257 + if (!urlSvrName) 1.258 + return NULL; 1.259 + now = ssl_Time(); 1.260 + LOCK_CACHE; 1.261 + sidp = &cache; 1.262 + while ((sid = *sidp) != 0) { 1.263 + PORT_Assert(sid->cached == in_client_cache); 1.264 + PORT_Assert(sid->references >= 1); 1.265 + 1.266 + SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid)); 1.267 + 1.268 + if (sid->expirationTime < now) { 1.269 + /* 1.270 + ** This session-id timed out. 1.271 + ** Don't even care who it belongs to, blow it out of our cache. 1.272 + */ 1.273 + SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d", 1.274 + now - sid->creationTime, sid->references)); 1.275 + 1.276 + *sidp = sid->next; /* delink it from the list. */ 1.277 + sid->cached = invalid_cache; /* mark not on list. */ 1.278 + ssl_FreeLockedSID(sid); /* drop ref count, free. */ 1.279 + } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */ 1.280 + (sid->port == port) && /* server port matches */ 1.281 + /* proxy (peerID) matches */ 1.282 + (((peerID == NULL) && (sid->peerID == NULL)) || 1.283 + ((peerID != NULL) && (sid->peerID != NULL) && 1.284 + PORT_Strcmp(sid->peerID, peerID) == 0)) && 1.285 + /* is cacheable */ 1.286 + (sid->version < SSL_LIBRARY_VERSION_3_0 || 1.287 + sid->u.ssl3.keys.resumable) && 1.288 + /* server hostname matches. */ 1.289 + (sid->urlSvrName != NULL) && 1.290 + ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) || 1.291 + ((sid->peerCert != NULL) && (SECSuccess == 1.292 + CERT_VerifyCertName(sid->peerCert, urlSvrName))) ) 1.293 + ) { 1.294 + /* Hit */ 1.295 + sid->lastAccessTime = now; 1.296 + sid->references++; 1.297 + break; 1.298 + } else { 1.299 + sidp = &sid->next; 1.300 + } 1.301 + } 1.302 + UNLOCK_CACHE; 1.303 + return sid; 1.304 +} 1.305 + 1.306 +/* 1.307 +** Add an sid to the cache or return a previously cached entry to the cache. 1.308 +** Although this is static, it is called via ss->sec.cache(). 1.309 +*/ 1.310 +static void 1.311 +CacheSID(sslSessionID *sid) 1.312 +{ 1.313 + PRUint32 expirationPeriod; 1.314 + 1.315 + PORT_Assert(sid->cached == never_cached); 1.316 + 1.317 + SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " 1.318 + "time=%x cached=%d", 1.319 + sid, sid->cached, sid->addr.pr_s6_addr32[0], 1.320 + sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], 1.321 + sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, 1.322 + sid->cached)); 1.323 + 1.324 + if (!sid->urlSvrName) { 1.325 + /* don't cache this SID because it can never be matched */ 1.326 + return; 1.327 + } 1.328 + 1.329 + /* XXX should be different trace for version 2 vs. version 3 */ 1.330 + if (sid->version < SSL_LIBRARY_VERSION_3_0) { 1.331 + expirationPeriod = ssl_sid_timeout; 1.332 + PRINT_BUF(8, (0, "sessionID:", 1.333 + sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID))); 1.334 + PRINT_BUF(8, (0, "masterKey:", 1.335 + sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len)); 1.336 + PRINT_BUF(8, (0, "cipherArg:", 1.337 + sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len)); 1.338 + } else { 1.339 + if (sid->u.ssl3.sessionIDLength == 0 && 1.340 + sid->u.ssl3.locked.sessionTicket.ticket.data == NULL) 1.341 + return; 1.342 + 1.343 + /* Client generates the SessionID if this was a stateless resume. */ 1.344 + if (sid->u.ssl3.sessionIDLength == 0) { 1.345 + SECStatus rv; 1.346 + rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, 1.347 + SSL3_SESSIONID_BYTES); 1.348 + if (rv != SECSuccess) 1.349 + return; 1.350 + sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; 1.351 + } 1.352 + expirationPeriod = ssl3_sid_timeout; 1.353 + PRINT_BUF(8, (0, "sessionID:", 1.354 + sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); 1.355 + 1.356 + sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL); 1.357 + if (!sid->u.ssl3.lock) { 1.358 + return; 1.359 + } 1.360 + } 1.361 + PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0); 1.362 + if (!sid->creationTime) 1.363 + sid->lastAccessTime = sid->creationTime = ssl_Time(); 1.364 + if (!sid->expirationTime) 1.365 + sid->expirationTime = sid->creationTime + expirationPeriod; 1.366 + 1.367 + /* 1.368 + * Put sid into the cache. Bump reference count to indicate that 1.369 + * cache is holding a reference. Uncache will reduce the cache 1.370 + * reference. 1.371 + */ 1.372 + LOCK_CACHE; 1.373 + sid->references++; 1.374 + sid->cached = in_client_cache; 1.375 + sid->next = cache; 1.376 + cache = sid; 1.377 + UNLOCK_CACHE; 1.378 +} 1.379 + 1.380 +/* 1.381 + * If sid "zap" is in the cache, 1.382 + * removes sid from cache, and decrements reference count. 1.383 + * Caller must hold cache lock. 1.384 + */ 1.385 +static void 1.386 +UncacheSID(sslSessionID *zap) 1.387 +{ 1.388 + sslSessionID **sidp = &cache; 1.389 + sslSessionID *sid; 1.390 + 1.391 + if (zap->cached != in_client_cache) { 1.392 + return; 1.393 + } 1.394 + 1.395 + SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " 1.396 + "time=%x cipher=%d", 1.397 + zap, zap->cached, zap->addr.pr_s6_addr32[0], 1.398 + zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2], 1.399 + zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime, 1.400 + zap->u.ssl2.cipherType)); 1.401 + if (zap->version < SSL_LIBRARY_VERSION_3_0) { 1.402 + PRINT_BUF(8, (0, "sessionID:", 1.403 + zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID))); 1.404 + PRINT_BUF(8, (0, "masterKey:", 1.405 + zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len)); 1.406 + PRINT_BUF(8, (0, "cipherArg:", 1.407 + zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len)); 1.408 + } 1.409 + 1.410 + /* See if it's in the cache, if so nuke it */ 1.411 + while ((sid = *sidp) != 0) { 1.412 + if (sid == zap) { 1.413 + /* 1.414 + ** Bingo. Reduce reference count by one so that when 1.415 + ** everyone is done with the sid we can free it up. 1.416 + */ 1.417 + *sidp = zap->next; 1.418 + zap->cached = invalid_cache; 1.419 + ssl_FreeLockedSID(zap); 1.420 + return; 1.421 + } 1.422 + sidp = &sid->next; 1.423 + } 1.424 +} 1.425 + 1.426 +/* If sid "zap" is in the cache, 1.427 + * removes sid from cache, and decrements reference count. 1.428 + * Although this function is static, it is called externally via 1.429 + * ss->sec.uncache(). 1.430 + */ 1.431 +static void 1.432 +LockAndUncacheSID(sslSessionID *zap) 1.433 +{ 1.434 + LOCK_CACHE; 1.435 + UncacheSID(zap); 1.436 + UNLOCK_CACHE; 1.437 + 1.438 +} 1.439 + 1.440 +/* choose client or server cache functions for this sslsocket. */ 1.441 +void 1.442 +ssl_ChooseSessionIDProcs(sslSecurityInfo *sec) 1.443 +{ 1.444 + if (sec->isServer) { 1.445 + sec->cache = ssl_sid_cache; 1.446 + sec->uncache = ssl_sid_uncache; 1.447 + } else { 1.448 + sec->cache = CacheSID; 1.449 + sec->uncache = LockAndUncacheSID; 1.450 + } 1.451 +} 1.452 + 1.453 +/* wipe out the entire client session cache. */ 1.454 +void 1.455 +SSL_ClearSessionCache(void) 1.456 +{ 1.457 + LOCK_CACHE; 1.458 + while(cache != NULL) 1.459 + UncacheSID(cache); 1.460 + UNLOCK_CACHE; 1.461 +} 1.462 + 1.463 +/* returns an unsigned int containing the number of seconds in PR_Now() */ 1.464 +PRUint32 1.465 +ssl_Time(void) 1.466 +{ 1.467 + PRUint32 myTime; 1.468 +#if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) 1.469 + myTime = time(NULL); /* accurate until the year 2038. */ 1.470 +#else 1.471 + /* portable, but possibly slower */ 1.472 + PRTime now; 1.473 + PRInt64 ll; 1.474 + 1.475 + now = PR_Now(); 1.476 + LL_I2L(ll, 1000000L); 1.477 + LL_DIV(now, now, ll); 1.478 + LL_L2UI(myTime, now); 1.479 +#endif 1.480 + return myTime; 1.481 +} 1.482 + 1.483 +void 1.484 +ssl3_SetSIDSessionTicket(sslSessionID *sid, 1.485 + /*in/out*/ NewSessionTicket *newSessionTicket) 1.486 +{ 1.487 + PORT_Assert(sid); 1.488 + PORT_Assert(newSessionTicket); 1.489 + PORT_Assert(newSessionTicket->ticket.data); 1.490 + PORT_Assert(newSessionTicket->ticket.len != 0); 1.491 + 1.492 + /* if sid->u.ssl3.lock, we are updating an existing entry that is already 1.493 + * cached or was once cached, so we need to acquire and release the write 1.494 + * lock. Otherwise, this is a new session that isn't shared with anything 1.495 + * yet, so no locking is needed. 1.496 + */ 1.497 + if (sid->u.ssl3.lock) { 1.498 + PR_RWLock_Wlock(sid->u.ssl3.lock); 1.499 + if (sid->u.ssl3.locked.sessionTicket.ticket.data) { 1.500 + SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, 1.501 + PR_FALSE); 1.502 + } 1.503 + } 1.504 + 1.505 + PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data); 1.506 + 1.507 + /* Do a shallow copy, moving the ticket data. */ 1.508 + sid->u.ssl3.locked.sessionTicket = *newSessionTicket; 1.509 + newSessionTicket->ticket.data = NULL; 1.510 + newSessionTicket->ticket.len = 0; 1.511 + 1.512 + if (sid->u.ssl3.lock) { 1.513 + PR_RWLock_Unlock(sid->u.ssl3.lock); 1.514 + } 1.515 +}