1"""Class for caching TLS sessions.""" 2 3import thread 4import time 5 6class SessionCache: 7 """This class is used by the server to cache TLS sessions. 8 9 Caching sessions allows the client to use TLS session resumption 10 and avoid the expense of a full handshake. To use this class, 11 simply pass a SessionCache instance into the server handshake 12 function. 13 14 This class is thread-safe. 15 """ 16 17 #References to these instances 18 #are also held by the caller, who may change the 'resumable' 19 #flag, so the SessionCache must return the same instances 20 #it was passed in. 21 22 def __init__(self, maxEntries=10000, maxAge=14400): 23 """Create a new SessionCache. 24 25 @type maxEntries: int 26 @param maxEntries: The maximum size of the cache. When this 27 limit is reached, the oldest sessions will be deleted as 28 necessary to make room for new ones. The default is 10000. 29 30 @type maxAge: int 31 @param maxAge: The number of seconds before a session expires 32 from the cache. The default is 14400 (i.e. 4 hours).""" 33 34 self.lock = thread.allocate_lock() 35 36 # Maps sessionIDs to sessions 37 self.entriesDict = {} 38 39 #Circular list of (sessionID, timestamp) pairs 40 self.entriesList = [(None,None)] * maxEntries 41 42 self.firstIndex = 0 43 self.lastIndex = 0 44 self.maxAge = maxAge 45 46 def __getitem__(self, sessionID): 47 self.lock.acquire() 48 try: 49 self._purge() #Delete old items, so we're assured of a new one 50 session = self.entriesDict[sessionID] 51 52 #When we add sessions they're resumable, but it's possible 53 #for the session to be invalidated later on (if a fatal alert 54 #is returned), so we have to check for resumability before 55 #returning the session. 56 57 if session.valid(): 58 return session 59 else: 60 raise KeyError() 61 finally: 62 self.lock.release() 63 64 65 def __setitem__(self, sessionID, session): 66 self.lock.acquire() 67 try: 68 #Add the new element 69 self.entriesDict[sessionID] = session 70 self.entriesList[self.lastIndex] = (sessionID, time.time()) 71 self.lastIndex = (self.lastIndex+1) % len(self.entriesList) 72 73 #If the cache is full, we delete the oldest element to make an 74 #empty space 75 if self.lastIndex == self.firstIndex: 76 del(self.entriesDict[self.entriesList[self.firstIndex][0]]) 77 self.firstIndex = (self.firstIndex+1) % len(self.entriesList) 78 finally: 79 self.lock.release() 80 81 #Delete expired items 82 def _purge(self): 83 currentTime = time.time() 84 85 #Search through the circular list, deleting expired elements until 86 #we reach a non-expired element. Since elements in list are 87 #ordered in time, we can break once we reach the first non-expired 88 #element 89 index = self.firstIndex 90 while index != self.lastIndex: 91 if currentTime - self.entriesList[index][1] > self.maxAge: 92 del(self.entriesDict[self.entriesList[index][0]]) 93 index = (index+1) % len(self.entriesList) 94 else: 95 break 96 self.firstIndex = index 97 98def _test(): 99 import doctest, SessionCache 100 return doctest.testmod(SessionCache) 101 102if __name__ == "__main__": 103 _test() 104