security/nss/lib/ssl/sslnonce.c

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:75395edaa0c4
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 SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
168 PORT_Assert(sid->references == 0);
169 PORT_Assert(sid->cached != in_client_cache);
170
171 if (sid->version < SSL_LIBRARY_VERSION_3_0) {
172 SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE);
173 SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE);
174 } else {
175 if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
176 SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
177 PR_FALSE);
178 }
179 if (sid->u.ssl3.srvName.data) {
180 SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
181 }
182
183 if (sid->u.ssl3.lock) {
184 PR_DestroyRWLock(sid->u.ssl3.lock);
185 }
186 }
187
188 if (sid->peerID != NULL)
189 PORT_Free((void *)sid->peerID); /* CONST */
190
191 if (sid->urlSvrName != NULL)
192 PORT_Free((void *)sid->urlSvrName); /* CONST */
193
194 if ( sid->peerCert ) {
195 CERT_DestroyCertificate(sid->peerCert);
196 }
197 if (sid->peerCertStatus.items) {
198 SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
199 }
200
201 if ( sid->localCert ) {
202 CERT_DestroyCertificate(sid->localCert);
203 }
204
205 PORT_ZFree(sid, sizeof(sslSessionID));
206 }
207
208 /* BEWARE: This function gets called for both client and server SIDs !!
209 * Decrement reference count, and
210 * free sid if ref count is zero, and sid is not in the cache.
211 * Does NOT remove from the cache first.
212 * If the sid is still in the cache, it is left there until next time
213 * the cache list is traversed.
214 */
215 static void
216 ssl_FreeLockedSID(sslSessionID *sid)
217 {
218 PORT_Assert(sid->references >= 1);
219 if (--sid->references == 0) {
220 ssl_DestroySID(sid);
221 }
222 }
223
224 /* BEWARE: This function gets called for both client and server SIDs !!
225 * Decrement reference count, and
226 * free sid if ref count is zero, and sid is not in the cache.
227 * Does NOT remove from the cache first.
228 * These locks are necessary because the sid _might_ be in the cache list.
229 */
230 void
231 ssl_FreeSID(sslSessionID *sid)
232 {
233 LOCK_CACHE;
234 ssl_FreeLockedSID(sid);
235 UNLOCK_CACHE;
236 }
237
238 /************************************************************************/
239
240 /*
241 ** Lookup sid entry in cache by Address, port, and peerID string.
242 ** If found, Increment reference count, and return pointer to caller.
243 ** If it has timed out or ref count is zero, remove from list and free it.
244 */
245
246 sslSessionID *
247 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
248 const char * urlSvrName)
249 {
250 sslSessionID **sidp;
251 sslSessionID * sid;
252 PRUint32 now;
253
254 if (!urlSvrName)
255 return NULL;
256 now = ssl_Time();
257 LOCK_CACHE;
258 sidp = &cache;
259 while ((sid = *sidp) != 0) {
260 PORT_Assert(sid->cached == in_client_cache);
261 PORT_Assert(sid->references >= 1);
262
263 SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid));
264
265 if (sid->expirationTime < now) {
266 /*
267 ** This session-id timed out.
268 ** Don't even care who it belongs to, blow it out of our cache.
269 */
270 SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
271 now - sid->creationTime, sid->references));
272
273 *sidp = sid->next; /* delink it from the list. */
274 sid->cached = invalid_cache; /* mark not on list. */
275 ssl_FreeLockedSID(sid); /* drop ref count, free. */
276 } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
277 (sid->port == port) && /* server port matches */
278 /* proxy (peerID) matches */
279 (((peerID == NULL) && (sid->peerID == NULL)) ||
280 ((peerID != NULL) && (sid->peerID != NULL) &&
281 PORT_Strcmp(sid->peerID, peerID) == 0)) &&
282 /* is cacheable */
283 (sid->version < SSL_LIBRARY_VERSION_3_0 ||
284 sid->u.ssl3.keys.resumable) &&
285 /* server hostname matches. */
286 (sid->urlSvrName != NULL) &&
287 ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) ||
288 ((sid->peerCert != NULL) && (SECSuccess ==
289 CERT_VerifyCertName(sid->peerCert, urlSvrName))) )
290 ) {
291 /* Hit */
292 sid->lastAccessTime = now;
293 sid->references++;
294 break;
295 } else {
296 sidp = &sid->next;
297 }
298 }
299 UNLOCK_CACHE;
300 return sid;
301 }
302
303 /*
304 ** Add an sid to the cache or return a previously cached entry to the cache.
305 ** Although this is static, it is called via ss->sec.cache().
306 */
307 static void
308 CacheSID(sslSessionID *sid)
309 {
310 PRUint32 expirationPeriod;
311
312 PORT_Assert(sid->cached == never_cached);
313
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->urlSvrName) {
322 /* don't cache this SID because it can never be matched */
323 return;
324 }
325
326 /* XXX should be different trace for version 2 vs. version 3 */
327 if (sid->version < SSL_LIBRARY_VERSION_3_0) {
328 expirationPeriod = ssl_sid_timeout;
329 PRINT_BUF(8, (0, "sessionID:",
330 sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID)));
331 PRINT_BUF(8, (0, "masterKey:",
332 sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len));
333 PRINT_BUF(8, (0, "cipherArg:",
334 sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len));
335 } else {
336 if (sid->u.ssl3.sessionIDLength == 0 &&
337 sid->u.ssl3.locked.sessionTicket.ticket.data == NULL)
338 return;
339
340 /* Client generates the SessionID if this was a stateless resume. */
341 if (sid->u.ssl3.sessionIDLength == 0) {
342 SECStatus rv;
343 rv = PK11_GenerateRandom(sid->u.ssl3.sessionID,
344 SSL3_SESSIONID_BYTES);
345 if (rv != SECSuccess)
346 return;
347 sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
348 }
349 expirationPeriod = ssl3_sid_timeout;
350 PRINT_BUF(8, (0, "sessionID:",
351 sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength));
352
353 sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL);
354 if (!sid->u.ssl3.lock) {
355 return;
356 }
357 }
358 PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0);
359 if (!sid->creationTime)
360 sid->lastAccessTime = sid->creationTime = ssl_Time();
361 if (!sid->expirationTime)
362 sid->expirationTime = sid->creationTime + expirationPeriod;
363
364 /*
365 * Put sid into the cache. Bump reference count to indicate that
366 * cache is holding a reference. Uncache will reduce the cache
367 * reference.
368 */
369 LOCK_CACHE;
370 sid->references++;
371 sid->cached = in_client_cache;
372 sid->next = cache;
373 cache = sid;
374 UNLOCK_CACHE;
375 }
376
377 /*
378 * If sid "zap" is in the cache,
379 * removes sid from cache, and decrements reference count.
380 * Caller must hold cache lock.
381 */
382 static void
383 UncacheSID(sslSessionID *zap)
384 {
385 sslSessionID **sidp = &cache;
386 sslSessionID *sid;
387
388 if (zap->cached != in_client_cache) {
389 return;
390 }
391
392 SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
393 "time=%x cipher=%d",
394 zap, zap->cached, zap->addr.pr_s6_addr32[0],
395 zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2],
396 zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime,
397 zap->u.ssl2.cipherType));
398 if (zap->version < SSL_LIBRARY_VERSION_3_0) {
399 PRINT_BUF(8, (0, "sessionID:",
400 zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID)));
401 PRINT_BUF(8, (0, "masterKey:",
402 zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len));
403 PRINT_BUF(8, (0, "cipherArg:",
404 zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len));
405 }
406
407 /* See if it's in the cache, if so nuke it */
408 while ((sid = *sidp) != 0) {
409 if (sid == zap) {
410 /*
411 ** Bingo. Reduce reference count by one so that when
412 ** everyone is done with the sid we can free it up.
413 */
414 *sidp = zap->next;
415 zap->cached = invalid_cache;
416 ssl_FreeLockedSID(zap);
417 return;
418 }
419 sidp = &sid->next;
420 }
421 }
422
423 /* If sid "zap" is in the cache,
424 * removes sid from cache, and decrements reference count.
425 * Although this function is static, it is called externally via
426 * ss->sec.uncache().
427 */
428 static void
429 LockAndUncacheSID(sslSessionID *zap)
430 {
431 LOCK_CACHE;
432 UncacheSID(zap);
433 UNLOCK_CACHE;
434
435 }
436
437 /* choose client or server cache functions for this sslsocket. */
438 void
439 ssl_ChooseSessionIDProcs(sslSecurityInfo *sec)
440 {
441 if (sec->isServer) {
442 sec->cache = ssl_sid_cache;
443 sec->uncache = ssl_sid_uncache;
444 } else {
445 sec->cache = CacheSID;
446 sec->uncache = LockAndUncacheSID;
447 }
448 }
449
450 /* wipe out the entire client session cache. */
451 void
452 SSL_ClearSessionCache(void)
453 {
454 LOCK_CACHE;
455 while(cache != NULL)
456 UncacheSID(cache);
457 UNLOCK_CACHE;
458 }
459
460 /* returns an unsigned int containing the number of seconds in PR_Now() */
461 PRUint32
462 ssl_Time(void)
463 {
464 PRUint32 myTime;
465 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
466 myTime = time(NULL); /* accurate until the year 2038. */
467 #else
468 /* portable, but possibly slower */
469 PRTime now;
470 PRInt64 ll;
471
472 now = PR_Now();
473 LL_I2L(ll, 1000000L);
474 LL_DIV(now, now, ll);
475 LL_L2UI(myTime, now);
476 #endif
477 return myTime;
478 }
479
480 void
481 ssl3_SetSIDSessionTicket(sslSessionID *sid,
482 /*in/out*/ NewSessionTicket *newSessionTicket)
483 {
484 PORT_Assert(sid);
485 PORT_Assert(newSessionTicket);
486 PORT_Assert(newSessionTicket->ticket.data);
487 PORT_Assert(newSessionTicket->ticket.len != 0);
488
489 /* if sid->u.ssl3.lock, we are updating an existing entry that is already
490 * cached or was once cached, so we need to acquire and release the write
491 * lock. Otherwise, this is a new session that isn't shared with anything
492 * yet, so no locking is needed.
493 */
494 if (sid->u.ssl3.lock) {
495 PR_RWLock_Wlock(sid->u.ssl3.lock);
496 if (sid->u.ssl3.locked.sessionTicket.ticket.data) {
497 SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket,
498 PR_FALSE);
499 }
500 }
501
502 PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data);
503
504 /* Do a shallow copy, moving the ticket data. */
505 sid->u.ssl3.locked.sessionTicket = *newSessionTicket;
506 newSessionTicket->ticket.data = NULL;
507 newSessionTicket->ticket.len = 0;
508
509 if (sid->u.ssl3.lock) {
510 PR_RWLock_Unlock(sid->u.ssl3.lock);
511 }
512 }

mercurial