Ejemplo n.º 1
0
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
Ejemplo n.º 2
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)
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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()