def serialize(self, name, value): """Serializes a signed cookie value. :param name: Cookie name. :param value: Cookie value to be serialized. :returns: A serialized value ready to be stored in a cookie. """ name = webapp3._to_utf8(name) timestamp = webapp3._to_utf8(str(self._get_timestamp())) value = self._encode(value) signature = self._get_signature(name, value, timestamp) return b'|'.join([value, timestamp, signature])
def __init__(self, secret_key): """Initiliazes the serializer/deserializer. :param secret_key: A random string to be used as the HMAC secret for the cookie signature. """ self.secret_key = webapp3._to_utf8(secret_key)
def deserialize(self, name, value, max_age=None): """Deserializes a signed cookie value. :param name: Cookie name. :param value: A cookie value to be deserialized. :param max_age: Maximum age in seconds for a valid cookie. If the cookie is older than this, returns None. :returns: The deserialized secure cookie, or None if it is not valid. """ if not value: return None name = webapp3._to_utf8(name) value = webapp3._to_utf8(value) # Unquote for old WebOb. value = http_cookies._unquote(value) parts = value.split(b'|') if len(parts) != 3: return None signature = self._get_signature(name, parts[0], parts[1]) if not security.compare_hashes(parts[2], signature): logging.warning('Invalid cookie signature %r', value) return None if max_age is not None: if int(parts[1]) < self._get_timestamp() - max_age: logging.warning('Expired cookie %r', value) return None try: return self._decode(parts[0]) except Exception: logging.warning('Cookie value failed to be decoded: %r', parts[0]) return None
def __init__(self, user_id, secret, current_time=None): """Initializes the XSRFToken object. :param user_id: A string representing the user that the token will be valid for. :param secret: A string containing a secret key that will be used to seed the hash used by the :class:`XSRFToken`. :param current_time: An int representing the number of seconds since the epoch. Will be used by `verify_token_string` to check for token expiry. If `None` then the current time will be used. """ self.user_id = webapp3._to_utf8(user_id) self.secret = webapp3._to_utf8(secret) if current_time is None: self.current_time = int(time.time()) else: self.current_time = int(current_time) self.current_time = webapp3._to_utf8(str(self.current_time))
def hash_password(password, method, salt=None, pepper=None): """Hashes a password. Supports plaintext without salt, unsalted and salted passwords. In case salted passwords are used hmac is used. :param password: The password to be hashed. :param method: A method from ``hashlib``, e.g., `sha1` or `md5`, or `plain`. :param salt: A random salt string. :param pepper: A secret constant stored in the application code. :returns: A hashed password. This function was ported and adapted from `Werkzeug`_. """ if method == 'plain': return password password = webapp3._to_utf8(password) method = getattr(hashlib, method, None) if not method: return None if salt: h = hmac.new(webapp3._to_utf8(salt), password, method) else: h = method(password) if pepper: h = hmac.new(webapp3._to_utf8(pepper), webapp3._to_utf8(h.hexdigest()), method) return h.hexdigest()
def generate_token_string(self, action=None): """Generate a hash of the given token contents that can be verified. :param action: A string representing the action that the generated hash is valid for. This string is usually a URL. :returns: A string containing the hash contents of the given `action` and the contents of the `XSRFToken`. Can be verified with `verify_token_string`. The string is base64 encoded so it is safe to use in HTML forms without escaping. """ digest_maker = self._digest_maker() digest_maker.update(self.user_id) digest_maker.update(self._DELIMITER) if action: digest_maker.update(webapp3._to_utf8(action)) digest_maker.update(self._DELIMITER) digest_maker.update(self.current_time) return base64.urlsafe_b64encode( self._DELIMITER.join([ webapp3._to_utf8(digest_maker.hexdigest()), self.current_time ]))
def verify_token_string(self, token_string, action=None, timeout=None, current_time=None): """Generate a hash of the given token contents that can be verified. :param token_string: A string containing the hashed token (generated by `generate_token_string`). :param action: A string containing the action that is being verified. :param timeout: An int or float representing the number of seconds that the token is valid for. If None then tokens are valid forever. :current_time: An int representing the number of seconds since the epoch. Will be used by to check for token expiry if `timeout` is set. If `None` then the current time will be used. :raises: XSRFTokenMalformed if the given token_string cannot be parsed. XSRFTokenExpiredException if the given token string is expired. XSRFTokenInvalid if the given token string does not match the contents of the `XSRFToken`. """ import binascii try: decoded_token_string = base64.urlsafe_b64decode( webapp3._to_utf8(token_string)) except (TypeError, binascii.Error): raise XSRFTokenMalformed() split_token = decoded_token_string.split(self._DELIMITER) if len(split_token) != 2: raise XSRFTokenMalformed() try: token_time = int(split_token[1]) except ValueError: raise XSRFTokenMalformed() if timeout is not None: if current_time is None: current_time = time.time() # If an attacker modifies the plain text time then # it will not match the hashed time so this check is sufficient. if (token_time + timeout) < current_time: raise XSRFTokenExpiredException() expected_token = XSRFToken(self.user_id, self.secret, token_time) expected_token_string = expected_token.generate_token_string(action) if len(expected_token_string) != len(token_string): raise XSRFTokenInvalid() # Compare the two strings in constant time to prevent timing attacks. different = 0 for a, b in zip(webapp3._to_basestring(token_string), webapp3._to_basestring(expected_token_string)): different |= ord(a) ^ ord(b) if different: raise XSRFTokenInvalid()
def _get_signature(self, *parts): """Generates an HMAC signature.""" signature = hmac.new(self.secret_key, digestmod=hashlib.sha1) signature.update(b'|'.join(parts)) return webapp3._to_utf8(signature.hexdigest())