diff --git a/castellan/common/__init__.py b/castellan/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/castellan/common/exception.py b/castellan/common/exception.py new file mode 100644 index 00000000..080b1785 --- /dev/null +++ b/castellan/common/exception.py @@ -0,0 +1,56 @@ +# Copyright (c) 2015 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. + +""" +Castellan exception subclasses +""" + +import six.moves.urllib.parse as urlparse + +from castellan.openstack.common import _i18n as u + +_FATAL_EXCEPTION_FORMAT_ERRORS = False + + +class RedirectException(Exception): + def __init__(self, url): + self.url = urlparse.urlparse(url) + + +class CastellanException(Exception): + """Base Castellan Exception + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + """ + message = u._("An unknown exception occurred") + + def __init__(self, message_arg=None, *args, **kwargs): + if not message_arg: + message_arg = self.message + try: + self.message = message_arg % kwargs + except Exception as e: + if _FATAL_EXCEPTION_FORMAT_ERRORS: + raise e + else: + # at least get the core message out if something happened + pass + super(CastellanException, self).__init__(self.message) + + +class Forbidden(CastellanException): + message = u._("You are not authorized to complete this action.") diff --git a/castellan/context.py b/castellan/context.py new file mode 100644 index 00000000..08d3fbae --- /dev/null +++ b/castellan/context.py @@ -0,0 +1,71 @@ +# Copyright 2011-2012 OpenStack LLC. +# 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_utils import uuidutils + +from castellan.openstack.common import local +from castellan.openstack.common import policy + + +class RequestContext(object): + """User security context object + + Stores information about the security context under which the user + accesses the system, as well as additional request information. + """ + + def __init__(self, auth_tok=None, user=None, project=None, roles=None, + is_admin=False, read_only=False, show_deleted=False, + owner_is_project=True, service_catalog=None, + policy_enforcer=None): + self.auth_tok = auth_tok + self.user = user + self.project = project + self.roles = roles or [] + self.read_only = read_only + self.owner_is_project = owner_is_project + self.request_id = uuidutils.generate_uuid() + self.service_catalog = service_catalog + self.policy_enforcer = policy_enforcer or policy.Enforcer() + self.is_admin = is_admin + + if not hasattr(local.store, 'context'): + self.update_store() + + def to_dict(self): + return { + 'request_id': self.request_id, + 'user': self.user, + 'user_id': self.user, + 'project': self.project, + 'project_id': self.project, + 'roles': self.roles, + 'auth_token': self.auth_tok, + 'service_catalog': self.service_catalog, + } + + @classmethod + def from_dict(cls, values): + return cls(**values) + + def update_store(self): + local.store.context = self + + @property + def owner(self): + """Return the owner to correlate with key.""" + if self.owner_is_project: + return self.project + return self.user diff --git a/castellan/keymgr/__init__.py b/castellan/keymgr/__init__.py new file mode 100644 index 00000000..88efcad1 --- /dev/null +++ b/castellan/keymgr/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015 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.utils import importutils +from oslo_config import cfg + +keymgr_opts = [ + cfg.StrOpt('api_class', + help='The full class name of the key manager API class'), +] + +CONF = cfg.CONF +CONF.register_opts(keymgr_opts, group='keymgr') + + +def API(): + cls = importutils.import_class(CONF.keymgr.api_class) + return cls() diff --git a/castellan/keymgr/key.py b/castellan/keymgr/key.py new file mode 100644 index 00000000..8ee1adeb --- /dev/null +++ b/castellan/keymgr/key.py @@ -0,0 +1,53 @@ +# Copyright (c) 2015 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 + +import six + + +@six.add_metaclass(abc.ABCMeta) +class Key(object): + """Base class to represent all keys.""" + + @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 diff --git a/castellan/keymgr/key_mgr.py b/castellan/keymgr/key_mgr.py new file mode 100644 index 00000000..567001da --- /dev/null +++ b/castellan/keymgr/key_mgr.py @@ -0,0 +1,100 @@ +# Copyright (c) 2015 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 + +import six + + +@six.add_metaclass(abc.ABCMeta) +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. + """ + + @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 copy_key(self, ctxt, key_id, **kwargs): + """Copies (i.e., clones) a key stored by the key manager. + + This method copies the specified key and returns the copy's UUID. If + the specified context does not permit copying keys, then a + NotAuthorized error should be raised. + + Implementation note: This method should behave identically to + store_key(context, get_key(context, )) + although it is preferable to perform this operation within the key + manager to avoid unnecessary handling of the key material. + """ + 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 diff --git a/castellan/keymgr/not_implemented_key_mgr.py b/castellan/keymgr/not_implemented_key_mgr.py new file mode 100644 index 00000000..fb72bea5 --- /dev/null +++ b/castellan/keymgr/not_implemented_key_mgr.py @@ -0,0 +1,42 @@ +# Copyright (c) 2015 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 implementation that raises NotImplementedError +""" + +from castellan.keymgr import key_mgr + + +class NotImplementedKeyManager(key_mgr.KeyManager): + """Key Manager Interface that raises NotImplementedError for all operations + + """ + + def create_key(self, ctxt, algorithm='AES', length=256, expiration=None, + **kwargs): + raise NotImplementedError() + + def store_key(self, ctxt, key, expiration=None, **kwargs): + raise NotImplementedError() + + def copy_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() + + def get_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() + + def delete_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() diff --git a/castellan/keymgr/symmetric_key.py b/castellan/keymgr/symmetric_key.py new file mode 100644 index 00000000..04fbb84a --- /dev/null +++ b/castellan/keymgr/symmetric_key.py @@ -0,0 +1,59 @@ +# Copyright (c) 2015 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 SymmetricKey Class + +This module defines the SymmetricKey class. +""" + +from castellan.keymgr import key + + +class SymmetricKey(key.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 + + def __eq__(self, other): + if isinstance(other, SymmetricKey): + return (self.alg == other.alg and + self.key == other.key) + return NotImplemented + + def __ne__(self, other): + result = self.__eq__(other) + if result is NotImplemented: + return result + return not result diff --git a/castellan/tests/keymgr/__init__.py b/castellan/tests/keymgr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/castellan/tests/keymgr/fake.py b/castellan/tests/keymgr/fake.py new file mode 100644 index 00000000..f893399e --- /dev/null +++ b/castellan/tests/keymgr/fake.py @@ -0,0 +1,24 @@ +# Copyright 2011 Justin Santa Barbara +# Copyright 2012 OpenStack Foundation +# 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. + +"""Implementation of a fake key manager.""" + + +from castellan.tests.keymgr import mock_key_mgr + + +def fake_api(): + return mock_key_mgr.MockKeyManager() diff --git a/castellan/tests/keymgr/mock_key_mgr.py b/castellan/tests/keymgr/mock_key_mgr.py new file mode 100644 index 00000000..1ab0b5e2 --- /dev/null +++ b/castellan/tests/keymgr/mock_key_mgr.py @@ -0,0 +1,166 @@ +# Copyright (c) 2015 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 that stores keys in a dictionary. + +This key manager implementation is primarily intended for testing. In +particular, it does not store keys persistently. Lack of a centralized key +store also makes this implementation unsuitable for use among different +services. + +Note: Instantiating this class multiple times will create separate key stores. +Keys created in one instance will not be accessible from other instances of +this class. +""" + +import array +import binascii +import random +import uuid + +from castellan.common import exception +from castellan.keymgr import key_mgr +from castellan.keymgr import symmetric_key as sym_key + + +class MockKeyManager(key_mgr.KeyManager): + + """Mocking manager for integration tests. + + 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 key manager is not suitable for use in production deployments. + """ + + def __init__(self): + self.keys = {} + + def _generate_hex_key(self, **kwargs): + key_length = kwargs.get('key_length', 256) + # hex digit => 4 bits + length = int(key_length / 4) + hex_encoded = self._generate_password(length=length, + symbolgroups='0123456789ABCDEF') + return hex_encoded + + def _generate_key(self, **kwargs): + _hex = self._generate_hex_key(**kwargs) + return sym_key.SymmetricKey('AES', + array.array('B', + binascii.unhexlify(_hex)) + .tolist()) + + def create_key(self, ctxt, **kwargs): + """Creates a key. + + This implementation returns a UUID for the created key. A + Forbidden exception is raised if the specified context is None. + """ + if ctxt is None: + raise exception.Forbidden() + + key = self._generate_key(**kwargs) + return self.store_key(ctxt, key) + + def _generate_key_id(self): + key_id = str(uuid.uuid4()) + while key_id in self.keys: + key_id = str(uuid.uuid4()) + + return key_id + + def store_key(self, ctxt, key, **kwargs): + """Stores (i.e., registers) a key with the key manager.""" + if ctxt is None: + raise exception.Forbidden() + + key_id = self._generate_key_id() + self.keys[key_id] = key + + return key_id + + def copy_key(self, ctxt, key_id, **kwargs): + if ctxt is None: + raise exception.Forbidden() + + copied_key_id = self._generate_key_id() + self.keys[copied_key_id] = self.keys[key_id] + + return copied_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 Forbidden exception is raised if the specified + context is None; a KeyError is raised if the UUID is invalid. + """ + if ctxt is None: + raise exception.Forbidden() + + return self.keys[key_id] + + def delete_key(self, ctxt, key_id, **kwargs): + """Deletes the key identified by the specified id. + + A Forbidden exception is raised if the context is None and a + KeyError is raised if the UUID is invalid. + """ + if ctxt is None: + raise exception.Forbidden() + + del self.keys[key_id] + + # Default symbols to use for passwords. Avoids visually confusing + # characters. ~6 bits per symbol. + DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1 + 'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O + 'abcdefghijkmnopqrstuvwxyz') # Removed: l + + def _generate_password(self, length=16, + symbolgroups=DEFAULT_PASSWORD_SYMBOLS): + """Generate a random password from the supplied symbol groups. + + At least one symbol from each group will be included. Unpredictable + results if length is less than the number of symbol groups. + + Believed to be reasonably secure (with a reasonable password length!) + + """ + # NOTE(jerdfelt): Some password policies require at least one character + # from each group of symbols, so start off with one random character + # from each symbol group + password = [random.choice(s) for s in symbolgroups] + # If length < len(symbolgroups), the leading characters will only + # be from the first length groups. Try our best to not be predictable + # by shuffling and then truncating. + random.shuffle(password) + password = password[:length] + length -= len(password) + + # then fill with random characters from all symbol groups + symbols = ''.join(symbolgroups) + password.extend([random.choice(symbols) for _i in range(length)]) + + # finally shuffle to ensure first x characters aren't from a + # predictable group + random.shuffle(password) + + return ''.join(password) diff --git a/castellan/tests/keymgr/test_key.py b/castellan/tests/keymgr/test_key.py new file mode 100644 index 00000000..b08a19cc --- /dev/null +++ b/castellan/tests/keymgr/test_key.py @@ -0,0 +1,68 @@ +# Copyright (c) 2015 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 +import binascii + +from castellan.keymgr import symmetric_key as sym_key +from castellan.tests import base + + +class KeyTestCase(base.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 sym_key.SymmetricKey(self.algorithm, self.encoded) + + def setUp(self): + self.algorithm = 'AES' + self.encoded = array.array('B', binascii.unhexlify('0' * 64)).tolist() + + super(SymmetricKeyTestCase, self).setUp() + + def test_get_algorithm(self): + self.assertEqual(self.key.get_algorithm(), self.algorithm) + + def test_get_format(self): + self.assertEqual(self.key.get_format(), 'RAW') + + def test_get_encoded(self): + self.assertEqual(self.key.get_encoded(), self.encoded) + + def test___eq__(self): + self.assertTrue(self.key == self.key) + + self.assertFalse(self.key is None) + self.assertFalse(None == self.key) + + def test___ne__(self): + self.assertFalse(self.key != self.key) + + self.assertTrue(self.key is not None) + self.assertTrue(None != self.key) diff --git a/castellan/tests/keymgr/test_key_mgr.py b/castellan/tests/keymgr/test_key_mgr.py new file mode 100644 index 00000000..334ff8c6 --- /dev/null +++ b/castellan/tests/keymgr/test_key_mgr.py @@ -0,0 +1,33 @@ +# Copyright (c) 2015 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 castellan.tests import base + + +class KeyManagerTestCase(base.TestCase): + def __init__(self, *args, **kwargs): + super(KeyManagerTestCase, self).__init__(*args, **kwargs) + + def _create_key_manager(self): + raise NotImplementedError() + + def setUp(self): + super(KeyManagerTestCase, self).setUp() + + self.key_mgr = self._create_key_manager() diff --git a/castellan/tests/keymgr/test_mock_key_mgr.py b/castellan/tests/keymgr/test_mock_key_mgr.py new file mode 100644 index 00000000..83ef28ef --- /dev/null +++ b/castellan/tests/keymgr/test_mock_key_mgr.py @@ -0,0 +1,103 @@ +# Copyright (c) 2015 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 +import binascii + +from castellan.common import exception +from castellan import context +from castellan.keymgr import symmetric_key as sym_key +from castellan.tests.keymgr import mock_key_mgr +from castellan.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.Forbidden, + self.key_mgr.create_key, None) + + def test_store_key(self): + secret_key = array.array('B', binascii.unhexlify('0' * 64)).tolist() + _key = sym_key.SymmetricKey('AES', secret_key) + 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.Forbidden, + self.key_mgr.store_key, None, None) + + def test_copy_key(self): + key_id = self.key_mgr.create_key(self.ctxt) + key = self.key_mgr.get_key(self.ctxt, key_id) + + copied_key_id = self.key_mgr.copy_key(self.ctxt, key_id) + copied_key = self.key_mgr.get_key(self.ctxt, copied_key_id) + + self.assertNotEqual(key_id, copied_key_id) + self.assertEqual(key, copied_key) + + def test_copy_null_context(self): + self.assertRaises(exception.Forbidden, + self.key_mgr.copy_key, None, None) + + def test_get_key(self): + pass + + def test_get_null_context(self): + self.assertRaises(exception.Forbidden, + 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.Forbidden, + self.key_mgr.delete_key, None, None) + + def test_delete_unknown_key(self): + self.assertRaises(KeyError, self.key_mgr.delete_key, self.ctxt, None) diff --git a/castellan/tests/keymgr/test_not_implemented_key_mgr.py b/castellan/tests/keymgr/test_not_implemented_key_mgr.py new file mode 100644 index 00000000..d0b46673 --- /dev/null +++ b/castellan/tests/keymgr/test_not_implemented_key_mgr.py @@ -0,0 +1,50 @@ +# Copyright (c) 2015 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 not implemented key manager. +""" + +from castellan.keymgr import not_implemented_key_mgr +from castellan.tests.keymgr import test_key_mgr + + +class NotImplementedKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): + + def _create_key_manager(self): + return not_implemented_key_mgr.NotImplementedKeyManager() + + def setUp(self): + super(NotImplementedKeyManagerTestCase, self).setUp() + + def test_create_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.create_key, None) + + def test_store_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.store_key, None, None) + + def test_copy_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.copy_key, None, None) + + def test_get_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.get_key, None, None) + + def test_delete_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.delete_key, None, None) diff --git a/requirements.txt b/requirements.txt index 95137a6f..f6df7718 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ pbr>=0.6,!=0.7,<1.0 Babel>=1.3 +oslo.config>=1.6.0 # Apache-2.0 +oslo.serialization>=1.2.0 # Apache-2.0 +oslo.utils>=1.2.0 # Apache-2.0