예제 #1
0
    def parse_token(self, token, now=None):
        """Extract the data embedded in the given token, if valid.

        The token is valid if it has a valid signature and if the embedded
        expiry time has not passed.  If the token is not valid then this
        method raises ValueError.
        """
        # Parse the payload and signature from the token.
        try:
            decoded_token = b64decode(token.encode("ascii"))
        except TypeError as e:
            raise ValueError(str(e))  # pragma: nocover
        payload = decoded_token[:-self.hashmod_digest_size]
        sig = decoded_token[-self.hashmod_digest_size:]
        # Carefully check the signature.
        # This is a deliberately slow string-compare to avoid timing attacks.
        # Read the docstring of strings_differ for more details.
        expected_sig = self._get_signature(payload)
        if strings_differ(sig, expected_sig):
            raise ValueError("token has invalid signature")
        # Only decode *after* we've confirmed the signature.
        data = json.loads(payload.decode("utf8"))
        # Check whether it has expired.
        if now is None:
            now = time.time()
        if data["expires"] <= now:
            raise ValueError("token has expired")
        return data
예제 #2
0
    def parse_token(self, token, now=None):
        """Extract the data embedded in the given token, if valid.

        The token is valid if it has a valid signature and if the embedded
        expiry time has not passed.  If the token is not valid then this
        method raises ValueError.
        """
        # Parse the payload and signature from the token.
        try:
            decoded_token = decode_token_bytes(token)
        except (TypeError, ValueError) as e:
            raise errors.MalformedTokenError(str(e))
        payload = decoded_token[:-self.hashmod_digest_size]
        sig = decoded_token[-self.hashmod_digest_size:]
        # Carefully check the signature.
        # This is a deliberately slow string-compare to avoid timing attacks.
        # Read the docstring of strings_differ for more details.
        expected_sig = self._get_signature(payload)
        if strings_differ(sig, expected_sig):
            raise errors.InvalidSignatureError()
        # Only decode *after* we've confirmed the signature.
        # This should never fail, but well, you can't be too careful.
        try:
            data = json.loads(payload.decode("utf8"))
        except ValueError as e:  # pragma: nocover
            raise errors.MalformedTokenError(str(e))
        # Check whether it has expired.
        if now is None:
            now = time.time()
        if data["expires"] <= now:
            raise errors.ExpiredTokenError()
        return data
예제 #3
0
    def parse_token(self, token, now=None):
        """Extract the data embedded in the given token, if valid.

        The token is valid if it has a valid signature and if the embedded
        expiry time has not passed.  If the token is not valid then this
        method raises ValueError.
        """
        # Parse the payload and signature from the token.
        try:
            decoded_token = decode_token_bytes(token)
        except (TypeError, ValueError) as e:
            raise errors.MalformedTokenError(str(e))
        payload = decoded_token[:-self.hashmod_digest_size]
        sig = decoded_token[-self.hashmod_digest_size:]
        # Carefully check the signature.
        # This is a deliberately slow string-compare to avoid timing attacks.
        # Read the docstring of strings_differ for more details.
        expected_sig = self._get_signature(payload)
        if strings_differ(sig, expected_sig):
            raise errors.InvalidSignatureError()
        # Only decode *after* we've confirmed the signature.
        # This should never fail, but well, you can't be too careful.
        try:
            data = json.loads(payload.decode("utf8"))
        except ValueError as e:  # pragma: nocover
            raise errors.MalformedTokenError(str(e))
        # Check whether it has expired.
        if now is None:
            now = time.time()
        if data["expires"] <= now:
            raise errors.ExpiredTokenError()
        return data
