Browse Source

Remove padding from Fernet tokens

Fernet tokens were previously percent encoded. This can cause issues with
clients doing their own encoding or not. By removing the padding and then
re-establishing it when we validate the token, we don't present that problem to
clients. This also shortens the length of a Fernet token.

Change-Id: I674bad86ccc9027ac3b365c10b3b142fc9d73c17
Related-Bug: 1433372
Closes-Bug: 1491926
Lance Bragstad 3 years ago
parent
commit
f3e3a653f9

+ 1
- 1
keystone/tests/unit/test_v3_federation.py View File

@@ -2401,7 +2401,7 @@ class FernetFederatedTokenTests(FederationTests, FederatedSetupMixin):
2401 2401
     def test_federated_unscoped_token_with_multiple_groups(self):
2402 2402
         assertion = 'ANOTHER_CUSTOMER_ASSERTION'
2403 2403
         resp = self._issue_unscoped_token(assertion=assertion)
2404
-        self.assertEqual(232, len(resp.headers['X-Subject-Token']))
2404
+        self.assertEqual(226, len(resp.headers['X-Subject-Token']))
2405 2405
         self.assertValidMappedUser(resp.json_body['token'])
2406 2406
 
2407 2407
     def test_validate_federated_unscoped_token(self):

+ 18
- 0
keystone/tests/unit/token/test_fernet_provider.py View File

@@ -10,6 +10,7 @@
10 10
 # License for the specific language governing permissions and limitations
11 11
 # under the License.
12 12
 
13
+import base64
13 14
 import datetime
14 15
 import hashlib
15 16
 import os
@@ -56,6 +57,23 @@ class TestFernetTokenProvider(unit.TestCase):
56 57
             uuid.uuid4().hex)
57 58
 
58 59
 
60
+class TestTokenFormatter(unit.TestCase):
61
+    def test_restore_padding(self):
62
+        # 'a' will result in '==' padding, 'aa' will result in '=' padding, and
63
+        # 'aaa' will result in no padding.
64
+        strings_to_test = ['a', 'aa', 'aaa']
65
+
66
+        for string in strings_to_test:
67
+            encoded_string = base64.urlsafe_b64encode(string)
68
+            encoded_str_without_padding = encoded_string.rstrip('=')
69
+            self.assertFalse(encoded_str_without_padding.endswith('='))
70
+            encoded_str_with_padding_restored = (
71
+                token_formatters.TokenFormatter.restore_padding(
72
+                    encoded_str_without_padding)
73
+            )
74
+            self.assertEqual(encoded_string, encoded_str_with_padding_restored)
75
+
76
+
59 77
 class TestPayloads(unit.TestCase):
60 78
     def test_uuid_hex_to_byte_conversions(self):
61 79
         payload_cls = token_formatters.BasePayload

+ 24
- 6
keystone/token/providers/fernet/token_formatters.py View File

@@ -21,7 +21,7 @@ from oslo_config import cfg
21 21
 from oslo_log import log
22 22
 from oslo_utils import timeutils
23 23
 import six
24
-from six.moves import map, urllib
24
+from six.moves import map
25 25
 
26 26
 from keystone.auth import plugins as auth_plugins
27 27
 from keystone.common import utils as ks_utils
@@ -67,11 +67,14 @@ class TokenFormatter(object):
67 67
     def pack(self, payload):
68 68
         """Pack a payload for transport as a token."""
69 69
         # base64 padding (if any) is not URL-safe
70
-        return urllib.parse.quote(self.crypto.encrypt(payload))
70
+        return self.crypto.encrypt(payload).rstrip('=')
71 71
 
72 72
     def unpack(self, token):
73 73
         """Unpack a token, and validate the payload."""
74
-        token = urllib.parse.unquote(six.binary_type(token))
74
+        token = six.binary_type(token)
75
+
76
+        # Restore padding on token before decoding it
77
+        token = TokenFormatter.restore_padding(token)
75 78
 
76 79
         try:
77 80
             return self.crypto.decrypt(token)
@@ -79,6 +82,21 @@ class TokenFormatter(object):
79 82
             raise exception.ValidationError(
80 83
                 _('This is not a recognized Fernet token'))
81 84
 
85
+    @classmethod
86
+    def restore_padding(cls, token):
87
+        """Restore padding based on token size.
88
+
89
+        :param token: token to restore padding on
90
+        :returns: token with correct padding
91
+
92
+        """
93
+        # Re-inflate the padding
94
+        mod_returned = len(token) % 4
95
+        if mod_returned:
96
+            missing_padding = 4 - mod_returned
97
+            token += b'=' * missing_padding
98
+        return token
99
+
82 100
     @classmethod
83 101
     def creation_time(cls, fernet_token):
84 102
         """Returns the creation time of a valid Fernet token."""
@@ -86,10 +104,10 @@ class TokenFormatter(object):
86 104
         # (pypi/cryptography will refuse to operate on Unicode input)
87 105
         fernet_token = six.binary_type(fernet_token)
88 106
 
89
-        # the base64 padding on fernet tokens is made URL-safe
90
-        fernet_token = urllib.parse.unquote(fernet_token)
107
+        # Restore padding on token before decoding it
108
+        fernet_token = TokenFormatter.restore_padding(fernet_token)
91 109
 
92
-        # fernet tokens are base64 encoded and the padding made URL-safe
110
+        # fernet tokens are base64 encoded, so we need to unpack them first
93 111
         token_bytes = base64.urlsafe_b64decode(fernet_token)
94 112
 
95 113
         # slice into the byte array to get just the timestamp

Loading…
Cancel
Save