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