Add managed objects hierarchy

Castellan will support multiple objects, not just symmetric keys. The bytes of
the managed object are returned as bytestrings.

Change-Id: If75ff5d458604a8210980a4f50d1e4fc27d2b037
This commit is contained in:
Kaitlin Farr 2015-06-12 12:41:26 -04:00
parent 573235913f
commit 39e139f88e
16 changed files with 444 additions and 64 deletions

View File

View File

@ -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."""

View File

@ -21,17 +21,19 @@ represent all encryption keys. The basis for this class was copied
from Java. from Java.
""" """
from castellan.common.objects import managed_object
import abc import abc
import six import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class Key(object): class Key(managed_object.ManagedObject):
"""Base class to represent all keys.""" """Base class to represent all keys."""
@abc.abstractmethod @abc.abstractproperty
def get_algorithm(self): def algorithm(self):
"""Returns the key's algorithm. """Returns the key's algorithm.
Returns the key's algorithm. For example, "DSA" indicates that this key Returns the key's algorithm. For example, "DSA" indicates that this key
@ -39,15 +41,12 @@ class Key(object):
""" """
pass pass
@abc.abstractmethod @abc.abstractproperty
def get_format(self): def bit_length(self):
"""Returns the encoding format. """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 pass
@abc.abstractmethod
def get_encoded(self):
"""Returns the key in the format specified by its encoding."""
pass

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -19,41 +19,49 @@ Base SymmetricKey Class
This module defines the SymmetricKey class. This module defines the SymmetricKey class.
""" """
from castellan.key_manager import key from castellan.common.objects import key
class SymmetricKey(key.Key): class SymmetricKey(key.Key):
"""This class represents symmetric keys.""" """This class represents symmetric keys."""
def __init__(self, alg, key): def __init__(self, algorithm, bit_length, key):
"""Create a new SymmetricKey object. """Create a new SymmetricKey object.
The arguments specify the algorithm for the symmetric encryption and The arguments specify the algorithm and bit length for the symmetric
the bytes for the key. encryption and the bytes for the key in a bytestring.
""" """
self.alg = alg self._alg = algorithm
self.key = key self._bit_length = bit_length
self._key = key
def get_algorithm(self): @property
def algorithm(self):
"""Returns the algorithm for symmetric encryption.""" """Returns the algorithm for symmetric encryption."""
return self.alg return self._alg
def get_format(self): @property
def format(self):
"""This method returns 'RAW'.""" """This method returns 'RAW'."""
return "RAW" return "RAW"
def get_encoded(self): def get_encoded(self):
"""Returns the key in its encoded format.""" """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): def __eq__(self, other):
if isinstance(other, SymmetricKey): if isinstance(other, SymmetricKey):
return (self.alg == other.alg and return (self._alg == other._alg and
self.key == other.key) self._bit_length == other._bit_length and
return NotImplemented self._key == other._key)
else:
return False
def __ne__(self, other): def __ne__(self, other):
result = self.__eq__(other) result = self.__eq__(other)
if result is NotImplemented:
return result
return not result return not result

View File

@ -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

View File

@ -25,8 +25,8 @@ from oslo_log import log as logging
from oslo_utils import excutils from oslo_utils import excutils
from castellan.common import exception 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 key_manager
from castellan.key_manager import symmetric_key as key_manager_key
from castellan.openstack.common import _i18n as u from castellan.openstack.common import _i18n as u
from six.moves import urllib from six.moves import urllib
@ -152,8 +152,8 @@ class BarbicanKeyManager(key_manager.KeyManager):
barbican_client = self._get_barbican_client(context) barbican_client = self._get_barbican_client(context)
try: try:
if key.get_algorithm(): if key.algorithm:
algorithm = key.get_algorithm() algorithm = key.algorithm
encoded_key = key.get_encoded() encoded_key = key.get_encoded()
# TODO(kfarr) add support for objects other than symmetric keys # TODO(kfarr) add support for objects other than symmetric keys
secret = barbican_client.secrets.create(payload=encoded_key, 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 = self._get_secret(context, key_id)
secret_data = self._get_secret_data(secret) secret_data = self._get_secret_data(secret)
# TODO(kfarr) modify to support other types of keys # 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) copy_uuid = self.store_key(context, key, secret.expiration)
return copy_uuid return copy_uuid
except (barbican_exceptions.HTTPAuthError, except (barbican_exceptions.HTTPAuthError,
@ -266,7 +266,9 @@ class BarbicanKeyManager(key_manager.KeyManager):
secret = self._get_secret(context, key_id) secret = self._get_secret(context, key_id)
secret_data = self._get_secret_data(secret) secret_data = self._get_secret_data(secret)
# TODO(kfarr) add support for other objects # 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 return key
except (barbican_exceptions.HTTPAuthError, except (barbican_exceptions.HTTPAuthError,
barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPClientError,

View File

@ -26,8 +26,8 @@ from keystoneclient.v3 import client
from oslo_context import context from oslo_context import context
from castellan.common import exception 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 barbican_key_manager
from castellan.key_manager import symmetric_key
from castellan.tests.functional import config from castellan.tests.functional import config
from castellan.tests.functional.key_manager import test_key_manager from castellan.tests.functional.key_manager import test_key_manager

View File

@ -26,14 +26,13 @@ Keys created in one instance will not be accessible from other instances of
this class. this class.
""" """
import array
import binascii import binascii
import random import random
import uuid import uuid
from castellan.common import exception 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 key_manager
from castellan.key_manager import symmetric_key as sym_key
class MockKeyManager(key_manager.KeyManager): class MockKeyManager(key_manager.KeyManager):
@ -52,8 +51,7 @@ class MockKeyManager(key_manager.KeyManager):
def __init__(self): def __init__(self):
self.keys = {} self.keys = {}
def _generate_hex_key(self, **kwargs): def _generate_hex_key(self, key_length):
key_length = kwargs.get('key_length', 256)
# hex digit => 4 bits # hex digit => 4 bits
length = int(key_length / 4) length = int(key_length / 4)
hex_encoded = self._generate_password(length=length, hex_encoded = self._generate_password(length=length,
@ -61,10 +59,12 @@ class MockKeyManager(key_manager.KeyManager):
return hex_encoded return hex_encoded
def _generate_key(self, **kwargs): 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( return sym_key.SymmetricKey(
'AES', 'AES',
array.array('B', binascii.unhexlify(_hex)).tolist()) key_length,
bytes(binascii.unhexlify(_hex)))
def create_key(self, context, **kwargs): def create_key(self, context, **kwargs):
"""Creates a key. """Creates a key.

View File

@ -17,13 +17,11 @@
Test cases for the barbican key manager. Test cases for the barbican key manager.
""" """
import array
import mock import mock
from castellan.common import exception 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 barbican_key_manager
from castellan.key_manager import symmetric_key as key_manager_key
from castellan.tests.unit.key_manager import test_key_manager 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): def fake_sym_key(alg, key):
self.mock_symKey.get_encoded.return_value = 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 return self.mock_symKey
self.original_key = key_manager_key.SymmetricKey self.original_key = key_manager_key.SymmetricKey
key_manager_key.SymmetricKey = fake_sym_key key_manager_key.SymmetricKey = fake_sym_key
@ -184,8 +183,10 @@ class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase):
def test_store_key_base64(self): def test_store_key_base64(self):
# Create Key to store # Create Key to store
secret_key = array.array('B', [0x01, 0x02, 0xA0, 0xB3]).tolist() secret_key = bytes(b'\x01\x02\xA0\xB3')
_key = key_manager_key.SymmetricKey('AES', secret_key) _key = key_manager_key.SymmetricKey('AES',
len(secret_key) * 8,
secret_key)
# Define the return values # Define the return values
secret = mock.Mock() secret = mock.Mock()
@ -203,7 +204,9 @@ class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase):
def test_store_key_plaintext(self): def test_store_key_plaintext(self):
# Create the plaintext key # Create the plaintext key
secret_key_text = "This is a test text 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 # Store the Key
self.key_mgr.store_key(self.ctxt, _key) self.key_mgr.store_key(self.ctxt, _key)

View File

@ -14,13 +14,10 @@
# under the License. # under the License.
""" """
Test cases for the key classes. Test cases for the symmetric key class.
""" """
import array from castellan.common.objects import symmetric_key as sym_key
import binascii
from castellan.key_manager import symmetric_key as sym_key
from castellan.tests import base from castellan.tests import base
@ -38,22 +35,28 @@ class KeyTestCase(base.TestCase):
class SymmetricKeyTestCase(KeyTestCase): class SymmetricKeyTestCase(KeyTestCase):
def _create_key(self): 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): def setUp(self):
self.algorithm = 'AES' 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() super(SymmetricKeyTestCase, self).setUp()
def test_get_algorithm(self):
self.assertEqual(self.key.get_algorithm(), self.algorithm)
def test_get_format(self): def test_get_format(self):
self.assertEqual(self.key.get_format(), 'RAW') self.assertEqual('RAW', self.key.format)
def test_get_encoded(self): 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): def test___eq__(self):
self.assertTrue(self.key == self.key) self.assertTrue(self.key == self.key)

View File

@ -17,13 +17,10 @@
Test cases for the mock key manager. Test cases for the mock key manager.
""" """
import array
import binascii
from oslo_context import context from oslo_context import context
from castellan.common import exception 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 mock_key_manager as mock_key_mgr
from castellan.tests.unit.key_manager import test_key_manager as test_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) self.key_mgr.create_key, None)
def test_store_and_get_key(self): def test_store_and_get_key(self):
secret_key = array.array('B', binascii.unhexlify('0' * 64)).tolist() secret_key = bytes(b'0' * 64)
_key = sym_key.SymmetricKey('AES', secret_key) _key = sym_key.SymmetricKey('AES', 64 * 8, secret_key)
key_id = self.key_mgr.store_key(self.context, _key) key_id = self.key_mgr.store_key(self.context, _key)
actual_key = self.key_mgr.get_key(self.context, key_id) actual_key = self.key_mgr.get_key(self.context, key_id)