예제 #4
0
 def test_strings_differ(self):
     # We can't really test the timing-invariance, but
     # we can test that we actually compute equality!
     self.assertTrue(strings_differ(b"", b"a"))
     self.assertTrue(strings_differ(b"b", b"a"))
     self.assertTrue(strings_differ(b"cc", b"a"))
     self.assertTrue(strings_differ(b"cc", b"aa"))
     self.assertFalse(strings_differ(b"", b""))
     self.assertFalse(strings_differ(b"D", b"D"))
     self.assertFalse(strings_differ(b"EEE", b"EEE"))
 def test_strings_differ(self):
     # We can't really test the timing-invariance, but
     # we can test that we actually compute equality!
     self.assertTrue(strings_differ(b"", b"a"))
     self.assertTrue(strings_differ(b"b", b"a"))
     self.assertTrue(strings_differ(b"cc", b"a"))
     self.assertTrue(strings_differ(b"cc", b"aa"))
     self.assertFalse(strings_differ(b"", b""))
     self.assertFalse(strings_differ(b"D", b"D"))
     self.assertFalse(strings_differ(b"EEE", b"EEE"))
예제 #6
0
class TokenManager(object):
    """Class for managing signed authentication tokens.

    This class provides a generic facility for creating and verifying signed
    authentication tokens.  It's useful as a basis for token-based auth schemes
    such as Two-Legged OAuth or MAC Access Auth, and should provide a good
    balance between speed, memory usage and security for most applications.

    The TokenManager must be initialized with a "master secret" which is used
    to crytographically secure the tokens.  Each token consists of a JSON
    object with an appended HMAC signature.  Tokens also have a corresponding
    "token secret" generated using HKDF, which can be given to clients for
    use in signature-based authentication schemes.

    The constructor takes the following arguments:

       * secret:  string key used for signing the token;
                  if not specified then a random bytestring is used.

       * timeout: the time after which a token will expire.

       * hashmod:  the hashing module to use for various HMAC operations;
                   if not specified then hashlib.sha1 will be used.

    """
    def __init__(self, secret=None, timeout=None, hashmod=None):
        if secret is None:
            secret = DEFAULT_SECRET
        if timeout is None:
            timeout = DEFAULT_TIMEOUT
        if hashmod is None:
            hashmod = DEFAULT_HASHMOD
        if isinstance(hashmod, basestring):
            hashmod = getattr(hashlib, hashmod)
        self.secret = secret
        self.timeout = timeout
        self.hashmod = hashmod
        self.hashmod_digest_size = hashmod().digest_size
        self._sig_secret = HKDF(self.secret,
                                salt=None,
                                info="SIGNING",
                                size=self.hashmod_digest_size)

    def make_token(self, data):
        """Generate a new token embedding the given dict of data.

        The token is a JSON dump of the given data along with an expiry
        time and salt.  It has a HMAC signature appended and is b64-encoded
        for transmission.
        """
        data = data.copy()
        if "salt" not in data:
            data["salt"] = os.urandom(3).encode("hex")
        if "expires" not in data:
            data["expires"] = time.time() + self.timeout
        payload = json.dumps(data)
        sig = self._get_signature(payload)
        assert len(sig) == self.hashmod_digest_size
        return b64encode(payload + sig)

    def parse_token(self, token, now=None):
        """Extract the data embedded in the given token, if valid.

        The token is valid is it has a valid signature and if the embedded
        expiry time has not passed.  If the token is not valid then this
        method raises ValueError.
        """
        # Parse the payload and signature from the token.
        try:
            decoded_token = b64decode(token)
        except TypeError, e:
            raise ValueError(str(e))
        payload = decoded_token[:-self.hashmod_digest_size]
        sig = decoded_token[-self.hashmod_digest_size:]
        # Carefully check the signature.
        # This is a deliberately slow string-compare to avoid timing attacks.
        # Read the docstring of strings_differ for more details.
        expected_sig = self._get_signature(payload)
        if strings_differ(sig, expected_sig):
            raise ValueError("token has invalid signature")
        # Only decode *after* we've confirmed the signature.
        data = json.loads(payload)
        # Check whether it has expired.
        if now is None:
            now = time.time()
        if data["expires"] <= now:
            raise ValueError("token has expired")
        return data