1 /* 2 * This file implements the CLIENT Session ID cache. 3 * 4 * ***** BEGIN LICENSE BLOCK ***** 5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 6 * 7 * The contents of this file are subject to the Mozilla Public License Version 8 * 1.1 (the "License"); you may not use this file except in compliance with 9 * the License. You may obtain a copy of the License at 10 * http://www.mozilla.org/MPL/ 11 * 12 * Software distributed under the License is distributed on an "AS IS" basis, 13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 14 * for the specific language governing rights and limitations under the 15 * License. 16 * 17 * The Original Code is the Netscape security libraries. 18 * 19 * The Initial Developer of the Original Code is 20 * Netscape Communications Corporation. 21 * Portions created by the Initial Developer are Copyright (C) 1994-2000 22 * the Initial Developer. All Rights Reserved. 23 * 24 * Contributor(s): 25 * 26 * Alternatively, the contents of this file may be used under the terms of 27 * either the GNU General Public License Version 2 or later (the "GPL"), or 28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 * in which case the provisions of the GPL or the LGPL are applicable instead 30 * of those above. If you wish to allow use of your version of this file only 31 * under the terms of either the GPL or the LGPL, and not to allow others to 32 * use your version of this file under the terms of the MPL, indicate your 33 * decision by deleting the provisions above and replace them with the notice 34 * and other provisions required by the GPL or the LGPL. If you do not delete 35 * the provisions above, a recipient may use your version of this file under 36 * the terms of any one of the MPL, the GPL or the LGPL. 37 * 38 * ***** END LICENSE BLOCK ***** */ 39 /* $Id: sslnonce.c,v 1.25 2008/03/10 00:01:28 wtc%google.com Exp $ */ 40 41 #include "cert.h" 42 #include "pk11pub.h" 43 #include "secitem.h" 44 #include "ssl.h" 45 #include "nss.h" 46 47 #include "sslimpl.h" 48 #include "sslproto.h" 49 #include "nssilock.h" 50 #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE) 51 #include <time.h> 52 #endif 53 54 PRUint32 ssl_sid_timeout = 100; 55 PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ 56 57 static sslSessionID *cache = NULL; 58 static PZLock * cacheLock = NULL; 59 60 /* sids can be in one of 4 states: 61 * 62 * never_cached, created, but not yet put into cache. 63 * in_client_cache, in the client cache's linked list. 64 * in_server_cache, entry came from the server's cache file. 65 * invalid_cache has been removed from the cache. 66 */ 67 68 #define LOCK_CACHE lock_cache() 69 #define UNLOCK_CACHE PZ_Unlock(cacheLock) 70 71 static SECStatus 72 ssl_InitClientSessionCacheLock(void) 73 { 74 cacheLock = PZ_NewLock(nssILockCache); 75 return cacheLock ? SECSuccess : SECFailure; 76 } 77 78 static SECStatus 79 ssl_FreeClientSessionCacheLock(void) 80 { 81 if (cacheLock) { 82 PZ_DestroyLock(cacheLock); 83 cacheLock = NULL; 84 return SECSuccess; 85 } 86 PORT_SetError(SEC_ERROR_NOT_INITIALIZED); 87 return SECFailure; 88 } 89 90 static PRBool LocksInitializedEarly = PR_FALSE; 91 92 static SECStatus 93 FreeSessionCacheLocks() 94 { 95 SECStatus rv1, rv2; 96 rv1 = ssl_FreeSymWrapKeysLock(); 97 rv2 = ssl_FreeClientSessionCacheLock(); 98 if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { 99 return SECSuccess; 100 } 101 return SECFailure; 102 } 103 104 static SECStatus 105 InitSessionCacheLocks(void) 106 { 107 SECStatus rv1, rv2; 108 PRErrorCode rc; 109 rv1 = ssl_InitSymWrapKeysLock(); 110 rv2 = ssl_InitClientSessionCacheLock(); 111 if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) { 112 return SECSuccess; 113 } 114 rc = PORT_GetError(); 115 FreeSessionCacheLocks(); 116 PORT_SetError(rc); 117 return SECFailure; 118 } 119 120 /* free the session cache locks if they were initialized early */ 121 SECStatus 122 ssl_FreeSessionCacheLocks() 123 { 124 PORT_Assert(PR_TRUE == LocksInitializedEarly); 125 if (!LocksInitializedEarly) { 126 PORT_SetError(SEC_ERROR_NOT_INITIALIZED); 127 return SECFailure; 128 } 129 FreeSessionCacheLocks(); 130 LocksInitializedEarly = PR_FALSE; 131 return SECSuccess; 132 } 133 134 static PRCallOnceType lockOnce; 135 136 /* free the session cache locks if they were initialized lazily */ 137 static SECStatus ssl_ShutdownLocks(void* appData, void* nssData) 138 { 139 PORT_Assert(PR_FALSE == LocksInitializedEarly); 140 if (LocksInitializedEarly) { 141 PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); 142 return SECFailure; 143 } 144 FreeSessionCacheLocks(); 145 memset(&lockOnce, 0, sizeof(lockOnce)); 146 return SECSuccess; 147 } 148 149 static PRStatus initSessionCacheLocksLazily(void) 150 { 151 SECStatus rv = InitSessionCacheLocks(); 152 if (SECSuccess != rv) { 153 return PR_FAILURE; 154 } 155 rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL); 156 PORT_Assert(SECSuccess == rv); 157 if (SECSuccess != rv) { 158 return PR_FAILURE; 159 } 160 return PR_SUCCESS; 161 } 162 163 /* lazyInit means that the call is not happening during a 1-time 164 * initialization function, but rather during dynamic, lazy initialization 165 */ 166 SECStatus 167 ssl_InitSessionCacheLocks(PRBool lazyInit) 168 { 169 if (LocksInitializedEarly) { 170 return SECSuccess; 171 } 172 173 if (lazyInit) { 174 return (PR_SUCCESS == 175 PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ? 176 SECSuccess : SECFailure; 177 } 178 179 if (SECSuccess == InitSessionCacheLocks()) { 180 LocksInitializedEarly = PR_TRUE; 181 return SECSuccess; 182 } 183 184 return SECFailure; 185 } 186 187 static void 188 lock_cache(void) 189 { 190 ssl_InitSessionCacheLocks(PR_TRUE); 191 PZ_Lock(cacheLock); 192 } 193 194 /* BEWARE: This function gets called for both client and server SIDs !! 195 * If the unreferenced sid is not in the cache, Free sid and its contents. 196 */ 197 static void 198 ssl_DestroySID(sslSessionID *sid) 199 { 200 SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); 201 PORT_Assert((sid->references == 0)); 202 203 if (sid->cached == in_client_cache) 204 return; /* it will get taken care of next time cache is traversed. */ 205 206 if (sid->version < SSL_LIBRARY_VERSION_3_0) { 207 SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE); 208 SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE); 209 } 210 if (sid->peerID != NULL) 211 PORT_Free((void *)sid->peerID); /* CONST */ 212 213 if (sid->urlSvrName != NULL) 214 PORT_Free((void *)sid->urlSvrName); /* CONST */ 215 216 if ( sid->peerCert ) { 217 CERT_DestroyCertificate(sid->peerCert); 218 } 219 if ( sid->localCert ) { 220 CERT_DestroyCertificate(sid->localCert); 221 } 222 if (sid->u.ssl3.sessionTicket.ticket.data) { 223 SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); 224 } 225 226 PORT_ZFree(sid, sizeof(sslSessionID)); 227 } 228 229 /* BEWARE: This function gets called for both client and server SIDs !! 230 * Decrement reference count, and 231 * free sid if ref count is zero, and sid is not in the cache. 232 * Does NOT remove from the cache first. 233 * If the sid is still in the cache, it is left there until next time 234 * the cache list is traversed. 235 */ 236 static void 237 ssl_FreeLockedSID(sslSessionID *sid) 238 { 239 PORT_Assert(sid->references >= 1); 240 if (--sid->references == 0) { 241 ssl_DestroySID(sid); 242 } 243 } 244 245 /* BEWARE: This function gets called for both client and server SIDs !! 246 * Decrement reference count, and 247 * free sid if ref count is zero, and sid is not in the cache. 248 * Does NOT remove from the cache first. 249 * These locks are necessary because the sid _might_ be in the cache list. 250 */ 251 void 252 ssl_FreeSID(sslSessionID *sid) 253 { 254 LOCK_CACHE; 255 ssl_FreeLockedSID(sid); 256 UNLOCK_CACHE; 257 } 258 259 /************************************************************************/ 260 261 /* 262 ** Lookup sid entry in cache by Address, port, and peerID string. 263 ** If found, Increment reference count, and return pointer to caller. 264 ** If it has timed out or ref count is zero, remove from list and free it. 265 */ 266 267 sslSessionID * 268 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, 269 const char * urlSvrName) 270 { 271 sslSessionID **sidp; 272 sslSessionID * sid; 273 PRUint32 now; 274 275 if (!urlSvrName) 276 return NULL; 277 now = ssl_Time(); 278 LOCK_CACHE; 279 sidp = &cache; 280 while ((sid = *sidp) != 0) { 281 PORT_Assert(sid->cached == in_client_cache); 282 PORT_Assert(sid->references >= 1); 283 284 SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid)); 285 286 if (sid->expirationTime < now || !sid->references) { 287 /* 288 ** This session-id timed out, or was orphaned. 289 ** Don't even care who it belongs to, blow it out of our cache. 290 */ 291 SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d", 292 now - sid->creationTime, sid->references)); 293 294 *sidp = sid->next; /* delink it from the list. */ 295 sid->cached = invalid_cache; /* mark not on list. */ 296 if (!sid->references) 297 ssl_DestroySID(sid); 298 else 299 ssl_FreeLockedSID(sid); /* drop ref count, free. */ 300 301 } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */ 302 (sid->port == port) && /* server port matches */ 303 /* proxy (peerID) matches */ 304 (((peerID == NULL) && (sid->peerID == NULL)) || 305 ((peerID != NULL) && (sid->peerID != NULL) && 306 PORT_Strcmp(sid->peerID, peerID) == 0)) && 307 /* is cacheable */ 308 (sid->version < SSL_LIBRARY_VERSION_3_0 || 309 sid->u.ssl3.keys.resumable) && 310 /* server hostname matches. */ 311 (sid->urlSvrName != NULL) && 312 ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) || 313 ((sid->peerCert != NULL) && (SECSuccess == 314 CERT_VerifyCertName(sid->peerCert, urlSvrName))) ) 315 ) { 316 /* Hit */ 317 sid->lastAccessTime = now; 318 sid->references++; 319 break; 320 } else { 321 sidp = &sid->next; 322 } 323 } 324 UNLOCK_CACHE; 325 return sid; 326 } 327 328 /* 329 ** Add an sid to the cache or return a previously cached entry to the cache. 330 ** Although this is static, it is called via ss->sec.cache(). 331 */ 332 static void 333 CacheSID(sslSessionID *sid) 334 { 335 PRUint32 expirationPeriod; 336 SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " 337 "time=%x cached=%d", 338 sid, sid->cached, sid->addr.pr_s6_addr32[0], 339 sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], 340 sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, 341 sid->cached)); 342 343 if (sid->cached == in_client_cache) 344 return; 345 346 if (!sid->urlSvrName) { 347 /* don't cache this SID because it can never be matched */ 348 return; 349 } 350 351 /* XXX should be different trace for version 2 vs. version 3 */ 352 if (sid->version < SSL_LIBRARY_VERSION_3_0) { 353 expirationPeriod = ssl_sid_timeout; 354 PRINT_BUF(8, (0, "sessionID:", 355 sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID))); 356 PRINT_BUF(8, (0, "masterKey:", 357 sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len)); 358 PRINT_BUF(8, (0, "cipherArg:", 359 sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len)); 360 } else { 361 if (sid->u.ssl3.sessionIDLength == 0 && 362 sid->u.ssl3.sessionTicket.ticket.data == NULL) 363 return; 364 /* Client generates the SessionID if this was a stateless resume. */ 365 if (sid->u.ssl3.sessionIDLength == 0) { 366 SECStatus rv; 367 rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, 368 SSL3_SESSIONID_BYTES); 369 if (rv != SECSuccess) 370 return; 371 sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; 372 } 373 expirationPeriod = ssl3_sid_timeout; 374 PRINT_BUF(8, (0, "sessionID:", 375 sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); 376 } 377 PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0); 378 if (!sid->creationTime) 379 sid->lastAccessTime = sid->creationTime = ssl_Time(); 380 if (!sid->expirationTime) 381 sid->expirationTime = sid->creationTime + expirationPeriod; 382 383 /* 384 * Put sid into the cache. Bump reference count to indicate that 385 * cache is holding a reference. Uncache will reduce the cache 386 * reference. 387 */ 388 LOCK_CACHE; 389 sid->references++; 390 sid->cached = in_client_cache; 391 sid->next = cache; 392 cache = sid; 393 UNLOCK_CACHE; 394 } 395 396 /* 397 * If sid "zap" is in the cache, 398 * removes sid from cache, and decrements reference count. 399 * Caller must hold cache lock. 400 */ 401 static void 402 UncacheSID(sslSessionID *zap) 403 { 404 sslSessionID **sidp = &cache; 405 sslSessionID *sid; 406 407 if (zap->cached != in_client_cache) { 408 return; 409 } 410 411 SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " 412 "time=%x cipher=%d", 413 zap, zap->cached, zap->addr.pr_s6_addr32[0], 414 zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2], 415 zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime, 416 zap->u.ssl2.cipherType)); 417 if (zap->version < SSL_LIBRARY_VERSION_3_0) { 418 PRINT_BUF(8, (0, "sessionID:", 419 zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID))); 420 PRINT_BUF(8, (0, "masterKey:", 421 zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len)); 422 PRINT_BUF(8, (0, "cipherArg:", 423 zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len)); 424 } 425 426 /* See if it's in the cache, if so nuke it */ 427 while ((sid = *sidp) != 0) { 428 if (sid == zap) { 429 /* 430 ** Bingo. Reduce reference count by one so that when 431 ** everyone is done with the sid we can free it up. 432 */ 433 *sidp = zap->next; 434 zap->cached = invalid_cache; 435 ssl_FreeLockedSID(zap); 436 return; 437 } 438 sidp = &sid->next; 439 } 440 } 441 442 /* If sid "zap" is in the cache, 443 * removes sid from cache, and decrements reference count. 444 * Although this function is static, it is called externally via 445 * ss->sec.uncache(). 446 */ 447 static void 448 LockAndUncacheSID(sslSessionID *zap) 449 { 450 LOCK_CACHE; 451 UncacheSID(zap); 452 UNLOCK_CACHE; 453 454 } 455 456 /* choose client or server cache functions for this sslsocket. */ 457 void 458 ssl_ChooseSessionIDProcs(sslSecurityInfo *sec) 459 { 460 if (sec->isServer) { 461 sec->cache = ssl_sid_cache; 462 sec->uncache = ssl_sid_uncache; 463 } else { 464 sec->cache = CacheSID; 465 sec->uncache = LockAndUncacheSID; 466 } 467 } 468 469 /* wipe out the entire client session cache. */ 470 void 471 SSL_ClearSessionCache(void) 472 { 473 LOCK_CACHE; 474 while(cache != NULL) 475 UncacheSID(cache); 476 UNLOCK_CACHE; 477 } 478 479 /* returns an unsigned int containing the number of seconds in PR_Now() */ 480 PRUint32 481 ssl_Time(void) 482 { 483 PRUint32 myTime; 484 #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE) 485 myTime = time(NULL); /* accurate until the year 2038. */ 486 #else 487 /* portable, but possibly slower */ 488 PRTime now; 489 PRInt64 ll; 490 491 now = PR_Now(); 492 LL_I2L(ll, 1000000L); 493 LL_DIV(now, now, ll); 494 LL_L2UI(myTime, now); 495 #endif 496 return myTime; 497 } 498 499 SECStatus 500 ssl3_SetSIDSessionTicket(sslSessionID *sid, NewSessionTicket *session_ticket) 501 { 502 SECStatus rv; 503 504 /* We need to lock the cache, as this sid might already be in the cache. */ 505 LOCK_CACHE; 506 507 /* A server might have sent us an empty ticket, which has the 508 * effect of clearing the previously known ticket. 509 */ 510 if (sid->u.ssl3.sessionTicket.ticket.data) 511 SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); 512 if (session_ticket->ticket.len > 0) { 513 rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.sessionTicket.ticket, 514 &session_ticket->ticket); 515 if (rv != SECSuccess) { 516 UNLOCK_CACHE; 517 return rv; 518 } 519 } else { 520 sid->u.ssl3.sessionTicket.ticket.data = NULL; 521 sid->u.ssl3.sessionTicket.ticket.len = 0; 522 } 523 sid->u.ssl3.sessionTicket.received_timestamp = 524 session_ticket->received_timestamp; 525 sid->u.ssl3.sessionTicket.ticket_lifetime_hint = 526 session_ticket->ticket_lifetime_hint; 527 528 UNLOCK_CACHE; 529 return SECSuccess; 530 } 531