2 * This file implements the CLIENT Session ID cache.
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 /* $Id: sslnonce.c,v 1.27 2012/04/25 14:50:12 gerv%gerv.net Exp $ */
18 #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE)
22 PRUint32 ssl_sid_timeout
= 100;
23 PRUint32 ssl3_sid_timeout
= 86400L; /* 24 hours */
25 static sslSessionID
*cache
= NULL
;
26 static PZLock
* cacheLock
= NULL
;
28 /* sids can be in one of 4 states:
30 * never_cached, created, but not yet put into cache.
31 * in_client_cache, in the client cache's linked list.
32 * in_server_cache, entry came from the server's cache file.
33 * invalid_cache has been removed from the cache.
36 #define LOCK_CACHE lock_cache()
37 #define UNLOCK_CACHE PZ_Unlock(cacheLock)
40 ssl_InitClientSessionCacheLock(void)
42 cacheLock
= PZ_NewLock(nssILockCache
);
43 return cacheLock
? SECSuccess
: SECFailure
;
47 ssl_FreeClientSessionCacheLock(void)
50 PZ_DestroyLock(cacheLock
);
54 PORT_SetError(SEC_ERROR_NOT_INITIALIZED
);
58 static PRBool LocksInitializedEarly
= PR_FALSE
;
61 FreeSessionCacheLocks()
64 rv1
= ssl_FreeSymWrapKeysLock();
65 rv2
= ssl_FreeClientSessionCacheLock();
66 if ( (SECSuccess
== rv1
) && (SECSuccess
== rv2
) ) {
73 InitSessionCacheLocks(void)
77 rv1
= ssl_InitSymWrapKeysLock();
78 rv2
= ssl_InitClientSessionCacheLock();
79 if ( (SECSuccess
== rv1
) && (SECSuccess
== rv2
) ) {
83 FreeSessionCacheLocks();
88 /* free the session cache locks if they were initialized early */
90 ssl_FreeSessionCacheLocks()
92 PORT_Assert(PR_TRUE
== LocksInitializedEarly
);
93 if (!LocksInitializedEarly
) {
94 PORT_SetError(SEC_ERROR_NOT_INITIALIZED
);
97 FreeSessionCacheLocks();
98 LocksInitializedEarly
= PR_FALSE
;
102 static PRCallOnceType lockOnce
;
104 /* free the session cache locks if they were initialized lazily */
105 static SECStatus
ssl_ShutdownLocks(void* appData
, void* nssData
)
107 PORT_Assert(PR_FALSE
== LocksInitializedEarly
);
108 if (LocksInitializedEarly
) {
109 PORT_SetError(SEC_ERROR_LIBRARY_FAILURE
);
112 FreeSessionCacheLocks();
113 memset(&lockOnce
, 0, sizeof(lockOnce
));
117 static PRStatus
initSessionCacheLocksLazily(void)
119 SECStatus rv
= InitSessionCacheLocks();
120 if (SECSuccess
!= rv
) {
123 rv
= NSS_RegisterShutdown(ssl_ShutdownLocks
, NULL
);
124 PORT_Assert(SECSuccess
== rv
);
125 if (SECSuccess
!= rv
) {
131 /* lazyInit means that the call is not happening during a 1-time
132 * initialization function, but rather during dynamic, lazy initialization
135 ssl_InitSessionCacheLocks(PRBool lazyInit
)
137 if (LocksInitializedEarly
) {
142 return (PR_SUCCESS
==
143 PR_CallOnce(&lockOnce
, initSessionCacheLocksLazily
)) ?
144 SECSuccess
: SECFailure
;
147 if (SECSuccess
== InitSessionCacheLocks()) {
148 LocksInitializedEarly
= PR_TRUE
;
158 ssl_InitSessionCacheLocks(PR_TRUE
);
162 /* BEWARE: This function gets called for both client and server SIDs !!
163 * If the unreferenced sid is not in the cache, Free sid and its contents.
166 ssl_DestroySID(sslSessionID
*sid
)
169 SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid
, sid
->cached
));
170 PORT_Assert((sid
->references
== 0));
172 if (sid
->cached
== in_client_cache
)
173 return; /* it will get taken care of next time cache is traversed. */
175 if (sid
->version
< SSL_LIBRARY_VERSION_3_0
) {
176 SECITEM_ZfreeItem(&sid
->u
.ssl2
.masterKey
, PR_FALSE
);
177 SECITEM_ZfreeItem(&sid
->u
.ssl2
.cipherArg
, PR_FALSE
);
179 if (sid
->peerID
!= NULL
)
180 PORT_Free((void *)sid
->peerID
); /* CONST */
182 if (sid
->urlSvrName
!= NULL
)
183 PORT_Free((void *)sid
->urlSvrName
); /* CONST */
185 if ( sid
->peerCert
) {
186 CERT_DestroyCertificate(sid
->peerCert
);
188 for (i
= 0; i
< MAX_PEER_CERT_CHAIN_SIZE
&& sid
->peerCertChain
[i
]; i
++) {
189 CERT_DestroyCertificate(sid
->peerCertChain
[i
]);
191 if ( sid
->localCert
) {
192 CERT_DestroyCertificate(sid
->localCert
);
194 if (sid
->u
.ssl3
.sessionTicket
.ticket
.data
) {
195 SECITEM_FreeItem(&sid
->u
.ssl3
.sessionTicket
.ticket
, PR_FALSE
);
197 if (sid
->u
.ssl3
.srvName
.data
) {
198 SECITEM_FreeItem(&sid
->u
.ssl3
.srvName
, PR_FALSE
);
201 PORT_ZFree(sid
, sizeof(sslSessionID
));
204 /* BEWARE: This function gets called for both client and server SIDs !!
205 * Decrement reference count, and
206 * free sid if ref count is zero, and sid is not in the cache.
207 * Does NOT remove from the cache first.
208 * If the sid is still in the cache, it is left there until next time
209 * the cache list is traversed.
212 ssl_FreeLockedSID(sslSessionID
*sid
)
214 PORT_Assert(sid
->references
>= 1);
215 if (--sid
->references
== 0) {
220 /* BEWARE: This function gets called for both client and server SIDs !!
221 * Decrement reference count, and
222 * free sid if ref count is zero, and sid is not in the cache.
223 * Does NOT remove from the cache first.
224 * These locks are necessary because the sid _might_ be in the cache list.
227 ssl_FreeSID(sslSessionID
*sid
)
230 ssl_FreeLockedSID(sid
);
234 /************************************************************************/
237 ** Lookup sid entry in cache by Address, port, and peerID string.
238 ** If found, Increment reference count, and return pointer to caller.
239 ** If it has timed out or ref count is zero, remove from list and free it.
243 ssl_LookupSID(const PRIPv6Addr
*addr
, PRUint16 port
, const char *peerID
,
244 const char * urlSvrName
)
255 while ((sid
= *sidp
) != 0) {
256 PORT_Assert(sid
->cached
== in_client_cache
);
257 PORT_Assert(sid
->references
>= 1);
259 SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid
));
261 if (sid
->expirationTime
< now
|| !sid
->references
) {
263 ** This session-id timed out, or was orphaned.
264 ** Don't even care who it belongs to, blow it out of our cache.
266 SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
267 now
- sid
->creationTime
, sid
->references
));
269 *sidp
= sid
->next
; /* delink it from the list. */
270 sid
->cached
= invalid_cache
; /* mark not on list. */
271 if (!sid
->references
)
274 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)) &&
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
))) )
292 sid
->lastAccessTime
= now
;
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().
308 CacheSID(sslSessionID
*sid
)
310 PRUint32 expirationPeriod
;
311 SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
313 sid
, sid
->cached
, sid
->addr
.pr_s6_addr32
[0],
314 sid
->addr
.pr_s6_addr32
[1], sid
->addr
.pr_s6_addr32
[2],
315 sid
->addr
.pr_s6_addr32
[3], sid
->port
, sid
->creationTime
,
318 if (sid
->cached
== in_client_cache
)
321 if (!sid
->urlSvrName
) {
322 /* don't cache this SID because it can never be matched */
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
));
336 if (sid
->u
.ssl3
.sessionIDLength
== 0 &&
337 sid
->u
.ssl3
.sessionTicket
.ticket
.data
== NULL
)
339 /* Client generates the SessionID if this was a stateless resume. */
340 if (sid
->u
.ssl3
.sessionIDLength
== 0) {
342 rv
= PK11_GenerateRandom(sid
->u
.ssl3
.sessionID
,
343 SSL3_SESSIONID_BYTES
);
344 if (rv
!= SECSuccess
)
346 sid
->u
.ssl3
.sessionIDLength
= SSL3_SESSIONID_BYTES
;
348 expirationPeriod
= ssl3_sid_timeout
;
349 PRINT_BUF(8, (0, "sessionID:",
350 sid
->u
.ssl3
.sessionID
, sid
->u
.ssl3
.sessionIDLength
));
352 PORT_Assert(sid
->creationTime
!= 0 && sid
->expirationTime
!= 0);
353 if (!sid
->creationTime
)
354 sid
->lastAccessTime
= sid
->creationTime
= ssl_Time();
355 if (!sid
->expirationTime
)
356 sid
->expirationTime
= sid
->creationTime
+ expirationPeriod
;
359 * Put sid into the cache. Bump reference count to indicate that
360 * cache is holding a reference. Uncache will reduce the cache
365 sid
->cached
= in_client_cache
;
372 * If sid "zap" is in the cache,
373 * removes sid from cache, and decrements reference count.
374 * Caller must hold cache lock.
377 UncacheSID(sslSessionID
*zap
)
379 sslSessionID
**sidp
= &cache
;
382 if (zap
->cached
!= in_client_cache
) {
386 SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
388 zap
, zap
->cached
, zap
->addr
.pr_s6_addr32
[0],
389 zap
->addr
.pr_s6_addr32
[1], zap
->addr
.pr_s6_addr32
[2],
390 zap
->addr
.pr_s6_addr32
[3], zap
->port
, zap
->creationTime
,
391 zap
->u
.ssl2
.cipherType
));
392 if (zap
->version
< SSL_LIBRARY_VERSION_3_0
) {
393 PRINT_BUF(8, (0, "sessionID:",
394 zap
->u
.ssl2
.sessionID
, sizeof(zap
->u
.ssl2
.sessionID
)));
395 PRINT_BUF(8, (0, "masterKey:",
396 zap
->u
.ssl2
.masterKey
.data
, zap
->u
.ssl2
.masterKey
.len
));
397 PRINT_BUF(8, (0, "cipherArg:",
398 zap
->u
.ssl2
.cipherArg
.data
, zap
->u
.ssl2
.cipherArg
.len
));
401 /* See if it's in the cache, if so nuke it */
402 while ((sid
= *sidp
) != 0) {
405 ** Bingo. Reduce reference count by one so that when
406 ** everyone is done with the sid we can free it up.
409 zap
->cached
= invalid_cache
;
410 ssl_FreeLockedSID(zap
);
417 /* If sid "zap" is in the cache,
418 * removes sid from cache, and decrements reference count.
419 * Although this function is static, it is called externally via
423 LockAndUncacheSID(sslSessionID
*zap
)
431 /* choose client or server cache functions for this sslsocket. */
433 ssl_ChooseSessionIDProcs(sslSecurityInfo
*sec
)
436 sec
->cache
= ssl_sid_cache
;
437 sec
->uncache
= ssl_sid_uncache
;
439 sec
->cache
= CacheSID
;
440 sec
->uncache
= LockAndUncacheSID
;
444 /* wipe out the entire client session cache. */
446 SSL_ClearSessionCache(void)
454 /* returns an unsigned int containing the number of seconds in PR_Now() */
459 #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE)
460 myTime
= time(NULL
); /* accurate until the year 2038. */
462 /* portable, but possibly slower */
467 LL_I2L(ll
, 1000000L);
468 LL_DIV(now
, now
, ll
);
469 LL_L2UI(myTime
, now
);
475 ssl3_SetSIDSessionTicket(sslSessionID
*sid
, NewSessionTicket
*session_ticket
)
479 /* We need to lock the cache, as this sid might already be in the cache. */
482 /* A server might have sent us an empty ticket, which has the
483 * effect of clearing the previously known ticket.
485 if (sid
->u
.ssl3
.sessionTicket
.ticket
.data
)
486 SECITEM_FreeItem(&sid
->u
.ssl3
.sessionTicket
.ticket
, PR_FALSE
);
487 if (session_ticket
->ticket
.len
> 0) {
488 rv
= SECITEM_CopyItem(NULL
, &sid
->u
.ssl3
.sessionTicket
.ticket
,
489 &session_ticket
->ticket
);
490 if (rv
!= SECSuccess
) {
495 sid
->u
.ssl3
.sessionTicket
.ticket
.data
= NULL
;
496 sid
->u
.ssl3
.sessionTicket
.ticket
.len
= 0;
498 sid
->u
.ssl3
.sessionTicket
.received_timestamp
=
499 session_ticket
->received_timestamp
;
500 sid
->u
.ssl3
.sessionTicket
.ticket_lifetime_hint
=
501 session_ticket
->ticket_lifetime_hint
;