Permalink
Browse files

Improved pluggable mechanism of encryption.

* Cleaned interface of drivers. Added encryption context to exchange
  of security data between unified encryption/decryption methods.
  Instance of key manager created externally towards drivers.
* Renamed FakeDriver to DummyDriver.
* ObjectController and DiskFile used new interface of drivers.

Change-Id: I4acb253b9a0f4c348fc6f27326e4219d086efbef
  • Loading branch information...
1 parent 8ecd3ff commit eed84aa308fcb2d865f8fe8d658fed533a720e9f @akscram akscram committed Dec 24, 2012
View
4 etc/object-server.conf-encryption-sample
@@ -58,8 +58,10 @@ use = egg:swift#object
# auto_create_account_prefix = .
# Encryption's configurations:
+## DummyDriver doesn't do encryption (this driver is default)
+# crypto_driver = swift.obj.encryptor.DummyDriver
## To use module M2Crypto for encryption specify driver:
-# crypto_driver = swift.obj.encryptor.M2Crypto
+# crypto_driver = swift.obj.encryptor.M2CryptoDriver
## M2Crypto driver support parameterized protocol(aes_128_cbc by default):
# crypto_protocol = aes_128_cbc
View
9 swift/common/key_manager/drivers/sql.py
@@ -116,14 +116,15 @@ def get_key(self, key_id):
:raise ValueError: if string include not only digits chars
:raise StandardError: if DB don't have row with current key_id
"""
- if type(key_id) is not long:
+ if not isinstance(key_id, long):
try:
key_id = long(key_id)
except TypeError:
- raise TypeError("Incorrect type. Must be string or int.")
+ raise TypeError("Incorrect type of value %r. Must be string or"
+ " int." % (key_id,))
except ValueError:
- raise ValueError("Incorrect value. String must include "
- "only digits chars.")
+ raise ValueError("Incorrect value %r. String must include "
+ "only digits chars." % (key_id,))
re = self.find_value("key_id", str(key_id))
if not re:
View
4 swift/common/middleware/key_manager.py
@@ -80,12 +80,10 @@ def __call__(self, env, start_response):
:return self.app: standart next WSGI app in the pipeline
"""
req = Request(env)
-
if req.method == "PUT":
account = self.get_account(req.path)
key_id = self.get_key_id(account)
- req.headers.update({'x-object-meta-key_id': key_id})
- # update environment
+ req.headers['X-Object-Meta-Key-Id'] = key_id
env = req.environ
return self.app(env, start_response)
View
225 swift/obj/encryptor.py
@@ -16,89 +16,103 @@
Encryption drivers for object storage server.
"""
-from M2Crypto.EVP import Cipher
-
-from swift.common.utils import create_instance
-from swift.common.key_manager.drivers.base import KeyDriver
+import M2Crypto
class CryptoDriver(object):
"""
- Crypt, decrypt and get key using key_id. Decision what crypto driver
- to use are taken here. Drivers for different realisation should implements
- this class and implements functions crypt and decrypt.
+ Base driver class that implements the functionality of encryption
+ and decryption of data blocks of objects.
+
+ :param conf: application configuration
+ :param key_manager: instance of
+ swift.common.key_manager.base.KeyDriver which
+ store encryption keys
"""
- key_value = ""
- def __init__(self, conf):
+ def __init__(self, conf, key_manager):
self.conf = conf
- self.protocol = conf.get("crypto_protocol")
- keystore_driver = conf.get('crypto_keystore_driver',
- 'swift.common.key_manager.drivers.fake.'
- 'FakeDriver')
- self.keystore_driver = create_instance(keystore_driver, KeyDriver,
- conf)
+ self.key_manager = key_manager
- def crypted_len(self, original_len):
+ def encrypted_chunk_size(self, context, original_size):
"""
- Count length of crypted string, base on length original
- string. Should be called only for classes, which implement
- this class and realise crypto algorithm.
+ Calculates the size of the encrypted data block based on the
+ size of the original data block.
- :param original_len: length of original string
+ :param cotext: encryption context
+ :param original_size: length of original string
:returns: length of crypted string
"""
- res = "a" * original_len
- return len(str(self.crypt(res)))
+ chunk = " " * original_size
+ return len(str(self.encrypt(context, chunk)))
- def crypt(self, chunk_string):
+ def encrypt(self, context, chunk):
"""
- :param chunk_string: string for encryption
- :returns: encrypted string
+ Returns the encrypted data block.
+
+ :param context: encryption context
+ :param chunk: data block to encrypt
+ :returns: encrypted data block
"""
raise NotImplementedError
- def decrypt(self, chunck_string):
+ def decrypt(self, context, chunk):
"""
- :param chunk_string: string for decryption
- :returns: decrypted string
+ Returns the decrypted data block.
+
+ :param context: encryption context
+ :param chunk: data block to decrypt
+ :returns: decrypted data block
"""
raise NotImplementedError
- def get_key_value(self, key_id):
+ def encryption_context(self, key_id):
"""
- Get key value from KeyController
- :param key_id: Id of key
- :returns key_value: directly key for this id.
+ Returns the context which needed to encrypt or decrypt the
+ data block.
+
+ :param key_id: unique key identifier
+ :returns: encryption context
"""
- self.key_value = self.keystore_driver.get_key(key_id)
+ context = {'key_id': key_id}
+ return context
-class FakeDriver(CryptoDriver):
+class DummyDriver(CryptoDriver):
"""
- Fake implementation of CryptoDriver, which does nothing. While
+ Dummy implementation of CryptoDriver, which does nothing. While
encryption/decryption it just return original string.
"""
- def __init__(self, conf):
- CryptoDriver.__init__(self, conf)
- def crypt(self, chunk_string):
+ def encrypted_chunk_size(self, context, original_size):
"""
- Make fake encryption. Just return original string.
+ Return original chunk size.
- :param chunk_string: original string for encryption
- :returns: original string
+ :param cotext: encryption context
+ :param original_size: length of original string
+ :returns: length of crypted string
"""
- return chunk_string
+ return original_size
- def decrypt(self, chunk_string):
+ def encrypt(self, context, chunk):
"""
- Make fake decryption. Just return original string.
+ Make dummy encryption. Just return original string.
- :param chunk_string: original string for decryption
- :returns: original string
+ :param context: encryption context
+ :param chunk: data block to decrypt
+ :returns: original data block
"""
- return chunk_string
+ return chunk
+
+ def decrypt(self, context, chunk):
+ """
+ Make dummy decryption. Just return original string.
+
+ :param context: encryption context
+ :param chunk: data block to decrypt
+ :returns: original data block
+ """
+ return chunk
class M2CryptoDriver(CryptoDriver):
@@ -108,54 +122,71 @@ class M2CryptoDriver(CryptoDriver):
because we use unified keys for all algorithm. So only key
value is used for crypting. Also using hardcoded value provides
better secure than not using it at all.
+
+ :param conf: application configuration
+ :param key_manager: instance of
+ swift.common.key_manager.base.KeyDriver which
+ store encryption keys
"""
- def __init__(self, conf):
- CryptoDriver.__init__(self, conf)
- self.iv = "3141527182810345"
- self.protocol = conf.get("crypto_protocol", "aes_128_cbc")
- if self.protocol != "aes_128_cbc":
- raise NotImplementedError("Incorrect protocol")
-
- def crypt(self, chunk):
- """
- Encrypt string whith protocol from crypto_protocol config field.
- Use hardcoded initial vector and key extracted from keystore.
-
- :param chunk: string for encryption
- :returns: encrypted string
- :raises ValueError: if crypto protocol is not supported
- by M2Crypto library
- """
- try:
- cipher = Cipher(alg=self.protocol,
- key=self.key_value, iv=self.iv, op=1)
- v = cipher.update(chunk)
- v = v + cipher.final()
- del cipher
- return v
- except ValueError, error:
- if error[0] == 'unknown cipher':
- raise ValueError('%r:%r please select avaliable protocol \
- type' % (error[0], error[1]))
-
- def decrypt(self, chunk):
- """
- Decrypt string whith protocol from crypto_protocol config field.
- Use hardcoded initial vector and key extracted from keystore.
-
- :param chunk: string for decryption
- :returns: decrypted string
- :raises ValueError: if crypto protocol is not supported
- by M2Crypto library
- """
- try:
- cipher = Cipher(alg=self.protocol,
- key=self.key_value, iv=self.iv, op=0)
- v = cipher.update(chunk)
- v = v + cipher.final()
- del cipher
- return v
- except ValueError, error:
- if error[0] == 'unknown cipher':
- raise ValueError('%r:%r please select avaliable protocol \
- type' % (error[0], error[1]))
+ default_protocol = 'aes_128_cbc'
+ default_iv = '3141527182810345'
+
+ def __init__(self, conf, key_manager):
+ CryptoDriver.__init__(self, conf, key_manager)
+ self.protocol = conf.get('crypto_protocol', self.default_protocol)
+ #TODO(ikharin): Now supported only aes_128_cbc protocol.
+ if self.protocol != self.default_protocol:
+ raise ValueError("M2CryptoDriver support only %r not %r "
+ "protocol." %
+ (self.default_protocol, self.protocol))
+
+ def encrypt(self, context, chunk):
+ """
+ Encrypt data block using protocol from crypto_protocol config
+ field. Key and initial vector extracted from encryption context.
+
+ :param context: encryption context
+ :param chunk: data block to encrypt
+ :returns: encrypted data block
+ """
+ cipher = M2Crypto.EVP.Cipher(alg=self.protocol,
+ key=context['key'],
+ iv=context['iv'],
+ op=1)
+ v = cipher.update(chunk)
+ v = v + cipher.final()
+ return v
+
+ def decrypt(self, context, chunk):
+ """
+ Decrypt data block using protocol from crypto_protocol config
+ field. Key and initial vector extracted from encryption context.
+
+ :param context: encryption context
+ :param chunk: data block to decrypt
+ :returns: decrypted data block
+ """
+ cipher = M2Crypto.EVP.Cipher(alg=self.protocol,
+ key=context['key'],
+ iv=context['iv'],
+ op=0)
+ v = cipher.update(chunk)
+ v = v + cipher.final()
+ return v
+
+ def encryption_context(self, key_id):
+ """
+ Returns the context which needed to encrypt or decrypt the
+ data block.
+
+ :param key_id: unique key ID
+ :returns: encryption context
+ """
+ context = super(M2CryptoDriver, self).encryption_context(key_id)
+ #TODO(ikharin): IV hardcoded now it will be generated and stored
+ # into key manager.
+ context.update({
+ 'key': self.key_manager.get_key(key_id),
+ 'iv': self.default_iv,
+ })
+ return context
View
54 swift/obj/server.py
@@ -48,6 +48,7 @@
HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, UTC, \
HTTPInsufficientStorage, multi_range_iterator
+from swift.common.key_manager.drivers.base import KeyDriver
from swift.obj.encryptor import CryptoDriver
@@ -112,7 +113,7 @@ class DiskFile(object):
def __init__(self, path, device, partition, account, container, obj,
logger, keep_data_fp=False, disk_chunk_size=65536,
- iter_hook=None, crypto_driver=None):
+ iter_hook=None, encryption_context=None, crypto_driver=None):
self.disk_chunk_size = disk_chunk_size
self.iter_hook = iter_hook
self.name = '/' + '/'.join((account, container, obj))
@@ -131,8 +132,9 @@ def __init__(self, path, device, partition, account, container, obj,
self.read_to_eof = False
self.quarantined_dir = None
self.keep_cache = False
- self.crypto_driver = crypto_driver
self.suppress_file_closing = False
+ self.encryption_context = encryption_context
+ self.crypto_driver = crypto_driver
if not os.path.exists(self.datadir):
return
files = sorted(os.listdir(self.datadir), reverse=True)
@@ -158,9 +160,10 @@ def __init__(self, path, device, partition, account, container, obj,
if key.lower() not in DISALLOWED_HEADERS:
del self.metadata[key]
self.metadata.update(read_metadata(mfp))
- _key_id = self.metadata.get("x-object-meta-key_id")
- if (_key_id and self.crypto_driver):
- self.crypto_driver.get_key_value(_key_id)
+ if self.crypto_driver and not self.encryption_context:
+ key_id = self.metadata.get('X-Object-Meta-Key-Id')
+ self.encryption_context = \
+ self.crypto_driver.encryption_context(key_id)
def __iter__(self):
"""Returns an iterator over the data file."""
@@ -183,7 +186,8 @@ def __iter__(self):
read - dropped_cache)
dropped_cache = read
if self.crypto_driver:
- chunk = self.crypto_driver.decrypt(chunk)
+ chunk = self.crypto_driver.decrypt(
+ self.encryption_context, chunk)
yield chunk
if self.iter_hook:
self.iter_hook()
@@ -391,14 +395,25 @@ def __init__(self, conf):
/etc/swift/object-server.conf-sample.
"""
self.logger = get_logger(conf, log_route='object-server')
- self.crypto_driver = self._get_crypto_driver(conf)
+ key_manager = conf.get('crypto_keystore_driver',
+ 'swift.common.key_manager.drivers.dummy.'
+ 'DummyDriver')
+ self.key_manager = create_instance(key_manager, KeyDriver, conf)
+ crypto_driver = conf.get('crypto_driver',
+ 'swift.obj.encryptor.DummyDriver')
+ self.crypto_driver = create_instance(crypto_driver, CryptoDriver, conf,
+ self.key_manager)
self.devices = conf.get('devices', '/srv/node/')
self.mount_check = config_true_value(conf.get('mount_check', 'true'))
self.node_timeout = int(conf.get('node_timeout', 3))
self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.network_chunk_size = int(conf.get('network_chunk_size', 65536))
+ disk_chunk_size = int(conf.get('disk_chunk_size', 65536))
+ key_id = self.key_manager.get_key_id('default')
+ encryption_context = self.crypto_driver.encryption_context(key_id)
self.disk_chunk_size = \
- self.crypto_driver.crypted_len(self.network_chunk_size)
+ self.crypto_driver.encrypted_chunk_size(encryption_context,
+ disk_chunk_size)
self.keep_cache_size = int(conf.get('keep_cache_size', 5242880))
self.keep_cache_private = \
config_true_value(conf.get('keep_cache_private', 'false'))
@@ -422,20 +437,6 @@ def __init__(self, conf):
self.expiring_objects_container_divisor = \
int(conf.get('expiring_objects_container_divisor') or 86400)
- def _get_crypto_driver(self, conf):
- """
- Create instance of encryption driver, initialize and return it.
- This function lookup driver path into configuration option
- 'crypto_driver'.
-
- :param conf: application configuration
- :returns: instance of subclass of
- swift.obj.encryptor.CryptoDriver
- """
- crypto_driver = conf.get('crypto_driver',
- 'swift.obj.encryptor.FakeDriver')
- return create_instance(crypto_driver, CryptoDriver, conf)
-
def async_update(self, op, account, container, obj, host, partition,
contdevice, headers_out, objdevice):
"""
@@ -603,9 +604,8 @@ def POST(self, request):
def PUT(self, request):
"""Handle HTTP PUT requests for the Swift Object Server."""
start_time = time.time()
- _key_str = request.headers.get("x-object-meta-key_id")
- if _key_str and self.crypto_driver:
- self.crypto_driver.get_key_value(_key_str)
+ key_id = request.headers.get('x-object-meta-key-id')
+ encryption_context = self.crypto_driver.encryption_context(key_id)
try:
device, partition, account, container, obj = \
split_path(unquote(request.path), 5, 5, True)
@@ -628,6 +628,7 @@ def PUT(self, request):
content_type='text/plain')
file = DiskFile(self.devices, device, partition, account, container,
obj, self.logger, disk_chunk_size=self.disk_chunk_size,
+ encryption_context=encryption_context,
crypto_driver=self.crypto_driver)
orig_timestamp = file.metadata.get('X-Timestamp')
upload_expiration = time.time() + self.max_upload_time
@@ -646,8 +647,7 @@ def PUT(self, request):
for chunk in iter(lambda: reader(self.network_chunk_size), ''):
etag_orig.update(chunk)
upload_size += len(chunk)
- if self.crypto_driver:
- chunk = self.crypto_driver.crypt(chunk)
+ chunk = self.crypto_driver.encrypt(encryption_context, chunk)
if time.time() > upload_expiration:
self.logger.increment('PUT.timeouts')
return HTTPRequestTimeout(request=request)
View
27 test/unit/obj/test_encryptor.py
@@ -14,27 +14,19 @@
# limitations under the License.
import unittest
-import mock
import os
-from swift.obj.encryptor import M2CryptoDriver, FakeDriver
+from swift.common.key_manager.drivers.fake import FakeDriver
+from swift.obj.encryptor import M2CryptoDriver, DummyDriver
class TestEncryptor(unittest.TestCase):
def setUp(self):
"""
Set up for testing swift.obj.encryptor.M2CryptoDriver and
- swift.obj.encryptor.FakeDriver encryption drivers.
+ swift.obj.encryptor.DummyDriver encryption drivers.
"""
- self.patcher = mock.patch('swift.obj.encryptor.create_instance')
- self.mock_create_instance = self.patcher.start()
-
- def tearDown(self):
- """
- Tear down for testing swift.obj.encryptor.M2CryptoDriver and
- swift.obj.encryptor.FakeDriver encryption drivers.
- """
- self.patcher.stop()
+ self.key_manager = FakeDriver({})
def _driver_testing(self, crypto_driver):
"""
@@ -43,18 +35,19 @@ def _driver_testing(self, crypto_driver):
:param crypto_driver: crypto driver for testing
"""
+ context = crypto_driver.encryption_context('fake')
text = os.urandom(20000)
- crypted_text = crypto_driver.crypt(text)
- self.assertEquals(text, crypto_driver.decrypt(crypted_text))
+ crypted_text = crypto_driver.encrypt(context, text)
+ self.assertEquals(text, crypto_driver.decrypt(context, crypted_text))
def test_M2CryptoDriver_aes_128_cbc(self):
"""Test for M2Crypto driver whith aes_128_cbc algorithm"""
conf = {"crypto_protocol": "aes_128_cbc"}
- crypto_driver = M2CryptoDriver(conf)
+ crypto_driver = M2CryptoDriver(conf, self.key_manager)
self._driver_testing(crypto_driver)
- def test_FakeDriver(self):
+ def test_DummyDriver(self):
"""Test for fake driver"""
conf = {}
- crypto_driver = FakeDriver(conf)
+ crypto_driver = DummyDriver(conf, self.key_manager)
self._driver_testing(crypto_driver)
View
2 test/unit/obj/test_server.py
@@ -368,7 +368,7 @@ def setUp(self):
os.path.join(mkdtemp(), 'tmp_test_object_server_ObjectController')
mkdirs(os.path.join(self.testdir, 'sda1', 'tmp'))
conf = {'devices': self.testdir, 'mount_check': 'false',
- "crypto_driver": "swift.obj.encryptor.FakeDriver",
+ "crypto_driver": "swift.obj.encryptor.DummyDriver",
"crypto_keystore_driver": "swift.common.key_manager.drivers."
"fake.FakeDriver"}
self.object_controller = object_server.ObjectController(conf)
View
2 test/unit/proxy/test_server.py
@@ -100,7 +100,7 @@ def setup():
'mount_check': 'false', 'allowed_headers':
'content-encoding, x-object-manifest, content-disposition, foo',
'allow_versions': 'True',
- 'crypto_driver': 'swift.obj.encryptor.FakeDriver'}
+ 'crypto_driver': 'swift.obj.encryptor.DummyDriver'}
prolis = listen(('localhost', 0))
acc1lis = listen(('localhost', 0))
acc2lis = listen(('localhost', 0))

0 comments on commit eed84aa

Please sign in to comment.