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
21PRUint32 ssl_sid_timeout = 100;
22PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */
23
24static sslSessionID *cache = NULL;
25static 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
38static 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. */
43static SECStatus
44FreeSessionCacheLocks(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. */
68static PRStatus
69InitSessionCacheLocks(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
94SECStatus
95ssl_InitSessionCacheLocks(void)
96{
97    return (PR_SUCCESS ==
98            PR_CallOnce(&lockOnce, InitSessionCacheLocks)) ?
99           SECSuccess : SECFailure;
100}
101
102static void
103lock_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 */
112static void
113ssl_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 */
173static void
174ssl_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 */
188void
189ssl_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
204sslSessionID *
205ssl_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*/
265static void
266CacheSID(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 */
340static void
341UncacheSID(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 */
386static void
387LockAndUncacheSID(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. */
396void
397ssl_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. */
409void
410SSL_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() */
419PRUint32
420ssl_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
438void
439ssl3_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}
473