Remove redundant creation timestamp from fernet tokens
This removes the creation timestamp from the token's payload in favor of extracting the token's creation timestamp from the Fernet token format itself. Change-Id: I170a07adc1fe6418dfaf2c78e1b439339f1c14ed Closes-Bug: 1428717
This commit is contained in:
parent
d87768313b
commit
c83f8920bf
|
@ -114,20 +114,17 @@ class TestScopedTokenFormatter(tests.TestCase, KeyRepositoryTestMixin):
|
|||
exp_project_id = uuid.uuid4().hex
|
||||
# All we are validating here is that the token is encrypted and
|
||||
# decrypted properly, not the actual validity of token data.
|
||||
exp_issued_at = timeutils.isotime(timeutils.utcnow())
|
||||
exp_expires_at = timeutils.isotime(timeutils.utcnow())
|
||||
exp_audit_ids = base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2]
|
||||
|
||||
token = self.formatter.create_token(
|
||||
exp_user_id, exp_project_id, exp_issued_at, exp_expires_at,
|
||||
exp_audit_ids)
|
||||
exp_user_id, exp_project_id, exp_expires_at, exp_audit_ids)
|
||||
|
||||
(user_id, project_id, issued_at, expires_at, audit_ids) = (
|
||||
(user_id, project_id, expires_at, audit_ids) = (
|
||||
self.formatter.validate_token(token[len('F00'):]))
|
||||
|
||||
self.assertEqual(exp_user_id, user_id)
|
||||
self.assertEqual(exp_project_id, project_id)
|
||||
self.assertEqual(exp_issued_at, issued_at)
|
||||
self.assertEqual(exp_expires_at, expires_at)
|
||||
self.assertEqual(exp_audit_ids, audit_ids)
|
||||
|
||||
|
@ -140,28 +137,9 @@ class TestScopedTokenFormatter(tests.TestCase, KeyRepositoryTestMixin):
|
|||
user_id,
|
||||
project_id,
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2])
|
||||
self.assertLess(len(encrypted_token), 255)
|
||||
|
||||
def test_tampered_encrypted_token_throws_exception(self):
|
||||
user_id = uuid.uuid4().hex
|
||||
project_id = uuid.uuid4().hex
|
||||
# All we are validating here is that the token is encrypted and
|
||||
# decrypted properly, not the actual validity of token data.
|
||||
et = self.formatter.create_token(
|
||||
user_id,
|
||||
project_id,
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2])
|
||||
some_id = uuid.uuid4().hex
|
||||
tampered_token = et[:50] + some_id + et[50 + len(some_id):]
|
||||
|
||||
self.assertRaises(exception.Unauthorized,
|
||||
self.formatter.validate_token,
|
||||
tampered_token[4:])
|
||||
|
||||
|
||||
class TestCustomTokenFormatter(TestScopedTokenFormatter):
|
||||
def setUp(self):
|
||||
|
@ -210,25 +188,6 @@ class TestTrustTokenFormatter(tests.TestCase, KeyRepositoryTestMixin):
|
|||
user_id,
|
||||
project_id,
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2],
|
||||
uuid.uuid4().hex)
|
||||
self.assertLess(len(encrypted_token), 255)
|
||||
|
||||
def test_tampered_encrypted_trust_token_throws_exception(self):
|
||||
user_id = uuid.uuid4().hex
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
# Grab an encrypted token
|
||||
et = self.formatter.create_token(
|
||||
user_id, project_id,
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
timeutils.isotime(timeutils.utcnow()),
|
||||
base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2],
|
||||
uuid.uuid4().hex)
|
||||
some_id = uuid.uuid4().hex
|
||||
tampered_token = et[:50] + some_id + et[50 + len(some_id):]
|
||||
|
||||
self.assertRaises(exception.Unauthorized,
|
||||
self.formatter.validate_token,
|
||||
tampered_token[6:])
|
||||
|
|
|
@ -10,8 +10,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import struct
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from keystone.common import dependency
|
||||
from keystone import exception
|
||||
|
@ -25,6 +30,12 @@ CONF = cfg.CONF
|
|||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
# Fernet byte indexes as as computed by pypi/keyless_fernet and defined in
|
||||
# https://github.com/fernet/spec
|
||||
TIMESTAMP_START = 1
|
||||
TIMESTAMP_END = 9
|
||||
|
||||
|
||||
@dependency.requires('trust_api')
|
||||
class Provider(common.BaseProvider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -100,7 +111,6 @@ class Provider(common.BaseProvider):
|
|||
token_id = token_format.create_token(
|
||||
user_id,
|
||||
project_id,
|
||||
token_data['token']['issued_at'],
|
||||
token_data['token']['expires_at'],
|
||||
token_data['token']['audit_ids'],
|
||||
token_data['token']['OS-TRUST:trust']['id'])
|
||||
|
@ -108,7 +118,6 @@ class Provider(common.BaseProvider):
|
|||
token_format = self.token_format_map[fm.UNSCOPED_TOKEN_PREFIX]
|
||||
token_id = token_format.create_token(
|
||||
user_id,
|
||||
token_data['token']['issued_at'],
|
||||
token_data['token']['expires_at'],
|
||||
token_data['token']['audit_ids'])
|
||||
else:
|
||||
|
@ -116,7 +125,6 @@ class Provider(common.BaseProvider):
|
|||
token_id = token_format.create_token(
|
||||
user_id,
|
||||
project_id,
|
||||
token_data['token']['issued_at'],
|
||||
token_data['token']['expires_at'],
|
||||
token_data['token']['audit_ids'])
|
||||
|
||||
|
@ -132,6 +140,24 @@ class Provider(common.BaseProvider):
|
|||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@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
|
||||
token_bytes = base64.urlsafe_b64decode(fernet_token)
|
||||
|
||||
# slice into the byte array to get just the timestamp
|
||||
timestamp_bytes = token_bytes[TIMESTAMP_START:TIMESTAMP_END]
|
||||
|
||||
# convert those bytes to an integer
|
||||
# (it's a 64-bit "unsigned long long int" in C)
|
||||
timestamp_int = struct.unpack(">Q", timestamp_bytes)[0]
|
||||
|
||||
# and with an integer, it's trivial to produce a datetime object
|
||||
created_at = datetime.datetime.utcfromtimestamp(timestamp_int)
|
||||
|
||||
return created_at
|
||||
|
||||
def validate_v3_token(self, token_ref):
|
||||
"""Validate a V3 formatted token.
|
||||
|
||||
|
@ -159,23 +185,27 @@ class Provider(common.BaseProvider):
|
|||
trust_ref = None
|
||||
|
||||
if token_format == fm.UNSCOPED_TOKEN_PREFIX:
|
||||
(user_id, issued_at, expires_at, audit_ids) = (
|
||||
(user_id, expires_at, audit_ids) = (
|
||||
token_formatter.validate_token(token_str))
|
||||
elif token_format == fm.SCOPED_TOKEN_PREFIX:
|
||||
(user_id, project_id, issued_at, expires_at, audit_ids) = (
|
||||
(user_id, project_id, expires_at, audit_ids) = (
|
||||
token_formatter.validate_token(token_str))
|
||||
elif token_format == fm.TRUST_TOKEN_PREFIX:
|
||||
(user_id, project_id, issued_at, expires_at, audit_ids,
|
||||
trust_id) = token_formatter.validate_token(token_str)
|
||||
(user_id, project_id, expires_at, audit_ids, trust_id) = (
|
||||
token_formatter.validate_token(token_str))
|
||||
|
||||
trust_ref = self.trust_api.get_trust(trust_id)
|
||||
|
||||
# rather than appearing in the payload, the creation time is encoded
|
||||
# into the token format itself
|
||||
created_at = Provider._creation_time(token_str)
|
||||
|
||||
return self.v3_token_data_helper.get_token_data(
|
||||
user_id,
|
||||
method_names=['password', 'token'],
|
||||
project_id=project_id,
|
||||
expires=expires_at,
|
||||
issued_at=issued_at,
|
||||
issued_at=timeutils.isotime(created_at),
|
||||
trust=trust_ref,
|
||||
audit_info=audit_ids)
|
||||
|
||||
|
|
|
@ -99,8 +99,8 @@ class BaseTokenFormatter(object):
|
|||
def _convert_int_to_time_string(self, time_int):
|
||||
"""Convert a timestamp integer to a string.
|
||||
|
||||
:param time_int: integer representing time
|
||||
:returns: a time formatted string
|
||||
:param time_int: integer representing timestamp
|
||||
:returns: a time formatted strings
|
||||
|
||||
"""
|
||||
time_object = datetime.datetime.utcfromtimestamp(int(time_int))
|
||||
|
@ -131,20 +131,18 @@ class UnscopedTokenFormatter(BaseTokenFormatter):
|
|||
|
||||
token_format = fm.UNSCOPED_TOKEN_PREFIX
|
||||
|
||||
def create_token(self, user_id, created_at, expires_at, audit_ids):
|
||||
def create_token(self, user_id, expires_at, audit_ids):
|
||||
"""Create a unscoped token.
|
||||
|
||||
:param user_id: identifier of the user in the token request
|
||||
:param created_at: datetime of the token's creation
|
||||
:param expires_at_int: datetime of the token's expiration
|
||||
:param expires_at: datetime of the token's expiration
|
||||
:param audit_ids: list of the token's audit IDs
|
||||
:returns: a string representing the token
|
||||
|
||||
"""
|
||||
b_user_id = self._convert_uuid_hex_to_bytes(user_id)
|
||||
issued_at_int = self._convert_time_string_to_int(created_at)
|
||||
expires_at_int = self._convert_time_string_to_int(expires_at)
|
||||
payload = (b_user_id, issued_at_int, expires_at_int, audit_ids)
|
||||
payload = (b_user_id, expires_at_int, audit_ids)
|
||||
|
||||
return self.pack(payload)
|
||||
|
||||
|
@ -160,44 +158,37 @@ class UnscopedTokenFormatter(BaseTokenFormatter):
|
|||
|
||||
# Rebuild and retrieve token information from the token string
|
||||
b_user_id = payload[0]
|
||||
issued_at_ts = payload[1]
|
||||
expires_at_ts = payload[2]
|
||||
audit_ids = payload[3]
|
||||
expires_at_ts = payload[1]
|
||||
audit_ids = payload[2]
|
||||
|
||||
user_id = self._convert_uuid_bytes_to_hex(b_user_id)
|
||||
|
||||
issued_at_str = self._convert_int_to_time_string(issued_at_ts)
|
||||
expires_at_str = self._convert_int_to_time_string(expires_at_ts)
|
||||
|
||||
return (user_id, issued_at_str, expires_at_str, audit_ids)
|
||||
return (user_id, expires_at_str, audit_ids)
|
||||
|
||||
|
||||
class ScopedTokenFormatter(BaseTokenFormatter):
|
||||
|
||||
token_format = fm.SCOPED_TOKEN_PREFIX
|
||||
|
||||
def create_token(self, user_id, project_id, created_at, expires_at,
|
||||
audit_ids):
|
||||
def create_token(self, user_id, project_id, expires_at, audit_ids):
|
||||
"""Create a standard formatted token.
|
||||
|
||||
:param user_id: ID of the user in the token request
|
||||
:param project_id: ID of the project to scope to
|
||||
:param created_at: datetime of the token's creation
|
||||
:param expires_at: datetime of the token's expiration
|
||||
:param audit_ids: list of the token's audit IDs
|
||||
:returns: a string representing the token
|
||||
|
||||
"""
|
||||
issued_at_int = self._convert_time_string_to_int(created_at)
|
||||
expires_at_int = self._convert_time_string_to_int(expires_at)
|
||||
b_user_id = self._convert_uuid_hex_to_bytes(user_id)
|
||||
if project_id:
|
||||
b_scope_id = self._convert_uuid_hex_to_bytes(project_id)
|
||||
payload = (
|
||||
b_user_id, b_scope_id, issued_at_int, expires_at_int,
|
||||
audit_ids)
|
||||
payload = (b_user_id, b_scope_id, expires_at_int, audit_ids)
|
||||
else:
|
||||
payload = (b_user_id, issued_at_int, expires_at_int, audit_ids)
|
||||
payload = (b_user_id, expires_at_int, audit_ids)
|
||||
|
||||
return self.pack(payload)
|
||||
|
||||
|
@ -216,13 +207,11 @@ class ScopedTokenFormatter(BaseTokenFormatter):
|
|||
b_project_id = None
|
||||
if isinstance(payload[1], str):
|
||||
b_project_id = payload[1]
|
||||
issued_at_ts = payload[2]
|
||||
expires_at_ts = payload[3]
|
||||
audit_ids = payload[4]
|
||||
else:
|
||||
issued_at_ts = payload[1]
|
||||
expires_at_ts = payload[2]
|
||||
audit_ids = payload[3]
|
||||
else:
|
||||
expires_at_ts = payload[1]
|
||||
audit_ids = payload[2]
|
||||
|
||||
# Uncompress the IDs
|
||||
user_id = self._convert_uuid_bytes_to_hex(b_user_id)
|
||||
|
@ -231,36 +220,33 @@ class ScopedTokenFormatter(BaseTokenFormatter):
|
|||
project_id = self._convert_uuid_bytes_to_hex(b_project_id)
|
||||
|
||||
# Generate created at and expires at times
|
||||
issued_at_str = self._convert_int_to_time_string(issued_at_ts)
|
||||
expires_at_str = self._convert_int_to_time_string(expires_at_ts)
|
||||
|
||||
return (user_id, project_id, issued_at_str, expires_at_str, audit_ids)
|
||||
return (user_id, project_id, expires_at_str, audit_ids)
|
||||
|
||||
|
||||
class TrustTokenFormatter(BaseTokenFormatter):
|
||||
|
||||
token_format = fm.TRUST_TOKEN_PREFIX
|
||||
|
||||
def create_token(self, user_id, project_id, created_at, expires_at,
|
||||
audit_ids, trust_id):
|
||||
def create_token(self, user_id, project_id, expires_at, audit_ids,
|
||||
trust_id):
|
||||
"""Create a trust formatted token.
|
||||
|
||||
:param user_id: ID of the user in the token request
|
||||
:param project_id: ID of the project to scope to
|
||||
:param created_at: datetime of the token's creation
|
||||
:param expires_at: datetime of the token's expiration
|
||||
:param audit_ids: list of the token's audit IDs
|
||||
:param trust_id: ID of the trust in effect
|
||||
:returns: a string representing the token
|
||||
|
||||
"""
|
||||
issued_at_int = self._convert_time_string_to_int(created_at)
|
||||
expires_at_int = self._convert_time_string_to_int(expires_at)
|
||||
b_user_id = self._convert_uuid_hex_to_bytes(user_id)
|
||||
b_project_id = self._convert_uuid_hex_to_bytes(project_id)
|
||||
b_trust_id = self._convert_uuid_hex_to_bytes(trust_id)
|
||||
payload = (b_user_id, b_project_id, b_trust_id, issued_at_int,
|
||||
expires_at_int, audit_ids)
|
||||
payload = (b_user_id, b_project_id, b_trust_id, expires_at_int,
|
||||
audit_ids)
|
||||
|
||||
return self.pack(payload)
|
||||
|
||||
|
@ -278,17 +264,14 @@ class TrustTokenFormatter(BaseTokenFormatter):
|
|||
b_user_id = payload[0]
|
||||
b_project_id = payload[1]
|
||||
b_trust_id = payload[2]
|
||||
issued_at_ts = payload[3]
|
||||
expires_at_ts = payload[4]
|
||||
audit_ids = payload[5]
|
||||
expires_at_ts = payload[3]
|
||||
audit_ids = payload[4]
|
||||
|
||||
# Uncompress the IDs
|
||||
user_id = self._convert_uuid_bytes_to_hex(b_user_id)
|
||||
project_id = self._convert_uuid_bytes_to_hex(b_project_id)
|
||||
trust_id = self._convert_uuid_bytes_to_hex(b_trust_id)
|
||||
# Generate created at and expires at times
|
||||
issued_at_str = self._convert_int_to_time_string(issued_at_ts)
|
||||
expires_at_str = self._convert_int_to_time_string(expires_at_ts)
|
||||
|
||||
return (user_id, project_id, issued_at_str, expires_at_str, audit_ids,
|
||||
trust_id)
|
||||
return (user_id, project_id, expires_at_str, audit_ids, trust_id)
|
||||
|
|
Loading…
Reference in New Issue