Merge "Update crypto plugin interface to support Dogtag"

This commit is contained in:
Jenkins 2014-04-21 16:01:24 +00:00 committed by Gerrit Code Review
commit e4e7ce18c9
6 changed files with 198 additions and 68 deletions

View File

@ -245,11 +245,12 @@ class CryptoExtensionManager(named.NamedExtensionManager):
kek_datum, kek_meta_dto = self._find_or_create_kek_objects(
encrypting_plugin, tenant, kek_repo)
encrypt_dto = plugin_mod.EncryptDTO(unencrypted)
# Create an encrypted datum instance and add the encrypted cypher text.
datum = models.EncryptedDatum(secret, kek_datum)
datum.content_type = content_type
datum.cypher_text, datum.kek_meta_extended = encrypting_plugin.encrypt(
unencrypted, kek_meta_dto, tenant.keystone_id
encrypt_dto, kek_meta_dto, tenant.keystone_id
)
# Convert binary data into a text-based format.
@ -279,10 +280,11 @@ class CryptoExtensionManager(named.NamedExtensionManager):
#TODO(jwood) Figure out by storing binary (BYTEA) data in
# Postgres isn't working.
encrypted = base64.b64decode(datum.cypher_text)
decrypt_dto = plugin_mod.DecryptDTO(encrypted)
# Decrypt the secret.
unencrypted = decrypting_plugin \
.decrypt(encrypted,
.decrypt(decrypt_dto,
kek_meta_dto,
datum.kek_meta_extended,
tenant.keystone_id)
@ -313,16 +315,28 @@ class CryptoExtensionManager(named.NamedExtensionManager):
else:
raise CryptoSupportedPluginNotFound()
# Create the secret.
kek_datum, kek_meta_dto = self._find_or_create_kek_objects(
encrypting_plugin, tenant, kek_repo)
data_key = encrypting_plugin.create(secret.bit_length,
generation_type,
secret.algorithm,
secret.mode)
# Create an encrypted datum instance and add the created cypher text.
datum = models.EncryptedDatum(secret, kek_datum)
datum.content_type = content_type
# Encrypt the secret.
return self.encrypt(data_key, content_type, None, secret, tenant,
kek_repo)
generate_dto = plugin_mod.GenerateDTO(generation_type,
secret.algorithm,
secret.bit_length,
secret.mode)
# Create the encrypted secret.
datum.cypher_text, datum.kek_meta_extended =\
encrypting_plugin.generate(
generate_dto, kek_meta_dto, tenant.keystone_id)
# Convert binary data into a text-based format.
# TODO(jwood) Figure out by storing binary (BYTEA) data in Postgres
# isn't working.
datum.cypher_text = base64.b64encode(datum.cypher_text)
return datum
def _determine_type(self, algorithm):
"""Determines the type (symmetric only for now) based on algorithm"""

View File

@ -124,12 +124,12 @@ class P11CryptoPlugin(plugin.CryptoPluginBase):
)
)
def encrypt(self, unencrypted, kek_meta_dto, keystone_id):
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
key = self._get_key_by_label(kek_meta_dto.kek_label)
iv = self._generate_iv()
gcm = self._build_gcm_params(iv)
mech = PyKCS11.Mechanism(self.algorithm, gcm)
encrypted = self.session.encrypt(key, unencrypted, mech)
encrypted = self.session.encrypt(key, encrypt_dto.unencrypted, mech)
cyphertext = b''.join(chr(i) for i in encrypted)
kek_meta_extended = json.dumps({
'iv': base64.b64encode(iv)
@ -137,13 +137,14 @@ class P11CryptoPlugin(plugin.CryptoPluginBase):
return cyphertext, kek_meta_extended
def decrypt(self, encrypted, kek_meta_dto, kek_meta_extended, keystone_id):
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
key = self._get_key_by_label(kek_meta_dto.kek_label)
meta_extended = json.loads(kek_meta_extended)
iv = base64.b64decode(meta_extended['iv'])
gcm = self._build_gcm_params(iv)
mech = PyKCS11.Mechanism(self.algorithm, gcm)
decrypted = self.session.decrypt(key, encrypted, mech)
decrypted = self.session.decrypt(key, decrypt_dto.encrypted, mech)
secret = b''.join(chr(i) for i in decrypted)
return secret
@ -161,12 +162,12 @@ class P11CryptoPlugin(plugin.CryptoPluginBase):
return kek_meta_dto
def create(self, bit_length, type_enum, algorithm=None, mode=None):
byte_length = bit_length / 8
def generate(self, generate_dto, kek_meta_dto, keystone_id):
byte_length = generate_dto.bit_length / 8
rand = self.session.generateRandom(byte_length)
if len(rand) != byte_length:
raise P11CryptoPluginException()
return rand
return self.encrypt(plugin.EncryptDTO(rand), kek_meta_dto, keystone_id)
def supports(self, type_enum, algorithm=None, mode=None):
if type_enum == plugin.PluginSupportTypes.ENCRYPT_DECRYPT:

View File

@ -64,6 +64,45 @@ class KEKMetaDTO(object):
self.plugin_meta = kek_datum.plugin_meta
class GenerateDTO(object):
"""
Data transfer object for secret generation.
All data needed to determine the kinds of secrets to be generated
is passed in instances of this object.
"""
def __init__(self, generation_type, algorithm, bit_length, mode):
self.generation_type = generation_type
self.algorithm = algorithm
self.bit_length = bit_length
self.mode = mode
class DecryptDTO(object):
"""
Data Transfer object for secret decryption.
All data needed to determine the kinds of secrets to be decrypted
is passed in instances of this object.
"""
def __init__(self, encrypted):
self.encrypted = encrypted
class EncryptDTO(object):
"""
Data Transfer object for secret encryption.
All data needed to determine the kinds of secrets to be encrypted
is passed in instances of this object.
"""
def __init__(self, unencrypted):
self.unencrypted = unencrypted
def indicate_bind_completed(kek_meta_dto, kek_datum):
"""
Updates the supplied kek_datum instance per the contents of the supplied
@ -88,10 +127,11 @@ class CryptoPluginBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def encrypt(self, unencrypted, kek_meta_dto, keystone_id):
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
"""Encrypt unencrypted data in the context of the provided tenant.
:param unencrypted: byte data to be encrypted.
:param encrypt_dto: data transfer object containing the byte data
to be encrypted.
:param kek_meta_dto: Key encryption key metadata to use for encryption.
:param keystone_id: keystone_id associated with the unencrypted data.
:returns: encrypted data and kek_meta_extended, the former the
@ -103,10 +143,12 @@ class CryptoPluginBase(object):
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def decrypt(self, encrypted, kek_meta_dto, kek_meta_extended, keystone_id):
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
"""Decrypt encrypted_datum in the context of the provided tenant.
:param encrypted: cyphertext to be decrypted.
:param decrypt_dto: data transfer object containing the cyphertext
to be decrypted.
:param kek_meta_dto: Key encryption key metadata to use for decryption
:param kek_meta_extended: Optional per-secret KEK metadata to use for
decryption.
@ -139,8 +181,21 @@ class CryptoPluginBase(object):
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def create(self, bit_length, type_enum, algorithm=None, mode=None):
"""Create a new key."""
def generate(self, generate_dto, kek_meta_dto, keystone_id):
"""
Generate a new key.
:param generate_dto: data transfer object for the record
associated with this generation request. Some relevant
parameters can be extracted from this object, including
bit_length, algorithm and mode
:param kek_meta_dto: Key encryption key metadata to use for decryption
:param keystone_id: keystone_id associated with the data.
:returns: encrypted data and kek_meta_extended, the former the
resultant cypher text, the latter being optional per-secret metadata
needed to decrypt (over and above the per-tenant metadata managed
outside of the plugins)
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
@ -172,7 +227,8 @@ class SimpleCryptoPlugin(CryptoPluginBase):
unpadded = unencrypted[:-pad_length]
return unpadded
def encrypt(self, unencrypted, kek_meta_dto, keystone_id):
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
unencrypted = encrypt_dto.unencrypted
if not isinstance(unencrypted, str):
raise ValueError('Unencrypted data must be a byte type, '
'but was {0}'.format(type(unencrypted)))
@ -184,7 +240,9 @@ class SimpleCryptoPlugin(CryptoPluginBase):
return cyphertext, None
def decrypt(self, encrypted, kek_meta_dto, kek_meta_extended, keystone_id):
def decrypt(self, encrypted_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
encrypted = encrypted_dto.encrypted
iv = encrypted[:self.block_size]
cypher_text = encrypted[self.block_size:]
decryptor = AES.new(self.kek, AES.MODE_CBC, iv)
@ -198,9 +256,10 @@ class SimpleCryptoPlugin(CryptoPluginBase):
kek_meta_dto.plugin_meta = None
return kek_meta_dto
def create(self, bit_length, type_enum, algorithm=None, mode=None):
byte_length = bit_length / 8
return Random.get_random_bytes(byte_length)
def generate(self, generate_dto, kek_meta_dto, keystone_id):
byte_length = int(generate_dto.bit_length) / 8
unencrypted = Random.get_random_bytes(byte_length)
return self.encrypt(EncryptDTO(unencrypted), kek_meta_dto, keystone_id)
def supports(self, type_enum, algorithm=None, mode=None):
if type_enum == PluginSupportTypes.ENCRYPT_DECRYPT:

View File

@ -25,16 +25,16 @@ from barbican.crypto.plugin import CryptoPluginBase, PluginSupportTypes
class TestSupportsCryptoPlugin(CryptoPluginBase):
"""Crypto plugin for testing supports."""
def encrypt(self, unencrypted, kek_meta_dto, tenant):
def encrypt(self, encrypt_dto, kek_meta_dto, tenant):
raise NotImplementedError()
def decrypt(self, encrypted, kek_meta_dto, kek_meta_extended, tenant):
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended, tenant):
raise NotImplementedError()
def bind_kek_metadata(self, kek_meta_dto):
return None
def create(self, bit_length, type_enum, algorithm=None, mode=None):
def generate(self, generate_dto, type_enum, kek_meta_dto, keystone_id):
raise NotImplementedError()
def supports(self, type_enum, algorithm=None, mode=None):

View File

@ -19,6 +19,7 @@ import testtools
from barbican.crypto import p11_crypto
from barbican.crypto import plugin as plugin_import
from barbican.model import models
class WhenTestingP11CryptoPlugin(testtools.TestCase):
@ -42,28 +43,41 @@ class WhenTestingP11CryptoPlugin(testtools.TestCase):
super(WhenTestingP11CryptoPlugin, self).tearDown()
self.patcher.stop()
def test_create_calls_generate_random(self):
def test_generate_calls_generate_random(self):
self.session.generateRandom.return_value = [1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32]
key = self.plugin.create(
256,
14, 15, 16]
secret = models.Secret()
secret.bit_length = 128
secret.algorithm = "AES"
generate_dto = plugin_import.GenerateDTO(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
"AES"
secret.algorithm,
secret.bit_length,
None)
encrypted, kek_ext = self.plugin.generate(
generate_dto,
mock.MagicMock(),
mock.MagicMock()
)
self.assertEqual(len(key), 32)
self.session.generateRandom.assert_called_once_with(32)
self.session.generateRandom.assert_called_twice_with(16)
def test_create_errors_when_rand_length_is_not_as_requested(self):
def test_generate_errors_when_rand_length_is_not_as_requested(self):
self.session.generateRandom.return_value = [1, 2, 3, 4, 5, 6, 7]
secret = models.Secret()
secret.bit_length = 192
secret.algorithm = "AES"
generate_dto = plugin_import.GenerateDTO(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
secret.algorithm,
secret.bit_length,
None)
self.assertRaises(
p11_crypto.P11CryptoPluginException,
self.plugin.create,
192,
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
"AES",
self.plugin.generate,
generate_dto,
mock.MagicMock(),
mock.MagicMock()
)
def test_raises_error_with_no_library_path(self):
@ -153,7 +167,8 @@ class WhenTestingP11CryptoPlugin(testtools.TestCase):
mech = mock.MagicMock()
self.p11_mock.Mechanism.return_value = mech
self.session.encrypt.return_value = [1, 2, 3, 4, 5]
cyphertext, kek_meta_extended = self.plugin.encrypt(payload,
encrypt_dto = plugin_import.EncryptDTO(payload)
cyphertext, kek_meta_extended = self.plugin.encrypt(encrypt_dto,
mock.MagicMock(),
mock.MagicMock())
@ -172,7 +187,8 @@ class WhenTestingP11CryptoPlugin(testtools.TestCase):
mech = mock.MagicMock()
self.p11_mock.Mechanism.return_value = mech
kek_meta_extended = '{"iv": "AQIDBAUGBwgJCgsMDQ4PEA=="}'
payload = self.plugin.decrypt(ct,
decrypt_dto = plugin_import.DecryptDTO(ct)
payload = self.plugin.decrypt(decrypt_dto,
mock.MagicMock(),
kek_meta_extended,
mock.MagicMock())

View File

@ -19,16 +19,18 @@ from mock import MagicMock
import testtools
from barbican.crypto import plugin
from barbican.model import models
class TestCryptoPlugin(plugin.CryptoPluginBase):
"""Crypto plugin implementation for testing the plugin manager."""
def encrypt(self, unencrypted, kek_meta_dto, keystone_id):
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
cypher_text = b'cypher_text'
return cypher_text, None
def decrypt(self, encrypted, kek_meta_dto, kek_meta_extended, keystone_id):
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
return b'unencrypted_data'
def bind_kek_metadata(self, kek_meta_dto):
@ -38,8 +40,8 @@ class TestCryptoPlugin(plugin.CryptoPluginBase):
kek_meta_dto.plugin_meta = None
return kek_meta_dto
def create(self, bit_length, type_enum, algorithm=None, mode=None):
return "insecure_key"
def generate(self, generate_dto, kek_meta_dto, keystone_id):
return "encrypted insecure key", None
def supports(self, type_enum, algorithm=None, mode=None):
if type_enum == plugin.PluginSupportTypes.ENCRYPT_DECRYPT:
@ -84,55 +86,93 @@ class WhenTestingSimpleCryptoPlugin(testtools.TestCase):
def test_encrypt_unicode_raises_value_error(self):
unencrypted = u'unicode_beer\U0001F37A'
encrypt_dto = plugin.EncryptDTO(unencrypted)
secret = MagicMock()
secret.mime_type = 'text/plain'
self.assertRaises(
ValueError,
self.plugin.encrypt,
unencrypted,
encrypt_dto,
MagicMock(),
MagicMock(),
)
def test_byte_string_encryption(self):
unencrypted = b'some_secret'
encrypted, kek_ext = self.plugin.encrypt(unencrypted,
encrypt_dto = plugin.EncryptDTO(unencrypted)
encrypted, kek_ext = self.plugin.encrypt(encrypt_dto,
MagicMock(),
MagicMock())
decrypted = self.plugin.decrypt(encrypted, MagicMock(),
decrypt_dto = plugin.DecryptDTO(encrypted)
decrypted = self.plugin.decrypt(decrypt_dto, MagicMock(),
kek_ext, MagicMock())
self.assertEqual(unencrypted, decrypted)
def test_random_bytes_encryption(self):
unencrypted = Random.get_random_bytes(10)
encrypted, kek_meta_ext = self.plugin.encrypt(unencrypted,
encrypt_dto = plugin.EncryptDTO(unencrypted)
encrypted, kek_meta_ext = self.plugin.encrypt(encrypt_dto,
MagicMock(), MagicMock())
decrypted = self.plugin.decrypt(encrypted, MagicMock(),
decrypt_dto = plugin.DecryptDTO(encrypted)
decrypted = self.plugin.decrypt(decrypt_dto, MagicMock(),
kek_meta_ext, MagicMock())
self.assertEqual(unencrypted, decrypted)
def test_create_256_bit_key(self):
key = self.plugin.create(
256,
def test_generate_256_bit_key(self):
secret = models.Secret()
secret.bit_length = 256
secret.algorithm = "AES"
generate_dto = plugin.GenerateDTO(
plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
"AES"
secret.algorithm,
secret.bit_length,
secret.mode)
encrypted, kek_ext = self.plugin.generate(
generate_dto,
MagicMock(),
MagicMock()
)
decrypt_dto = plugin.DecryptDTO(encrypted)
key = self.plugin.decrypt(decrypt_dto, MagicMock(),
kek_ext, MagicMock())
self.assertEqual(len(key), 32)
def test_create_192_bit_key(self):
key = self.plugin.create(
192,
def test_generate_192_bit_key(self):
secret = models.Secret()
secret.bit_length = 192
secret.algorithm = "AES"
generate_dto = plugin.GenerateDTO(
plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
"AES"
secret.algorithm,
secret.bit_length,
None)
encrypted, kek_ext = self.plugin.generate(
generate_dto,
MagicMock(),
MagicMock()
)
decrypt_dto = plugin.DecryptDTO(encrypted)
key = self.plugin.decrypt(decrypt_dto, MagicMock(),
kek_ext, MagicMock())
self.assertEqual(len(key), 24)
def test_create_128_bit_key(self):
key = self.plugin.create(
128,
def test_generate_128_bit_key(self):
secret = models.Secret()
secret.bit_length = 128
secret.algorithm = "AES"
generate_dto = plugin.GenerateDTO(
plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
"AES"
secret.algorithm,
secret.bit_length,
None)
encrypted, kek_ext = self.plugin.generate(
generate_dto,
MagicMock(),
MagicMock()
)
decrypt_dto = plugin.DecryptDTO(encrypted)
key = self.plugin.decrypt(decrypt_dto, MagicMock(),
kek_ext, MagicMock())
self.assertEqual(len(key), 16)
def test_supports_encrypt_decrypt(self):