From 8dcd82fb9c76d43f26338bee293b32f4af473ad9 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Wed, 9 Sep 2015 14:09:06 +0000 Subject: [PATCH] Ensure token validation works irrespective of padding In change I674bad86ccc9027ac3b365c10b3b142fc9d73c17 we removed the padding from Fernet tokens. This commit makes it so that we can validate Fernet tokens that come in with and without padding. Change-Id: I60e52af6c4cb39f2d136540a487bba8505a2c6b4 Closes-Bug: 1491926 (cherry picked from commit 90aca980587283d26fcfd20ae6be86ca1bbe2788) --- .../tests/unit/token/test_fernet_provider.py | 39 +++++++++++++++++++ .../providers/fernet/token_formatters.py | 16 +++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/keystone/tests/unit/token/test_fernet_provider.py b/keystone/tests/unit/token/test_fernet_provider.py index 5f74b430e1..a909ee276d 100644 --- a/keystone/tests/unit/token/test_fernet_provider.py +++ b/keystone/tests/unit/token/test_fernet_provider.py @@ -16,7 +16,9 @@ import hashlib import os import uuid +import msgpack from oslo_utils import timeutils +from six.moves import urllib from keystone.common import config from keystone.common import utils @@ -58,6 +60,10 @@ class TestFernetTokenProvider(unit.TestCase): class TestTokenFormatter(unit.TestCase): + def setUp(self): + super(TestTokenFormatter, self).setUp() + self.useFixture(ksfixtures.KeyRepository(self.config_fixture)) + def test_restore_padding(self): # 'a' will result in '==' padding, 'aa' will result in '=' padding, and # 'aaa' will result in no padding. @@ -73,6 +79,39 @@ class TestTokenFormatter(unit.TestCase): ) self.assertEqual(encoded_string, encoded_str_with_padding_restored) + def test_legacy_padding_validation(self): + first_value = uuid.uuid4().hex + second_value = uuid.uuid4().hex + payload = (first_value, second_value) + msgpack_payload = msgpack.packb(payload) + + # NOTE(lbragstad): This method perserves the way that keystone used to + # percent encode the tokens, prior to bug #1491926. + def legacy_pack(payload): + tf = token_formatters.TokenFormatter() + encrypted_payload = tf.crypto.encrypt(payload) + + # the encrypted_payload is returned with padding appended + self.assertTrue(encrypted_payload.endswith('=')) + + # using urllib.parse.quote will percent encode the padding, like + # keystone did in Kilo. + percent_encoded_payload = urllib.parse.quote(encrypted_payload) + + # ensure that the padding was actaully percent encoded + self.assertTrue(percent_encoded_payload.endswith('%3D')) + return percent_encoded_payload + + token_with_legacy_padding = legacy_pack(msgpack_payload) + tf = token_formatters.TokenFormatter() + + # demonstrate the we can validate a payload that has been percent + # encoded with the Fernet logic that existed in Kilo + serialized_payload = tf.unpack(token_with_legacy_padding) + returned_payload = msgpack.unpackb(serialized_payload) + self.assertEqual(first_value, returned_payload[0]) + self.assertEqual(second_value, returned_payload[1]) + class TestPayloads(unit.TestCase): def test_uuid_hex_to_byte_conversions(self): diff --git a/keystone/token/providers/fernet/token_formatters.py b/keystone/token/providers/fernet/token_formatters.py index f0b6271d1d..a6b42197f8 100644 --- a/keystone/token/providers/fernet/token_formatters.py +++ b/keystone/token/providers/fernet/token_formatters.py @@ -22,6 +22,7 @@ from oslo_log import log from oslo_utils import timeutils import six from six.moves import map +from six.moves import urllib from keystone.auth import plugins as auth_plugins from keystone.common import utils as ks_utils @@ -73,8 +74,19 @@ class TokenFormatter(object): """Unpack a token, and validate the payload.""" token = six.binary_type(token) - # Restore padding on token before decoding it - token = TokenFormatter.restore_padding(token) + # TODO(lbragstad): Restore padding on token before decoding it. + # Initially in Kilo, Fernet tokens were returned to the user with + # padding appended to the token. Later in Liberty this padding was + # removed and restored in the Fernet provider. The following if + # statement ensures that we can validate tokens with and without token + # padding, in the event of an upgrade and the tokens that are issued + # throughout the upgrade. Remove this if statement when Mitaka opens + # for development and exclusively use the restore_padding() class + # method. + if token.endswith('%3D'): + token = urllib.parse.unquote(token) + else: + token = TokenFormatter.restore_padding(token) try: return self.crypto.decrypt(token)