1a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch# Authors: 2a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch# Trevor Perrin 3a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch# Martin von Loewis - python 3 port 4a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch# 5a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch# See the LICENSE file for legal information regarding use of this file. 6a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Class for caching TLS sessions.""" 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 9a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochimport threading 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 12a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochclass SessionCache(object): 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """This class is used by the server to cache TLS sessions. 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Caching sessions allows the client to use TLS session resumption 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) and avoid the expense of a full handshake. To use this class, 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) simply pass a SessionCache instance into the server handshake 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function. 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) This class is thread-safe. 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #References to these instances 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #are also held by the caller, who may change the 'resumable' 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #flag, so the SessionCache must return the same instances 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #it was passed in. 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, maxEntries=10000, maxAge=14400): 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Create a new SessionCache. 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @type maxEntries: int 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @param maxEntries: The maximum size of the cache. When this 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) limit is reached, the oldest sessions will be deleted as 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) necessary to make room for new ones. The default is 10000. 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @type maxAge: int 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @param maxAge: The number of seconds before a session expires 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) from the cache. The default is 14400 (i.e. 4 hours).""" 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 40a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch self.lock = threading.Lock() 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Maps sessionIDs to sessions 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.entriesDict = {} 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #Circular list of (sessionID, timestamp) pairs 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.entriesList = [(None,None)] * maxEntries 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.firstIndex = 0 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lastIndex = 0 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.maxAge = maxAge 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __getitem__(self, sessionID): 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lock.acquire() 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._purge() #Delete old items, so we're assured of a new one 56a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch session = self.entriesDict[bytes(sessionID)] 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #When we add sessions they're resumable, but it's possible 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #for the session to be invalidated later on (if a fatal alert 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #is returned), so we have to check for resumability before 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #returning the session. 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if session.valid(): 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return session 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise KeyError() 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) finally: 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lock.release() 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __setitem__(self, sessionID, session): 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lock.acquire() 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #Add the new element 75a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch self.entriesDict[bytes(sessionID)] = session 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.entriesList[self.lastIndex] = (sessionID, time.time()) 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lastIndex = (self.lastIndex+1) % len(self.entriesList) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #If the cache is full, we delete the oldest element to make an 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #empty space 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.lastIndex == self.firstIndex: 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) del(self.entriesDict[self.entriesList[self.firstIndex][0]]) 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.firstIndex = (self.firstIndex+1) % len(self.entriesList) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) finally: 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.lock.release() 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #Delete expired items 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _purge(self): 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) currentTime = time.time() 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #Search through the circular list, deleting expired elements until 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #we reach a non-expired element. Since elements in list are 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #ordered in time, we can break once we reach the first non-expired 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) #element 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) index = self.firstIndex 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while index != self.lastIndex: 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if currentTime - self.entriesList[index][1] > self.maxAge: 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) del(self.entriesDict[self.entriesList[index][0]]) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) index = (index+1) % len(self.entriesList) 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.firstIndex = index 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _test(): 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) import doctest, SessionCache 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return doctest.testmod(SessionCache) 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == "__main__": 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) _test() 110