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