diff --git a/castellan/common/objects/__init__.py b/castellan/common/objects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/castellan/common/objects/certificate.py b/castellan/common/objects/certificate.py new file mode 100644 index 00000000..a8416f0c --- /dev/null +++ b/castellan/common/objects/certificate.py @@ -0,0 +1,31 @@ +# 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 Certificate Class + +This module defines the Certificate class. +""" + +import abc + +import six + +from castellan.common.objects import managed_object + + +@six.add_metaclass(abc.ABCMeta) +class Certificate(managed_object.ManagedObject): + """Base class to represent all certificates.""" diff --git a/castellan/key_manager/key.py b/castellan/common/objects/key.py similarity index 73% rename from castellan/key_manager/key.py rename to castellan/common/objects/key.py index 0263426b..2fe11660 100644 --- a/castellan/key_manager/key.py +++ b/castellan/common/objects/key.py @@ -21,17 +21,19 @@ represent all encryption keys. The basis for this class was copied from Java. """ +from castellan.common.objects import managed_object + import abc import six @six.add_metaclass(abc.ABCMeta) -class Key(object): +class Key(managed_object.ManagedObject): """Base class to represent all keys.""" - @abc.abstractmethod - def get_algorithm(self): + @abc.abstractproperty + def algorithm(self): """Returns the key's algorithm. Returns the key's algorithm. For example, "DSA" indicates that this key @@ -39,15 +41,12 @@ class Key(object): """ pass - @abc.abstractmethod - def get_format(self): - """Returns the encoding format. + @abc.abstractproperty + def bit_length(self): + """Returns the key's bit length. - Returns the key's encoding format or None if this key is not encoded. + Returns the key's bit length. For example, for AES symmetric keys, + this refers to the length of the key, and for RSA keys, this refers to + the length of the modulus. """ pass - - @abc.abstractmethod - def get_encoded(self): - """Returns the key in the format specified by its encoding.""" - pass diff --git a/castellan/common/objects/managed_object.py b/castellan/common/objects/managed_object.py new file mode 100644 index 00000000..19c42398 --- /dev/null +++ b/castellan/common/objects/managed_object.py @@ -0,0 +1,48 @@ +# Copyright (c) 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 ManagedObject Class + +This module defines the ManagedObject class. The ManagedObject class +is the base class to represent all objects managed by the key manager. +""" + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class ManagedObject(object): + """Base class to represent all managed objects.""" + + @abc.abstractproperty + def format(self): + """Returns the encoding format. + + Returns the object's encoding format or None if this object is not + encoded. + """ + pass + + @abc.abstractmethod + def get_encoded(self): + """Returns the encoded object. + + Returns a bytestring object in a format represented in the encoding + specified. + """ + pass diff --git a/castellan/common/objects/opaque_data.py b/castellan/common/objects/opaque_data.py new file mode 100644 index 00000000..baf112e7 --- /dev/null +++ b/castellan/common/objects/opaque_data.py @@ -0,0 +1,52 @@ +# 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 OpaqueData Class + +This module defines the OpaqueData class. +""" + +from castellan.common.objects import managed_object + + +class OpaqueData(managed_object.ManagedObject): + """This class represents opaque data.""" + + def __init__(self, data): + """Create a new OpaqueData object. + + Expected type for data is a bytestring. + """ + self._data = data + + @property + def format(self): + """This method returns 'Opaque'.""" + return "Opaque" + + def get_encoded(self): + """Returns the data in its original format.""" + return self._data + + def __eq__(self, other): + if isinstance(other, OpaqueData): + return self._data == other._data + else: + return False + + def __ne__(self, other): + result = self.__eq__(other) + return not result diff --git a/castellan/common/objects/passphrase.py b/castellan/common/objects/passphrase.py new file mode 100644 index 00000000..4517921f --- /dev/null +++ b/castellan/common/objects/passphrase.py @@ -0,0 +1,52 @@ +# 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 Passphrase Class + +This module defines the Passphrase class. +""" + +from castellan.common.objects import managed_object + + +class Passphrase(managed_object.ManagedObject): + """This class represents a passphrase.""" + + def __init__(self, passphrase): + """Create a new Passphrase object. + + The expected type for the passphrase is a bytestring. + """ + self._passphrase = passphrase + + @property + def format(self): + """This method returns 'RAW'.""" + return "RAW" + + def get_encoded(self): + """Returns the data in a bytestring.""" + return self._passphrase + + def __eq__(self, other): + if isinstance(other, Passphrase): + return self._passphrase == other._passphrase + else: + return False + + def __ne__(self, other): + result = self.__eq__(other) + return not result diff --git a/castellan/common/objects/private_key.py b/castellan/common/objects/private_key.py new file mode 100644 index 00000000..5ef5af2b --- /dev/null +++ b/castellan/common/objects/private_key.py @@ -0,0 +1,66 @@ +# 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 PrivateKey Class + +This module defines the PrivateKey class. +""" + +from castellan.common.objects import key + + +class PrivateKey(key.Key): + """This class represents private keys.""" + + def __init__(self, algorithm, bit_length, key): + """Create a new PrivateKey object. + + The arguments specify the algorithm and bit length for the asymmetric + encryption and the bytes for the key in a bytestring. + """ + self._alg = algorithm + self._bit_length = bit_length + self._key = key + + @property + def algorithm(self): + """Returns the algorithm for asymmetric encryption.""" + return self._alg + + @property + def format(self): + """This method returns 'PKCS8'.""" + return "PKCS8" + + @property + def bit_length(self): + """Returns the key length.""" + return self._bit_length + + def get_encoded(self): + """Returns the key in DER encoded format.""" + return self._key + + def __eq__(self, other): + if isinstance(other, PrivateKey): + return (self._alg == other._alg and + self._key == other._key) + else: + return False + + def __ne__(self, other): + result = self.__eq__(other) + return not result diff --git a/castellan/common/objects/public_key.py b/castellan/common/objects/public_key.py new file mode 100644 index 00000000..c1c66cc1 --- /dev/null +++ b/castellan/common/objects/public_key.py @@ -0,0 +1,67 @@ +# 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 PublicKey Class + +This module defines the PublicKey class. +""" + +from castellan.common.objects import key + + +class PublicKey(key.Key): + """This class represents public keys.""" + + def __init__(self, algorithm, bit_length, key): + """Create a new PublicKey object. + + The arguments specify the algorithm and bit length for the asymmetric + encryption and the bytes for the key. The bytes should be in a + bytestring. + """ + self._alg = algorithm + self._bit_length = bit_length + self._key = key + + @property + def algorithm(self): + """Returns the algorithm for asymmetric encryption.""" + return self._alg + + @property + def format(self): + """This method returns 'SubjectPublicKeyInfo'.""" + return "SubjectPublicKeyInfo" + + def get_encoded(self): + """Returns the key in its encoded format.""" + return self._key + + @property + def bit_length(self): + """Returns the key length.""" + return self._bit_length + + def __eq__(self, other): + if isinstance(other, PublicKey): + return (self._alg == other._alg and + self._key == other._key) + else: + return False + + def __ne__(self, other): + result = self.__eq__(other) + return not result diff --git a/castellan/key_manager/symmetric_key.py b/castellan/common/objects/symmetric_key.py similarity index 62% rename from castellan/key_manager/symmetric_key.py rename to castellan/common/objects/symmetric_key.py index f0b993d3..e67d47cb 100644 --- a/castellan/key_manager/symmetric_key.py +++ b/castellan/common/objects/symmetric_key.py @@ -19,41 +19,49 @@ Base SymmetricKey Class This module defines the SymmetricKey class. """ -from castellan.key_manager import key +from castellan.common.objects import key class SymmetricKey(key.Key): """This class represents symmetric keys.""" - def __init__(self, alg, key): + def __init__(self, algorithm, bit_length, key): """Create a new SymmetricKey object. - The arguments specify the algorithm for the symmetric encryption and - the bytes for the key. + The arguments specify the algorithm and bit length for the symmetric + encryption and the bytes for the key in a bytestring. """ - self.alg = alg - self.key = key + self._alg = algorithm + self._bit_length = bit_length + self._key = key - def get_algorithm(self): + @property + def algorithm(self): """Returns the algorithm for symmetric encryption.""" - return self.alg + return self._alg - def get_format(self): + @property + def format(self): """This method returns 'RAW'.""" return "RAW" def get_encoded(self): """Returns the key in its encoded format.""" - return self.key + return self._key + + @property + def bit_length(self): + """Returns the key length.""" + return self._bit_length def __eq__(self, other): if isinstance(other, SymmetricKey): - return (self.alg == other.alg and - self.key == other.key) - return NotImplemented + return (self._alg == other._alg and + self._bit_length == other._bit_length and + self._key == other._key) + else: + return False def __ne__(self, other): result = self.__eq__(other) - if result is NotImplemented: - return result return not result diff --git a/castellan/common/objects/x_509.py b/castellan/common/objects/x_509.py new file mode 100644 index 00000000..717c90c7 --- /dev/null +++ b/castellan/common/objects/x_509.py @@ -0,0 +1,52 @@ +# 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. + +""" +X509 Class + +This module defines the X509 class, used to represent X.509 certificates. +""" + +from castellan.common.objects import certificate + + +class X509(certificate.Certificate): + """This class represents X.509 certificates.""" + + def __init__(self, data): + """Create a new X509 object. + + The data should be in a bytestring. + """ + self._data = data + + @property + def format(self): + """This method returns 'X.509'.""" + return "X.509" + + def get_encoded(self): + """Returns the data in its encoded format.""" + return self._data + + def __eq__(self, other): + if isinstance(other, X509): + return (self._data == other._data) + else: + return False + + def __ne__(self, other): + result = self.__eq__(other) + return not result diff --git a/castellan/key_manager/barbican_key_manager.py b/castellan/key_manager/barbican_key_manager.py index 8240acf0..4d227d2c 100644 --- a/castellan/key_manager/barbican_key_manager.py +++ b/castellan/key_manager/barbican_key_manager.py @@ -25,8 +25,8 @@ from oslo_log import log as logging from oslo_utils import excutils from castellan.common import exception +from castellan.common.objects import symmetric_key as sym_key from castellan.key_manager import key_manager -from castellan.key_manager import symmetric_key as key_manager_key from castellan.openstack.common import _i18n as u from six.moves import urllib @@ -152,8 +152,8 @@ class BarbicanKeyManager(key_manager.KeyManager): barbican_client = self._get_barbican_client(context) try: - if key.get_algorithm(): - algorithm = key.get_algorithm() + if key.algorithm: + algorithm = key.algorithm encoded_key = key.get_encoded() # TODO(kfarr) add support for objects other than symmetric keys secret = barbican_client.secrets.create(payload=encoded_key, @@ -183,7 +183,7 @@ class BarbicanKeyManager(key_manager.KeyManager): secret = self._get_secret(context, key_id) secret_data = self._get_secret_data(secret) # TODO(kfarr) modify to support other types of keys - key = key_manager_key.SymmetricKey(secret.algorithm, secret_data) + key = sym_key.SymmetricKey(secret.algorithm, secret_data) copy_uuid = self.store_key(context, key, secret.expiration) return copy_uuid except (barbican_exceptions.HTTPAuthError, @@ -266,7 +266,9 @@ class BarbicanKeyManager(key_manager.KeyManager): secret = self._get_secret(context, key_id) secret_data = self._get_secret_data(secret) # TODO(kfarr) add support for other objects - key = key_manager_key.SymmetricKey(secret.algorithm, secret_data) + key = sym_key.SymmetricKey(secret.algorithm, + secret.bit_length, + secret_data) return key except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, diff --git a/castellan/tests/functional/key_manager/test_barbican_key_manager.py b/castellan/tests/functional/key_manager/test_barbican_key_manager.py index d29d1042..b44a8a88 100644 --- a/castellan/tests/functional/key_manager/test_barbican_key_manager.py +++ b/castellan/tests/functional/key_manager/test_barbican_key_manager.py @@ -26,8 +26,8 @@ from keystoneclient.v3 import client from oslo_context import context from castellan.common import exception +from castellan.common.objects import symmetric_key from castellan.key_manager import barbican_key_manager -from castellan.key_manager import symmetric_key from castellan.tests.functional import config from castellan.tests.functional.key_manager import test_key_manager diff --git a/castellan/tests/unit/key_manager/mock_key_manager.py b/castellan/tests/unit/key_manager/mock_key_manager.py index 8722f7d6..0ff11e99 100644 --- a/castellan/tests/unit/key_manager/mock_key_manager.py +++ b/castellan/tests/unit/key_manager/mock_key_manager.py @@ -26,14 +26,13 @@ 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.common.objects import symmetric_key as sym_key from castellan.key_manager import key_manager -from castellan.key_manager import symmetric_key as sym_key class MockKeyManager(key_manager.KeyManager): @@ -52,8 +51,7 @@ class MockKeyManager(key_manager.KeyManager): def __init__(self): self.keys = {} - def _generate_hex_key(self, **kwargs): - key_length = kwargs.get('key_length', 256) + def _generate_hex_key(self, key_length): # hex digit => 4 bits length = int(key_length / 4) hex_encoded = self._generate_password(length=length, @@ -61,10 +59,12 @@ class MockKeyManager(key_manager.KeyManager): return hex_encoded def _generate_key(self, **kwargs): - _hex = self._generate_hex_key(**kwargs) + key_length = kwargs.get('key_length', 256) + _hex = self._generate_hex_key(key_length) return sym_key.SymmetricKey( 'AES', - array.array('B', binascii.unhexlify(_hex)).tolist()) + key_length, + bytes(binascii.unhexlify(_hex))) def create_key(self, context, **kwargs): """Creates a key. diff --git a/castellan/tests/unit/key_manager/test_barbican_key_manager.py b/castellan/tests/unit/key_manager/test_barbican_key_manager.py index 0bf21878..8aa30214 100644 --- a/castellan/tests/unit/key_manager/test_barbican_key_manager.py +++ b/castellan/tests/unit/key_manager/test_barbican_key_manager.py @@ -17,13 +17,11 @@ Test cases for the barbican key manager. """ -import array - import mock from castellan.common import exception +from castellan.common.objects import symmetric_key as key_manager_key from castellan.key_manager import barbican_key_manager -from castellan.key_manager import symmetric_key as key_manager_key from castellan.tests.unit.key_manager import test_key_manager @@ -75,7 +73,8 @@ class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def fake_sym_key(alg, key): self.mock_symKey.get_encoded.return_value = key - self.mock_symKey.get_algorithm.return_value = alg + p = mock.PropertyMock(return_value=alg) + type(self.mock_symKey).algorithm = p return self.mock_symKey self.original_key = key_manager_key.SymmetricKey key_manager_key.SymmetricKey = fake_sym_key @@ -184,8 +183,10 @@ class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def test_store_key_base64(self): # Create Key to store - secret_key = array.array('B', [0x01, 0x02, 0xA0, 0xB3]).tolist() - _key = key_manager_key.SymmetricKey('AES', secret_key) + secret_key = bytes(b'\x01\x02\xA0\xB3') + _key = key_manager_key.SymmetricKey('AES', + len(secret_key) * 8, + secret_key) # Define the return values secret = mock.Mock() @@ -203,7 +204,9 @@ class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): def test_store_key_plaintext(self): # Create the plaintext key secret_key_text = "This is a test text key." - _key = key_manager_key.SymmetricKey('AES', secret_key_text) + _key = key_manager_key.SymmetricKey('AES', + len(secret_key_text) * 8, + secret_key_text) # Store the Key self.key_mgr.store_key(self.ctxt, _key) diff --git a/castellan/tests/unit/key_manager/test_key.py b/castellan/tests/unit/key_manager/test_key.py index 20c1db59..9c5df455 100644 --- a/castellan/tests/unit/key_manager/test_key.py +++ b/castellan/tests/unit/key_manager/test_key.py @@ -14,13 +14,10 @@ # under the License. """ -Test cases for the key classes. +Test cases for the symmetric key class. """ -import array -import binascii - -from castellan.key_manager import symmetric_key as sym_key +from castellan.common.objects import symmetric_key as sym_key from castellan.tests import base @@ -38,22 +35,28 @@ class KeyTestCase(base.TestCase): class SymmetricKeyTestCase(KeyTestCase): def _create_key(self): - return sym_key.SymmetricKey(self.algorithm, self.encoded) + return sym_key.SymmetricKey(self.algorithm, + self.bit_length, + self.encoded) def setUp(self): self.algorithm = 'AES' - self.encoded = array.array('B', binascii.unhexlify('0' * 64)).tolist() + self.encoded = bytes(b'0' * 64) + self.bit_length = len(self.encoded) * 8 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') + self.assertEqual('RAW', self.key.format) def test_get_encoded(self): - self.assertEqual(self.key.get_encoded(), self.encoded) + self.assertEqual(self.encoded, self.key.get_encoded()) + + def test_get_algorithm(self): + self.assertEqual(self.algorithm, self.key.algorithm) + + def test_get_bit_length(self): + self.assertEqual(self.bit_length, self.key.bit_length) def test___eq__(self): self.assertTrue(self.key == self.key) diff --git a/castellan/tests/unit/key_manager/test_mock_key_manager.py b/castellan/tests/unit/key_manager/test_mock_key_manager.py index ef8bf5b6..26ce6688 100644 --- a/castellan/tests/unit/key_manager/test_mock_key_manager.py +++ b/castellan/tests/unit/key_manager/test_mock_key_manager.py @@ -17,13 +17,10 @@ Test cases for the mock key manager. """ -import array -import binascii - from oslo_context import context from castellan.common import exception -from castellan.key_manager import symmetric_key as sym_key +from castellan.common.objects import symmetric_key as sym_key from castellan.tests.unit.key_manager import mock_key_manager as mock_key_mgr from castellan.tests.unit.key_manager import test_key_manager as test_key_mgr @@ -55,8 +52,8 @@ class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): self.key_mgr.create_key, None) def test_store_and_get_key(self): - secret_key = array.array('B', binascii.unhexlify('0' * 64)).tolist() - _key = sym_key.SymmetricKey('AES', secret_key) + secret_key = bytes(b'0' * 64) + _key = sym_key.SymmetricKey('AES', 64 * 8, secret_key) key_id = self.key_mgr.store_key(self.context, _key) actual_key = self.key_mgr.get_key(self.context, key_id)