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
def test_token_validation(self): manager = tokenlib.TokenManager(timeout=0.2) token = manager.make_token({"hello": "world"}) token_bytes = decode_token_bytes(token) # Proper token == valid. data = manager.parse_token(token) self.assertEquals(data["hello"], "world") # Badly-encoded bytes == not valid. bad_token = "@" + token[1:] with self.assertRaises(errors.MalformedTokenError): manager.parse_token(bad_token) # Bad signature == not valid. bad_token = encode_token_bytes(b"X" * 50) with self.assertRaises(errors.InvalidSignatureError): manager.parse_token(bad_token) bad_token_bytes = token_bytes[:-1] bad_token_bytes += b"X" if token_bytes[-1] == b"Z" else b"Z" bad_token = encode_token_bytes(bad_token_bytes) with self.assertRaises(errors.InvalidSignatureError): manager.parse_token(bad_token) # Modified payload == not valid. bad_token = encode_token_bytes(b"admin" + token_bytes[6:]) with self.assertRaises(errors.InvalidSignatureError): manager.parse_token(bad_token) # Expired token == not valid. time.sleep(0.2) with self.assertRaises(errors.ExpiredTokenError): manager.parse_token(token)
def get_derived_secret(self, token): """Get the derived secret key associated with the given token. A per-token secret key is calculated by deriving it from the master secret with HKDF. """ try: payload = decode_token_bytes(token)[:-self.hashmod_digest_size] salt = json.loads(payload.decode("utf8"))["salt"].encode("ascii") except (TypeError, KeyError, ValueError) as e: raise errors.MalformedTokenError(str(e)) info = HKDF_INFO_DERIVE + token.encode("ascii") secret = HKDF(self.secret, salt=salt, info=info, size=self.hashmod_digest_size, hashmod=self.hashmod) return encode_token_bytes(secret)
def unsafelyParseToken(self, token): # For testing purposes, don't check HMAC or anything... return json.loads(decode_token_bytes(token)[:-32].decode('utf8'))
def unsafelyParseToken(self, token): # For testing purposes, don't check HMAC or anything... token = token.encode("utf8") return json.loads(decode_token_bytes(token)[:-32].decode("utf8"))