[python-keystoneclient/f19] Selective backports from stable/grizzly.

Jakub Ruzicka jruzicka at fedoraproject.org
Thu Jul 25 17:45:44 UTC 2013


commit 00f8bb09e759f2c31dc0345a5be04dab626d7f3b
Author: Jakub Ruzicka <jruzicka at redhat.com>
Date:   Thu Jul 25 19:35:51 2013 +0200

    Selective backports from stable/grizzly.
    
    Changelog:
    - Ec2Signer: Initial support for v4 signature verification.
    - Default signing_dir to secure temp dir.
    - Fix memcache encryption middleware. (CVE-2013-2166, CVE-2013-2167)

 ...nitial-support-for-v4-signature-verificat.patch |  341 +++++++++
 ...igning_dir-to-secure-temp-dir-bug-1181157.patch |   44 ++
 0006-Fix-memcache-encryption-middleware.patch      |  742 ++++++++++++++++++++
 python-keystoneclient.spec                         |   13 +-
 4 files changed, 1139 insertions(+), 1 deletions(-)
---
diff --git a/0004-Ec2Signer-Initial-support-for-v4-signature-verificat.patch b/0004-Ec2Signer-Initial-support-for-v4-signature-verificat.patch
new file mode 100644
index 0000000..c1ca0bb
--- /dev/null
+++ b/0004-Ec2Signer-Initial-support-for-v4-signature-verificat.patch
@@ -0,0 +1,341 @@
+From 34d9cd1795002f4e26d5c603e3dc1a08d02dfa1b Mon Sep 17 00:00:00 2001
+From: Steven Hardy <shardy at redhat.com>
+Date: Wed, 3 Apr 2013 17:14:30 +0100
+Subject: [PATCH] Ec2Signer: Initial support for v4 signature verification
+
+Adds initial support for verifying AWS v4 signatures, tested with
+the latest boto trunk (which now uses v4 signatures by default)
+
+Change-Id: Id163363e259cf08aa251a7a00ff4293b742cbef6
+blueprint: ec2signer-v4signatures
+(cherry picked from commit 5c37d85944d9eed73ec6dd6254842108386bcc4f)
+---
+ keystoneclient/contrib/ec2/utils.py | 196 ++++++++++++++++++++++++++++++++----
+ tests/test_ec2utils.py              |  72 ++++++++++++-
+ 2 files changed, 249 insertions(+), 19 deletions(-)
+
+diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py
+index fcd8ee3..ca5afa7 100644
+--- a/keystoneclient/contrib/ec2/utils.py
++++ b/keystoneclient/contrib/ec2/utils.py
+@@ -32,24 +32,69 @@ class Ec2Signer(object):
+     """
+ 
+     def __init__(self, secret_key):
+-        secret_key = secret_key.encode()
+-        self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
++        self.secret_key = secret_key.encode()
++        self.hmac = hmac.new(self.secret_key, digestmod=hashlib.sha1)
+         if hashlib.sha256:
+-            self.hmac_256 = hmac.new(secret_key, digestmod=hashlib.sha256)
++            self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256)
++
++    def _v4_creds(self, credentials):
++        """
++        Detect if the credentials are for a v4 signed request, since AWS
++        removed the SignatureVersion field from the v4 request spec...
++        This expects a dict of the request headers to be passed in the
++        credentials dict, since the recommended way to pass v4 creds is
++        via the 'Authorization' header
++        see http://docs.aws.amazon.com/general/latest/gr/
++            sigv4-signed-request-examples.html
++
++        Alternatively X-Amz-Algorithm can be specified as a query parameter,
++        and the authentication data can also passed as query parameters.
++
++        Note a hash of the request body is also required in the credentials
++        for v4 auth to work in the body_hash key, calculated via:
++        hashlib.sha256(req.body).hexdigest()
++        """
++        try:
++            auth_str = credentials['headers']['Authorization']
++            if auth_str.startswith('AWS4-HMAC-SHA256'):
++                return True
++        except KeyError:
++            # Alternatively the Authorization data can be passed via
++            # the query params list, check X-Amz-Algorithm=AWS4-HMAC-SHA256
++            try:
++                if (credentials['params']['X-Amz-Algorithm'] ==
++                    'AWS4-HMAC-SHA256'):
++                    return True
++            except KeyError:
++                pass
++
++        return False
+ 
+     def generate(self, credentials):
+         """Generate auth string according to what SignatureVersion is given."""
+-        if credentials['params']['SignatureVersion'] == '0':
++        signature_version = credentials['params'].get('SignatureVersion')
++        if signature_version == '0':
+             return self._calc_signature_0(credentials['params'])
+-        if credentials['params']['SignatureVersion'] == '1':
++        if signature_version == '1':
+             return self._calc_signature_1(credentials['params'])
+-        if credentials['params']['SignatureVersion'] == '2':
++        if signature_version == '2':
+             return self._calc_signature_2(credentials['params'],
+                                           credentials['verb'],
+                                           credentials['host'],
+                                           credentials['path'])
+-        raise Exception('Unknown Signature Version: %s' %
+-                        credentials['params']['SignatureVersion'])
++        if self._v4_creds(credentials):
++            return self._calc_signature_4(credentials['params'],
++                                          credentials['verb'],
++                                          credentials['host'],
++                                          credentials['path'],
++                                          credentials['headers'],
++                                          credentials['body_hash'])
++
++        if signature_version is not None:
++            raise Exception('Unknown signature version: %s' %
++                            signature_version)
++        else:
++            raise Exception('Unexpected signature format')
+ 
+     @staticmethod
+     def _get_utf8_value(value):
+@@ -77,6 +122,22 @@ class Ec2Signer(object):
+             self.hmac.update(val)
+         return base64.b64encode(self.hmac.digest())
+ 
++    @staticmethod
++    def _canonical_qs(params):
++        """
++        Construct a sorted, correctly encoded query string as required for
++        _calc_signature_2 and _calc_signature_4
++        """
++        keys = params.keys()
++        keys.sort()
++        pairs = []
++        for key in keys:
++            val = Ec2Signer._get_utf8_value(params[key])
++            val = urllib.quote(val, safe='-_~')
++            pairs.append(urllib.quote(key, safe='') + '=' + val)
++        qs = '&'.join(pairs)
++        return qs
++
+     def _calc_signature_2(self, params, verb, server_string, path):
+         """Generate AWS signature version 2 string."""
+         string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
+@@ -86,15 +147,116 @@ class Ec2Signer(object):
+         else:
+             current_hmac = self.hmac
+             params['SignatureMethod'] = 'HmacSHA1'
+-        keys = params.keys()
+-        keys.sort()
+-        pairs = []
+-        for key in keys:
+-            val = self._get_utf8_value(params[key])
+-            val = urllib.quote(val, safe='-_~')
+-            pairs.append(urllib.quote(key, safe='') + '=' + val)
+-        qs = '&'.join(pairs)
+-        string_to_sign += qs
++        string_to_sign += self._canonical_qs(params)
+         current_hmac.update(string_to_sign)
+         b64 = base64.b64encode(current_hmac.digest())
+         return b64
++
++    def _calc_signature_4(self, params, verb, server_string, path, headers,
++                          body_hash):
++        """Generate AWS signature version 4 string."""
++
++        def sign(key, msg):
++            return hmac.new(key, self._get_utf8_value(msg),
++                            hashlib.sha256).digest()
++
++        def signature_key(datestamp, region_name, service_name):
++            """
++            Signature key derivation, see
++            http://docs.aws.amazon.com/general/latest/gr/
++            signature-v4-examples.html#signature-v4-examples-python
++            """
++            k_date = sign(self._get_utf8_value("AWS4" + self.secret_key),
++                          datestamp)
++            k_region = sign(k_date, region_name)
++            k_service = sign(k_region, service_name)
++            k_signing = sign(k_service, "aws4_request")
++            return k_signing
++
++        def auth_param(param_name):
++            """
++            Get specified auth parameter, provided via one of:
++            - the Authorization header
++            - the X-Amz-* query parameters
++            """
++            try:
++                auth_str = headers['Authorization']
++                param_str = auth_str.partition(
++                                '%s=' % param_name)[2].split(',')[0]
++            except KeyError:
++                param_str = params.get('X-Amz-%s' % param_name)
++            return param_str
++
++        def date_param():
++            """
++            Get the X-Amz-Date' value, which can be either a header or paramter
++
++            Note AWS supports parsing the Date header also, but this is not
++            currently supported here as it will require some format mangling
++            So the X-Amz-Date value must be YYYYMMDDTHHMMSSZ format, then it
++            can be used to match against the YYYYMMDD format provided in the
++            credential scope.
++            see:
++            http://docs.aws.amazon.com/general/latest/gr/
++            sigv4-date-handling.html
++            """
++            try:
++                return headers['X-Amz-Date']
++            except KeyError:
++                return params.get('X-Amz-Date')
++
++        def canonical_header_str():
++            # Get the list of headers to include, from either
++            # - the Authorization header (SignedHeaders key)
++            # - the X-Amz-SignedHeaders query parameter
++            headers_lower = dict((k.lower().strip(), v.strip())
++                                 for (k, v) in headers.iteritems())
++            header_list = []
++            sh_str = auth_param('SignedHeaders')
++            for h in sh_str.split(';'):
++                if h not in headers_lower:
++                    continue
++                if h == 'host':
++                    # Note we discard any port suffix
++                    header_list.append('%s:%s' %
++                                       (h, headers_lower[h].split(':')[0]))
++                else:
++                    header_list.append('%s:%s' % (h, headers_lower[h]))
++            return '\n'.join(header_list) + '\n'
++
++        # Create canonical request:
++        # http://docs.aws.amazon.com/general/latest/gr/
++        # sigv4-create-canonical-request.html
++        # Get parameters and headers in expected string format
++        cr = "\n".join((verb.upper(), path,
++                        self._canonical_qs(params),
++                        canonical_header_str(),
++                        auth_param('SignedHeaders'),
++                        body_hash))
++
++        # Check the date, reject any request where the X-Amz-Date doesn't
++        # match the credential scope
++        credential = auth_param('Credential')
++        credential_split = credential.split('/')
++        credential_scope = '/'.join(credential_split[1:])
++        credential_date = credential_split[1]
++        param_date = date_param()
++        if not param_date.startswith(credential_date):
++            raise Exception('Request date mismatch error')
++
++        # Create the string to sign
++        # http://docs.aws.amazon.com/general/latest/gr/
++        # sigv4-create-string-to-sign.html
++        string_to_sign = '\n'.join(('AWS4-HMAC-SHA256',
++                                    param_date,
++                                    credential_scope,
++                                    hashlib.sha256(cr).hexdigest()))
++
++        # Calculate the derived key, this requires a datestamp, region
++        # and service, which can be extracted from the credential scope
++        (req_region, req_service) = credential_split[2:4]
++        s_key = signature_key(credential_date, req_region, req_service)
++        # Finally calculate the signature!
++        signature = hmac.new(s_key, self._get_utf8_value(string_to_sign),
++                             hashlib.sha256).hexdigest()
++        return signature
+diff --git a/tests/test_ec2utils.py b/tests/test_ec2utils.py
+index b0bd4df..a3c36fa 100644
+--- a/tests/test_ec2utils.py
++++ b/tests/test_ec2utils.py
+@@ -27,6 +27,36 @@ class Ec2SignerTest(testtools.TestCase):
+         self.secret = '89cdf9e94e2643cab35b8b8ac5a51f83'
+         self.signer = Ec2Signer(self.secret)
+ 
++    def tearDown(self):
++        super(Ec2SignerTest, self).tearDown()
++
++    def test_v4_creds_header(self):
++        auth_str = 'AWS4-HMAC-SHA256 blah'
++        credentials = {'host': '127.0.0.1',
++                       'verb': 'GET',
++                       'path': '/v1/',
++                       'params': {},
++                       'headers': {'Authorization': auth_str}}
++        self.assertTrue(self.signer._v4_creds(credentials))
++
++    def test_v4_creds_param(self):
++        credentials = {'host': '127.0.0.1',
++                       'verb': 'GET',
++                       'path': '/v1/',
++                       'params': {'X-Amz-Algorithm': 'AWS4-HMAC-SHA256'},
++                       'headers': {}}
++        self.assertTrue(self.signer._v4_creds(credentials))
++
++    def test_v4_creds_false(self):
++        credentials = {'host': '127.0.0.1',
++                       'verb': 'GET',
++                       'path': '/v1/',
++                       'params': {'SignatureVersion': '0',
++                                  'AWSAccessKeyId': self.access,
++                                  'Timestamp': '2012-11-27T11:47:02Z',
++                                  'Action': 'Foo'}}
++        self.assertFalse(self.signer._v4_creds(credentials))
++
+     def test_generate_0(self):
+         """Test generate function for v0 signature"""
+         credentials = {'host': '127.0.0.1',
+@@ -40,8 +70,6 @@ class Ec2SignerTest(testtools.TestCase):
+         expected = 'SmXQEZAUdQw5glv5mX8mmixBtas='
+         self.assertEqual(signature, expected)
+ 
+-        pass
+-
+     def test_generate_1(self):
+         """Test generate function for v1 signature"""
+         credentials = {'host': '127.0.0.1',
+@@ -75,3 +103,43 @@ class Ec2SignerTest(testtools.TestCase):
+         signature = self.signer.generate(credentials)
+         expected = 'ZqCxMI4ZtTXWI175743mJ0hy/Gc='
+         self.assertEqual(signature, expected)
++
++    def test_generate_v4(self):
++        """
++        Test v4 generator with data from AWS docs example, see:
++        http://docs.aws.amazon.com/general/latest/gr/
++        sigv4-create-canonical-request.html
++        and
++        http://docs.aws.amazon.com/general/latest/gr/
++        sigv4-signed-request-examples.html
++        """
++        # Create a new signer object with the AWS example key
++        secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
++        signer = Ec2Signer(secret)
++
++        body_hash = ('b6359072c78d70ebee1e81adcbab4f0'
++                     '1bf2c23245fa365ef83fe8f1f955085e2')
++        auth_str = ('AWS4-HMAC-SHA256 '
++                    'Credential=AKIAIOSFODNN7EXAMPLE/20110909/'
++                    'us-east-1/iam/aws4_request,'
++                    'SignedHeaders=content-type;host;x-amz-date,')
++        headers = {'Content-type':
++                   'application/x-www-form-urlencoded; charset=utf-8',
++                   'X-Amz-Date': '20110909T233600Z',
++                   'Host': 'iam.amazonaws.com',
++                   'Authorization': auth_str}
++        # Note the example in the AWS docs is inconsistent, previous
++        # examples specify no query string, but the final POST example
++        # does, apparently incorrectly since an empty parameter list
++        # aligns all steps and the final signature with the examples
++        params = {}
++        credentials = {'host': 'iam.amazonaws.com',
++                       'verb': 'POST',
++                       'path': '/',
++                       'params': params,
++                       'headers': headers,
++                       'body_hash': body_hash}
++        signature = signer.generate(credentials)
++        expected = ('ced6826de92d2bdeed8f846f0bf508e8'
++                    '559e98e4b0199114b84c54174deb456c')
++        self.assertEqual(signature, expected)
diff --git a/0005-Default-signing_dir-to-secure-temp-dir-bug-1181157.patch b/0005-Default-signing_dir-to-secure-temp-dir-bug-1181157.patch
new file mode 100644
index 0000000..31deef6
--- /dev/null
+++ b/0005-Default-signing_dir-to-secure-temp-dir-bug-1181157.patch
@@ -0,0 +1,44 @@
+From c9225f29e9652b9f642a7f5fe4a48b1c5a0cd2f7 Mon Sep 17 00:00:00 2001
+From: Dolph Mathews <dolph.mathews at gmail.com>
+Date: Fri, 17 May 2013 10:38:25 -0500
+Subject: [PATCH] Default signing_dir to secure temp dir (bug 1181157)
+
+Change-Id: I1a29f50b07a60de3d0519bf40074dbea92fa8656
+---
+ keystoneclient/middleware/auth_token.py | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py
+index 985ee6f..71b4d15 100644
+--- a/keystoneclient/middleware/auth_token.py
++++ b/keystoneclient/middleware/auth_token.py
+@@ -150,6 +150,7 @@ import json
+ import logging
+ import os
+ import stat
++import tempfile
+ import time
+ import urllib
+ import webob.exc
+@@ -211,8 +212,7 @@ opts = [
+     cfg.StrOpt('cache', default=None),   # env key for the swift cache
+     cfg.StrOpt('certfile'),
+     cfg.StrOpt('keyfile'),
+-    cfg.StrOpt('signing_dir',
+-               default=os.path.expanduser('~/keystone-signing')),
++    cfg.StrOpt('signing_dir'),
+     cfg.ListOpt('memcache_servers'),
+     cfg.IntOpt('token_cache_time', default=300),
+     cfg.IntOpt('revocation_cache_time', default=1),
+@@ -292,8 +292,10 @@ class AuthProtocol(object):
+         self.cert_file = self._conf_get('certfile')
+         self.key_file = self._conf_get('keyfile')
+ 
+-        #signing
++        # signing
+         self.signing_dirname = self._conf_get('signing_dir')
++        if self.signing_dirname is None:
++            self.signing_dirname = tempfile.mkdtemp(prefix='keystone-signing-')
+         self.LOG.info('Using %s as cache directory for signing certificate' %
+                       self.signing_dirname)
+         if (os.path.exists(self.signing_dirname) and
diff --git a/0006-Fix-memcache-encryption-middleware.patch b/0006-Fix-memcache-encryption-middleware.patch
new file mode 100644
index 0000000..15afdf3
--- /dev/null
+++ b/0006-Fix-memcache-encryption-middleware.patch
@@ -0,0 +1,742 @@
+From 7d6a87efdc04f1eaaca88db6b87b5961e20154f8 Mon Sep 17 00:00:00 2001
+From: "Bryan D. Payne" <bdpayne at acm.org>
+Date: Fri, 7 Jun 2013 09:34:25 -0700
+Subject: [PATCH] Fix memcache encryption middleware
+
+This fixes lp1175367 and lp1175368 by redesigning the memcache crypt
+middleware to not do dangerous things. It is forward compatible, but
+will invalidate any existing ephemeral encrypted or signed memcache
+entries.
+
+Change-Id: Ice8724949a48bfad3b8b7c41b5f50a18a9ad9f42
+Signed-off-by: Bryan D. Payne <bdpayne at acm.org>
+---
+ doc/source/middlewarearchitecture.rst       |  37 +++---
+ keystoneclient/middleware/auth_token.py     | 131 +++++++++---------
+ keystoneclient/middleware/memcache_crypt.py | 197 +++++++++++++++++-----------
+ tests/test_auth_token_middleware.py         |  89 +++----------
+ tests/test_memcache_crypt.py                |  96 ++++++++------
+ 5 files changed, 277 insertions(+), 273 deletions(-)
+
+diff --git a/doc/source/middlewarearchitecture.rst b/doc/source/middlewarearchitecture.rst
+index 803fbd9..894d40d 100644
+--- a/doc/source/middlewarearchitecture.rst
++++ b/doc/source/middlewarearchitecture.rst
+@@ -1,5 +1,5 @@
+ ..
+-      Copyright 2011-2012 OpenStack, LLC
++      Copyright 2011-2013 OpenStack, LLC
+       All Rights Reserved.
+ 
+       Licensed under the Apache License, Version 2.0 (the "License"); you may
+@@ -188,7 +188,8 @@ Configuration Options
+   the timeout when validating token by http).
+ * ``auth_port``: (optional, default `35357`) the port used to validate tokens
+ * ``auth_protocol``: (optional, default `https`)
+-* ``auth_uri``: (optional, defaults to `auth_protocol`://`auth_host`:`auth_port`)
++* ``auth_uri``: (optional, defaults to
++  `auth_protocol`://`auth_host`:`auth_port`)
+ * ``certfile``: (required, if Keystone server requires client cert)
+ * ``keyfile``: (required, if Keystone server requires client cert)  This can be
+   the same as the certfile if the certfile includes the private key.
+@@ -232,22 +233,24 @@ Memcache Protection
+ ===================
+ 
+ When using memcached, we are storing user tokens and token validation
+-information into the cache as raw data. Which means anyone who have access
+-to the memcache servers can read and modify data stored there. To mitigate
+-this risk, ``auth_token`` middleware provides an option to either encrypt
+-or authenticate the token data stored in the cache.
+-
+-* ``memcache_security_strategy``: (optional) if defined, indicate whether token
+-  data should be encrypted or authenticated. Acceptable values are ``ENCRYPT``
+-  or ``MAC``. If ``ENCRYPT``, token data is encrypted in the cache. If
+-  ``MAC``, token data is authenticated (with HMAC) in the cache. If its value
+-  is neither ``MAC`` nor ``ENCRYPT``, ``auth_token`` will raise an exception
+-  on initialization.
++information into the cache as raw data. Which means that anyone who
++has access to the memcache servers can read and modify data stored
++there. To mitigate this risk, ``auth_token`` middleware provides an
++option to authenticate and optionally encrypt the token data stored in
++the cache.
++
++* ``memcache_security_strategy``: (optional) if defined, indicate
++  whether token data should be authenticated or authenticated and
++  encrypted. Acceptable values are ``MAC`` or ``ENCRYPT``. If ``MAC``,
++  token data is authenticated (with HMAC) in the cache. If
++  ``ENCRYPT``, token data is encrypted and authenticated in the
++  cache. If the value is not one of these options or empty,
++  ``auth_token`` will raise an exception on initialization.
+ * ``memcache_secret_key``: (optional, mandatory if
+-  ``memcache_security_strategy`` is defined) if defined,
+-  a random string to be used for key derivation. If
+-  ``memcache_security_strategy`` is defined and ``memcache_secret_key`` is
+-  absent, ``auth_token`` will raise an exception on initialization.
++  ``memcache_security_strategy`` is defined) this string is used for
++  key derivation. If ``memcache_security_strategy`` is defined and
++  ``memcache_secret_key`` is absent, ``auth_token`` will raise an
++  exception on initialization.
+ 
+ Exchanging User Information
+ ===========================
+diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py
+index 71b4d15..b245640 100644
+--- a/keystoneclient/middleware/auth_token.py
++++ b/keystoneclient/middleware/auth_token.py
+@@ -222,6 +222,7 @@ opts = [
+ CONF.register_opts(opts, group='keystone_authtoken')
+ 
+ LIST_OF_VERSIONS_TO_ATTEMPT = ['v2.0', 'v3.0']
++CACHE_KEY_TEMPLATE = 'tokens/%s'
+ 
+ 
+ def will_expire_soon(expiry):
+@@ -839,91 +840,81 @@ class AuthProtocol(object):
+         env_key = self._header_to_env_var(key)
+         return env.get(env_key, default)
+ 
+-    def _protect_cache_value(self, token, data):
+-        """ Encrypt or sign data if necessary. """
+-        try:
+-            if self._memcache_security_strategy == 'ENCRYPT':
+-                return memcache_crypt.encrypt_data(token,
+-                                                   self._memcache_secret_key,
+-                                                   data)
+-            elif self._memcache_security_strategy == 'MAC':
+-                return memcache_crypt.sign_data(token, data)
+-            else:
+-                return data
+-        except:
+-            msg = 'Failed to encrypt/sign cache data.'
+-            self.LOG.exception(msg)
+-            return data
+-
+-    def _unprotect_cache_value(self, token, data):
+-        """ Decrypt or verify signed data if necessary. """
+-        if data is None:
+-            return data
+-
+-        try:
+-            if self._memcache_security_strategy == 'ENCRYPT':
+-                return memcache_crypt.decrypt_data(token,
+-                                                   self._memcache_secret_key,
+-                                                   data)
+-            elif self._memcache_security_strategy == 'MAC':
+-                return memcache_crypt.verify_signed_data(token, data)
+-            else:
+-                return data
+-        except:
+-            msg = 'Failed to decrypt/verify cache data.'
+-            self.LOG.exception(msg)
+-            # this should have the same effect as data not found in cache
+-            return None
+-
+-    def _get_cache_key(self, token):
+-        """ Return the cache key.
+-
+-        Do not use clear token as key if memcache protection is on.
+-
+-        """
+-        htoken = token
+-        if self._memcache_security_strategy in ('ENCRYPT', 'MAC'):
+-            derv_token = token + self._memcache_secret_key
+-            htoken = memcache_crypt.hash_data(derv_token)
+-        return 'tokens/%s' % htoken
+-
+-    def _cache_get(self, token):
++    def _cache_get(self, token, ignore_expires=False):
+         """Return token information from cache.
+ 
+         If token is invalid raise InvalidUserToken
+         return token only if fresh (not expired).
+         """
++
+         if self._cache and token:
+-            key = self._get_cache_key(token)
+-            cached = self._cache.get(key)
+-            cached = self._unprotect_cache_value(token, cached)
++            if self._memcache_security_strategy is None:
++                key = CACHE_KEY_TEMPLATE % token
++                serialized = self._cache.get(key)
++            else:
++                keys = memcache_crypt.derive_keys(
++                    token,
++                    self._memcache_secret_key,
++                    self._memcache_security_strategy)
++                cache_key = CACHE_KEY_TEMPLATE % (
++                    memcache_crypt.get_cache_key(keys))
++                raw_cached = self._cache.get(cache_key)
++                try:
++                    # unprotect_data will return None if raw_cached is None
++                    serialized = memcache_crypt.unprotect_data(keys,
++                                                               raw_cached)
++                except Exception:
++                    msg = 'Failed to decrypt/verify cache data'
++                    self.LOG.exception(msg)
++                    # this should have the same effect as data not
++                    # found in cache
++                    serialized = None
++
++            if serialized is None:
++                return None
++
++            # Note that 'invalid' and (data, expires) are the only
++            # valid types of serialized cache entries, so there is not
++            # a collision with json.loads(serialized) == None.
++            cached = json.loads(serialized)
+             if cached == 'invalid':
+                 self.LOG.debug('Cached Token %s is marked unauthorized', token)
+                 raise InvalidUserToken('Token authorization failed')
+-            if cached:
+-                data, expires = cached
+-                if time.time() < float(expires):
+-                    self.LOG.debug('Returning cached token %s', token)
+-                    return data
+-                else:
+-                    self.LOG.debug('Cached Token %s seems expired', token)
+-
+-    def _cache_store(self, token, data, expires=None):
+-        """ Store value into memcache. """
+-        key = self._get_cache_key(token)
+-        data = self._protect_cache_value(token, data)
+-        data_to_store = data
+-        if expires:
+-            data_to_store = (data, expires)
++
++            data, expires = cached
++            if ignore_expires or time.time() < float(expires):
++                self.LOG.debug('Returning cached token %s', token)
++                return data
++            else:
++                self.LOG.debug('Cached Token %s seems expired', token)
++
++    def _cache_store(self, token, data):
++        """ Store value into memcache.
++
++        data may be the string 'invalid' or a tuple like (data, expires)
++
++        """
++        serialized_data = json.dumps(data)
++        if self._memcache_security_strategy is None:
++            cache_key = CACHE_KEY_TEMPLATE % token
++            data_to_store = serialized_data
++        else:
++            keys = memcache_crypt.derive_keys(
++                token,
++                self._memcache_secret_key,
++                self._memcache_security_strategy)
++            cache_key = CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(keys)
++            data_to_store = memcache_crypt.protect_data(keys, serialized_data)
++
+         # we need to special-case set() because of the incompatibility between
+         # Swift MemcacheRing and python-memcached. See
+         # https://bugs.launchpad.net/swift/+bug/1095730
+         if self._use_keystone_cache:
+-            self._cache.set(key,
++            self._cache.set(cache_key,
+                             data_to_store,
+                             time=self.token_cache_time)
+         else:
+-            self._cache.set(key,
++            self._cache.set(cache_key,
+                             data_to_store,
+                             timeout=self.token_cache_time)
+ 
+@@ -951,7 +942,7 @@ class AuthProtocol(object):
+         """
+         if self._cache:
+                 self.LOG.debug('Storing %s token in memcache', token)
+-                self._cache_store(token, data, expires)
++                self._cache_store(token, (data, expires))
+ 
+     def _cache_store_invalid(self, token):
+         """Store invalid token in cache."""
+diff --git a/keystoneclient/middleware/memcache_crypt.py b/keystoneclient/middleware/memcache_crypt.py
+index 91e261d..6cadf3a 100755
+--- a/keystoneclient/middleware/memcache_crypt.py
++++ b/keystoneclient/middleware/memcache_crypt.py
+@@ -1,6 +1,6 @@
+ # vim: tabstop=4 shiftwidth=4 softtabstop=4
+ 
+-# Copyright 2010-2012 OpenStack LLC
++# Copyright 2010-2013 OpenStack LLC
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License");
+ # you may not use this file except in compliance with the License.
+@@ -18,33 +18,34 @@
+ """
+ Utilities for memcache encryption and integrity check.
+ 
+-Data is serialized before been encrypted or MACed. Encryption have a
+-dependency on the pycrypto. If pycrypto is not available,
+-CryptoUnabailableError will be raised.
++Data should be serialized before entering these functions. Encryption
++has a dependency on the pycrypto. If pycrypto is not available,
++CryptoUnavailableError will be raised.
+ 
+-Encrypted data stored in memcache are prefixed with '{ENCRYPT:AES256}'.
+-
+-MACed data stored in memcache are prefixed with '{MAC:SHA1}'.
++This module will not be called unless signing or encryption is enabled
++in the config. It will always validate signatures, and will decrypt
++data if encryption is enabled. It is not valid to mix protection
++modes.
+ 
+ """
+ 
+ import base64
+ import functools
+ import hashlib
+-import json
++import hmac
++import math
+ import os
+ 
+-# make sure pycrypt is available
++# make sure pycrypto is available
+ try:
+     from Crypto.Cipher import AES
+ except ImportError:
+     AES = None
+ 
+-
+-# prefix marker indicating data is HMACed (signed by a secret key)
+-MAC_MARKER = '{MAC:SHA1}'
+-# prefix marker indicating data is encrypted
+-ENCRYPT_MARKER = '{ENCRYPT:AES256}'
++HASH_FUNCTION = hashlib.sha384
++DIGEST_LENGTH = HASH_FUNCTION().digest_size
++DIGEST_SPLIT = DIGEST_LENGTH // 3
++DIGEST_LENGTH_B64 = 4 * int(math.ceil(DIGEST_LENGTH / 3.0))
+ 
+ 
+ class InvalidMacError(Exception):
+@@ -81,77 +82,121 @@ def assert_crypto_availability(f):
+     return wrapper
+ 
+ 
+-def generate_aes_key(token, secret):
+-    """ Generates and returns a 256 bit AES key, based on sha256 hash. """
+-    return hashlib.sha256(token + secret).digest()
+-
+-
+-def compute_mac(token, serialized_data):
+-    """ Computes and returns the base64 encoded MAC. """
+-    return hash_data(serialized_data + token)
++def constant_time_compare(first, second):
++    """ Returns True if both string inputs are equal, otherwise False
+ 
++    This function should take a constant amount of time regardless of
++    how many characters in the strings match.
+ 
+-def hash_data(data):
+-    """ Return the base64 encoded SHA1 hash of the data. """
+-    return base64.b64encode(hashlib.sha1(data).digest())
+-
+-
+-def sign_data(token, data):
+-    """ MAC the data using SHA1. """
+-    mac_data = {}
+-    mac_data['serialized_data'] = json.dumps(data)
+-    mac = compute_mac(token, mac_data['serialized_data'])
+-    mac_data['mac'] = mac
+-    md = MAC_MARKER + base64.b64encode(json.dumps(mac_data))
+-    return md
++    """
++    if len(first) != len(second):
++        return False
++    result = 0
++    for x, y in zip(first, second):
++        result |= ord(x) ^ ord(y)
++    return result == 0
++
++
++def derive_keys(token, secret, strategy):
++    """ Derives keys for MAC and ENCRYPTION from the user-provided
++    secret. The resulting keys should be passed to the protect and
++    unprotect functions.
++
++    As suggested by NIST Special Publication 800-108, this uses the
++    first 128 bits from the sha384 KDF for the obscured cache key
++    value, the second 128 bits for the message authentication key and
++    the remaining 128 bits for the encryption key.
++
++    This approach is faster than computing a separate hmac as the KDF
++    for each desired key.
++    """
++    digest = hmac.new(secret, token + strategy, HASH_FUNCTION).digest()
++    return {'CACHE_KEY': digest[:DIGEST_SPLIT],
++            'MAC': digest[DIGEST_SPLIT: 2 * DIGEST_SPLIT],
++            'ENCRYPTION': digest[2 * DIGEST_SPLIT:],
++            'strategy': strategy}
+ 
+ 
+-def verify_signed_data(token, data):
+-    """ Verify data integrity by ensuring MAC is valid. """
+-    if data.startswith(MAC_MARKER):
+-        try:
+-            data = data[len(MAC_MARKER):]
+-            mac_data = json.loads(base64.b64decode(data))
+-            mac = compute_mac(token, mac_data['serialized_data'])
+-            if mac != mac_data['mac']:
+-                raise InvalidMacError('invalid MAC; expect=%s, actual=%s' %
+-                                      (mac_data['mac'], mac))
+-            return json.loads(mac_data['serialized_data'])
+-        except:
+-            raise InvalidMacError('invalid MAC; data appeared to be corrupted')
+-    else:
+-        # doesn't appear to be MACed data
+-        return data
++def sign_data(key, data):
++    """ Sign the data using the defined function and the derived key"""
++    mac = hmac.new(key, data, HASH_FUNCTION).digest()
++    return base64.b64encode(mac)
+ 
+ 
+ @assert_crypto_availability
+-def encrypt_data(token, secret, data):
+-    """ Encryptes the data with the given secret key. """
++def encrypt_data(key, data):
++    """ Encrypt the data with the given secret key.
++
++    Padding is n bytes of the value n, where 1 <= n <= blocksize.
++    """
+     iv = os.urandom(16)
+-    aes_key = generate_aes_key(token, secret)
+-    cipher = AES.new(aes_key, AES.MODE_CFB, iv)
+-    data = json.dumps(data)
+-    encoded_data = base64.b64encode(iv + cipher.encrypt(data))
+-    encoded_data = ENCRYPT_MARKER + encoded_data
+-    return encoded_data
++    cipher = AES.new(key, AES.MODE_CBC, iv)
++    padding = 16 - len(data) % 16
++    return iv + cipher.encrypt(data + chr(padding) * padding)
+ 
+ 
+ @assert_crypto_availability
+-def decrypt_data(token, secret, data):
++def decrypt_data(key, data):
+     """ Decrypt the data with the given secret key. """
+-    if data.startswith(ENCRYPT_MARKER):
+-        try:
+-            # encrypted data
+-            encoded_data = data[len(ENCRYPT_MARKER):]
+-            aes_key = generate_aes_key(token, secret)
+-            decoded_data = base64.b64decode(encoded_data)
+-            iv = decoded_data[:16]
+-            encrypted_data = decoded_data[16:]
+-            cipher = AES.new(aes_key, AES.MODE_CFB, iv)
+-            decrypted_data = cipher.decrypt(encrypted_data)
+-            return json.loads(decrypted_data)
+-        except:
+-            raise DecryptError('data appeared to be corrupted')
+-    else:
+-        # doesn't appear to be encrypted data
+-        return data
++    iv = data[:16]
++    cipher = AES.new(key, AES.MODE_CBC, iv)
++    try:
++        result = cipher.decrypt(data[16:])
++    except Exception:
++        raise DecryptError('Encrypted data appears to be corrupted.')
++
++    # Strip the last n padding bytes where n is the last value in
++    # the plaintext
++    padding = ord(result[-1])
++    return result[:-1 * padding]
++
++
++def protect_data(keys, data):
++    """ Given keys and serialized data, returns an appropriately
++    protected string suitable for storage in the cache.
++
++    """
++    if keys['strategy'] == 'ENCRYPT':
++        data = encrypt_data(keys['ENCRYPTION'], data)
++
++    encoded_data = base64.b64encode(data)
++
++    signature = sign_data(keys['MAC'], encoded_data)
++    return signature + encoded_data
++
++
++def unprotect_data(keys, signed_data):
++    """ Given keys and cached string data, verifies the signature,
++    decrypts if necessary, and returns the original serialized data.
++
++    """
++    # cache backends return None when no data is found. We don't mind
++    # that this particular special value is unsigned.
++    if signed_data is None:
++        return None
++
++    # First we calculate the signature
++    provided_mac = signed_data[:DIGEST_LENGTH_B64]
++    calculated_mac = sign_data(
++        keys['MAC'],
++        signed_data[DIGEST_LENGTH_B64:])
++
++    # Then verify that it matches the provided value
++    if not constant_time_compare(provided_mac, calculated_mac):
++        raise InvalidMacError('Invalid MAC; data appears to be corrupted.')
++
++    data = base64.b64decode(signed_data[DIGEST_LENGTH_B64:])
++
++    # then if necessary decrypt the data
++    if keys['strategy'] == 'ENCRYPT':
++        data = decrypt_data(keys['ENCRYPTION'], data)
++
++    return data
++
++
++def get_cache_key(keys):
++    """ Given keys generated by derive_keys(), returns a base64
++    encoded value suitable for use as a cache key in memcached.
++
++    """
++    return base64.b64encode(keys['CACHE_KEY'])
+diff --git a/tests/test_auth_token_middleware.py b/tests/test_auth_token_middleware.py
+index 88c0339..7fdee23 100644
+--- a/tests/test_auth_token_middleware.py
++++ b/tests/test_auth_token_middleware.py
+@@ -27,7 +27,6 @@ import webob
+ from keystoneclient.common import cms
+ from keystoneclient import utils
+ from keystoneclient.middleware import auth_token
+-from keystoneclient.middleware import memcache_crypt
+ from keystoneclient.openstack.common import memorycache
+ from keystoneclient.openstack.common import jsonutils
+ from keystoneclient.openstack.common import timeutils
+@@ -935,9 +934,7 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+     def _get_cached_token(self, token):
+         token_id = cms.cms_hash_token(token)
+         # NOTE(vish): example tokens are expired so skip the expiration check.
+-        key = self.middleware._get_cache_key(token_id)
+-        cached = self.middleware._cache.get(key)
+-        return self.middleware._unprotect_cache_value(token, cached)
++        return self.middleware._cache_get(token_id, ignore_expires=True)
+ 
+     def test_memcache(self):
+         req = webob.Request.blank('/')
+@@ -958,7 +955,8 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+         token = 'invalid-token'
+         req.headers['X-Auth-Token'] = token
+         self.middleware(req.environ, self.start_fake_response)
+-        self.assertEqual(self._get_cached_token(token), "invalid")
++        self.assertRaises(auth_token.InvalidUserToken,
++                          self._get_cached_token, token)
+ 
+     def test_memcache_set_expired(self):
+         token_cache_time = 10
+@@ -1041,18 +1039,11 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+             'memcache_secret_key': 'mysecret'
+         }
+         self.set_middleware(conf=conf)
+-        encrypted_data = self.middleware._protect_cache_value(
+-            'token', TOKEN_RESPONSES[self.token_dict['uuid_token_default']])
+-        self.assertEqual('{ENCRYPT:AES256}', encrypted_data[:16])
+-        self.assertEqual(
+-            TOKEN_RESPONSES[self.token_dict['uuid_token_default']],
+-            self.middleware._unprotect_cache_value('token', encrypted_data))
+-        # should return None if unable to decrypt
+-        self.assertIsNone(
+-            self.middleware._unprotect_cache_value(
+-                'token', '{ENCRYPT:AES256}corrupted'))
+-        self.assertIsNone(
+-            self.middleware._unprotect_cache_value('mykey', encrypted_data))
++        token = 'my_token'
++        data = ('this_data', 10e100)
++        self.middleware._init_cache({})
++        self.middleware._cache_store(token, data)
++        self.assertEqual(self.middleware._cache_get(token), data[0])
+ 
+     def test_sign_cache_data(self):
+         conf = {
+@@ -1064,19 +1055,11 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+             'memcache_secret_key': 'mysecret'
+         }
+         self.set_middleware(conf=conf)
+-        signed_data = self.middleware._protect_cache_value(
+-            'mykey', TOKEN_RESPONSES[self.token_dict['uuid_token_default']])
+-        expected = '{MAC:SHA1}'
+-        self.assertEqual(
+-            signed_data[:10],
+-            expected)
+-        self.assertEqual(
+-            TOKEN_RESPONSES[self.token_dict['uuid_token_default']],
+-            self.middleware._unprotect_cache_value('mykey', signed_data))
+-        # should return None on corrupted data
+-        self.assertIsNone(
+-            self.middleware._unprotect_cache_value('mykey',
+-                                                   '{MAC:SHA1}corrupted'))
++        token = 'my_token'
++        data = ('this_data', 10e100)
++        self.middleware._init_cache({})
++        self.middleware._cache_store(token, data)
++        self.assertEqual(self.middleware._cache_get(token), data[0])
+ 
+     def test_no_memcache_protection(self):
+         conf = {
+@@ -1087,47 +1070,11 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+             'memcache_secret_key': 'mysecret'
+         }
+         self.set_middleware(conf=conf)
+-        data = self.middleware._protect_cache_value('mykey',
+-                                                    'This is a test!')
+-        self.assertEqual(data, 'This is a test!')
+-        self.assertEqual(
+-            'This is a test!',
+-            self.middleware._unprotect_cache_value('mykey', data))
+-
+-    def test_get_cache_key(self):
+-        conf = {
+-            'auth_host': 'keystone.example.com',
+-            'auth_port': 1234,
+-            'auth_admin_prefix': '/testadmin',
+-            'memcache_servers': 'localhost:11211',
+-            'memcache_secret_key': 'mysecret'
+-        }
+-        self.set_middleware(conf=conf)
+-        self.assertEqual(
+-            'tokens/mytoken',
+-            self.middleware._get_cache_key('mytoken'))
+-        conf = {
+-            'auth_host': 'keystone.example.com',
+-            'auth_port': 1234,
+-            'auth_admin_prefix': '/testadmin',
+-            'memcache_servers': 'localhost:11211',
+-            'memcache_security_strategy': 'mac',
+-            'memcache_secret_key': 'mysecret'
+-        }
+-        self.set_middleware(conf=conf)
+-        expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'mysecret')
+-        self.assertEqual(self.middleware._get_cache_key('mytoken'), expected)
+-        conf = {
+-            'auth_host': 'keystone.example.com',
+-            'auth_port': 1234,
+-            'auth_admin_prefix': '/testadmin',
+-            'memcache_servers': 'localhost:11211',
+-            'memcache_security_strategy': 'Encrypt',
+-            'memcache_secret_key': 'abc!'
+-        }
+-        self.set_middleware(conf=conf)
+-        expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'abc!')
+-        self.assertEqual(self.middleware._get_cache_key('mytoken'), expected)
++        token = 'my_token'
++        data = ('this_data', 10e100)
++        self.middleware._init_cache({})
++        self.middleware._cache_store(token, data)
++        self.assertEqual(self.middleware._cache_get(token), data[0])
+ 
+     def test_assert_valid_memcache_protection_config(self):
+         # test missing memcache_secret_key
+diff --git a/tests/test_memcache_crypt.py b/tests/test_memcache_crypt.py
+index b2281d9..524cd21 100644
+--- a/tests/test_memcache_crypt.py
++++ b/tests/test_memcache_crypt.py
+@@ -4,48 +4,66 @@ from keystoneclient.middleware import memcache_crypt
+ 
+ 
+ class MemcacheCryptPositiveTests(testtools.TestCase):
+-    def test_generate_aes_key(self):
+-        self.assertEqual(
+-            len(memcache_crypt.generate_aes_key('Gimme Da Key', 'hush')), 32)
++    def _setup_keys(self, strategy):
++        return memcache_crypt.derive_keys('token', 'secret', strategy)
+ 
+-    def test_compute_mac(self):
+-        self.assertEqual(
+-            memcache_crypt.compute_mac('mykey', 'This is a test!'),
+-            'tREu41yR5tEgeBWIuv9ag4AeKA8=')
++    def test_constant_time_compare(self):
++        # make sure it works as a compare, the "constant time" aspect
++        # isn't appropriate to test in unittests
++        ctc = memcache_crypt.constant_time_compare
++        self.assertTrue(ctc('abcd', 'abcd'))
++        self.assertTrue(ctc('', ''))
++        self.assertFalse(ctc('abcd', 'efgh'))
++        self.assertFalse(ctc('abc', 'abcd'))
++        self.assertFalse(ctc('abc', 'abc\x00'))
++        self.assertFalse(ctc('', 'abc'))
++
++    def test_derive_keys(self):
++        keys = memcache_crypt.derive_keys('token', 'secret', 'strategy')
++        self.assertEqual(len(keys['ENCRYPTION']),
++                         len(keys['CACHE_KEY']))
++        self.assertEqual(len(keys['CACHE_KEY']),
++                         len(keys['MAC']))
++        self.assertNotEqual(keys['ENCRYPTION'],
++                            keys['MAC'])
++        self.assertIn('strategy', keys.keys())
++
++    def test_key_strategy_diff(self):
++        k1 = self._setup_keys('MAC')
++        k2 = self._setup_keys('ENCRYPT')
++        self.assertNotEqual(k1, k2)
+ 
+     def test_sign_data(self):
+-        expected = '{MAC:SHA1}eyJtYWMiOiAiM0FrQmdPZHRybGo1RFFESHA1eUxqcDVq' +\
+-                   'Si9BPSIsICJzZXJpYWxpemVkX2RhdGEiOiAiXCJUaGlzIGlzIGEgdG' +\
+-                   'VzdCFcIiJ9'
+-        self.assertEqual(
+-            memcache_crypt.sign_data('mykey', 'This is a test!'),
+-            expected)
+-
+-    def test_verify_signed_data(self):
+-        signed = memcache_crypt.sign_data('mykey', 'Testz')
+-        self.assertEqual(
+-            memcache_crypt.verify_signed_data('mykey', signed),
+-            'Testz')
+-        self.assertEqual(
+-            memcache_crypt.verify_signed_data('aasSFWE13WER', 'not MACed'),
+-            'not MACed')
+-
+-    def test_encrypt_data(self):
+-        expected = '{ENCRYPT:AES256}'
+-        self.assertEqual(
+-            memcache_crypt.encrypt_data('mykey', 'mysecret',
+-                                        'This is a test!')[:16],
+-            expected)
+-
+-    def test_decrypt_data(self):
+-        encrypted = memcache_crypt.encrypt_data('mykey', 'mysecret', 'Testz')
+-        self.assertEqual(
+-            memcache_crypt.decrypt_data('mykey', 'mysecret', encrypted),
+-            'Testz')
+-        self.assertEqual(
+-            memcache_crypt.decrypt_data('mykey', 'mysecret',
+-                                        'Not Encrypted!'),
+-            'Not Encrypted!')
++        keys = self._setup_keys('MAC')
++        sig = memcache_crypt.sign_data(keys['MAC'], 'data')
++        self.assertEqual(len(sig), memcache_crypt.DIGEST_LENGTH_B64)
++
++    def test_encryption(self):
++        keys = self._setup_keys('ENCRYPT')
++        # what you put in is what you get out
++        for data in ['data', '1234567890123456', '\x00\xFF' * 13
++                     ] + [chr(x % 256) * x for x in range(768)]:
++            crypt = memcache_crypt.encrypt_data(keys['ENCRYPTION'], data)
++            decrypt = memcache_crypt.decrypt_data(keys['ENCRYPTION'], crypt)
++            self.assertEqual(data, decrypt)
++            self.assertRaises(memcache_crypt.DecryptError,
++                              memcache_crypt.decrypt_data,
++                              keys['ENCRYPTION'], crypt[:-1])
++
++    def test_protect_wrappers(self):
++        data = 'My Pretty Little Data'
++        for strategy in ['MAC', 'ENCRYPT']:
++            keys = self._setup_keys(strategy)
++            protected = memcache_crypt.protect_data(keys, data)
++            self.assertNotEqual(protected, data)
++            if strategy == 'ENCRYPT':
++                self.assertNotIn(data, protected)
++            unprotected = memcache_crypt.unprotect_data(keys, protected)
++            self.assertEqual(data, unprotected)
++            self.assertRaises(memcache_crypt.InvalidMacError,
++                              memcache_crypt.unprotect_data,
++                              keys, protected[:-1])
++            self.assertIsNone(memcache_crypt.unprotect_data(keys, None))
+ 
+     def test_no_pycrypt(self):
+         aes = memcache_crypt.AES
diff --git a/python-keystoneclient.spec b/python-keystoneclient.spec
index 8c7e44d..da6d8bf 100644
--- a/python-keystoneclient.spec
+++ b/python-keystoneclient.spec
@@ -4,7 +4,7 @@ Name:       python-keystoneclient
 # https://lists.launchpad.net/openstack/msg14248.html
 Epoch:      1
 Version:    0.2.3
