def compare_hashes(a, b): """Checks if two hash strings are identical. The intention is to make the running time be less dependant on the size of the string. :param a: String 1. :param b: String 2. :returns: True if both strings are equal, False otherwise. """ if len(a) != len(b): return False result = 0 for x, y in zip(webapp2._to_basestring(a), webapp2._to_basestring(b)): result |= ord(x) ^ ord(y) return result == 0
def decode(value, *args, **kwargs): """Deserializes a value from JSON. This comes from `Tornado`_. :param value: A value to be deserialized. :param args: Extra arguments to be passed to `json.loads()`. :param kwargs: Extra keyword arguments to be passed to `json.loads()`. :returns: The deserialized value. """ return _json.loads(webapp2._to_basestring(value), *args, **kwargs)
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( webapp2._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(webapp2._to_basestring(token_string), webapp2._to_basestring(expected_token_string)): different |= ord(a) ^ ord(b) if different: raise XSRFTokenInvalid()
def test_cookie_unicode(self): import base64 from six.moves.urllib.parse import unquote from six.moves.urllib.parse import quote # With base64 --------------------------------------------------------- value = webapp2._to_basestring(base64.b64encode(u'á'.encode('utf-8'))) rsp = webapp2.Response() rsp.set_cookie('foo', value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), value) self.assertEqual( base64.b64decode(req.cookies.get('foo')).decode('utf-8'), u'á') # Without quote ------------------------------------------------------- value = u'föö=bär; föo, bär, bäz=dïng;' rsp = webapp2.Response() rsp.set_cookie('foo', value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), value) # With quote, hard way ------------------------------------------------ # Here is our test value. x = u'föö' # We must store cookies quoted. To quote unicode, we need to encode it. y = quote(x.encode('utf8')) # The encoded, quoted string looks ugly. self.assertEqual(y, 'f%C3%B6%C3%B6') # But it is easy to get it back to our initial value. z = unquote(y) if not six.PY3: z = z.decode('utf8') # And it is indeed the same value. self.assertEqual(z, x) # Set a cookie using the encoded/quoted value. rsp = webapp2.Response() rsp.set_cookie('foo', y) cookie = rsp.headers.get('Set-Cookie') self.assertEqual(cookie, 'foo=f%C3%B6%C3%B6; Path=/') # Get the cookie back. req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), y) # Here is our original value, again. Problem: the value is decoded # before we had a chance to unquote it. # w = unquote(req.cookies.get('foo').encode('utf8')).decode('utf8') if six.PY2: w = unquote(req.cookies.get('foo').encode('utf8')).decode('utf8') else: w = unquote(req.cookies.get('foo')) # And it is indeed the same value. self.assertEqual(w, x) # With quote, easy way ------------------------------------------------ value = u'föö=bär; föo, bär, bäz=dïng;' quoted_value = quote(value.encode('utf8')) rsp = webapp2.Response() rsp.set_cookie('foo', quoted_value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) cookie_value = req.cookies.get('foo') if six.PY2: unquoted_cookie_value = unquote( cookie_value.encode('utf8')).decode('utf8') else: unquoted_cookie_value = unquote(cookie_value) self.assertEqual(cookie_value, quoted_value) self.assertEqual(unquoted_cookie_value, value)
def test_cookie_unicode(self): import base64 from six.moves.urllib.parse import unquote from six.moves.urllib.parse import quote # With base64 --------------------------------------------------------- value = webapp2._to_basestring(base64.b64encode(u'á'.encode('utf-8'))) rsp = webapp2.Response() rsp.set_cookie('foo', value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), value) self.assertEqual( base64.b64decode(req.cookies.get('foo')).decode('utf-8'), u'á' ) # Without quote ------------------------------------------------------- value = u'föö=bär; föo, bär, bäz=dïng;' rsp = webapp2.Response() rsp.set_cookie('foo', value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), value) # With quote, hard way ------------------------------------------------ # Here is our test value. x = u'föö' # We must store cookies quoted. To quote unicode, we need to encode it. y = quote(x.encode('utf8')) # The encoded, quoted string looks ugly. self.assertEqual(y, 'f%C3%B6%C3%B6') # But it is easy to get it back to our initial value. z = unquote(y) if not six.PY3: z = z.decode('utf8') # And it is indeed the same value. self.assertEqual(z, x) # Set a cookie using the encoded/quoted value. rsp = webapp2.Response() rsp.set_cookie('foo', y) cookie = rsp.headers.get('Set-Cookie') self.assertEqual(cookie, 'foo=f%C3%B6%C3%B6; Path=/') # Get the cookie back. req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) self.assertEqual(req.cookies.get('foo'), y) # Here is our original value, again. Problem: the value is decoded # before we had a chance to unquote it. # w = unquote(req.cookies.get('foo').encode('utf8')).decode('utf8') if six.PY2: w = unquote(req.cookies.get('foo').encode('utf8')).decode('utf8') else: w = unquote(req.cookies.get('foo')) # And it is indeed the same value. self.assertEqual(w, x) # With quote, easy way ------------------------------------------------ value = u'föö=bär; föo, bär, bäz=dïng;' quoted_value = quote(value.encode('utf8')) rsp = webapp2.Response() rsp.set_cookie('foo', quoted_value) cookie = rsp.headers.get('Set-Cookie') req = webapp2.Request.blank('/', headers=[('Cookie', cookie)]) cookie_value = req.cookies.get('foo') if six.PY2: unquoted_cookie_value = unquote( cookie_value.encode('utf8')).decode('utf8') else: unquoted_cookie_value = unquote(cookie_value) self.assertEqual(cookie_value, quoted_value) self.assertEqual(unquoted_cookie_value, value)
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( webapp2._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( webapp2._to_basestring(token_string), webapp2._to_basestring(expected_token_string)): different |= ord(a) ^ ord(b) if different: raise XSRFTokenInvalid()