--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo.config import cfg
+
+from cinder.openstack.common import importutils
+from cinder.openstack.common import log as logging
+
+keymgr_opts = [
+ cfg.StrOpt('keymgr_api_class',
+ default='cinder.keymgr.key_mgr.KeyManager',
+ help='The full class name of the key manager API class'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(keymgr_opts)
+
+LOG = logging.getLogger(__name__)
+
+
+def API():
+ keymgr_api_class = CONF.keymgr_api_class
+ cls = importutils.import_class(keymgr_api_class)
+ return cls()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Base Key and SymmetricKey Classes
+
+This module defines the Key and SymmetricKey classes. The Key class is the base
+class to represent all encryption keys. The basis for this class was copied
+from Java.
+"""
+
+import abc
+
+
+class Key(object):
+ """Base class to represent all keys."""
+
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def get_algorithm(self):
+ """Returns the key's algorithm.
+
+ Returns the key's algorithm. For example, "DSA" indicates that this key
+ is a DSA key and "AES" indicates that this key is an AES key.
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_format(self):
+ """Returns the encoding format.
+
+ Returns the key's encoding format or None if this key is not encoded.
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_encoded(self):
+ """Returns the key in the format specified by its encoding."""
+ pass
+
+
+class SymmetricKey(Key):
+ """
+ This class represents symmetric keys
+ """
+
+ def __init__(self, alg, key):
+ """Create a new SymmetricKey object.
+
+ The arguments specify the algorithm for the symmetric encryption and
+ the bytes for the key.
+ """
+ self.alg = alg
+ self.key = key
+
+ def get_algorithm(self):
+ """Returns the algorithm for symmetric encryption."""
+ return self.alg
+
+ def get_format(self):
+ """This method returns 'RAW'."""
+ return "RAW"
+
+ def get_encoded(self):
+ """Returns the key in its encoded format."""
+ return self.key
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Key manager API
+"""
+
+import abc
+
+
+class KeyManager(object):
+ """Base Key Manager Interface
+
+ A Key Manager is responsible for managing encryption keys for volumes. A
+ Key Manager is responsible for creating, reading, and deleting keys.
+ """
+
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def create_key(self, ctxt, algorithm='AES', length=256, expiration=None,
+ **kwargs):
+ """Creates a key.
+
+ This method creates a key and returns the key's UUID. If the specified
+ context does not permit the creation of keys, then a NotAuthorized
+ exception should be raised.
+ """
+ pass
+
+ @abc.abstractmethod
+ def store_key(self, ctxt, key, expiration=None, **kwargs):
+ """Stores (i.e., registers) a key with the key manager.
+
+ This method stores the specified key and returns its UUID that
+ identifies it within the key manager. If the specified context does
+ not permit the creation of keys, then a NotAuthorized exception should
+ be raised.
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_key(self, ctxt, key_id, **kwargs):
+ """Retrieves the specified key.
+
+ Implementations should verify that the caller has permissions to
+ retrieve the key by checking the context object passed in as ctxt. If
+ the user lacks permission then a NotAuthorized exception is raised.
+
+ If the specified key does not exist, then a KeyError should be raised.
+ Implementations should preclude users from discerning the UUIDs of
+ keys that belong to other users by repeatedly calling this method.
+ That is, keys that belong to other users should be considered "non-
+ existent" and completely invisible.
+ """
+ pass
+
+ @abc.abstractmethod
+ def delete_key(self, ctxt, key_id, **kwargs):
+ """Deletes the specified key.
+
+ Implementations should verify that the caller has permission to delete
+ the key by checking the context object (ctxt). A NotAuthorized
+ exception should be raised if the caller lacks permission.
+
+ If the specified key does not exist, then a KeyError should be raised.
+ Implementations should preclude users from discerning the UUIDs of
+ keys that belong to other users by repeatedly calling this method.
+ That is, keys that belong to other users should be considered "non-
+ existent" and completely invisible.
+ """
+ pass
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+A mock implementation of a key manager. This module should NOT be used for
+anything but integration testing.
+"""
+
+import array
+
+from cinder import exception
+from cinder.keymgr import key
+from cinder.keymgr import key_mgr
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import uuidutils
+from cinder import utils
+
+
+LOG = logging.getLogger(__name__)
+
+
+class MockKeyManager(key_mgr.KeyManager):
+
+ """
+ This mock key manager implementation supports all the methods specified
+ by the key manager interface. This implementation stores keys within a
+ dictionary, and as a result, it is not acceptable for use across different
+ services. Side effects (e.g., raising exceptions) for each method are
+ handled as specified by the key manager interface.
+
+ This class should NOT be used for anything but integration testing because
+ keys are not stored persistently.
+ """
+
+ def __init__(self):
+ self.keys = {}
+
+ def create_key(self, ctxt, **kwargs):
+ """Creates a key.
+
+ This implementation returns a UUID for the created key. A
+ NotAuthorized exception is raised if the specified context is None.
+ """
+ if ctxt is None:
+ raise exception.NotAuthorized()
+
+ # generate the key
+ key_length = kwargs.get('key_length', 256)
+ # hex digit => 4 bits
+ hex_string = utils.generate_password(length=key_length / 4,
+ symbolgroups='0123456789ABCDEF')
+
+ _bytes = array.array('B', hex_string.decode('hex')).tolist()
+ _key = key.SymmetricKey('AES', _bytes)
+
+ return self.store_key(ctxt, _key)
+
+ def store_key(self, ctxt, key, **kwargs):
+ """Stores (i.e., registers) a key with the key manager.
+ """
+ if ctxt is None:
+ raise exception.NotAuthorized()
+
+ # generate UUID and ensure that it isn't in use
+ key_id = uuidutils.generate_uuid()
+ while key_id in self.keys:
+ key_id = uuidutils.generate_uuid()
+
+ self.keys[key_id] = key
+
+ return key_id
+
+ def get_key(self, ctxt, key_id, **kwargs):
+ """Retrieves the key identified by the specified id.
+
+ This implementation returns the key that is associated with the
+ specified UUID. A NotAuthorized exception is raised if the specified
+ context is None; a KeyError is raised if the UUID is invalid.
+ """
+ if ctxt is None:
+ raise exception.NotAuthorized()
+
+ return self.keys[key_id]
+
+ def delete_key(self, ctxt, key_id, **kwargs):
+ """Deletes the key identified by the specified id.
+
+ A NotAuthorized exception is raised if the context is None and a
+ KeyError is raised if the UUID is invalid.
+ """
+ if ctxt is None:
+ raise exception.NotAuthorized()
+
+ del self.keys[key_id]
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Test cases for the key classes.
+"""
+
+import array
+
+from cinder.keymgr import key
+from cinder import test
+
+
+class KeyTestCase(test.TestCase):
+
+ def _create_key(self):
+ raise NotImplementedError()
+
+ def setUp(self):
+ super(KeyTestCase, self).setUp()
+
+ self.key = self._create_key()
+
+
+class SymmetricKeyTestCase(KeyTestCase):
+
+ def _create_key(self):
+ return key.SymmetricKey(self.algorithm, self.encoded)
+
+ def setUp(self):
+ self.algorithm = 'AES'
+ self.encoded = array.array('B', ('0' * 64).decode('hex')).tolist()
+
+ super(SymmetricKeyTestCase, self).setUp()
+
+ def test_get_algorithm(self):
+ self.assertEquals(self.key.get_algorithm(), self.algorithm)
+
+ def test_get_format(self):
+ self.assertEquals(self.key.get_format(), 'RAW')
+
+ def test_get_encoded(self):
+ self.assertEquals(self.key.get_encoded(), self.encoded)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Test cases for the key manager.
+"""
+
+from cinder import test
+
+
+class KeyManagerTestCase(test.TestCase):
+
+ def _create_key_manager(self):
+ raise NotImplementedError()
+
+ def setUp(self):
+ super(KeyManagerTestCase, self).setUp()
+
+ self.key_mgr = self._create_key_manager()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Test cases for the mock key manager.
+"""
+
+import array
+
+from cinder import context
+from cinder import exception
+from cinder.keymgr import key
+from cinder.tests.keymgr import mock_key_mgr
+from cinder.tests.keymgr import test_key_mgr
+
+
+class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase):
+
+ def _create_key_manager(self):
+ return mock_key_mgr.MockKeyManager()
+
+ def setUp(self):
+ super(MockKeyManagerTestCase, self).setUp()
+
+ self.ctxt = context.RequestContext('fake', 'fake')
+
+ def test_create_key(self):
+ key_id_1 = self.key_mgr.create_key(self.ctxt)
+ key_id_2 = self.key_mgr.create_key(self.ctxt)
+ # ensure that the UUIDs are unique
+ self.assertNotEqual(key_id_1, key_id_2)
+
+ def test_create_key_with_length(self):
+ for length in [64, 128, 256]:
+ key_id = self.key_mgr.create_key(self.ctxt, key_length=length)
+ key = self.key_mgr.get_key(self.ctxt, key_id)
+ self.assertEqual(length / 8, len(key.get_encoded()))
+
+ def test_create_null_context(self):
+ self.assertRaises(exception.NotAuthorized,
+ self.key_mgr.create_key, None)
+
+ def test_store_key(self):
+ _key = key.SymmetricKey('AES',
+ array.array('B',
+ ('0' * 64).decode('hex')).tolist())
+ key_id = self.key_mgr.store_key(self.ctxt, _key)
+
+ actual_key = self.key_mgr.get_key(self.ctxt, key_id)
+ self.assertEqual(_key, actual_key)
+
+ def test_store_null_context(self):
+ self.assertRaises(exception.NotAuthorized,
+ self.key_mgr.store_key, None, None)
+
+ def test_get_key(self):
+ pass
+
+ def test_get_null_context(self):
+ self.assertRaises(exception.NotAuthorized,
+ self.key_mgr.get_key, None, None)
+
+ def test_get_unknown_key(self):
+ self.assertRaises(KeyError, self.key_mgr.get_key, self.ctxt, None)
+
+ def test_delete_key(self):
+ key_id = self.key_mgr.create_key(self.ctxt)
+ self.key_mgr.delete_key(self.ctxt, key_id)
+
+ self.assertRaises(KeyError, self.key_mgr.get_key, self.ctxt, key_id)
+
+ def test_delete_null_context(self):
+ self.assertRaises(exception.NotAuthorized,
+ self.key_mgr.delete_key, None, None)
+
+ def test_delete_unknown_key(self):
+ self.assertRaises(KeyError, self.key_mgr.delete_key, self.ctxt, None)