Add S3 signature v4 checking
Keystone can check signature v1 for s3, but many new tools uses new v4 signature protocol. This patchset adds checking of v4 signature. Architecture of implementation is the same as v1 implementated. Change-Id: I14121b4df2cae1407102335671c3f6878d46fc35 Closes-Bug: #1473042
This commit is contained in:
parent
28a30f53a6
commit
f11d396546
@ -33,6 +33,7 @@ from keystone.common import utils
|
|||||||
from keystone.common import wsgi
|
from keystone.common import wsgi
|
||||||
from keystone.contrib.ec2 import controllers
|
from keystone.contrib.ec2 import controllers
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
|
from keystone.i18n import _
|
||||||
|
|
||||||
|
|
||||||
EXTENSION_DATA = {
|
EXTENSION_DATA = {
|
||||||
@ -67,16 +68,60 @@ class S3Extension(wsgi.V3ExtensionRouter):
|
|||||||
|
|
||||||
class S3Controller(controllers.Ec2Controller):
|
class S3Controller(controllers.Ec2Controller):
|
||||||
def check_signature(self, creds_ref, credentials):
|
def check_signature(self, creds_ref, credentials):
|
||||||
msg = base64.urlsafe_b64decode(str(credentials['token']))
|
string_to_sign = base64.urlsafe_b64decode(str(credentials['token']))
|
||||||
key = str(creds_ref['secret']).encode('utf-8')
|
|
||||||
|
|
||||||
|
if string_to_sign[0:4] != b'AWS4':
|
||||||
|
signature = self._calculate_signature_v1(string_to_sign,
|
||||||
|
creds_ref['secret'])
|
||||||
|
else:
|
||||||
|
signature = self._calculate_signature_v4(string_to_sign,
|
||||||
|
creds_ref['secret'])
|
||||||
|
|
||||||
|
if not utils.auth_str_equal(credentials['signature'], signature):
|
||||||
|
raise exception.Unauthorized(
|
||||||
|
message=_('Credential signature mismatch'))
|
||||||
|
|
||||||
|
def _calculate_signature_v1(self, string_to_sign, secret_key):
|
||||||
|
"""Calculates a v1 signature.
|
||||||
|
|
||||||
|
:param bytes string_to_sign: String that contains request params and
|
||||||
|
is used for calculate signature of request
|
||||||
|
:param text secret_key: Second auth key of EC2 account that is used to
|
||||||
|
sign requests
|
||||||
|
"""
|
||||||
|
key = str(secret_key).encode('utf-8')
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
b64_encode = base64.encodestring
|
b64_encode = base64.encodestring
|
||||||
else:
|
else:
|
||||||
b64_encode = base64.encodebytes
|
b64_encode = base64.encodebytes
|
||||||
|
signed = b64_encode(hmac.new(key, string_to_sign, hashlib.sha1)
|
||||||
|
.digest()).decode('utf-8').strip()
|
||||||
|
return signed
|
||||||
|
|
||||||
signed = b64_encode(
|
def _calculate_signature_v4(self, string_to_sign, secret_key):
|
||||||
hmac.new(key, msg, hashlib.sha1).digest()).decode('utf-8').strip()
|
"""Calculates a v4 signature.
|
||||||
|
|
||||||
if not utils.auth_str_equal(credentials['signature'], signed):
|
:param bytes string_to_sign: String that contains request params and
|
||||||
raise exception.Unauthorized('Credential signature mismatch')
|
is used for calculate signature of request
|
||||||
|
:param text secret_key: Second auth key of EC2 account that is used to
|
||||||
|
sign requests
|
||||||
|
"""
|
||||||
|
parts = string_to_sign.split(b'\n')
|
||||||
|
if len(parts) != 4 or parts[0] != b'AWS4-HMAC-SHA256':
|
||||||
|
raise exception.Unauthorized(
|
||||||
|
message=_('Invalid EC2 signature.'))
|
||||||
|
scope = parts[2].split(b'/')
|
||||||
|
if len(scope) != 4 or scope[2] != b's3' or scope[3] != b'aws4_request':
|
||||||
|
raise exception.Unauthorized(
|
||||||
|
message=_('Invalid EC2 signature.'))
|
||||||
|
|
||||||
|
def _sign(key, msg):
|
||||||
|
return hmac.new(key, msg, hashlib.sha256).digest()
|
||||||
|
|
||||||
|
signed = _sign(six.b('AWS4' + secret_key), scope[0])
|
||||||
|
signed = _sign(signed, scope[1])
|
||||||
|
signed = _sign(signed, scope[2])
|
||||||
|
signed = _sign(signed, b'aws4_request')
|
||||||
|
|
||||||
|
signature = hmac.new(signed, string_to_sign, hashlib.sha256)
|
||||||
|
return signature.hexdigest()
|
||||||
|
@ -27,7 +27,7 @@ class S3ContribCore(unit.TestCase):
|
|||||||
|
|
||||||
self.controller = s3.S3Controller()
|
self.controller = s3.S3Controller()
|
||||||
|
|
||||||
def test_good_signature(self):
|
def test_good_signature_v1(self):
|
||||||
creds_ref = {'secret':
|
creds_ref = {'secret':
|
||||||
'b121dd41cdcc42fe9f70e572e84295aa'}
|
'b121dd41cdcc42fe9f70e572e84295aa'}
|
||||||
credentials = {'token':
|
credentials = {'token':
|
||||||
@ -40,7 +40,7 @@ class S3ContribCore(unit.TestCase):
|
|||||||
self.assertIsNone(self.controller.check_signature(creds_ref,
|
self.assertIsNone(self.controller.check_signature(creds_ref,
|
||||||
credentials))
|
credentials))
|
||||||
|
|
||||||
def test_bad_signature(self):
|
def test_bad_signature_v1(self):
|
||||||
creds_ref = {'secret':
|
creds_ref = {'secret':
|
||||||
'b121dd41cdcc42fe9f70e572e84295aa'}
|
'b121dd41cdcc42fe9f70e572e84295aa'}
|
||||||
credentials = {'token':
|
credentials = {'token':
|
||||||
@ -53,3 +53,51 @@ class S3ContribCore(unit.TestCase):
|
|||||||
self.assertRaises(exception.Unauthorized,
|
self.assertRaises(exception.Unauthorized,
|
||||||
self.controller.check_signature,
|
self.controller.check_signature,
|
||||||
creds_ref, credentials)
|
creds_ref, credentials)
|
||||||
|
|
||||||
|
def test_good_signature_v4(self):
|
||||||
|
creds_ref = {'secret':
|
||||||
|
'e7a7a2240136494986991a6598d9fb9f'}
|
||||||
|
credentials = {'token':
|
||||||
|
'QVdTNC1ITUFDLVNIQTI1NgoyMDE1MDgyNFQxMTIwNDFaCjIw'
|
||||||
|
'MTUwODI0L1JlZ2lvbk9uZS9zMy9hd3M0X3JlcXVlc3QKZjIy'
|
||||||
|
'MTU1ODBlZWI5YTE2NzM1MWJkOTNlODZjM2I2ZjA0YTkyOGY1'
|
||||||
|
'YzU1MjBhMzkzNWE0NTM1NDBhMDk1NjRiNQ==',
|
||||||
|
'signature':
|
||||||
|
'730ba8f58df6ffeadd78f402e990b2910d60'
|
||||||
|
'bc5c2aec63619734f096a4dd77be'}
|
||||||
|
|
||||||
|
self.assertIsNone(self.controller.check_signature(creds_ref,
|
||||||
|
credentials))
|
||||||
|
|
||||||
|
def test_bad_signature_v4(self):
|
||||||
|
creds_ref = {'secret':
|
||||||
|
'e7a7a2240136494986991a6598d9fb9f'}
|
||||||
|
credentials = {'token':
|
||||||
|
'QVdTNC1ITUFDLVNIQTI1NgoyMDE1MDgyNFQxMTIwNDFaCjIw'
|
||||||
|
'MTUwODI0L1JlZ2lvbk9uZS9zMy9hd3M0X3JlcXVlc3QKZjIy'
|
||||||
|
'MTU1ODBlZWI5YTE2NzM1MWJkOTNlODZjM2I2ZjA0YTkyOGY1'
|
||||||
|
'YzU1MjBhMzkzNWE0NTM1NDBhMDk1NjRiNQ==',
|
||||||
|
'signature': uuid.uuid4().hex}
|
||||||
|
|
||||||
|
self.assertRaises(exception.Unauthorized,
|
||||||
|
self.controller.check_signature,
|
||||||
|
creds_ref, credentials)
|
||||||
|
|
||||||
|
def test_bad_token_v4(self):
|
||||||
|
creds_ref = {'secret':
|
||||||
|
'e7a7a2240136494986991a6598d9fb9f'}
|
||||||
|
# token has invalid format of first part
|
||||||
|
credentials = {'token':
|
||||||
|
'QVdTNC1BQUEKWApYClg=',
|
||||||
|
'signature': ''}
|
||||||
|
self.assertRaises(exception.Unauthorized,
|
||||||
|
self.controller.check_signature,
|
||||||
|
creds_ref, credentials)
|
||||||
|
|
||||||
|
# token has invalid format of scope
|
||||||
|
credentials = {'token':
|
||||||
|
'QVdTNC1ITUFDLVNIQTI1NgpYCi8vczMvYXdzTl9yZXF1ZXN0Clg=',
|
||||||
|
'signature': ''}
|
||||||
|
self.assertRaises(exception.Unauthorized,
|
||||||
|
self.controller.check_signature,
|
||||||
|
creds_ref, credentials)
|
||||||
|
Loading…
Reference in New Issue
Block a user