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