|
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 } |