From 68a54c2cf1a7210abae9292aede5737203eb733a Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Wed, 18 Mar 2015 03:18:31 +0000 Subject: [PATCH] URL quote Fernet tokens The padding in base64.urlsafe_b64encode()'s output is not actually URL safe, so you have to quote the result when it's of variable length. In addition, Fernet tokens can always be handled as bytes, despite being passed in from json.loads() as Unicode. Change-Id: I72dbd4ddc066706f6af6ea2f2bcd5f0a6cb9b30c Closes-Bug: 1433372 Closes-Bug: 1431669 --- keystone/tests/unit/test_v3_federation.py | 2 +- .../token/providers/fernet/token_formatters.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/keystone/tests/unit/test_v3_federation.py b/keystone/tests/unit/test_v3_federation.py index c15cab59dd..0e20469d86 100644 --- a/keystone/tests/unit/test_v3_federation.py +++ b/keystone/tests/unit/test_v3_federation.py @@ -2402,7 +2402,7 @@ class FernetFederatedTokenTests(FederationTests, FederatedSetupMixin): def test_federated_unscoped_token(self): resp = self._issue_unscoped_token() - self.assertEqual(184, len(resp.headers['X-Subject-Token'])) + self.assertEqual(186, len(resp.headers['X-Subject-Token'])) def test_federated_unscoped_token_with_multiple_groups(self): assertion = 'ANOTHER_CUSTOMER_ASSERTION' diff --git a/keystone/token/providers/fernet/token_formatters.py b/keystone/token/providers/fernet/token_formatters.py index 35fe9e0aa9..30cbc3e004 100644 --- a/keystone/token/providers/fernet/token_formatters.py +++ b/keystone/token/providers/fernet/token_formatters.py @@ -21,6 +21,7 @@ from oslo_config import cfg from oslo_log import log from oslo_utils import timeutils import six +from six.moves import urllib from keystone.auth import plugins as auth_plugins from keystone import exception @@ -64,19 +65,29 @@ class TokenFormatter(object): def pack(self, payload): """Pack a payload for transport as a token.""" - return self.crypto.encrypt(payload) + # base64 padding (if any) is not URL-safe + return urllib.parse.quote(self.crypto.encrypt(payload)) def unpack(self, token): """Unpack a token, and validate the payload.""" + token = urllib.parse.unquote(six.binary_type(token)) + try: - return self.crypto.decrypt(token, ttl=CONF.token.expiration) + return self.crypto.decrypt(token) except fernet.InvalidToken as e: raise exception.Unauthorized(six.text_type(e)) @classmethod def creation_time(cls, fernet_token): """Returns the creation time of a valid Fernet token.""" - # fernet tokens are base64 encoded, so we need to unpack them first + # tokens may be transmitted as Unicode, but they're just ASCII + # (pypi/cryptography will refuse to operate on Unicode input) + fernet_token = six.binary_type(fernet_token) + + # the base64 padding on fernet tokens is made URL-safe + fernet_token = urllib.parse.unquote(fernet_token) + + # fernet tokens are base64 encoded and the padding made URL-safe token_bytes = base64.urlsafe_b64decode(fernet_token) # slice into the byte array to get just the timestamp