1ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik# -*- coding: utf-8 -*- 2ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik""" 3ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik webapp2_extras.securecookie 4ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik =========================== 5ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 6ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik A serializer for signed cookies. 7ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 8ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :copyright: 2011 by tipfy.org. 9ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :license: Apache Sotware License, see LICENSE for details. 10ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik""" 11ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport Cookie 12ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport hashlib 13ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport hmac 14ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport logging 15ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikimport time 16ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 17ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webapp2_extras import json 18ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikfrom webapp2_extras import security 19ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 20ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 21ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craikclass SecureCookieSerializer(object): 22ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """Serializes and deserializes secure cookie values. 23ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 24ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik Extracted from `Tornado`_ and modified. 25ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """ 26ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 27ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def __init__(self, secret_key): 28ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """Initiliazes the serializer/deserializer. 29ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 30ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param secret_key: 31ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik A random string to be used as the HMAC secret for the cookie 32ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik signature. 33ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """ 34ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik self.secret_key = secret_key 35ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 36ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def serialize(self, name, value): 37ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """Serializes a signed cookie value. 38ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 39ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param name: 40ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik Cookie name. 41ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param value: 42ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik Cookie value to be serialized. 43ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :returns: 44ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik A serialized value ready to be stored in a cookie. 45ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """ 46ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik timestamp = str(self._get_timestamp()) 47ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik value = self._encode(value) 48ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik signature = self._get_signature(name, value, timestamp) 49ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return '|'.join([value, timestamp, signature]) 50ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 51ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def deserialize(self, name, value, max_age=None): 52ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """Deserializes a signed cookie value. 53ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 54ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param name: 55ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik Cookie name. 56ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param value: 57ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik A cookie value to be deserialized. 58ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :param max_age: 59ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik Maximum age in seconds for a valid cookie. If the cookie is older 60ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik than this, returns None. 61ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik :returns: 62ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik The deserialized secure cookie, or None if it is not valid. 63ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """ 64ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik if not value: 65ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return None 66ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 67ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik # Unquote for old WebOb. 68ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik value = Cookie._unquote(value) 69ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 70ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik parts = value.split('|') 71ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik if len(parts) != 3: 72ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return None 73ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 74ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik signature = self._get_signature(name, parts[0], parts[1]) 75ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 76ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik if not security.compare_hashes(parts[2], signature): 77ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik logging.warning('Invalid cookie signature %r', value) 78ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return None 79ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 80ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik if max_age is not None: 81ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik if int(parts[1]) < self._get_timestamp() - max_age: 82ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik logging.warning('Expired cookie %r', value) 83ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return None 84ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 85ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik try: 86ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return self._decode(parts[0]) 87ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik except Exception, e: 88ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik logging.warning('Cookie value failed to be decoded: %r', parts[0]) 89ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return None 90ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 91ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def _encode(self, value): 92ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return json.b64encode(value) 93ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 94ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def _decode(self, value): 95ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return json.b64decode(value) 96ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 97ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def _get_timestamp(self): 98ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return int(time.time()) 99ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik 100ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik def _get_signature(self, *parts): 101ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik """Generates an HMAC signature.""" 102ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik signature = hmac.new(self.secret_key, digestmod=hashlib.sha1) 103ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik signature.update('|'.join(parts)) 104ced05db70069f9d84c4b0dd9b3b26b94e3482336Chris Craik return signature.hexdigest() 105