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/. */
17 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
21 PRUint32 ssl_sid_timeout
= 100;
22 PRUint32 ssl3_sid_timeout
= 86400L; /* 24 hours */
24 sslSessionID
*cache
= NULL
;
25 PZLock
* cacheLock
= NULL
;
27 /* sids can be in one of 4 states:
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.
35 #define LOCK_CACHE lock_cache()
36 #define UNLOCK_CACHE PZ_Unlock(cacheLock)
38 static PRCallOnceType lockOnce
;
40 /* FreeSessionCacheLocks is a callback from NSS_RegisterShutdown which destroys
41 * the session cache locks on shutdown and resets them to their initial
44 FreeSessionCacheLocks(void* appData
, void* nssData
)
46 static const PRCallOnceType pristineCallOnce
;
50 PORT_SetError(SEC_ERROR_NOT_INITIALIZED
);
54 PZ_DestroyLock(cacheLock
);
57 rv
= ssl_FreeSymWrapKeysLock();
58 if (rv
!= SECSuccess
) {
62 lockOnce
= pristineCallOnce
;
66 /* InitSessionCacheLocks is called, protected by lockOnce, to create the
67 * session cache locks. */
69 InitSessionCacheLocks(void)
73 cacheLock
= PZ_NewLock(nssILockCache
);
74 if (cacheLock
== NULL
) {
77 rv
= ssl_InitSymWrapKeysLock();
78 if (rv
!= SECSuccess
) {
79 PRErrorCode error
= PORT_GetError();
80 PZ_DestroyLock(cacheLock
);
86 rv
= NSS_RegisterShutdown(FreeSessionCacheLocks
, NULL
);
87 PORT_Assert(SECSuccess
== rv
);
88 if (SECSuccess
!= rv
) {
95 ssl_InitSessionCacheLocks(void)
98 PR_CallOnce(&lockOnce
, InitSessionCacheLocks
)) ?
99 SECSuccess
: SECFailure
;
105 ssl_InitSessionCacheLocks();
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.
113 ssl_DestroySID(sslSessionID
*sid
)
116 SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid
, sid
->cached
));
117 PORT_Assert((sid
->references
== 0));
119 if (sid
->cached
== in_client_cache
)
120 return; /* it will get taken care of next time cache is traversed. */
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
);
126 if (sid
->u
.ssl3
.locked
.sessionTicket
.ticket
.data
) {
127 SECITEM_FreeItem(&sid
->u
.ssl3
.locked
.sessionTicket
.ticket
,
130 if (sid
->u
.ssl3
.srvName
.data
) {
131 SECITEM_FreeItem(&sid
->u
.ssl3
.srvName
, PR_FALSE
);
133 if (sid
->u
.ssl3
.originalHandshakeHash
.data
) {
134 SECITEM_FreeItem(&sid
->u
.ssl3
.originalHandshakeHash
, PR_FALSE
);
136 if (sid
->u
.ssl3
.signedCertTimestamps
.data
) {
137 SECITEM_FreeItem(&sid
->u
.ssl3
.signedCertTimestamps
, PR_FALSE
);
140 if (sid
->u
.ssl3
.lock
) {
141 NSSRWLock_Destroy(sid
->u
.ssl3
.lock
);
145 if (sid
->peerID
!= NULL
)
146 PORT_Free((void *)sid
->peerID
); /* CONST */
148 if (sid
->urlSvrName
!= NULL
)
149 PORT_Free((void *)sid
->urlSvrName
); /* CONST */
151 if ( sid
->peerCert
) {
152 CERT_DestroyCertificate(sid
->peerCert
);
154 for (i
= 0; i
< MAX_PEER_CERT_CHAIN_SIZE
&& sid
->peerCertChain
[i
]; i
++) {
155 CERT_DestroyCertificate(sid
->peerCertChain
[i
]);
157 if (sid
->peerCertStatus
.items
) {
158 SECITEM_FreeArray(&sid
->peerCertStatus
, PR_FALSE
);
161 if ( sid
->localCert
) {
162 CERT_DestroyCertificate(sid
->localCert
);
165 PORT_ZFree(sid
, sizeof(sslSessionID
));
168 /* BEWARE: This function gets called for both client and server SIDs !!
169 * Decrement reference count, and
170 * free sid if ref count is zero, and sid is not in the cache.
171 * Does NOT remove from the cache first.
172 * If the sid is still in the cache, it is left there until next time
173 * the cache list is traversed.
176 ssl_FreeLockedSID(sslSessionID
*sid
)
178 PORT_Assert(sid
->references
>= 1);
179 if (--sid
->references
== 0) {
184 /* BEWARE: This function gets called for both client and server SIDs !!
185 * Decrement reference count, and
186 * free sid if ref count is zero, and sid is not in the cache.
187 * Does NOT remove from the cache first.
188 * These locks are necessary because the sid _might_ be in the cache list.
191 ssl_FreeSID(sslSessionID
*sid
)
194 ssl_FreeLockedSID(sid
);
198 /************************************************************************/
201 ** Lookup sid entry in cache by Address, port, and peerID string.
202 ** If found, Increment reference count, and return pointer to caller.
203 ** If it has timed out or ref count is zero, remove from list and free it.
207 ssl_LookupSID(const PRIPv6Addr
*addr
, PRUint16 port
, const char *peerID
,
208 const char * urlSvrName
)
219 while ((sid
= *sidp
) != 0) {
220 PORT_Assert(sid
->cached
== in_client_cache
);
221 PORT_Assert(sid
->references
>= 1);
223 SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid
));
225 if (sid
->expirationTime
< now
|| !sid
->references
) {
227 ** This session-id timed out, or was orphaned.
228 ** Don't even care who it belongs to, blow it out of our cache.
230 SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
231 now
- sid
->creationTime
, sid
->references
));
233 *sidp
= sid
->next
; /* delink it from the list. */
234 sid
->cached
= invalid_cache
; /* mark not on list. */
235 if (!sid
->references
)
238 ssl_FreeLockedSID(sid
); /* drop ref count, free. */
240 } else if (!memcmp(&sid
->addr
, addr
, sizeof(PRIPv6Addr
)) && /* server IP addr matches */
241 (sid
->port
== port
) && /* server port matches */
242 /* proxy (peerID) matches */
243 (((peerID
== NULL
) && (sid
->peerID
== NULL
)) ||
244 ((peerID
!= NULL
) && (sid
->peerID
!= NULL
) &&
245 PORT_Strcmp(sid
->peerID
, peerID
) == 0)) &&
247 (sid
->version
< SSL_LIBRARY_VERSION_3_0
||
248 sid
->u
.ssl3
.keys
.resumable
) &&
249 /* server hostname matches. */
250 (sid
->urlSvrName
!= NULL
) &&
251 ((0 == PORT_Strcmp(urlSvrName
, sid
->urlSvrName
)) ||
252 ((sid
->peerCert
!= NULL
) && (SECSuccess
==
253 CERT_VerifyCertName(sid
->peerCert
, urlSvrName
))) )
256 sid
->lastAccessTime
= now
;
268 ** Add an sid to the cache or return a previously cached entry to the cache.
269 ** Although this is static, it is called via ss->sec.cache().
272 CacheSID(sslSessionID
*sid
)
274 PRUint32 expirationPeriod
;
276 PORT_Assert(sid
->cached
== never_cached
);
278 SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
280 sid
, sid
->cached
, sid
->addr
.pr_s6_addr32
[0],
281 sid
->addr
.pr_s6_addr32
[1], sid
->addr
.pr_s6_addr32
[2],
282 sid
->addr
.pr_s6_addr32
[3], sid
->port
, sid
->creationTime
,
285 if (!sid
->urlSvrName
) {
286 /* don't cache this SID because it can never be matched */
290 /* XXX should be different trace for version 2 vs. version 3 */
291 if (sid
->version
< SSL_LIBRARY_VERSION_3_0
) {
292 expirationPeriod
= ssl_sid_timeout
;
293 PRINT_BUF(8, (0, "sessionID:",
294 sid
->u
.ssl2
.sessionID
, sizeof(sid
->u
.ssl2
.sessionID
)));
295 PRINT_BUF(8, (0, "masterKey:",
296 sid
->u
.ssl2
.masterKey
.data
, sid
->u
.ssl2
.masterKey
.len
));
297 PRINT_BUF(8, (0, "cipherArg:",
298 sid
->u
.ssl2
.cipherArg
.data
, sid
->u
.ssl2
.cipherArg
.len
));
300 if (sid
->u
.ssl3
.sessionIDLength
== 0 &&
301 sid
->u
.ssl3
.locked
.sessionTicket
.ticket
.data
== NULL
)
304 /* Client generates the SessionID if this was a stateless resume. */
305 if (sid
->u
.ssl3
.sessionIDLength
== 0) {
307 rv
= PK11_GenerateRandom(sid
->u
.ssl3
.sessionID
,
308 SSL3_SESSIONID_BYTES
);
309 if (rv
!= SECSuccess
)
311 sid
->u
.ssl3
.sessionIDLength
= SSL3_SESSIONID_BYTES
;
313 expirationPeriod
= ssl3_sid_timeout
;
314 PRINT_BUF(8, (0, "sessionID:",
315 sid
->u
.ssl3
.sessionID
, sid
->u
.ssl3
.sessionIDLength
));
317 sid
->u
.ssl3
.lock
= NSSRWLock_New(NSS_RWLOCK_RANK_NONE
, NULL
);
318 if (!sid
->u
.ssl3
.lock
) {
322 PORT_Assert(sid
->creationTime
!= 0 && sid
->expirationTime
!= 0);
323 if (!sid
->creationTime
)
324 sid
->lastAccessTime
= sid
->creationTime
= ssl_Time();
325 if (!sid
->expirationTime
)
326 sid
->expirationTime
= sid
->creationTime
+ expirationPeriod
;
329 * Put sid into the cache. Bump reference count to indicate that
330 * cache is holding a reference. Uncache will reduce the cache
335 sid
->cached
= in_client_cache
;
342 * If sid "zap" is in the cache,
343 * removes sid from cache, and decrements reference count.
344 * Caller must hold cache lock.
347 UncacheSID(sslSessionID
*zap
)
349 sslSessionID
**sidp
= &cache
;
352 if (zap
->cached
!= in_client_cache
) {
356 SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
358 zap
, zap
->cached
, zap
->addr
.pr_s6_addr32
[0],
359 zap
->addr
.pr_s6_addr32
[1], zap
->addr
.pr_s6_addr32
[2],
360 zap
->addr
.pr_s6_addr32
[3], zap
->port
, zap
->creationTime
,
361 zap
->u
.ssl2
.cipherType
));
362 if (zap
->version
< SSL_LIBRARY_VERSION_3_0
) {
363 PRINT_BUF(8, (0, "sessionID:",
364 zap
->u
.ssl2
.sessionID
, sizeof(zap
->u
.ssl2
.sessionID
)));
365 PRINT_BUF(8, (0, "masterKey:",
366 zap
->u
.ssl2
.masterKey
.data
, zap
->u
.ssl2
.masterKey
.len
));
367 PRINT_BUF(8, (0, "cipherArg:",
368 zap
->u
.ssl2
.cipherArg
.data
, zap
->u
.ssl2
.cipherArg
.len
));
371 /* See if it's in the cache, if so nuke it */
372 while ((sid
= *sidp
) != 0) {
375 ** Bingo. Reduce reference count by one so that when
376 ** everyone is done with the sid we can free it up.
379 zap
->cached
= invalid_cache
;
380 ssl_FreeLockedSID(zap
);
387 /* If sid "zap" is in the cache,
388 * removes sid from cache, and decrements reference count.
389 * Although this function is static, it is called externally via
393 LockAndUncacheSID(sslSessionID
*zap
)
401 /* choose client or server cache functions for this sslsocket. */
403 ssl_ChooseSessionIDProcs(sslSecurityInfo
*sec
)
406 sec
->cache
= ssl_sid_cache
;
407 sec
->uncache
= ssl_sid_uncache
;
409 sec
->cache
= CacheSID
;
410 sec
->uncache
= LockAndUncacheSID
;
414 /* wipe out the entire client session cache. */
416 SSL_ClearSessionCache(void)
424 /* returns an unsigned int containing the number of seconds in PR_Now() */
429 #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
430 myTime
= time(NULL
); /* accurate until the year 2038. */
432 /* portable, but possibly slower */
437 LL_I2L(ll
, 1000000L);
438 LL_DIV(now
, now
, ll
);
439 LL_L2UI(myTime
, now
);
445 ssl3_SetSIDSessionTicket(sslSessionID
*sid
,
446 /*in/out*/ NewSessionTicket
*newSessionTicket
)
449 PORT_Assert(newSessionTicket
);
451 /* if sid->u.ssl3.lock, we are updating an existing entry that is already
452 * cached or was once cached, so we need to acquire and release the write
453 * lock. Otherwise, this is a new session that isn't shared with anything
454 * yet, so no locking is needed.
456 if (sid
->u
.ssl3
.lock
) {
457 NSSRWLock_LockWrite(sid
->u
.ssl3
.lock
);
459 /* A server might have sent us an empty ticket, which has the
460 * effect of clearing the previously known ticket.
462 if (sid
->u
.ssl3
.locked
.sessionTicket
.ticket
.data
) {
463 SECITEM_FreeItem(&sid
->u
.ssl3
.locked
.sessionTicket
.ticket
,
468 PORT_Assert(!sid
->u
.ssl3
.locked
.sessionTicket
.ticket
.data
);
470 /* Do a shallow copy, moving the ticket data. */
471 sid
->u
.ssl3
.locked
.sessionTicket
= *newSessionTicket
;
472 newSessionTicket
->ticket
.data
= NULL
;
473 newSessionTicket
->ticket
.len
= 0;
475 if (sid
->u
.ssl3
.lock
) {
476 NSSRWLock_UnlockWrite(sid
->u
.ssl3
.lock
);