-Release:    4%{?dist}
+Release:    5%{?dist}
 Summary:    Client library for OpenStack Identity API
 License:    ASL 2.0
 URL:        http://pypi.python.org/pypi/%{name}
@@ -16,6 +16,9 @@ Source0:    http://pypi.python.org/packages/source/p/%{name}/%{name}-%{version}.
 Patch0001: 0001-Config-value-for-revocation-list-timeout.patch
 Patch0002: 0002-Fix-v3-with-UUID-and-memcache-expiring.patch
 Patch0003: 0003-Check-Expiry.patch
+Patch0004: 0004-Ec2Signer-Initial-support-for-v4-signature-verificat.patch
+Patch0005: 0005-Default-signing_dir-to-secure-temp-dir-bug-1181157.patch
+Patch0006: 0006-Fix-memcache-encryption-middleware.patch
 
 BuildArch:  noarch
 
@@ -55,6 +58,9 @@ Identity API.
 %patch0001 -p1
 %patch0002 -p1
 %patch0003 -p1
+%patch0004 -p1
+%patch0005 -p1
+%patch0006 -p1
 
 # Remove bundled egg-info
 rm -rf python_keystoneclient.egg-info
@@ -90,6 +96,11 @@ rm -fr doc/build/html/.doctrees doc/build/html/.buildinfo
 %doc LICENSE doc/build/html
 
 %changelog
+* Thu Jul 25 2013 Jakub Ruzicka <jruzicka at redhat.com> 0.2.3-5
+- Ec2Signer: Initial support for v4 signature verification.
+- Default signing_dir to secure temp dir.
+- Fix memcache encryption middleware. (CVE-2013-2166, CVE-2013-2167)
+
 * Tue May 28 2013 Jakub Ruzicka <jruzicka at redhat.com> 0.2.3-4
 - Check token expiry. (CVE-2013-2104)
 


More information about the scm-commits mailing list