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