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