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