Move crypto package contents to the new plugin structure

Move the HSM-style crypto source modules into the new plugin structure, and
integrate this into the rest of the Barbican API and worker source flows, via
the secret-store to HSM-style plugin adapter store_crypto.py. Removed
crypto source and unit test files from the old locations.

Implements: blueprint restructure-for-plugins
Change-Id: Ie31531ac71a32b14598b903632e8a3127a263e56
This commit is contained in:
jfwood 2014-06-29 17:00:17 -05:00
parent 68a2309949
commit 82561c3653
32 changed files with 1899 additions and 2482 deletions

View File

@ -22,10 +22,10 @@ import pkgutil
from barbican.common import exception
from barbican.common import utils
from barbican.crypto import extension_manager as em
from barbican.openstack.common import gettextutils as u
from barbican.openstack.common import jsonutils as json
from barbican.openstack.common import policy
from barbican.plugin.interface import secret_store as s
LOG = utils.getLogger(__name__)
@ -111,40 +111,34 @@ def generate_safe_exception_message(operation_name, excep):
'please review your '
'user/tenant privileges').format(operation_name)
status = 403
except em.CryptoContentTypeNotSupportedException as cctnse:
except s.SecretContentTypeNotSupportedException as sctnse:
reason = u._("content-type of '{0}' not "
"supported").format(cctnse.content_type)
"supported").format(sctnse.content_type)
status = 400
except em.CryptoContentEncodingNotSupportedException as cc:
except s.SecretContentEncodingNotSupportedException as ce:
reason = u._("content-encoding of '{0}' not "
"supported").format(cc.content_encoding)
"supported").format(ce.content_encoding)
status = 400
except em.CryptoAcceptNotSupportedException as canse:
reason = u._("accept of '{0}' not "
"supported").format(canse.accept)
status = 406
except em.CryptoNoPayloadProvidedException:
reason = u._("No payload provided")
status = 400
except em.CryptoNoSecretOrDataFoundException:
reason = u._("Not Found. Sorry but your secret is in "
"another castle")
status = 404
except em.CryptoPayloadDecodingError:
reason = u._("Problem decoding payload")
status = 400
except em.CryptoContentEncodingMustBeBase64:
reason = u._("Text-based binary secret payloads must "
"specify a content-encoding of 'base64'")
status = 400
except em.CryptoAlgorithmNotSupportedException:
reason = u._("No plugin was found that supports the "
"requested algorithm")
status = 400
except em.CryptoSupportedPluginNotFound:
except s.SecretStorePluginNotFound:
reason = u._("No plugin was found that could support "
"your request")
status = 400
except s.SecretPayloadDecodingError:
reason = u._("Problem decoding payload")
status = 400
except s.SecretContentEncodingMustBeBase64:
reason = u._("Text-based binary secret payloads must "
"specify a content-encoding of 'base64'")
status = 400
except s.SecretNotFoundException:
reason = u._("Not Found. Sorry but your secret is in "
"another castle")
status = 404
except s.SecretAlgorithmNotSupportedException:
reason = u._("Requested algorithm is not supported")
status = 400
except exception.NoDataToProcess:
reason = u._("No information provided to process")
status = 400
@ -152,6 +146,7 @@ def generate_safe_exception_message(operation_name, excep):
reason = u._("Provided information too large "
"to process")
status = 413
except Exception:
message = u._('{0} failure seen - please contact site '
'administrator.').format(operation_name)

View File

@ -36,7 +36,6 @@ from barbican.api.controllers import secrets
from barbican.api.controllers import transportkeys
from barbican.api.controllers import versions
from barbican.common import config
from barbican.crypto import extension_manager as ext
from barbican.openstack.common import log
from barbican import queue
@ -92,15 +91,13 @@ def create_main_app(global_config, **local_conf):
config.parse_args()
log.setup('barbican')
config.setup_remote_pydev_debug()
# Crypto Plugin Manager
crypto_mgr = ext.CryptoExtensionManager()
# Queuing initialization
CONF = cfg.CONF
queue.init(CONF)
class RootController(object):
secrets = secrets.SecretsController(crypto_mgr)
secrets = secrets.SecretsController()
orders = orders.OrdersController()
containers = containers.ContainersController()
transport_keys = transportkeys.TransportKeysController()

View File

@ -22,9 +22,11 @@ from barbican.common import exception
from barbican.common import resources as res
from barbican.common import utils
from barbican.common import validators
from barbican.crypto import mime_types
from barbican.model import repositories as repo
from barbican.openstack.common import gettextutils as u
from barbican.plugin import resources as plugin
from barbican.plugin import util as putil
LOG = utils.getLogger(__name__)
@ -51,16 +53,18 @@ def _secret_already_has_data():
class SecretController(object):
"""Handles Secret retrieval and deletion requests."""
def __init__(self, secret_id, crypto_manager,
def __init__(self, secret_id,
tenant_repo=None, secret_repo=None, datum_repo=None,
kek_repo=None):
kek_repo=None, secret_meta_repo=None):
LOG.debug('=== Creating SecretController ===')
self.secret_id = secret_id
self.crypto_manager = crypto_manager
self.tenant_repo = tenant_repo or repo.TenantRepo()
self.repo = secret_repo or repo.SecretRepo()
self.datum_repo = datum_repo or repo.EncryptedDatumRepo()
self.kek_repo = kek_repo or repo.KEKDatumRepo()
#TODO(john-wood-w) Remove passed-in repositories in favor of
# repository factories and patches in unit tests.
self.repos = repo.Repositories(tenant_repo=tenant_repo,
secret_repo=secret_repo,
datum_repo=datum_repo,
kek_repo=kek_repo)
@pecan.expose(generic=True)
@allow_all_content_types
@ -68,26 +72,26 @@ class SecretController(object):
@controllers.handle_rbac('secret:get')
def index(self, keystone_id):
secret = self.repo.get(entity_id=self.secret_id,
keystone_id=keystone_id,
suppress_exception=True)
secret = self.repos.secret_repo.get(entity_id=self.secret_id,
keystone_id=keystone_id,
suppress_exception=True)
if not secret:
_secret_not_found()
if controllers.is_json_request_accept(pecan.request):
# Metadata-only response, no decryption necessary.
# Metadata-only response, no secret retrieval is necessary.
pecan.override_template('json', 'application/json')
secret_fields = mime_types.augment_fields_with_content_types(
secret_fields = putil.mime_types.augment_fields_with_content_types(
secret)
return hrefs.convert_to_hrefs(keystone_id, secret_fields)
else:
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
tenant = res.get_or_create_tenant(keystone_id,
self.repos.tenant_repo)
pecan.override_template('', pecan.request.accept.header_value)
return self.crypto_manager.decrypt(
pecan.request.accept.header_value,
secret,
tenant
)
return plugin.get_secret(pecan.request.accept.header_value,
secret,
tenant)
@index.when(method='PUT')
@allow_all_content_types
@ -104,61 +108,68 @@ class SecretController(object):
)
)
secret = self.repo.get(entity_id=self.secret_id,
keystone_id=keystone_id,
suppress_exception=True)
if not secret:
payload = pecan.request.body
if not payload:
raise exception.NoDataToProcess()
if validators.secret_too_big(payload):
raise exception.LimitExceeded()
secret_model = self.repos.secret_repo.get(entity_id=self.secret_id,
keystone_id=keystone_id,
suppress_exception=True)
if not secret_model:
_secret_not_found()
if secret.encrypted_data:
if secret_model.encrypted_data:
_secret_already_has_data()
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
tenant_model = res.get_or_create_tenant(keystone_id,
self.repos.tenant_repo)
content_type = pecan.request.content_type
content_encoding = pecan.request.headers.get('Content-Encoding')
res.create_encrypted_datum(secret,
pecan.request.body,
content_type,
content_encoding,
tenant,
self.crypto_manager,
self.datum_repo,
self.kek_repo)
plugin.store_secret(payload, content_type,
content_encoding, secret_model.to_dict_fields,
secret_model, tenant_model, self.repos)
@index.when(method='DELETE')
@controllers.handle_exceptions(u._('Secret deletion'))
@controllers.handle_rbac('secret:delete')
def on_delete(self, keystone_id, **kwargs):
try:
self.repo.delete_entity_by_id(entity_id=self.secret_id,
keystone_id=keystone_id)
except exception.NotFound:
LOG.exception('Problem deleting secret')
secret_model = self.repos.secret_repo.get(entity_id=self.secret_id,
keystone_id=keystone_id,
suppress_exception=True)
if not secret_model:
_secret_not_found()
plugin.delete_secret(secret_model, keystone_id, self.repos)
class SecretsController(object):
"""Handles Secret creation requests."""
def __init__(self, crypto_manager,
def __init__(self,
tenant_repo=None, secret_repo=None,
tenant_secret_repo=None, datum_repo=None, kek_repo=None):
tenant_secret_repo=None, datum_repo=None, kek_repo=None,
secret_meta_repo=None):
LOG.debug('Creating SecretsController')
self.tenant_repo = tenant_repo or repo.TenantRepo()
self.secret_repo = secret_repo or repo.SecretRepo()
self.tenant_secret_repo = tenant_secret_repo or repo.TenantSecretRepo()
self.datum_repo = datum_repo or repo.EncryptedDatumRepo()
self.kek_repo = kek_repo or repo.KEKDatumRepo()
self.crypto_manager = crypto_manager
self.validator = validators.NewSecretValidator()
self.repos = repo.Repositories(tenant_repo=tenant_repo,
tenant_secret_repo=tenant_secret_repo,
secret_repo=secret_repo,
datum_repo=datum_repo,
kek_repo=kek_repo,
secret_meta_repo=secret_meta_repo)
@pecan.expose()
def _lookup(self, secret_id, *remainder):
return SecretController(secret_id, self.crypto_manager,
self.tenant_repo, self.secret_repo,
self.datum_repo, self.kek_repo), remainder
return SecretController(secret_id,
self.repos.tenant_repo,
self.repos.secret_repo,
self.repos.datum_repo,
self.repos.kek_repo,
self.repos.secret_meta_repo), remainder
@pecan.expose(generic=True, template='json')
@controllers.handle_exceptions(u._('Secret(s) retrieval'))
@ -179,7 +190,7 @@ class SecretsController(object):
# the default should be used.
bits = 0
result = self.secret_repo.get_by_create_date(
result = self.repos.secret_repo.get_by_create_date(
keystone_id,
offset_arg=kw.get('offset', 0),
limit_arg=kw.get('limit', None),
@ -196,8 +207,8 @@ class SecretsController(object):
secrets_resp_overall = {'secrets': [],
'total': total}
else:
secret_fields = lambda s: mime_types\
.augment_fields_with_content_types(s)
secret_fields = lambda sf: putil.mime_types\
.augment_fields_with_content_types(sf)
secrets_resp = [
hrefs.convert_to_hrefs(keystone_id, secret_fields(s))
for s in secrets
@ -217,13 +228,14 @@ class SecretsController(object):
LOG.debug('Start on_post for tenant-ID {0}:...'.format(keystone_id))
data = api.load_body(pecan.request, validator=self.validator)
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
tenant = res.get_or_create_tenant(keystone_id, self.repos.tenant_repo)
new_secret = res.create_secret(data, tenant, self.crypto_manager,
self.secret_repo,
self.tenant_secret_repo,
self.datum_repo,
self.kek_repo)
new_secret = plugin.store_secret(data.get('payload'),
data.get('payload_content_type',
'application/octet-stream'),
data.get('payload_content_encoding'),
data, None, tenant,
self.repos)
pecan.response.status = 201
pecan.response.headers['Location'] = '/{0}/secrets/{1}'.format(

View File

@ -21,9 +21,9 @@ import six
from barbican.common import exception
from barbican.common import utils
from barbican.crypto import mime_types
from barbican.openstack.common import gettextutils as u
from barbican.openstack.common import timeutils
from barbican.plugin.util import mime_types
LOG = utils.getLogger(__name__)

View File

@ -1,18 +0,0 @@
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# 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.
"""
Encryption/decryption services for Barbican.
"""

View File

@ -1,237 +0,0 @@
# Copyright (c) 2014 Red Hat, Inc.
#
# 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.
import base64
import os
import uuid
from oslo.config import cfg
import pki
import pki.client
import pki.cryptoutil as cryptoutil
import pki.key as key
import pki.kraclient
from barbican.common import exception
from barbican.crypto import plugin
from barbican.openstack.common import gettextutils as u
CONF = cfg.CONF
dogtag_crypto_plugin_group = cfg.OptGroup(name='dogtag_crypto_plugin',
title="Dogtag Crypto Plugin Options")
dogtag_crypto_plugin_opts = [
cfg.StrOpt('pem_path',
help=u._('Path to PEM file for authentication')),
cfg.StrOpt('pem_password',
help=u._('Password to unlock PEM file')),
cfg.StrOpt('drm_host',
default="localhost",
help=u._('Hostname for the DRM')),
cfg.StrOpt('drm_port',
default="8443",
help=u._('Port for the DRM')),
cfg.StrOpt('nss_db_path',
help=u._('Path to the NSS certificate database')),
cfg.StrOpt('nss_password',
help=u._('Password for NSS certificate database'))
]
CONF.register_group(dogtag_crypto_plugin_group)
CONF.register_opts(dogtag_crypto_plugin_opts, group=dogtag_crypto_plugin_group)
class DogtagPluginAlgorithmException(exception.BarbicanException):
message = u._("Invalid algorithm passed in")
class DogtagCryptoPlugin(plugin.CryptoPluginBase):
"""Dogtag implementation of the crypto plugin with DRM as the backend."""
TRANSPORT_NICK = "DRM transport cert"
def __init__(self, conf=CONF):
"""Constructor - create the keyclient."""
pem_path = conf.dogtag_crypto_plugin.pem_path
if pem_path is None:
raise ValueError(u._("pem_path is required"))
pem_password = conf.dogtag_crypto_plugin.pem_password
if pem_password is None:
raise ValueError(u._("pem_password is required"))
crypto = None
create_nss_db = False
nss_db_path = conf.dogtag_crypto_plugin.nss_db_path
if nss_db_path is not None:
nss_password = conf.dogtag_crypto_plugin.nss_password
if nss_password is None:
raise ValueError(u._("nss_password is required"))
if not os.path.exists(nss_db_path):
create_nss_db = True
cryptoutil.NSSCryptoUtil.setup_database(
nss_db_path, nss_password, over_write=True)
crypto = cryptoutil.NSSCryptoUtil(nss_db_path, nss_password)
# set up connection
connection = pki.client.PKIConnection(
'https',
conf.dogtag_crypto_plugin.drm_host,
conf.dogtag_crypto_plugin.drm_port,
'kra')
connection.set_authentication_cert(pem_path)
# what happened to the password?
# until we figure out how to pass the password to requests, we'll
# just use -nodes to create the admin cert pem file. Any required
# code will end up being in the DRM python client
#create kraclient
kraclient = pki.kraclient.KRAClient(connection, crypto)
self.keyclient = kraclient.keys
self.systemcert_client = kraclient.system_certs
if crypto is not None:
if create_nss_db:
# Get transport cert and insert in the certdb
transport_cert = self.systemcert_client.get_transport_cert()
tcert = transport_cert[
len(pki.CERT_HEADER):
len(transport_cert) - len(pki.CERT_FOOTER)]
crypto.import_cert(DogtagCryptoPlugin.TRANSPORT_NICK,
base64.decodestring(tcert), "u,u,u")
crypto.initialize()
self.keyclient.set_transport_cert(
DogtagCryptoPlugin.TRANSPORT_NICK)
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
"""Store a secret in the DRM
This will likely require another parameter which includes the wrapped
session key to be passed. Until that is added, we will call
archive_key() which relies on the DRM python client to create the
session keys.
We may also be able to be more specific in terms of the data_type
if we know that the data being stored is a symmetric key. Until
then, we need to assume that the secret is pass_phrase_type.
"""
data_type = key.KeyClient.PASS_PHRASE_TYPE
client_key_id = uuid.uuid4().hex
response = self.keyclient.archive_key(client_key_id,
data_type,
encrypt_dto.unencrypted,
key_algorithm=None,
key_size=None)
return plugin.ResponseDTO(response.get_key_id(), None)
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
"""Retrieve a secret from the DRM
The encrypted parameter simply contains the plain text key_id by which
the secret is known to the DRM. The remaining parameters are not
used.
Note: There are two ways to retrieve secrets from the DRM.
The first, which is implemented here, will call retrieve_key without
a wrapping key. This relies on the DRM client to generate a wrapping
key (and wrap it with the DRM transport cert), and is completely
transparent to the Barbican server. What is returned to the caller
is the unencrypted secret.
The second way is to provide a wrapping key that ideally would be
generated on the barbican client. That way only the client will be
able to unwrap the secret. This is not yet implemented because
decrypt() and the barbican API still need to be changed to pass the
wrapping key.
"""
key_id = decrypt_dto.encrypted
key = self.keyclient.retrieve_key(key_id)
return key.data
def bind_kek_metadata(self, kek_meta_dto):
"""This function is not used by this plugin."""
return kek_meta_dto
def generate_symmetric(self, generate_dto, kek_meta_dto, keystone_id):
"""Generate a symmetric key
This calls generate_symmetric_key() on the DRM passing in the
algorithm, bit_length and id (used as the client_key_id) from
the secret. The remaining parameters are not used.
Returns a keyId which will be stored in an EncryptedDatum
table for later retrieval.
"""
usages = [key.SymKeyGenerationRequest.DECRYPT_USAGE,
key.SymKeyGenerationRequest.ENCRYPT_USAGE]
client_key_id = uuid.uuid4().hex
algorithm = self._map_algorithm(generate_dto.algorithm.lower())
if algorithm is None:
raise DogtagPluginAlgorithmException
response = self.keyclient.generate_symmetric_key(
client_key_id,
algorithm,
generate_dto.bit_length,
usages)
return plugin.ResponseDTO(response.get_key_id(), None)
def generate_asymmetric(self, generate_dto, kek_meta_dto, keystone_id):
"""Generate an asymmetric key."""
raise NotImplementedError("Feature not implemented for dogtag crypto")
def supports(self, type_enum, algorithm=None, bit_length=None,
mode=None):
"""Specifies what operations the plugin supports."""
if type_enum == plugin.PluginSupportTypes.ENCRYPT_DECRYPT:
return True
elif type_enum == plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION:
return self._is_algorithm_supported(algorithm,
bit_length)
elif type_enum == plugin.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION:
return False
else:
return False
@staticmethod
def _map_algorithm(algorithm):
"""Map Barbican algorithms to Dogtag plugin algorithms."""
if algorithm == "aes":
return key.KeyClient.AES_ALGORITHM
elif algorithm == "des":
return key.KeyClient.DES_ALGORITHM
elif algorithm == "3des":
return key.KeyClient.DES3_ALGORITHM
else:
return None
def _is_algorithm_supported(self, algorithm, bit_length=None):
"""Check if algorithm and bit length are supported
For now, we will just check the algorithm. When dogtag adds a
call to check the bit length per algorithm, we can modify to
make that call
"""
return self._map_algorithm(algorithm) is not None

View File

@ -1,460 +0,0 @@
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# 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.
import base64
from oslo.config import cfg
from stevedore import named
from barbican.common import exception
from barbican.common import utils
from barbican.crypto import mime_types
from barbican.crypto import plugin as plugin_mod
from barbican.model import models
from barbican.openstack.common import gettextutils as u
CONF = cfg.CONF
DEFAULT_PLUGIN_NAMESPACE = 'barbican.crypto.plugin'
DEFAULT_PLUGINS = ['simple_crypto']
crypto_opt_group = cfg.OptGroup(name='crypto',
title='Crypto Plugin Options')
crypto_opts = [
cfg.StrOpt('namespace',
default=DEFAULT_PLUGIN_NAMESPACE,
help=u._('Extension namespace to search for plugins.')
),
cfg.MultiStrOpt('enabled_crypto_plugins',
default=DEFAULT_PLUGINS,
help=u._('List of crypto plugins to load.')
)
]
CONF.register_group(crypto_opt_group)
CONF.register_opts(crypto_opts, group=crypto_opt_group)
class CryptoContentTypeNotSupportedException(exception.BarbicanException):
"""Raised when support for payload content type is not available."""
def __init__(self, content_type):
super(CryptoContentTypeNotSupportedException, self).__init__(
u._("Crypto Content Type "
"of '{0}' not supported").format(content_type)
)
self.content_type = content_type
class CryptoContentEncodingNotSupportedException(exception.BarbicanException):
"""Raised when support for payload content encoding is not available."""
def __init__(self, content_encoding):
super(CryptoContentEncodingNotSupportedException, self).__init__(
u._("Crypto Content-Encoding of '{0}' not supported").format(
content_encoding)
)
self.content_encoding = content_encoding
class CryptoAcceptNotSupportedException(exception.BarbicanException):
"""Raised when requested decrypted content-type is not available."""
def __init__(self, accept):
super(CryptoAcceptNotSupportedException, self).__init__(
u._("Crypto Accept of '{0}' not supported").format(accept)
)
self.accept = accept
class CryptoAlgorithmNotSupportedException(exception.BarbicanException):
"""Raised when support for an algorithm is not available."""
def __init__(self, algorithm):
super(CryptoAlgorithmNotSupportedException, self).__init__(
u._("Crypto algorithm of '{0}' not supported").format(
algorithm)
)
self.algorithm = algorithm
class CryptoPayloadDecodingError(exception.BarbicanException):
"""Raised when payload could not be decoded."""
def __init__(self):
super(CryptoPayloadDecodingError, self).__init__(
u._("Problem decoding payload")
)
class CryptoSupportedPluginNotFound(exception.BarbicanException):
"""Raised when no plugins are found that support the requested
operation.
"""
message = "Crypto plugin not found for requested operation."
class CryptoPluginNotFound(exception.BarbicanException):
"""Raised when no plugins are installed."""
message = u._("Crypto plugin not found.")
class CryptoNoPayloadProvidedException(exception.BarbicanException):
"""Raised when secret information is not provided."""
def __init__(self):
super(CryptoNoPayloadProvidedException, self).__init__(
u._('No secret information provided to encrypt.')
)
class CryptoNoSecretOrDataFoundException(exception.BarbicanException):
"""Raised when secret information could not be located."""
def __init__(self, secret_id):
super(CryptoNoSecretOrDataFoundException, self).__init__(
u._('No secret information located for '
'secret {0}').format(secret_id)
)
self.secret_id = secret_id
class CryptoContentEncodingMustBeBase64(exception.BarbicanException):
"""Raised when encoding must be base64."""
def __init__(self):
super(CryptoContentEncodingMustBeBase64, self).__init__(
u._("Encoding type must be 'base64' for text-based payloads.")
)
class CryptoKEKBindingException(exception.BarbicanException):
"""Raised when the bind_kek_metadata method from a plugin returns None."""
def __init__(self, plugin_name=u._('Unknown')):
super(CryptoKEKBindingException, self).__init__(
u._('Failed to bind kek metadata for '
'plugin: {0}').format(plugin_name)
)
self.plugin_name = plugin_name
class CryptoGeneralException(exception.BarbicanException):
"""Raised when a system fault has occurred."""
def __init__(self, reason=u._('Unknown')):
super(CryptoGeneralException, self).__init__(
u._('Problem seen during crypto processing - '
'Reason: {0}').format(reason)
)
self.reason = reason
def normalize_before_encryption(unencrypted, content_type, content_encoding,
enforce_text_only=False):
"""Normalize unencrypted prior to plugin encryption processing."""
if not unencrypted:
raise CryptoNoPayloadProvidedException()
# Validate and normalize content-type.
normalized_mime = mime_types.normalize_content_type(content_type)
if not mime_types.is_supported(normalized_mime):
raise CryptoContentTypeNotSupportedException(content_type)
# Process plain-text type.
if normalized_mime in mime_types.PLAIN_TEXT:
# normalize text to binary string
unencrypted = unencrypted.encode('utf-8')
# Process binary type.
else:
# payload has to be decoded
if mime_types.is_base64_processing_needed(content_type,
content_encoding):
try:
unencrypted = base64.b64decode(unencrypted)
except TypeError:
raise CryptoPayloadDecodingError()
elif enforce_text_only:
# For text-based protocols (such as the one-step secret POST),
# only 'base64' encoding is possible/supported.
raise CryptoContentEncodingMustBeBase64()
elif content_encoding:
# Unsupported content-encoding request.
raise CryptoContentEncodingNotSupportedException(content_encoding)
return unencrypted, normalized_mime
def analyze_before_decryption(content_type):
"""Determine support for desired content type."""
if not mime_types.is_supported(content_type):
raise CryptoAcceptNotSupportedException(content_type)
def denormalize_after_decryption(unencrypted, content_type):
"""Translate the decrypted data into the desired content type."""
# Process plain-text type.
if content_type in mime_types.PLAIN_TEXT:
# normalize text to binary string
try:
unencrypted = unencrypted.decode('utf-8')
except UnicodeDecodeError:
raise CryptoAcceptNotSupportedException(content_type)
# Process binary type.
elif content_type not in mime_types.BINARY:
raise CryptoGeneralException(
u._("Unexpected content-type: '{0}'").format(content_type))
return unencrypted
class CryptoExtensionManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
super(CryptoExtensionManager, self).__init__(
conf.crypto.namespace,
conf.crypto.enabled_crypto_plugins,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
def encrypt(self, unencrypted, content_type, content_encoding,
secret, tenant, kek_repo, enforce_text_only=False):
"""Delegates encryption to first plugin that supports it."""
if len(self.extensions) < 1:
raise CryptoPluginNotFound()
for ext in self.extensions:
if ext.obj.supports(plugin_mod.PluginSupportTypes.ENCRYPT_DECRYPT):
encrypting_plugin = ext.obj
break
else:
raise CryptoSupportedPluginNotFound()
unencrypted, content_type = normalize_before_encryption(
unencrypted, content_type, content_encoding,
enforce_text_only=enforce_text_only)
# Find or create a key encryption key metadata.
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
response_dto = encrypting_plugin.encrypt(
encrypt_dto, kek_meta_dto, tenant.keystone_id
)
datum.cypher_text = response_dto.cypher_text
datum.kek_meta_extended = response_dto.kek_meta_extended
# 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 decrypt(self, content_type, secret, tenant):
"""Delegates decryption to active plugins."""
if not secret or not secret.encrypted_data:
raise CryptoNoSecretOrDataFoundException(secret.id)
analyze_before_decryption(content_type)
for ext in self.extensions:
decrypting_plugin = ext.obj
for datum in secret.encrypted_data:
if self._plugin_supports(decrypting_plugin,
datum.kek_meta_tenant):
# wrap the KEKDatum instance in our DTO
kek_meta_dto = plugin_mod.KEKMetaDTO(datum.kek_meta_tenant)
# Convert from text-based storage format to binary.
#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(decrypt_dto,
kek_meta_dto,
datum.kek_meta_extended,
tenant.keystone_id)
# Denormalize the decrypted info per request.
return denormalize_after_decryption(unencrypted,
content_type)
else:
raise CryptoPluginNotFound()
def generate_symmetric_encryption_key(self, secret, content_type, tenant,
kek_repo):
"""Delegates generating a key to the first supported plugin.
Note that this key can be used by clients for their encryption
processes. This generated key is then be encrypted via
the plug-in key encryption process, and that encrypted datum
is then returned from this method.
"""
encrypting_plugin = \
self._determine_crypto_plugin(secret.algorithm,
secret.bit_length,
secret.mode)
kek_datum, kek_meta_dto = self._find_or_create_kek_objects(
encrypting_plugin, tenant, kek_repo)
# Create an encrypted datum instance and add the created cypher text.
datum = models.EncryptedDatum(secret, kek_datum)
datum.content_type = content_type
generate_dto = plugin_mod.GenerateDTO(secret.algorithm,
secret.bit_length,
secret.mode, None)
# Create the encrypted meta.
response_dto = encrypting_plugin.generate_symmetric(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(response_dto.cypher_text)
datum.kek_meta_extended = response_dto.kek_meta_extended
return datum
def generate_asymmetric_encryption_keys(self, meta, content_type, tenant,
kek_repo):
"""Delegates generating asymmteric keys to the first
supported plugin based on `meta`. meta will provide extra
information to help key generation.
Based on passpharse in meta this method will return a tuple
with two/three objects.
Note that this key can be used by clients for their encryption
processes. This generated key is then be encrypted via
the plug-in key encryption process, and that encrypted datum
is then returned from this method.
"""
encrypting_plugin = \
self._determine_crypto_plugin(meta.algorithm,
meta.bit_length,
meta.passphrase)
kek_datum, kek_meta_dto = self._find_or_create_kek_objects(
encrypting_plugin, tenant, kek_repo)
generate_dto = plugin_mod.GenerateDTO(meta.algorithm,
meta.bit_length,
None, meta.passphrase)
# generate the secret.
private_key_dto, public_key_dto, passwd_dto = \
encrypting_plugin.generate_asymmetric(
generate_dto,
kek_meta_dto,
tenant.keystone_id)
# Create an encrypted datum instances for each secret type
# and add the created cypher text.
priv_datum = models.EncryptedDatum(None, kek_datum)
priv_datum.content_type = content_type
priv_datum.cypher_text = base64.b64encode(private_key_dto.cypher_text)
priv_datum.kek_meta_extended = private_key_dto.kek_meta_extended
public_datum = models.EncryptedDatum(None, kek_datum)
public_datum.content_type = content_type
public_datum.cypher_text = base64.b64encode(public_key_dto.cypher_text)
public_datum.kek_meta_extended = public_key_dto.kek_meta_extended
passwd_datum = None
if passwd_dto:
passwd_datum = models.EncryptedDatum(None, kek_datum)
passwd_datum.content_type = content_type
passwd_datum.cypher_text = base64.b64encode(passwd_dto.cypher_text)
passwd_datum.kek_meta_extended = \
passwd_dto.kek_meta_extended
return (priv_datum, public_datum, passwd_datum)
def _determine_type(self, algorithm):
"""Determines the type (symmetric and asymmetric for now)
based on algorithm
"""
symmetric_algs = plugin_mod.PluginSupportTypes.SYMMETRIC_ALGORITHMS
asymmetric_algs = plugin_mod.PluginSupportTypes.ASYMMETRIC_ALGORITHMS
if algorithm.lower() in symmetric_algs:
return plugin_mod.PluginSupportTypes.SYMMETRIC_KEY_GENERATION
elif algorithm.lower() in asymmetric_algs:
return plugin_mod.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
else:
raise CryptoAlgorithmNotSupportedException(algorithm)
#TODO(atiwari): Use meta object instead of individual attribute
#This has to be done while integration rest resources
def _determine_crypto_plugin(self, algorithm, bit_length=None,
mode=None):
"""Determines the generation type and encrypting plug-in
which supports the generation of secret based on
generation type
"""
if len(self.extensions) < 1:
raise CryptoPluginNotFound()
generation_type = self._determine_type(algorithm)
for ext in self.extensions:
if ext.obj.supports(generation_type, algorithm,
bit_length,
mode):
encrypting_plugin = ext.obj
break
else:
raise CryptoSupportedPluginNotFound()
return encrypting_plugin
def _plugin_supports(self, plugin_inst, kek_metadata_tenant):
"""Tests for plugin support.
Tests if the supplied plugin supports operations on the supplied
key encryption key (KEK) metadata.
:param plugin_inst: The plugin instance to test.
:param kek_metadata: The KEK metadata to test.
:return: True if the plugin can support operations on the KEK metadata.
"""
plugin_name = utils.generate_fullname_for(plugin_inst)
return plugin_name == kek_metadata_tenant.plugin_name
def _find_or_create_kek_objects(self, plugin_inst, tenant, kek_repo):
# Find or create a key encryption key.
full_plugin_name = utils.generate_fullname_for(plugin_inst)
kek_datum = kek_repo.find_or_create_kek_datum(tenant,
full_plugin_name)
# Bind to the plugin's key management.
# TODO(jwood): Does this need to be in a critical section? Should the
# bind operation just be declared idempotent in the plugin contract?
kek_meta_dto = plugin_mod.KEKMetaDTO(kek_datum)
if not kek_datum.bind_completed:
kek_meta_dto = plugin_inst.bind_kek_metadata(kek_meta_dto)
# By contract, enforce that plugins return a
# (typically modified) DTO.
if kek_meta_dto is None:
raise CryptoKEKBindingException(full_plugin_name)
plugin_mod.indicate_bind_completed(kek_meta_dto, kek_datum)
kek_repo.save(kek_datum)
return kek_datum, kek_meta_dto

View File

@ -1,192 +0,0 @@
# 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.
try:
import PyKCS11
except ImportError:
PyKCS11 = {} # TODO(reaperhulk): remove testing workaround
import base64
from oslo.config import cfg
from barbican.common import exception
from barbican.crypto import plugin
from barbican.openstack.common import gettextutils as u
from barbican.openstack.common import jsonutils as json
CONF = cfg.CONF
p11_crypto_plugin_group = cfg.OptGroup(name='p11_crypto_plugin',
title="PKCS11 Crypto Plugin Options")
p11_crypto_plugin_opts = [
cfg.StrOpt('library_path',
help=u._('Path to vendor PKCS11 library')),
cfg.StrOpt('login',
help=u._('Password to login to PKCS11 session'))
]
CONF.register_group(p11_crypto_plugin_group)
CONF.register_opts(p11_crypto_plugin_opts, group=p11_crypto_plugin_group)
class P11CryptoPluginKeyException(exception.BarbicanException):
message = u._("More than one key found for label")
class P11CryptoPluginException(exception.BarbicanException):
message = u._("General exception")
class P11CryptoPlugin(plugin.CryptoPluginBase):
"""PKCS11 supporting implementation of the crypto plugin.
Generates a key per tenant and encrypts using AES-256-GCM.
This implementation currently relies on an unreleased fork of PyKCS11.
"""
def __init__(self, conf=cfg.CONF):
self.block_size = 16 # in bytes
self.kek_key_length = 32 # in bytes (256-bit)
self.algorithm = 0x8000011c # CKM_AES_GCM vendor prefixed.
self.pkcs11 = PyKCS11.PyKCS11Lib()
if conf.p11_crypto_plugin.library_path is None:
raise ValueError(u._("library_path is required"))
else:
self.pkcs11.load(conf.p11_crypto_plugin.library_path)
# initialize the library. PyKCS11 does not supply this for free
self._check_error(self.pkcs11.lib.C_Initialize())
self.session = self.pkcs11.openSession(1)
self.session.login(conf.p11_crypto_plugin.login)
self.rw_session = self.pkcs11.openSession(1, PyKCS11.CKF_RW_SESSION)
self.rw_session.login(conf.p11_crypto_plugin.login)
def _check_error(self, value):
if value != PyKCS11.CKR_OK:
raise PyKCS11.PyKCS11Error(value)
def _get_key_by_label(self, key_label):
template = (
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
(PyKCS11.CKA_LABEL, key_label))
keys = self.session.findObjects(template)
if len(keys) == 1:
return keys[0]
elif len(keys) == 0:
return None
else:
raise P11CryptoPluginKeyException()
def _generate_iv(self):
iv = self.session.generateRandom(self.block_size)
iv = b''.join(chr(i) for i in iv)
if len(iv) != self.block_size:
raise P11CryptoPluginException()
return iv
def _build_gcm_params(self, iv):
gcm = PyKCS11.LowLevel.CK_AES_GCM_PARAMS()
gcm.pIv = iv
gcm.ulIvLen = len(iv)
gcm.ulIvBits = len(iv) * 8
gcm.ulTagBits = 128
return gcm
def _generate_kek(self, kek_label):
# TODO(reaperhulk): review template to ensure it's what we want
template = (
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
(PyKCS11.CKA_VALUE_LEN, self.kek_key_length),
(PyKCS11.CKA_LABEL, kek_label),
(PyKCS11.CKA_PRIVATE, True),
(PyKCS11.CKA_SENSITIVE, True),
(PyKCS11.CKA_ENCRYPT, True),
(PyKCS11.CKA_DECRYPT, True),
(PyKCS11.CKA_TOKEN, True),
(PyKCS11.CKA_WRAP, True),
(PyKCS11.CKA_UNWRAP, True),
(PyKCS11.CKA_EXTRACTABLE, False))
ckattr = self.session._template2ckattrlist(template)
m = PyKCS11.LowLevel.CK_MECHANISM()
m.mechanism = PyKCS11.LowLevel.CKM_AES_KEY_GEN
key = PyKCS11.LowLevel.CK_OBJECT_HANDLE()
self._check_error(
self.pkcs11.lib.C_GenerateKey(
self.rw_session.session,
m,
ckattr,
key
)
)
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, encrypt_dto.unencrypted, mech)
cyphertext = b''.join(chr(i) for i in encrypted)
kek_meta_extended = json.dumps({
'iv': base64.b64encode(iv)
})
return plugin.ResponseDTO(cyphertext, kek_meta_extended)
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, decrypt_dto.encrypted, mech)
secret = b''.join(chr(i) for i in decrypted)
return secret
def bind_kek_metadata(self, kek_meta_dto):
# Enforce idempotency: If we've already generated a key for the
# kek_label, leave now.
key = self._get_key_by_label(kek_meta_dto.kek_label)
if not key:
self._generate_kek(kek_meta_dto.kek_label)
# To be persisted by Barbican:
kek_meta_dto.algorithm = 'AES'
kek_meta_dto.bit_length = self.kek_key_length * 8
kek_meta_dto.mode = 'GCM'
kek_meta_dto.plugin_meta = None
return kek_meta_dto
def generate_symmetric(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 self.encrypt(plugin.EncryptDTO(rand), kek_meta_dto, keystone_id)
def generate_asymmetric(self, generate_dto, kek_meta_dto, keystone_id):
raise NotImplementedError("Feature not implemented for PKCS11")
def supports(self, type_enum, algorithm=None, bit_length=None, mode=None):
if type_enum == plugin.PluginSupportTypes.ENCRYPT_DECRYPT:
return True
elif type_enum == plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION:
return True
elif type_enum == plugin.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION:
return False
else:
return False

View File

@ -1,528 +0,0 @@
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# 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.
import abc
import os
from Crypto.PublicKey import DSA
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
from cryptography import fernet
from oslo.config import cfg
import six
from barbican.common import utils
from barbican.openstack.common import gettextutils as u
LOG = utils.getLogger(__name__)
CONF = cfg.CONF
simple_crypto_plugin_group = cfg.OptGroup(name='simple_crypto_plugin',
title="Simple Crypto Plugin Options")
simple_crypto_plugin_opts = [
cfg.StrOpt('kek',
default=b'dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=',
help=u._('Key encryption key to be used by Simple Crypto '
'Plugin'))
]
CONF.register_group(simple_crypto_plugin_group)
CONF.register_opts(simple_crypto_plugin_opts, group=simple_crypto_plugin_group)
class PluginSupportTypes(object):
"""Class to hold the type enumeration that plugins may support."""
ENCRYPT_DECRYPT = "ENCRYPT_DECRYPT"
SYMMETRIC_KEY_GENERATION = "SYMMETRIC_KEY_GENERATION"
# A list of symmetric algorithms that are used to determine type of key gen
SYMMETRIC_ALGORITHMS = ['aes', 'des', '3des', 'hmacsha1',
'hmacsha256', 'hmacsha384', 'hmacsha512']
SYMMETRIC_KEY_LENGTHS = [64, 128, 192, 256]
ASYMMETRIC_KEY_GENERATION = "ASYMMETRIC_KEY_GENERATION"
ASYMMETRIC_ALGORITHMS = ['rsa', 'dsa']
ASYMMETRIC_KEY_LENGTHS = [1024, 2048, 4096]
class KEKMetaDTO(object):
"""Key Encryption Keys (KEKs) in Barbican are intended to represent a
distinct key that is used to perform encryption on secrets for a particular
project (tenant).
``KEKMetaDTO`` objects are provided to cryptographic backends by Barbican
to allow plugins to persist metadata related to the project's (tenant's)
KEK.
For example, a plugin that interfaces with a Hardware Security Module (HSM)
may want to use a different encryption key for each tenant. Such a plugin
could use the ``KEKMetaDTO`` object to save the key ID used for that
tenant. Barbican will persist the KEK metadata and ensure that it is
provided to the plugin every time a request from that same tenant is
processed.
.. attribute:: plugin_name
String attribute used by Barbican to identify the plugin that is bound
to the KEK metadata. Plugins should not change this attribute.
.. attribute:: kek_label
String attribute used to label the project's (tenant's) KEK by the
plugin. The value of this attribute should be meaningful to the
plugin. Barbican does not use this value.
.. attribute:: algorithm
String attribute used to identify the encryption algorithm used by the
plugin. e.g. "AES", "3DES", etc. This value should be meaningful to
the plugin. Barbican does not use this value.
.. attribute:: mode
String attribute used to identify the algorithm mode used by the
plugin. e.g. "CBC", "GCM", etc. This value should be meaningful to
the plugin. Barbican does not use this value.
.. attribute:: bit_length
Integer attribute used to identify the bit length of the KEK by the
plugin. This value should be meaningful to the plugin. Barbican does
not use this value.
.. attribute:: plugin_meta
String attribute used to persist any additional metadata that does not
fit in any other attribute. The value of this attribute is defined by
the plugin. It could be used to store external system references, such
as Key IDs in an HSM, URIs to an external service, or any other data
that the plugin deems necessary to persist. Because this is just a
plain text field, a plug in may even choose to persist data such as key
value pairs in a JSON object.
"""
def __init__(self, kek_datum):
"""kek_datum is typically a barbican.model.models.EncryptedDatum
instance. Plugins should never have to create their own instance of
this class.
"""
self.kek_label = kek_datum.kek_label
self.plugin_name = kek_datum.plugin_name
self.algorithm = kek_datum.algorithm
self.bit_length = kek_datum.bit_length
self.mode = kek_datum.mode
self.plugin_meta = kek_datum.plugin_meta
class GenerateDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to generate a secret on behalf of the user.
.. attribute:: generation_type
String attribute used to identify the type of secret that should be
generated. This will be either ``"symmetric"`` or ``"asymmetric"``.
.. attribute:: algorithm
String attribute used to specify what type of algorithm the secret will
be used for. e.g. ``"AES"`` for a ``"symmetric"`` type, or ``"RSA"``
for ``"asymmetric"``.
.. attribute:: mode
String attribute used to specify what algorithm mode the secret will be
used for. e.g. ``"CBC"`` for ``"AES"`` algorithm.
.. attribute:: bit_length
Integer attribute used to specify the bit length of the secret. For
example, this attribute could specify the key length for an encryption
key to be used in AES-CBC.
"""
def __init__(self, algorithm, bit_length, mode, passphrase=None):
self.algorithm = algorithm
self.bit_length = bit_length
self.mode = mode
self.passphrase = passphrase
class ResponseDTO(object):
"""Data transfer object for secret generation response."""
def __init__(self, cypher_text, kek_meta_extended=None):
self.cypher_text = cypher_text
self.kek_meta_extended = kek_meta_extended
class DecryptDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to perform decryption of a secret.
Currently, this DTO only contains the data produced by the plugin during
encryption, but in the future this DTO will contain more information, such
as a transport key for secret wrapping back to the client.
.. attribute:: encrypted
The data that was produced by the plugin during encryption. For some
plugins this will be the actual bytes that need to be decrypted to
produce the secret. In other implementations, this may just be a
reference to some external system that can produce the unencrypted
secret.
"""
def __init__(self, encrypted):
self.encrypted = encrypted
class EncryptDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to perform encryption of a secret.
Currently, this DTO only contains the raw bytes to be encrypted by the
plugin, but in the future this may contain more information.
.. attribute:: unencrypted
The secret data in Bytes to be encrypted by the plugin.
"""
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
kek_meta_dto instance. This function is typically used once plugins have
had a chance to bind kek_meta_dto to their crypto systems.
:param kek_meta_dto:
:param kek_datum:
:return: None
"""
kek_datum.bind_completed = True
kek_datum.algorithm = kek_meta_dto.algorithm
kek_datum.bit_length = kek_meta_dto.bit_length
kek_datum.mode = kek_meta_dto.mode
kek_datum.plugin_meta = kek_meta_dto.plugin_meta
@six.add_metaclass(abc.ABCMeta)
class CryptoPluginBase(object):
"""Base class for all Crypto plugins. Implementations of this abstract
base class will be used by Barbican to perform cryptographic operations on
secrets.
Barbican requests operations by invoking the methods on an instance of the
implementing class. Barbican's plugin manager handles the life-cycle of
the Data Transfer Objects (DTOs) that are passed into these methods, and
persist the data that is assigned to these DTOs by the plugin.
"""
@abc.abstractmethod
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
"""This method will be called by Barbican when requesting an encryption
operation on a secret on behalf of a project (tenant).
:param encrypt_dto: :class:`EncryptDTO` instance containing the raw
secret byte data to be encrypted.
:type encrypt_dto: :class:`EncryptDTO`
:param kek_meta_dto: :class:`KEKMetaDTO` instance containing
information about the project's (tenant's) Key Encryption Key (KEK)
to be used for encryption. Plugins may assume that binding via
:meth:`bind_kek_metadata` has already taken place before this
instance is passed in.
:type kek_meta_dto: :class:`KEKMetaDTO`
:param keystone_id: Project (tenant) ID associated with the unencrypted
data.
:return: A tuple containing two items ``(ciphertext,
kek_metadata_extended)``. In a typical plugin implementation, the
first item in the tuple should be the ciphertext byte data
resulting from the encryption of the secret data. The second item
is an optional String object to be persisted alongside the
ciphertext.
Barbican guarantees that both the ``ciphertext`` and
``kek_metadata_extended`` will be persisted and then given back to
the plugin when requesting a decryption operation.
It should be noted that Barbican does not require that the data
returned for the ``ciphertext`` be the actual encrypted
bytes of the secret data. The only requirement is that the plugin
is able to use whatever data it chooses to return in ``ciphertext``
to produce the secret data during decryption. This allows more
complex plugins to make decisions regarding the storage of the
encrypted data. For example, the DogTag plugin stores the
encrypted bytes in an external system and uses Barbican to store an
identifier to the external system in ``ciphertext``. During
decryption, Barbican gives the external identifier back to the
DogTag plugin, and then the plugin is able to use the identifier to
retrieve the secret data from the external storage system.
``kek_metadata_extended`` takes the idea of Key Encryption Key
(KEK) metadata further by giving plugins the option to store
secret-level KEK metadata. One example of using secret-level KEK
metadata would be plugins that want to use a unique KEK for every
secret that is encrypted. Such a plugin could use
``kek_metadata_extended`` to store the Key ID for the KEK used to
encrypt this particular secret.
:rtype: tuple
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
"""Decrypt encrypted_datum in the context of the provided tenant.
: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.
:param keystone_id: keystone_id associated with the encrypted datum.
:returns: str -- unencrypted byte data
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def bind_kek_metadata(self, kek_meta_dto):
"""Bind a key encryption key (KEK) metadata to the sub-system
handling encryption/decryption, updating information about the
key encryption key (KEK) metadata in the supplied 'kek_metadata'
data-transfer-object instance, and then returning this instance.
This method is invoked prior to the encrypt() method above.
Implementors should fill out the supplied 'kek_meta_dto' instance
(an instance of KEKMetadata above) as needed to completely describe
the kek metadata and to complete the binding process. Barbican will
persist the contents of this instance once this method returns.
:param kek_meta_dto: Key encryption key metadata to bind, with the
'kek_label' attribute guaranteed to be unique, and the
and 'plugin_name' attribute already configured.
:returns: kek_meta_dto: Returns the specified DTO, after
modifications.
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def generate_symmetric(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: An object of type ResponseDTO containing 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
def generate_asymmetric(self, generate_dto,
kek_meta_dto, keystone_id):
"""Create a new asymmetric 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 passphrase
:param kek_meta_dto: Key encryption key metadata to use for decryption
:param keystone_id: keystone_id associated with the data.
:returns: A tuple containing objects for private_key, public_key and
optionally one for passphrase. The objects will be of type ResponseDTO.
Each object containing 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
def supports(self, type_enum, algorithm=None, bit_length=None,
mode=None):
"""Used to determine if the plugin supports the requested operation.
:param type_enum: Enumeration from PluginSupportsType class
:param algorithm: String algorithm name if needed
"""
raise NotImplementedError # pragma: no cover
class SimpleCryptoPlugin(CryptoPluginBase):
"""Insecure implementation of the crypto plugin."""
def __init__(self, conf=CONF):
self.master_kek = conf.simple_crypto_plugin.kek
def _get_kek(self, kek_meta_dto):
if not kek_meta_dto.plugin_meta:
raise ValueError('KEK not yet created.')
# the kek is stored encrypted. Need to decrypt.
encryptor = fernet.Fernet(self.master_kek)
# Note : If plugin_meta type is unicode, encode to byte.
if isinstance(kek_meta_dto.plugin_meta, six.text_type):
kek_meta_dto.plugin_meta = kek_meta_dto.plugin_meta.encode('utf-8')
return encryptor.decrypt(kek_meta_dto.plugin_meta)
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
kek = self._get_kek(kek_meta_dto)
unencrypted = encrypt_dto.unencrypted
if not isinstance(unencrypted, str):
raise ValueError('Unencrypted data must be a byte type, '
'but was {0}'.format(type(unencrypted)))
encryptor = fernet.Fernet(kek)
cyphertext = encryptor.encrypt(unencrypted)
return ResponseDTO(cyphertext, None)
def decrypt(self, encrypted_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
kek = self._get_kek(kek_meta_dto)
encrypted = encrypted_dto.encrypted
decryptor = fernet.Fernet(kek)
return decryptor.decrypt(encrypted)
def bind_kek_metadata(self, kek_meta_dto):
kek_meta_dto.algorithm = 'aes'
kek_meta_dto.bit_length = 128
kek_meta_dto.mode = 'cbc'
if not kek_meta_dto.plugin_meta:
# the kek is stored encrypted in the plugin_meta field
encryptor = fernet.Fernet(self.master_kek)
key = fernet.Fernet.generate_key()
kek_meta_dto.plugin_meta = encryptor.encrypt(key)
return kek_meta_dto
def generate_symmetric(self, generate_dto, kek_meta_dto, keystone_id):
byte_length = int(generate_dto.bit_length) / 8
unencrypted = os.urandom(byte_length)
return self.encrypt(EncryptDTO(unencrypted),
kek_meta_dto,
keystone_id)
def generate_asymmetric(self, generate_dto, kek_meta_dto, keystone_id):
"""Generate asymmetric keys based on below rule
- RSA, with passphrase (supported)
- RSA, without passphrase (supported)
- DSA, without passphrase (supported)
- DSA, with passphrase (not supported)
Note: PyCrypto is not capable of serializing DSA
keys and DER formated keys. Such keys will be
serialized to Base64 PEM to store in DB.
TODO (atiwari/reaperhulk): PyCrypto is not capable to serialize
DSA keys and DER formated keys, later we need to pick better
crypto lib.
"""
if generate_dto.algorithm is None\
or generate_dto.algorithm.lower() == 'rsa':
private_key = RSA.generate(
generate_dto.bit_length, None, None, 65537)
elif generate_dto.algorithm.lower() == 'dsa':
private_key = DSA.generate(generate_dto.bit_length, None, None)
public_key = private_key.publickey()
# Note (atiwari): key wrapping format PEM only supported
if generate_dto.algorithm.lower() == 'rsa':
public_key, private_key = self._wrap_key(public_key, private_key,
generate_dto.passphrase)
if generate_dto.algorithm.lower() == 'dsa':
if generate_dto.passphrase:
raise ValueError('Passphrase not supported for DSA key')
public_key, private_key = self._serialize_dsa_key(public_key,
private_key)
private_dto = self.encrypt(EncryptDTO(private_key),
kek_meta_dto,
keystone_id)
public_dto = self.encrypt(EncryptDTO(public_key),
kek_meta_dto,
keystone_id)
passphrase_dto = None
if generate_dto.passphrase:
passphrase_dto = self.encrypt(EncryptDTO(generate_dto.passphrase),
kek_meta_dto,
keystone_id)
return private_dto, public_dto, passphrase_dto
def supports(self, type_enum, algorithm=None, bit_length=None,
mode=None):
if type_enum == PluginSupportTypes.ENCRYPT_DECRYPT:
return True
if type_enum == PluginSupportTypes.SYMMETRIC_KEY_GENERATION:
return self._is_algorithm_supported(algorithm,
bit_length)
elif type_enum == PluginSupportTypes.ASYMMETRIC_KEY_GENERATION:
return self._is_algorithm_supported(algorithm,
bit_length)
else:
return False
def _wrap_key(self, public_key, private_key,
passphrase):
pkcs = 8
key_wrap_format = 'PEM'
private_key = private_key.exportKey(key_wrap_format, passphrase, pkcs)
public_key = public_key.exportKey()
return (public_key, private_key)
def _serialize_dsa_key(self, public_key, private_key):
pub_seq = asn1.DerSequence()
pub_seq[:] = [0, public_key.p, public_key.q,
public_key.g, public_key.y]
public_key = "-----BEGIN DSA PUBLIC KEY-----\n%s"\
"-----END DSA PUBLIC KEY-----" % pub_seq.encode().encode("base64")
prv_seq = asn1.DerSequence()
prv_seq[:] = [0, private_key.p, private_key.q,
private_key.g, private_key.y, private_key.x]
private_key = "-----BEGIN DSA PRIVATE KEY-----\n%s"\
"-----END DSA PRIVATE KEY-----" % prv_seq.encode().encode("base64")
return (public_key, private_key)
def _is_algorithm_supported(self, algorithm=None, bit_length=None):
"""check if algorithm and bit_length combination is supported."""
if algorithm is None or bit_length is None:
return False
if algorithm.lower() in PluginSupportTypes.SYMMETRIC_ALGORITHMS \
and bit_length in PluginSupportTypes.SYMMETRIC_KEY_LENGTHS:
return True
elif algorithm.lower() in PluginSupportTypes.ASYMMETRIC_ALGORITHMS \
and bit_length in PluginSupportTypes.ASYMMETRIC_KEY_LENGTHS:
return True
else:
return False

View File

@ -20,6 +20,7 @@ import sqlalchemy as sa
from sqlalchemy.ext import compiler
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from sqlalchemy.orm import collections as col
from sqlalchemy import types as sql_types
from barbican.common import exception
@ -242,8 +243,12 @@ class Secret(BASE, ModelBase):
# metadata is retrieved.
# See barbican.api.resources.py::SecretsResource.on_get()
encrypted_data = orm.relationship("EncryptedDatum", lazy='joined')
secret_store_metadata = orm.relationship("SecretStoreMetadatum",
lazy='joined')
secret_store_metadata = orm.\
relationship("SecretStoreMetadatum",
collection_class=col.attribute_mapped_collection('key'),
backref="secret",
cascade="all, delete-orphan")
def __init__(self, parsed_request=None):
"""Creates secret from a dict."""
@ -260,14 +265,14 @@ class Secret(BASE, ModelBase):
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
for datum in self.secret_store_metadata:
datum.delete(session)
for k, v in self.secret_store_metadata.items():
v.delete(session)
for datum in self.encrypted_data:
datum.delete(session)
for secret_ref in self.container_secrets:
session.delete(secret_ref)
session.delete(secret_ref)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
@ -290,16 +295,12 @@ class SecretStoreMetadatum(BASE, ModelBase):
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.String(255), nullable=False)
def __init__(self, secret, key, value):
def __init__(self, key, value):
super(SecretStoreMetadatum, self).__init__()
msg = ("Must supply non-None {0} argument "
"for SecretStoreMetadatum entry.")
if secret is None:
raise exception.MissingArgumentError(msg.format("secret"))
self.secret_id = secret.id
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key

View File

@ -239,6 +239,38 @@ def clean_paging_values(offset_arg=0, limit_arg=CONF.default_limit_paging):
return offset, limit
class Repositories(object):
"""Convenient way to pass repositories around.
Selecting a given repository has 3 choices:
1) Use a specified repository instance via **kwargs
2) Create a repository here if it is specified as None via **kwargs
3) Just use None if no repository is specified
"""
def __init__(self, **kwargs):
if kwargs:
# Enforce that either all arguments are non-None or else all None.
test_set = set(kwargs.values())
if None in test_set and len(test_set) > 1:
raise NotImplementedError('No support for mixing None '
'and non-None repository instances')
# Only set properties for specified repositories.
self._set_repo('tenant_repo', TenantRepo, kwargs)
self._set_repo('tenant_secret_repo', TenantSecretRepo, kwargs)
self._set_repo('secret_repo', SecretRepo, kwargs)
self._set_repo('datum_repo', EncryptedDatumRepo, kwargs)
self._set_repo('kek_repo', KEKDatumRepo, kwargs)
self._set_repo('secret_meta_repo', SecretStoreMetadatumRepo,
kwargs)
self._set_repo('order_repo', OrderRepo, kwargs)
self._set_repo('transport_key_repo', TransportKeyRepo, kwargs)
def _set_repo(self, repo_name, repo_cls, specs):
if specs and repo_name in specs:
setattr(self, repo_name, specs[repo_name] or repo_cls())
class BaseRepo(object):
"""Base repository for the barbican entities.
@ -589,6 +621,42 @@ class EncryptedDatumRepo(BaseRepo):
pass
class SecretStoreMetadatumRepo(BaseRepo):
"""Repository for the SecretStoreMetadatum entity (that stores key/value
information on behalf of a Secret).
"""
def save(self, metadata, secret_model):
"""Saves the the specified metadata for the secret.
:raises NotFound if entity does not exist.
"""
now = timeutils.utcnow()
session = get_session()
with session.begin():
for k, v in metadata.items():
meta_model = models.SecretStoreMetadatum(k, v)
meta_model.updated_at = now
meta_model.secret = secret_model
meta_model.save(session=session)
def _do_entity_name(self):
"""Sub-class hook: return entity name, such as for debugging."""
return "SecretStoreMetadatum"
def _do_create_instance(self):
return models.SecretStoreMetadatum()
def _do_build_get_query(self, entity_id, keystone_id, session):
"""Sub-class hook: build a retrieve query."""
return session.query(models.SecretStoreMetadatum).\
filter_by(id=entity_id)
def _do_validate(self, values):
"""Sub-class hook: validate values."""
pass
class KEKDatumRepo(BaseRepo):
"""Repository for the KEKDatum entity (that stores key encryption key (KEK)
metadata used by crypto plugins to encrypt/decrypt secrets).

View File

@ -1,2 +1,422 @@
#TODO(john-wood-w) Pull over current crypto package elements: plugin.py and
# lookup parts of extension_manager.py
# 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.
import abc
from oslo.config import cfg
from stevedore import named
import six
from barbican.common import exception
from barbican.common import utils
from barbican.openstack.common import gettextutils as u
from barbican.plugin.interface import secret_store
LOG = utils.getLogger(__name__)
CONF = cfg.CONF
DEFAULT_PLUGIN_NAMESPACE = 'barbican.crypto.plugin'
DEFAULT_PLUGINS = ['simple_crypto']
crypto_opt_group = cfg.OptGroup(name='crypto',
title='Crypto Plugin Options')
crypto_opts = [
cfg.StrOpt('namespace',
default=DEFAULT_PLUGIN_NAMESPACE,
help=u._('Extension namespace to search for plugins.')
),
cfg.MultiStrOpt('enabled_crypto_plugins',
default=DEFAULT_PLUGINS,
help=u._('List of crypto plugins to load.')
)
]
CONF.register_group(crypto_opt_group)
CONF.register_opts(crypto_opts, group=crypto_opt_group)
class CryptoPluginNotFound(exception.BarbicanException):
"""Raised when no plugins are installed."""
message = u._("Crypto plugin not found.")
class CryptoKEKBindingException(exception.BarbicanException):
"""Raised when the bind_kek_metadata method from a plugin returns None."""
def __init__(self, plugin_name=u._('Unknown')):
super(CryptoKEKBindingException, self).__init__(
u._('Failed to bind kek metadata for '
'plugin: {0}').format(plugin_name)
)
self.plugin_name = plugin_name
class CryptoPrivateKeyFailureException(exception.BarbicanException):
"""Raised when could not generate private key."""
def __init__(self):
super(CryptoPrivateKeyFailureException, self).__init__(
u._('Could not generate private key')
)
#TODO(john-wood-w) Need to harmonize these lower-level constants with the
# higher level constants in secret_store.py.
class PluginSupportTypes(object):
"""Class to hold the type enumeration that plugins may support."""
ENCRYPT_DECRYPT = "ENCRYPT_DECRYPT"
SYMMETRIC_KEY_GENERATION = "SYMMETRIC_KEY_GENERATION"
# A list of symmetric algorithms that are used to determine type of key gen
SYMMETRIC_ALGORITHMS = ['aes', 'des', '3des', 'hmacsha1',
'hmacsha256', 'hmacsha384', 'hmacsha512']
SYMMETRIC_KEY_LENGTHS = [64, 128, 192, 256]
ASYMMETRIC_KEY_GENERATION = "ASYMMETRIC_KEY_GENERATION"
ASYMMETRIC_ALGORITHMS = ['rsa', 'dsa']
ASYMMETRIC_KEY_LENGTHS = [1024, 2048, 4096]
class KEKMetaDTO(object):
"""Key Encryption Keys (KEKs) in Barbican are intended to represent a
distinct key that is used to perform encryption on secrets for a particular
project (tenant).
``KEKMetaDTO`` objects are provided to cryptographic backends by Barbican
to allow plugins to persist metadata related to the project's (tenant's)
KEK.
For example, a plugin that interfaces with a Hardware Security Module (HSM)
may want to use a different encryption key for each tenant. Such a plugin
could use the ``KEKMetaDTO`` object to save the key ID used for that
tenant. Barbican will persist the KEK metadata and ensure that it is
provided to the plugin every time a request from that same tenant is
processed.
.. attribute:: plugin_name
String attribute used by Barbican to identify the plugin that is bound
to the KEK metadata. Plugins should not change this attribute.
.. attribute:: kek_label
String attribute used to label the project's (tenant's) KEK by the
plugin. The value of this attribute should be meaningful to the
plugin. Barbican does not use this value.
.. attribute:: algorithm
String attribute used to identify the encryption algorithm used by the
plugin. e.g. "AES", "3DES", etc. This value should be meaningful to
the plugin. Barbican does not use this value.
.. attribute:: mode
String attribute used to identify the algorithm mode used by the
plugin. e.g. "CBC", "GCM", etc. This value should be meaningful to
the plugin. Barbican does not use this value.
.. attribute:: bit_length
Integer attribute used to identify the bit length of the KEK by the
plugin. This value should be meaningful to the plugin. Barbican does
not use this value.
.. attribute:: plugin_meta
String attribute used to persist any additional metadata that does not
fit in any other attribute. The value of this attribute is defined by
the plugin. It could be used to store external system references, such
as Key IDs in an HSM, URIs to an external service, or any other data
that the plugin deems necessary to persist. Because this is just a
plain text field, a plug in may even choose to persist data such as key
value pairs in a JSON object.
"""
def __init__(self, kek_datum):
"""kek_datum is typically a barbican.model.models.EncryptedDatum
instance. Plugins should never have to create their own instance of
this class.
"""
self.kek_label = kek_datum.kek_label
self.plugin_name = kek_datum.plugin_name
self.algorithm = kek_datum.algorithm
self.bit_length = kek_datum.bit_length
self.mode = kek_datum.mode
self.plugin_meta = kek_datum.plugin_meta
class GenerateDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to generate a secret on behalf of the user.
.. attribute:: generation_type
String attribute used to identify the type of secret that should be
generated. This will be either ``"symmetric"`` or ``"asymmetric"``.
.. attribute:: algorithm
String attribute used to specify what type of algorithm the secret will
be used for. e.g. ``"AES"`` for a ``"symmetric"`` type, or ``"RSA"``
for ``"asymmetric"``.
.. attribute:: mode
String attribute used to specify what algorithm mode the secret will be
used for. e.g. ``"CBC"`` for ``"AES"`` algorithm.
.. attribute:: bit_length
Integer attribute used to specify the bit length of the secret. For
example, this attribute could specify the key length for an encryption
key to be used in AES-CBC.
"""
def __init__(self, algorithm, bit_length, mode, passphrase=None):
self.algorithm = algorithm
self.bit_length = bit_length
self.mode = mode
self.passphrase = passphrase
class ResponseDTO(object):
"""Data transfer object for secret generation response."""
def __init__(self, cypher_text, kek_meta_extended=None):
self.cypher_text = cypher_text
self.kek_meta_extended = kek_meta_extended
class DecryptDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to perform decryption of a secret.
Currently, this DTO only contains the data produced by the plugin during
encryption, but in the future this DTO will contain more information, such
as a transport key for secret wrapping back to the client.
.. attribute:: encrypted
The data that was produced by the plugin during encryption. For some
plugins this will be the actual bytes that need to be decrypted to
produce the secret. In other implementations, this may just be a
reference to some external system that can produce the unencrypted
secret.
"""
def __init__(self, encrypted):
self.encrypted = encrypted
class EncryptDTO(object):
"""Data Transfer Object used to pass all the necessary data for the plugin
to perform encryption of a secret.
Currently, this DTO only contains the raw bytes to be encrypted by the
plugin, but in the future this may contain more information.
.. attribute:: unencrypted
The secret data in Bytes to be encrypted by the plugin.
"""
def __init__(self, unencrypted):
self.unencrypted = unencrypted
@six.add_metaclass(abc.ABCMeta)
class CryptoPluginBase(object):
"""Base class for all Crypto plugins. Implementations of this abstract
base class will be used by Barbican to perform cryptographic operations on
secrets.
Barbican requests operations by invoking the methods on an instance of the
implementing class. Barbican's plugin manager handles the life-cycle of
the Data Transfer Objects (DTOs) that are passed into these methods, and
persist the data that is assigned to these DTOs by the plugin.
"""
@abc.abstractmethod
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
"""This method will be called by Barbican when requesting an encryption
operation on a secret on behalf of a project (tenant).
:param encrypt_dto: :class:`EncryptDTO` instance containing the raw
secret byte data to be encrypted.
:type encrypt_dto: :class:`EncryptDTO`
:param kek_meta_dto: :class:`KEKMetaDTO` instance containing
information about the project's (tenant's) Key Encryption Key (KEK)
to be used for encryption. Plugins may assume that binding via
:meth:`bind_kek_metadata` has already taken place before this
instance is passed in.
:type kek_meta_dto: :class:`KEKMetaDTO`
:param keystone_id: Project (tenant) ID associated with the unencrypted
data.
:return: A tuple containing two items ``(ciphertext,
kek_metadata_extended)``. In a typical plugin implementation, the
first item in the tuple should be the ciphertext byte data
resulting from the encryption of the secret data. The second item
is an optional String object to be persisted alongside the
ciphertext.
Barbican guarantees that both the ``ciphertext`` and
``kek_metadata_extended`` will be persisted and then given back to
the plugin when requesting a decryption operation.
``kek_metadata_extended`` takes the idea of Key Encryption Key
(KEK) metadata further by giving plugins the option to store
secret-level KEK metadata. One example of using secret-level KEK
metadata would be plugins that want to use a unique KEK for every
secret that is encrypted. Such a plugin could use
``kek_metadata_extended`` to store the Key ID for the KEK used to
encrypt this particular secret.
:rtype: tuple
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def decrypt(self, decrypt_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
"""Decrypt encrypted_datum in the context of the provided tenant.
: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.
:param keystone_id: keystone_id associated with the encrypted datum.
:returns: str -- unencrypted byte data
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def bind_kek_metadata(self, kek_meta_dto):
"""Bind a key encryption key (KEK) metadata to the sub-system
handling encryption/decryption, updating information about the
key encryption key (KEK) metadata in the supplied 'kek_metadata'
data-transfer-object instance, and then returning this instance.
This method is invoked prior to the encrypt() method above.
Implementors should fill out the supplied 'kek_meta_dto' instance
(an instance of KEKMetadata above) as needed to completely describe
the kek metadata and to complete the binding process. Barbican will
persist the contents of this instance once this method returns.
:param kek_meta_dto: Key encryption key metadata to bind, with the
'kek_label' attribute guaranteed to be unique, and the
and 'plugin_name' attribute already configured.
:returns: kek_meta_dto: Returns the specified DTO, after
modifications.
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def generate_symmetric(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: An object of type ResponseDTO containing 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
def generate_asymmetric(self, generate_dto,
kek_meta_dto, keystone_id):
"""Create a new asymmetric 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 passphrase
:param kek_meta_dto: Key encryption key metadata to use for decryption
:param keystone_id: keystone_id associated with the data.
:returns: A tuple containing objects for private_key, public_key and
optionally one for passphrase. The objects will be of type ResponseDTO.
Each object containing 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
def supports(self, type_enum, algorithm=None, bit_length=None,
mode=None):
"""Used to determine if the plugin supports the requested operation.
:param type_enum: Enumeration from PluginSupportsType class
:param algorithm: String algorithm name if needed
"""
raise NotImplementedError # pragma: no cover
class CryptoPluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
super(CryptoPluginManager, self).__init__(
conf.crypto.namespace,
conf.crypto.enabled_crypto_plugins,
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
def get_plugin_store_generate(self, type_needed, algorithm=None,
bit_length=None, mode=None):
"""Gets a secret store or generate plugin that supports provided type.
:param type_needed: PluginSupportTypes that contains details on the
type of plugin required
:returns: CryptoPluginBase plugin implementation
"""
if len(self.extensions) < 1:
raise CryptoPluginNotFound()
for ext in self.extensions:
if ext.obj.supports(type_needed, algorithm, bit_length, mode):
plugin = ext.obj
break
else:
raise secret_store.SecretStorePluginNotFound()
return plugin
def get_plugin_retrieve(self, plugin_name_for_store):
"""Gets a secret retrieve plugin that supports the provided type.
:param type_needed: PluginSupportTypes that contains details on the
type of plugin required
:returns: CryptoPluginBase plugin implementation
"""
if len(self.extensions) < 1:
raise CryptoPluginNotFound()
for ext in self.extensions:
decrypting_plugin = ext.obj
plugin_name = utils.generate_fullname_for(decrypting_plugin)
if plugin_name == plugin_name_for_store:
break
else:
raise secret_store.SecretStorePluginNotFound()
return decrypting_plugin

View File

@ -1 +1,192 @@
#TODO(john-wood-w) Pull over current crypto package's p11_crypto.py
# 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.
try:
import PyKCS11
except ImportError:
PyKCS11 = {} # TODO(reaperhulk): remove testing workaround
import base64
from oslo.config import cfg
from barbican.common import exception
from barbican.plugin.crypto import crypto as plugin
from barbican.openstack.common import gettextutils as u
from barbican.openstack.common import jsonutils as json
CONF = cfg.CONF
p11_crypto_plugin_group = cfg.OptGroup(name='p11_crypto_plugin',
title="PKCS11 Crypto Plugin Options")
p11_crypto_plugin_opts = [
cfg.StrOpt('library_path',
help=u._('Path to vendor PKCS11 library')),
cfg.StrOpt('login',
help=u._('Password to login to PKCS11 session'))
]
CONF.register_group(p11_crypto_plugin_group)
CONF.register_opts(p11_crypto_plugin_opts, group=p11_crypto_plugin_group)
class P11CryptoPluginKeyException(exception.BarbicanException):
message = u._("More than one key found for label")
class P11CryptoPluginException(exception.BarbicanException):
message = u._("General exception")
class P11CryptoPlugin(plugin.CryptoPluginBase):
"""PKCS11 supporting implementation of the crypto plugin.
Generates a key per tenant and encrypts using AES-256-GCM.
This implementation currently relies on an unreleased fork of PyKCS11.
"""
def __init__(self, conf=cfg.CONF):
self.block_size = 16 # in bytes
self.kek_key_length = 32 # in bytes (256-bit)
self.algorithm = 0x8000011c # CKM_AES_GCM vendor prefixed.
self.pkcs11 = PyKCS11.PyKCS11Lib()
if conf.p11_crypto_plugin.library_path is None:
raise ValueError(u._("library_path is required"))
else:
self.pkcs11.load(conf.p11_crypto_plugin.library_path)
# initialize the library. PyKCS11 does not supply this for free
self._check_error(self.pkcs11.lib.C_Initialize())
self.session = self.pkcs11.openSession(1)
self.session.login(conf.p11_crypto_plugin.login)
self.rw_session = self.pkcs11.openSession(1, PyKCS11.CKF_RW_SESSION)
self.rw_session.login(conf.p11_crypto_plugin.login)
def _check_error(self, value):
if value != PyKCS11.CKR_OK:
raise PyKCS11.PyKCS11Error(value)
def _get_key_by_label(self, key_label):
template = (
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
(PyKCS11.CKA_LABEL, key_label))
keys = self.session.findObjects(template)
if len(keys) == 1:
return keys[0]
elif len(keys) == 0:
return None
else:
raise P11CryptoPluginKeyException()
def _generate_iv(self):
iv = self.session.generateRandom(self.block_size)
iv = b''.join(chr(i) for i in iv)
if len(iv) != self.block_size:
raise P11CryptoPluginException()
return iv
def _build_gcm_params(self, iv):
gcm = PyKCS11.LowLevel.CK_AES_GCM_PARAMS()
gcm.pIv = iv
gcm.ulIvLen = len(iv)
gcm.ulIvBits = len(iv) * 8
gcm.ulTagBits = 128
return gcm
def _generate_kek(self, kek_label):
# TODO(reaperhulk): review template to ensure it's what we want
template = (
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
(PyKCS11.CKA_VALUE_LEN, self.kek_key_length),
(PyKCS11.CKA_LABEL, kek_label),
(PyKCS11.CKA_PRIVATE, True),
(PyKCS11.CKA_SENSITIVE, True),
(PyKCS11.CKA_ENCRYPT, True),
(PyKCS11.CKA_DECRYPT, True),
(PyKCS11.CKA_TOKEN, True),
(PyKCS11.CKA_WRAP, True),
(PyKCS11.CKA_UNWRAP, True),
(PyKCS11.CKA_EXTRACTABLE, False))
ckattr = self.session._template2ckattrlist(template)
m = PyKCS11.LowLevel.CK_MECHANISM()
m.mechanism = PyKCS11.LowLevel.CKM_AES_KEY_GEN
key = PyKCS11.LowLevel.CK_OBJECT_HANDLE()
self._check_error(
self.pkcs11.lib.C_GenerateKey(
self.rw_session.session,
m,
ckattr,
key
)
)
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, encrypt_dto.unencrypted, mech)
cyphertext = b''.join(chr(i) for i in encrypted)
kek_meta_extended = json.dumps({
'iv': base64.b64encode(iv)
})
return plugin.ResponseDTO(cyphertext, kek_meta_extended)
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, decrypt_dto.encrypted, mech)
secret = b''.join(chr(i) for i in decrypted)
return secret
def bind_kek_metadata(self, kek_meta_dto):
# Enforce idempotency: If we've already generated a key for the
# kek_label, leave now.
key = self._get_key_by_label(kek_meta_dto.kek_label)
if not key:
self._generate_kek(kek_meta_dto.kek_label)
# To be persisted by Barbican:
kek_meta_dto.algorithm = 'AES'
kek_meta_dto.bit_length = self.kek_key_length * 8
kek_meta_dto.mode = 'GCM'
kek_meta_dto.plugin_meta = None
return kek_meta_dto
def generate_symmetric(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 self.encrypt(plugin.EncryptDTO(rand), kek_meta_dto, keystone_id)
def generate_asymmetric(self, generate_dto, kek_meta_dto, keystone_id):
raise NotImplementedError("Feature not implemented for PKCS11")
def supports(self, type_enum, algorithm=None, bit_length=None, mode=None):
if type_enum == plugin.PluginSupportTypes.ENCRYPT_DECRYPT:
return True
elif type_enum == plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION:
return True
elif type_enum == plugin.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION:
return False
else:
return False

View File

@ -0,0 +1,200 @@
# 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.
import os
from Crypto.PublicKey import DSA
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
from cryptography import fernet
from oslo.config import cfg
import six
from barbican.openstack.common import gettextutils as u
from barbican.plugin.crypto import crypto as c
CONF = cfg.CONF
simple_crypto_plugin_group = cfg.OptGroup(name='simple_crypto_plugin',
title="Simple Crypto Plugin Options")
simple_crypto_plugin_opts = [
cfg.StrOpt('kek',
default=b'dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=',
help=u._('Key encryption key to be used by Simple Crypto '
'Plugin'))
]
CONF.register_group(simple_crypto_plugin_group)
CONF.register_opts(simple_crypto_plugin_opts, group=simple_crypto_plugin_group)
class SimpleCryptoPlugin(c.CryptoPluginBase):
"""Insecure implementation of the crypto plugin."""
def __init__(self, conf=CONF):
self.master_kek = conf.simple_crypto_plugin.kek
def _get_kek(self, kek_meta_dto):
if not kek_meta_dto.plugin_meta:
raise ValueError('KEK not yet created.')
# the kek is stored encrypted. Need to decrypt.
encryptor = fernet.Fernet(self.master_kek)
# Note : If plugin_meta type is unicode, encode to byte.
if isinstance(kek_meta_dto.plugin_meta, six.text_type):
kek_meta_dto.plugin_meta = kek_meta_dto.plugin_meta.encode('utf-8')
return encryptor.decrypt(kek_meta_dto.plugin_meta)
def encrypt(self, encrypt_dto, kek_meta_dto, keystone_id):
kek = self._get_kek(kek_meta_dto)
unencrypted = encrypt_dto.unencrypted
if not isinstance(unencrypted, str):
raise ValueError('Unencrypted data must be a byte type, '
'but was {0}'.format(type(unencrypted)))
encryptor = fernet.Fernet(kek)
cyphertext = encryptor.encrypt(unencrypted)
return c.ResponseDTO(cyphertext, None)
def decrypt(self, encrypted_dto, kek_meta_dto, kek_meta_extended,
keystone_id):
kek = self._get_kek(kek_meta_dto)
encrypted = encrypted_dto.encrypted
decryptor = fernet.Fernet(kek)
return decryptor.decrypt(encrypted)
def bind_kek_metadata(self, kek_meta_dto):
kek_meta_dto.algorithm = 'aes'
kek_meta_dto.bit_length = 128
kek_meta_dto.mode = 'cbc'
if not kek_meta_dto.plugin_meta:
# the kek is stored encrypted in the plugin_meta field
encryptor = fernet.Fernet(self.master_kek)
key = fernet.Fernet.generate_key()
kek_meta_dto.plugin_meta = encryptor.encrypt(key)
return kek_meta_dto
def generate_symmetric(self, generate_dto, kek_meta_dto, keystone_id):
byte_length = int(generate_dto.bit_length) / 8
unencrypted = os.urandom(byte_length)
return self.encrypt(c.EncryptDTO(unencrypted),
kek_meta_dto,
keystone_id)
def generate_asymmetric(self, generate_dto, kek_meta_dto, keystone_id):
"""Generate asymmetric keys based on below rule
- RSA, with passphrase (supported)
- RSA, without passphrase (supported)
- DSA, without passphrase (supported)
- DSA, with passphrase (not supported)
Note: PyCrypto is not capable of serializing DSA
keys and DER formated keys. Such keys will be
serialized to Base64 PEM to store in DB.
TODO (atiwari/reaperhulk): PyCrypto is not capable to serialize
DSA keys and DER formated keys, later we need to pick better
crypto lib.
"""
if generate_dto.algorithm is None\
or generate_dto.algorithm.lower() == 'rsa':
private_key = RSA.generate(
generate_dto.bit_length, None, None, 65537)
elif generate_dto.algorithm.lower() == 'dsa':
private_key = DSA.generate(generate_dto.bit_length, None, None)
else:
raise c.CryptoPrivateKeyFailureException()
public_key = private_key.publickey()
# Note (atiwari): key wrapping format PEM only supported
if generate_dto.algorithm.lower() == 'rsa':
public_key, private_key = self._wrap_key(public_key, private_key,
generate_dto.passphrase)
if generate_dto.algorithm.lower() == 'dsa':
if generate_dto.passphrase:
raise ValueError('Passphrase not supported for DSA key')
public_key, private_key = self._serialize_dsa_key(public_key,
private_key)
private_dto = self.encrypt(c.EncryptDTO(private_key),
kek_meta_dto,
keystone_id)
public_dto = self.encrypt(c.EncryptDTO(public_key),
kek_meta_dto,
keystone_id)
passphrase_dto = None
if generate_dto.passphrase:
encrypt_dto = c.EncryptDTO(generate_dto.passphrase)
passphrase_dto = self.encrypt(encrypt_dto,
kek_meta_dto,
keystone_id)
return private_dto, public_dto, passphrase_dto
def supports(self, type_enum, algorithm=None, bit_length=None,
mode=None):
if type_enum == c.PluginSupportTypes.ENCRYPT_DECRYPT:
return True
if type_enum == c.PluginSupportTypes.SYMMETRIC_KEY_GENERATION:
return self._is_algorithm_supported(algorithm,
bit_length)
elif type_enum == c.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION:
return self._is_algorithm_supported(algorithm,
bit_length)
else:
return False
def _wrap_key(self, public_key, private_key,
passphrase):
pkcs = 8
key_wrap_format = 'PEM'
private_key = private_key.exportKey(key_wrap_format, passphrase, pkcs)
public_key = public_key.exportKey()
return public_key, private_key
def _serialize_dsa_key(self, public_key, private_key):
pub_seq = asn1.DerSequence()
pub_seq[:] = [0, public_key.p, public_key.q,
public_key.g, public_key.y]
public_key = "-----BEGIN DSA PUBLIC KEY-----\n%s"\
"-----END DSA PUBLIC KEY-----" % pub_seq.encode().encode("base64")
prv_seq = asn1.DerSequence()
prv_seq[:] = [0, private_key.p, private_key.q,
private_key.g, private_key.y, private_key.x]
private_key = "-----BEGIN DSA PRIVATE KEY-----\n%s"\
"-----END DSA PRIVATE KEY-----" % prv_seq.encode().encode("base64")
return public_key, private_key
def _is_algorithm_supported(self, algorithm=None, bit_length=None):
"""check if algorithm and bit_length combination is supported."""
if algorithm is None or bit_length is None:
return False
if algorithm.lower() in c.PluginSupportTypes.SYMMETRIC_ALGORITHMS \
and bit_length in c.PluginSupportTypes.SYMMETRIC_KEY_LENGTHS:
return True
elif algorithm.lower() in c.PluginSupportTypes.ASYMMETRIC_ALGORITHMS \
and bit_length in c.PluginSupportTypes.ASYMMETRIC_KEY_LENGTHS:
return True
else:
return False

View File

@ -20,6 +20,7 @@ from oslo.config import cfg
from stevedore import named
from barbican.common import exception
from barbican.common import utils
from barbican.openstack.common import gettextutils as u
@ -55,6 +56,87 @@ class SecretStoreSupportedPluginNotFound(exception.BarbicanException):
message = "Secret store plugin not found for requested operation."
class SecretContentTypeNotSupportedException(exception.BarbicanException):
"""Raised when support for payload content type is not available."""
def __init__(self, content_type):
super(SecretContentTypeNotSupportedException, self).__init__(
u._("Secret Content Type "
"of '{0}' not supported").format(content_type)
)
self.content_type = content_type
class SecretContentEncodingNotSupportedException(exception.BarbicanException):
"""Raised when support for payload content encoding is not available."""
def __init__(self, content_encoding):
super(SecretContentEncodingNotSupportedException, self).__init__(
u._("Secret Content-Encoding of '{0}' not supported").format(
content_encoding)
)
self.content_encoding = content_encoding
class SecretNoPayloadProvidedException(exception.BarbicanException):
"""Raised when secret information is not provided."""
def __init__(self):
super(SecretNoPayloadProvidedException, self).__init__(
u._('No secret information provided to encrypt.')
)
class SecretContentEncodingMustBeBase64(exception.BarbicanException):
"""Raised when encoding must be base64."""
def __init__(self):
super(SecretContentEncodingMustBeBase64, self).__init__(
u._("Encoding type must be 'base64' for text-based payloads.")
)
class SecretGeneralException(exception.BarbicanException):
"""Raised when a system fault has occurred."""
def __init__(self, reason=u._('Unknown')):
super(SecretGeneralException, self).__init__(
u._('Problem seen during crypto processing - '
'Reason: {0}').format(reason)
)
self.reason = reason
class SecretPayloadDecodingError(exception.BarbicanException):
"""Raised when payload could not be decoded."""
def __init__(self):
super(SecretPayloadDecodingError, self).__init__(
u._("Problem decoding payload")
)
class SecretAcceptNotSupportedException(exception.BarbicanException):
"""Raised when requested decrypted content-type is not available."""
def __init__(self, accept):
super(SecretAcceptNotSupportedException, self).__init__(
u._("Secret Accept of '{0}' not supported").format(accept)
)
self.accept = accept
class SecretNotFoundException(exception.BarbicanException):
"""Raised when secret information could not be located."""
def __init__(self):
super(SecretNotFoundException, self).__init__(
u._('No secret information found')
)
class SecretAlgorithmNotSupportedException(exception.BarbicanException):
"""Raised when support for an algorithm is not available."""
def __init__(self, algorithm):
super(SecretAlgorithmNotSupportedException, self).__init__(
u._("Secret algorithm of '{0}' not supported").format(
algorithm)
)
self.algorithm = algorithm
class SecretType(object):
"""Constant to define the symmetric key type. Used by getSecret to retrieve
@ -90,63 +172,79 @@ class KeyAlgorithm(object):
DESEDE = "desede"
class KeyFormat(object):
"""Key format that indicates that key value is a bytearray of the raw bytes
of the string.
"""
RAW = "raw"
"""PKCS #1 encoding format."""
PKCS1 = "pkcs1"
"""PKCS #8 encoding format."""
PKCS8 = "pkcs8"
"""X.509 encoding format."""
X509 = "x509"
class KeySpec(object):
"""This object specifies the algorithm and bit length for a key."""
def __init__(self, alg, bit_length):
def __init__(self, alg=None, bit_length=None, mode=None):
"""Creates a new KeySpec.
:param alg:algorithm for the key
:param bit_length:bit length of the key
:param mode:algorithm mode for the key
"""
self.alg = alg
self.bit_length = bit_length
self.mode = mode # TODO(john-wood-w) Paul, is 'mode' required?
class SecretDTO(object):
"""This object is a secret data transfer object (DTO). This object
encapsulates a key and attributes about the key. The attributes include a
KeySpec that contains the algorithm and bit length. The attributes also
include information on the format and encoding of the key.
include information on the encoding of the key.
"""
def __init__(self, type, format, secret, key_spec):
#TODO(john-wood-w) Remove 'content_type' once secret normalization work is
# completed.
def __init__(self, type, secret, key_spec, content_type):
"""Creates a new SecretDTO.
The secret is stored in the secret parameter. The format parameter
indicates the format of the bytes for the secret. In the future this
The secret is stored in the secret parameter. In the future this
DTO may include compression and key wrapping information.
:param type: SecretType for secret
:param format: KeyFormat key format
:param secret: secret
:param secret: secret, as a base64-encoded string
:param key_spec: KeySpec key specifications
:param content_type: Content type of the secret, one of MIME
types such as 'text/plain' or 'application/octet-stream'
"""
self.type = type
self.format = format
self.secret = secret
self.key_spec = key_spec
self.content_type = content_type
#TODO(john-wood-w) Remove this class once repository factory work is
# completed.
class SecretStoreContext(object):
"""Context for secret store plugins.
Some plugins implementations (such as the crypto implementation) might
require access to core Barbican resources such as datastore repositories.
This object provides access to such storage.
"""
def __init__(self, **kwargs):
if kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
@six.add_metaclass(abc.ABCMeta)
class SecretStoreBase(object):
#TODO(john-wood-w) Remove 'context' once repository factory and secret
# normalization work is completed.
#TODO(john-wood-w) Combine generate_symmetric_key() and
# generate_asymmetric_key() into one method: generate_key(), that will
# return a dict with this structure:
# { SecretType.xxxxx: {secret-meta dict}
# So for symmetric keys, this would look like:
# { SecretType.SYMMETRIC: {secret-meta dict}
# And for asymmetric keys:
# { SecretType.PUBLIC: {secret-meta for public},
# SecretType.PRIVATE: {secret-meta for private}}
@abc.abstractmethod
def generate_symmetric_key(self, key_spec):
def generate_symmetric_key(self, key_spec, context):
"""Generate a new symmetric key and store it.
Generates a new symmetric key and stores it in the secret store.
@ -159,12 +257,13 @@ class SecretStoreBase(object):
:param key_spec: KeySpec that contains details on the type of key to
generate
:param context: SecretStoreContext for secret
:returns: a dictionary that contains metadata about the key
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def generate_asymmetric_key(self, key_spec):
def generate_asymmetric_key(self, key_spec, context):
"""Generate a new asymmetric key and store it.
Generates a new asymmetric key and stores it in the secret store.
@ -177,12 +276,13 @@ class SecretStoreBase(object):
:param key_spec: KeySpec that contains details on the type of key to
generate
:param context: SecretStoreContext for secret
:returns: a dictionary that contains metadata about the key
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def store_secret(self, secret_dto):
def store_secret(self, secret_dto, context):
"""Stores a key.
The SecretDTO contains the bytes of the secret and properties of the
@ -193,12 +293,13 @@ class SecretStoreBase(object):
dictionary may be empty if the SecretStore does not require it.
:param secret_dto: SecretDTO for secret
:param context: SecretStoreContext for secret
:returns: a dictionary that contains metadata about the secret
"""
raise NotImplementedError # pragma: no cover
@abc.abstractmethod
def get_secret(self, secret_metadata):
def get_secret(self, secret_metadata, context):
"""Retrieves a secret from the secret store.
Retrieves a secret from the secret store and returns a SecretDTO that
@ -209,6 +310,7 @@ class SecretStoreBase(object):
the key.
:param secret_metadata: secret metadata
:param context: SecretStoreContext for secret
:returns: SecretDTO that contains secret
"""
raise NotImplementedError # pragma: no cover
@ -262,6 +364,24 @@ class SecretStorePluginManager(named.NamedExtensionManager):
return self.extensions[0].obj
def get_plugin_retrieve_delete(self, plugin_name):
"""Gets a secret retrieve/delete plugin.
:returns: SecretStoreBase plugin implementation
"""
if len(self.extensions) < 1:
raise SecretStorePluginNotFound()
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
retrieve_delete_plugin = ext.obj
break
else:
raise SecretStoreSupportedPluginNotFound()
return retrieve_delete_plugin
def get_plugin_generate(self, key_spec):
"""Gets a secret generate plugin.

View File

@ -0,0 +1,185 @@
# 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 barbican.common import utils
from barbican.model import models
from barbican.plugin.interface import secret_store
from barbican.plugin.util import translations as tr
def store_secret(unencrypted_raw, content_type_raw, content_encoding,
spec, secret_model, tenant_model,
repos):
"""Store a provided secret into secure backend."""
# Create a secret model is one isn't provided.
# Note: For one-step secret stores, the model is not provided. For
# two-step secrets, the secret entity is already created and should then
# be passed into this function.
if not secret_model:
secret_model = models.Secret(spec)
elif _secret_already_has_stored_data(secret_model):
raise ValueError('Secret already has encrypted data stored for it.')
# If there is no secret data to store, then just create Secret entity and
# leave. A subsequent call to this method should provide both the Secret
# entity created here *and* the secret data to store into it.
if not unencrypted_raw:
_save_secret(secret_model, tenant_model, repos)
return secret_model
# Locate a suitable plugin to store the secret.
store_plugin = secret_store.SecretStorePluginManager().get_plugin_store()
# Normalize inputs prior to storage.
#TODO(john-wood-w) Normalize all secrets to base64, so we don't have to
# pass in 'content' type to the store_secret() call below.
unencrypted, content_type = tr.normalize_before_encryption(
unencrypted_raw, content_type_raw, content_encoding,
enforce_text_only=True)
# Store the secret securely.
#TODO(john-wood-w) Remove the SecretStoreContext once repository factory
# and unit test patch work is completed.
context = secret_store.SecretStoreContext(secret_model=secret_model,
tenant_model=tenant_model,
repos=repos)
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
bit_length=spec.get('bit_length'),
mode=spec.get('mode'))
secret_dto = secret_store.SecretDTO(None, unencrypted, key_spec,
content_type)
secret_metadata = store_plugin.store_secret(secret_dto, context)
# Save secret and metadata.
_save_secret(secret_model, tenant_model, repos)
_save_secret_metadata(secret_model, secret_metadata, store_plugin,
content_type, repos)
return secret_model
def get_secret(requesting_content_type, secret_model, tenant_model):
tr.analyze_before_decryption(requesting_content_type)
# Construct metadata dict from data model.
# Note: Must use the dict/tuple format for py2.6 usage.
secret_metadata = dict((k, v.value) for (k, v) in
secret_model.secret_store_metadata.items())
# Locate a suitable plugin to store the secret.
retrieve_plugin = secret_store.SecretStorePluginManager()\
.get_plugin_retrieve_delete(secret_metadata.get('plugin_name'))
# Retrieve the secret.
#TODO(john-wood-w) Remove the SecretStoreContext once repository factory
# and unit test patch work is completed.
context = secret_store.SecretStoreContext(secret_model=secret_model,
tenant_model=tenant_model)
secret_dto = retrieve_plugin.get_secret(secret_metadata, context)
# Denormalize the secret.
return tr.denormalize_after_decryption(secret_dto.secret,
requesting_content_type)
def generate_secret(spec, content_type,
tenant_model, repos):
"""Generate a secret and store into a secure backend."""
# Locate a suitable plugin to store the secret.
key_spec = secret_store.KeySpec(alg=spec.get('algorithm'),
bit_length=spec.get('bit_length'),
mode=spec.get('mode'))
generate_plugin = secret_store.SecretStorePluginManager()\
.get_plugin_generate(key_spec)
# Create secret model to eventually save metadata to.
secret_model = models.Secret(spec)
# Generate the secret.
#TODO(john-wood-w) Remove the SecretStoreContext once repository factory
# and unit test patch work is completed.
context = secret_store.SecretStoreContext(content_type=content_type,
secret_model=secret_model,
tenant_model=tenant_model,
repos=repos)
#TODO(john-wood-w) Replace with single 'generate_key()' call once
# asymmetric and symmetric generation is combined.
secret_metadata = generate_plugin.\
generate_symmetric_key(key_spec, context)
# Save secret and metadata.
_save_secret(secret_model, tenant_model, repos)
_save_secret_metadata(secret_model, secret_metadata, generate_plugin,
content_type, repos)
return secret_model
def delete_secret(secret_model, project_id, repos):
"""Remove a secret from secure backend."""
# Construct metadata dict from data model.
# Note: Must use the dict/tuple format for py2.6 usage.
secret_metadata = dict((k, v.value) for (k, v) in
secret_model.secret_store_metadata.items())
# Locate a suitable plugin to delete the secret from.
delete_plugin = secret_store.SecretStorePluginManager()\
.get_plugin_retrieve_delete(secret_metadata.get('plugin_name'))
# Delete the secret from plugin storage.
delete_plugin.delete_secret(secret_metadata)
# Delete the secret from data model.
repos.secret_repo.delete_entity_by_id(entity_id=secret_model.id,
keystone_id=project_id)
def _save_secret_metadata(secret_model, secret_metadata,
store_plugin, content_type, repos):
"""Add secret metadata to a secret."""
if not secret_metadata:
secret_metadata = dict()
secret_metadata['plugin_name'] = utils\
.generate_fullname_for(store_plugin)
secret_metadata['content_type'] = content_type
repos.secret_meta_repo.save(secret_metadata, secret_model)
def _save_secret(secret_model, tenant_model, repos):
"""Save a Secret entity."""
# Create Secret entities in data store.
if not secret_model.id:
repos.secret_repo.create_from(secret_model)
new_assoc = models.TenantSecret()
new_assoc.tenant_id = tenant_model.id
new_assoc.secret_id = secret_model.id
new_assoc.role = "admin"
new_assoc.status = models.States.ACTIVE
repos.tenant_secret_repo.create_from(new_assoc)
else:
repos.secret_repo.save(secret_model)
def _secret_already_has_stored_data(secret_model):
if not secret_model:
return False
return secret_model.encrypted_data or secret_model.secret_store_metadata

View File

@ -1 +1,243 @@
#TODO(john-wood-w) Add store to crypto adapter logic here.
# 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.
import base64
from oslo.config import cfg
from barbican.common import utils
from barbican.model import models
from barbican.plugin.crypto import crypto
from barbican.plugin.interface import secret_store as sstore
CONF = cfg.CONF
class StoreCryptoAdapterPlugin(sstore.SecretStoreBase):
"""Secret store plugin adapting to 'crypto' devices as backend.
HSM-style 'crypto' devices perform encryption/decryption processing but
do not actually store the encrypted information, unlike other 'secret
store' plugins that do provide storage. Hence, this adapter bridges
between these two plugin styles, providing Barbican persistence services
as needed to store information.
"""
def __init__(self):
super(StoreCryptoAdapterPlugin, self).__init__()
def store_secret(self, secret_dto, context):
"""Store a secret.
Returns a dict with the relevant metadata (which in this case is just
the key_id
"""
# Find HSM-style 'crypto' plugin.
encrypting_plugin = crypto.CryptoPluginManager()\
.get_plugin_store_generate(
crypto.PluginSupportTypes.ENCRYPT_DECRYPT
)
# Find or create a key encryption key metadata.
kek_datum_model, kek_meta_dto = self._find_or_create_kek_objects(
encrypting_plugin, context.tenant_model, context.repos.kek_repo)
encrypt_dto = crypto.EncryptDTO(secret_dto.secret)
# Create an encrypted datum instance and add the encrypted cyphertext.
datum_model = models.EncryptedDatum(context.secret_model,
kek_datum_model)
datum_model.content_type = secret_dto.content_type
response_dto = encrypting_plugin.encrypt(
encrypt_dto, kek_meta_dto, context.tenant_model.keystone_id
)
datum_model.kek_meta_extended = response_dto.kek_meta_extended
# Convert binary data into a text-based format.
#TODO(jwood) Figure out by storing binary (BYTEA) data in Postgres
# isn't working.
datum_model.cypher_text = base64.b64encode(response_dto.cypher_text)
self._store_secret_and_datum(context.tenant_model,
context.secret_model,
datum_model, context.repos)
return None
def get_secret(self, secret_metadata, context):
"""Retrieve a secret."""
if not context.secret_model \
or not context.secret_model.encrypted_data:
raise sstore.SecretNotFoundException()
#TODO(john-wood-w) Need to revisit 1 to many datum relationship.
datum_model = context.secret_model.encrypted_data[0]
# Find HSM-style 'crypto' plugin.
decrypting_plugin = crypto.CryptoPluginManager().get_plugin_retrieve(
datum_model.kek_meta_tenant.plugin_name)
# wrap the KEKDatum instance in our DTO
kek_meta_dto = crypto.KEKMetaDTO(datum_model.kek_meta_tenant)
# Convert from text-based storage format to binary.
#TODO(jwood) Figure out by storing binary (BYTEA) data in
# Postgres isn't working.
encrypted = base64.b64decode(datum_model.cypher_text)
decrypt_dto = crypto.DecryptDTO(encrypted)
# Decrypt the secret.
secret = decrypting_plugin.decrypt(decrypt_dto,
kek_meta_dto,
datum_model.kek_meta_extended,
context.tenant_model.keystone_id)
key_spec = sstore.KeySpec(alg=context.secret_model.algorithm,
bit_length=context.secret_model.bit_length,
mode=context.secret_model.mode)
return sstore.SecretDTO(sstore.SecretType.SYMMETRIC,
secret, key_spec,
datum_model.content_type)
def delete_secret(self, secret_metadata):
"""Delete a secret."""
pass
def generate_symmetric_key(self, key_spec, context):
"""Generate a symmetric key.
Returns a metadata object that can be used for retrieving the secret.
"""
# Find HSM-style 'crypto' plugin.
plugin_type = self._determine_generation_type(key_spec.alg)
if crypto.PluginSupportTypes.SYMMETRIC_KEY_GENERATION != plugin_type:
raise sstore.SecretAlgorithmNotSupportedException(key_spec.alg)
generating_plugin = crypto.CryptoPluginManager()\
.get_plugin_store_generate(plugin_type,
key_spec.alg,
key_spec.bit_length,
key_spec.mode)
# Find or create a key encryption key metadata.
kek_datum_model, kek_meta_dto = self._find_or_create_kek_objects(
generating_plugin, context.tenant_model, context.repos.kek_repo)
# Create an encrypted datum instance and add the created cypher text.
datum_model = models.EncryptedDatum(context.secret_model,
kek_datum_model)
datum_model.content_type = context.content_type
generate_dto = crypto.GenerateDTO(key_spec.alg,
key_spec.bit_length,
key_spec.mode, None)
# Create the encrypted meta.
response_dto = generating_plugin.\
generate_symmetric(generate_dto, kek_meta_dto,
context.tenant_model.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_model.cypher_text = base64.b64encode(response_dto.cypher_text)
datum_model.kek_meta_extended = response_dto.kek_meta_extended
self._store_secret_and_datum(context.tenant_model,
context.secret_model, datum_model,
context.repos)
return None
def generate_asymmetric_key(self, key_spec, context):
"""Generate an asymmetric key."""
#TODO(john-wood-w) Pull over https://github.com/openstack/barbican/
# blob/master/barbican/crypto/extension_manager.py#L336
raise NotImplementedError('No support for generate_asymmetric_key')
def generate_supports(self, key_spec):
"""Key generation supported?
Specifies whether the plugin supports key generation with the
given key_spec.
"""
return key_spec and sstore.KeyAlgorithm.AES == key_spec.alg.lower()
def _find_or_create_kek_objects(self, plugin_inst, tenant_model, kek_repo):
# Find or create a key encryption key.
full_plugin_name = utils.generate_fullname_for(plugin_inst)
kek_datum_model = kek_repo.find_or_create_kek_datum(tenant_model,
full_plugin_name)
# Bind to the plugin's key management.
# TODO(jwood): Does this need to be in a critical section? Should the
# bind operation just be declared idempotent in the plugin contract?
kek_meta_dto = crypto.KEKMetaDTO(kek_datum_model)
if not kek_datum_model.bind_completed:
kek_meta_dto = plugin_inst.bind_kek_metadata(kek_meta_dto)
# By contract, enforce that plugins return a
# (typically modified) DTO.
if kek_meta_dto is None:
raise crypto.CryptoKEKBindingException(full_plugin_name)
self._indicate_bind_completed(kek_meta_dto, kek_datum_model)
kek_repo.save(kek_datum_model)
return kek_datum_model, kek_meta_dto
def _store_secret_and_datum(self, tenant_model, secret_model, datum_model,
repos=None):
# Create Secret entities in data store.
if not secret_model.id:
repos.secret_repo.create_from(secret_model)
new_assoc = models.TenantSecret()
new_assoc.tenant_id = tenant_model.id
new_assoc.secret_id = secret_model.id
new_assoc.role = "admin"
new_assoc.status = models.States.ACTIVE
repos.tenant_secret_repo.create_from(new_assoc)
if datum_model:
datum_model.secret_id = secret_model.id
repos.datum_repo.create_from(datum_model)
def _indicate_bind_completed(self, kek_meta_dto, kek_datum):
"""Updates the supplied kek_datum instance
Updates the the kek_datum per the contents of the supplied
kek_meta_dto instance. This function is typically used once plugins
have had a chance to bind kek_meta_dto to their crypto systems.
:param kek_meta_dto:
:param kek_datum:
:return: None
"""
kek_datum.bind_completed = True
kek_datum.algorithm = kek_meta_dto.algorithm
kek_datum.bit_length = kek_meta_dto.bit_length
kek_datum.mode = kek_meta_dto.mode
kek_datum.plugin_meta = kek_meta_dto.plugin_meta
#TODO(john-wood-w) Move this to the more generic secret_store.py?
def _determine_generation_type(self, algorithm):
"""Determines the type (symmetric and asymmetric for now)
based on algorithm
"""
symmetric_algs = crypto.PluginSupportTypes.SYMMETRIC_ALGORITHMS
asymmetric_algs = crypto.PluginSupportTypes.ASYMMETRIC_ALGORITHMS
if algorithm.lower() in symmetric_algs:
return crypto.PluginSupportTypes.SYMMETRIC_KEY_GENERATION
elif algorithm.lower() in asymmetric_algs:
return crypto.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
else:
raise sstore.SecretAlgorithmNotSupportedException(algorithm)

View File

@ -0,0 +1,78 @@
# 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.
import base64
from barbican.plugin.interface import secret_store as s
from barbican.plugin.util import mime_types
def normalize_before_encryption(unencrypted, content_type, content_encoding,
enforce_text_only=False):
"""Normalize unencrypted prior to plugin encryption processing."""
if not unencrypted:
raise s.SecretNoPayloadProvidedException()
# Validate and normalize content-type.
normalized_mime = mime_types.normalize_content_type(content_type)
if not mime_types.is_supported(normalized_mime):
raise s.SecretContentTypeNotSupportedException(content_type)
# Process plain-text type.
if normalized_mime in mime_types.PLAIN_TEXT:
# normalize text to binary string
unencrypted = unencrypted.encode('utf-8')
# Process binary type.
else:
# payload has to be decoded
if mime_types.is_base64_processing_needed(content_type,
content_encoding):
try:
unencrypted = base64.b64decode(unencrypted)
except TypeError:
raise s.SecretPayloadDecodingError()
elif enforce_text_only:
# For text-based protocols (such as the one-step secret POST),
# only 'base64' encoding is possible/supported.
raise s.SecretContentEncodingMustBeBase64()
elif content_encoding:
# Unsupported content-encoding request.
raise s.SecretContentEncodingNotSupportedException(
content_encoding
)
return unencrypted, normalized_mime
def analyze_before_decryption(content_type):
"""Determine support for desired content type."""
if not mime_types.is_supported(content_type):
raise s.SecretAcceptNotSupportedException(content_type)
def denormalize_after_decryption(unencrypted, content_type):
"""Translate the decrypted data into the desired content type."""
# Process plain-text type.
if content_type in mime_types.PLAIN_TEXT:
# normalize text to binary string
try:
unencrypted = unencrypted.decode('utf-8')
except UnicodeDecodeError:
raise s.SecretAcceptNotSupportedException(content_type)
# Process binary type.
elif content_type not in mime_types.BINARY:
raise s.SecretContentTypeNotSupportedException(content_type)
return unencrypted

View File

@ -21,12 +21,12 @@ import abc
import six
from barbican import api
from barbican.common import resources as res
from barbican.common import utils
from barbican.crypto import extension_manager as em
from barbican.model import models
from barbican.model import repositories as rep
from barbican.openstack.common import gettextutils as u
from barbican.plugin import resources as plugin
LOG = utils.getLogger(__name__)
@ -145,21 +145,21 @@ class BeginOrder(BaseTask):
def get_name(self):
return u._('Create Secret')
def __init__(self, crypto_manager=None, tenant_repo=None, order_repo=None,
def __init__(self, tenant_repo=None, order_repo=None,
secret_repo=None, tenant_secret_repo=None,
datum_repo=None, kek_repo=None):
datum_repo=None, kek_repo=None, secret_meta_repo=None):
LOG.debug('Creating BeginOrder task processor')
self.order_repo = order_repo or rep.OrderRepo()
self.tenant_repo = tenant_repo or rep.TenantRepo()
self.secret_repo = secret_repo or rep.SecretRepo()
self.tenant_secret_repo = tenant_secret_repo or rep.TenantSecretRepo()
self.datum_repo = datum_repo or rep.EncryptedDatumRepo()
self.kek_repo = kek_repo or rep.KEKDatumRepo()
self.crypto_manager = crypto_manager or em.CryptoExtensionManager()
self.repos = rep.Repositories(tenant_repo=tenant_repo,
tenant_secret_repo=tenant_secret_repo,
secret_repo=secret_repo,
datum_repo=datum_repo,
kek_repo=kek_repo,
secret_meta_repo=secret_meta_repo,
order_repo=order_repo)
def retrieve_entity(self, order_id, keystone_id):
return self.order_repo.get(entity_id=order_id,
keystone_id=keystone_id)
return self.repos.order_repo.get(entity_id=order_id,
keystone_id=keystone_id)
def handle_processing(self, order, *args, **kwargs):
self.handle_order(order)
@ -169,11 +169,11 @@ class BeginOrder(BaseTask):
order.status = models.States.ERROR
order.error_status_code = status
order.error_reason = message
self.order_repo.save(order)
self.repos.order_repo.save(order)
def handle_success(self, order, *args, **kwargs):
order.status = models.States.ACTIVE
self.order_repo.save(order)
self.repos.order_repo.save(order)
def handle_order(self, order):
"""Handle secret creation.
@ -188,14 +188,15 @@ class BeginOrder(BaseTask):
secret_info = order_info['secret']
# Retrieve the tenant.
tenant = self.tenant_repo.get(order.tenant_id)
tenant = self.repos.tenant_repo.get(order.tenant_id)
# Create Secret
new_secret = res.create_secret(secret_info, tenant,
self.crypto_manager, self.secret_repo,
self.tenant_secret_repo,
self.datum_repo, self.kek_repo,
ok_to_generate=True)
new_secret = plugin.\
generate_secret(secret_info,
secret_info.get('payload_content_type',
'application/octet-stream'),
tenant, self.repos)
order.secret_id = new_secret.id
LOG.debug("...done creating order's secret.")

View File

@ -20,6 +20,7 @@ resource classes. For RBAC tests of these classes, see the
"""
import base64
import logging
import urllib
import mimetypes
@ -32,11 +33,12 @@ from barbican import api
from barbican.api import app
from barbican.api import controllers
from barbican.common import exception as excep
from barbican.common import utils
from barbican.common import validators
from barbican.crypto import extension_manager as em
from barbican.model import models
from barbican.tests.crypto import test_plugin as ctp
from barbican.openstack.common import timeutils
LOG = logging.getLogger(__name__)
def create_secret(id_ref="id", name="name",
@ -166,8 +168,9 @@ class BaseSecretsResource(FunctionalTest):
class RootController(object):
secrets = controllers.secrets.SecretsController(
self.crypto_mgr, self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo
self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo,
self.secret_meta_repo
)
return RootController()
@ -202,8 +205,10 @@ class BaseSecretsResource(FunctionalTest):
self.tenant_repo = mock.MagicMock()
self.tenant_repo.find_by_keystone_id.return_value = self.tenant
self.secret = models.Secret()
self.secret.id = '123'
self.secret_repo = mock.MagicMock()
self.secret_repo.create_from.return_value = None
self.secret_repo.create_from.return_value = self.secret
self.tenant_secret_repo = mock.MagicMock()
self.tenant_secret_repo.create_from.return_value = None
@ -212,19 +217,22 @@ class BaseSecretsResource(FunctionalTest):
self.datum_repo.create_from.return_value = None
self.kek_datum = models.KEKDatum()
self.kek_datum.plugin_name = utils.generate_fullname_for(
ctp.TestCryptoPlugin())
self.kek_datum.kek_label = "kek_label"
self.kek_datum.bind_completed = False
self.kek_datum.algorithm = ''
self.kek_datum.bit_length = 0
self.kek_datum.mode = ''
self.kek_datum.plugin_meta = ''
self.kek_repo = mock.MagicMock()
self.kek_repo.find_or_create_kek_metadata.return_value = self.kek_datum
self.kek_repo.find_or_create_kek_datum.return_value = self.kek_datum
self.conf = mock.MagicMock()
self.conf.crypto.namespace = 'barbican.test.crypto.plugin'
self.conf.crypto.enabled_crypto_plugins = ['test_crypto']
self.crypto_mgr = em.CryptoExtensionManager(conf=self.conf)
self.secret_meta_repo = mock.MagicMock()
@mock.patch('barbican.plugin.resources.store_secret')
def _test_should_add_new_secret_with_expiration(self, mock_store_secret):
mock_store_secret.return_value = self.secret
def _test_should_add_new_secret_with_expiration(self):
expiration = '2114-02-28 12:14:44.180394-05:00'
self.secret_req.update({'expiration': expiration})
@ -235,45 +243,48 @@ class BaseSecretsResource(FunctionalTest):
self.assertEqual(resp.status_int, 201)
args, kwargs = self.secret_repo.create_from.call_args
secret = args[0]
expected = expiration[:-6].replace('12', '17', 1)
self.assertEqual(expected, str(secret.expiration))
# Validation replaces the time.
expected = dict(self.secret_req)
expiration_raw = expected['expiration']
expiration_raw = expiration_raw[:-6].replace('12', '17', 1)
expiration_tz = timeutils.parse_isotime(expiration_raw.strip())
expected['expiration'] = timeutils.normalize_time(expiration_tz)
mock_store_secret\
.assert_called_once_with(
self.secret_req.get('payload'),
self.secret_req.get('payload_content_type',
'application/octet-stream'),
self.secret_req.get('payload_content_encoding'),
expected, None, self.tenant, mock.ANY
)
def _test_should_add_new_secret_one_step(self, check_tenant_id=True):
@mock.patch('barbican.plugin.resources.store_secret')
def _test_should_add_new_secret_one_step(self, mock_store_secret,
check_tenant_id=True):
"""Test the one-step secret creation.
:param check_tenant_id: True if the retrieved Tenant id needs to be
verified, False to skip this check (necessary for new-Tenant flows).
"""
mock_store_secret.return_value = self.secret
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
args, kwargs = self.secret_repo.create_from.call_args
secret = args[0]
self.assertIsInstance(secret, models.Secret)
self.assertEqual(secret.name, self.name)
self.assertEqual(secret.algorithm, self.secret_algorithm)
self.assertEqual(secret.bit_length, self.secret_bit_length)
self.assertEqual(secret.mode, self.secret_mode)
args, kwargs = self.tenant_secret_repo.create_from.call_args
tenant_secret = args[0]
self.assertIsInstance(tenant_secret, models.TenantSecret)
if check_tenant_id:
self.assertEqual(tenant_secret.tenant_id, self.tenant_entity_id)
self.assertEqual(tenant_secret.secret_id, secret.id)
args, kwargs = self.datum_repo.create_from.call_args
datum = args[0]
self.assertIsInstance(datum, models.EncryptedDatum)
self.assertEqual(base64.b64encode('cypher_text'), datum.cypher_text)
self.assertEqual(self.payload_content_type, datum.content_type)
validate_datum(self, datum)
expected = dict(self.secret_req)
expected['expiration'] = None
mock_store_secret\
.assert_called_once_with(
self.secret_req.get('payload'),
self.secret_req.get('payload_content_type',
'application/octet-stream'),
self.secret_req.get('payload_content_encoding'),
expected, None, self.tenant if check_tenant_id else mock.ANY,
mock.ANY
)
def _test_should_add_new_secret_if_tenant_does_not_exist(self):
self.tenant_repo.get.return_value = None
@ -305,7 +316,11 @@ class BaseSecretsResource(FunctionalTest):
self.assertFalse(self.datum_repo.create_from.called)
def _test_should_add_new_secret_payload_almost_too_large(self):
@mock.patch('barbican.plugin.resources.store_secret')
def _test_should_add_secret_payload_almost_too_large(self,
mock_store_secret):
mock_store_secret.return_value = self.secret
if validators.DEFAULT_MAX_SECRET_BYTES % 4:
raise ValueError('Tests currently require max secrets divides by '
'4 evenly, due to base64 encoding.')
@ -382,7 +397,7 @@ class WhenCreatingPlainTextSecretsUsingSecretsResource(BaseSecretsResource):
self._test_should_add_new_secret_metadata_without_payload()
def test_should_add_new_secret_payload_almost_too_large(self):
self._test_should_add_new_secret_payload_almost_too_large()
self._test_should_add_secret_payload_almost_too_large()
def test_should_fail_due_to_payload_too_large(self):
self._test_should_fail_due_to_payload_too_large()
@ -405,83 +420,88 @@ class WhenCreatingPlainTextSecretsUsingSecretsResource(BaseSecretsResource):
)
self.assertEqual(resp.status_int, 400)
def test_create_secret_content_type_text_plain(self):
# payload_content_type has trailing space
self.secret_req = {'name': self.name,
'payload_content_type': 'text/plain ',
'algorithm': self.secret_algorithm,
'bit_length': self.secret_bit_length,
'mode': self.secret_mode,
'payload': self.payload}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
self.secret_req = {'name': self.name,
'payload_content_type': ' text/plain',
'algorithm': self.secret_algorithm,
'bit_length': self.secret_bit_length,
'mode': self.secret_mode,
'payload': self.payload}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
def test_create_secret_content_type_text_plain_space_charset_utf8(self):
# payload_content_type has trailing space
self.secret_req = {'name': self.name,
'payload_content_type':
'text/plain; charset=utf-8 ',
'algorithm': self.secret_algorithm,
'bit_length': self.secret_bit_length,
'mode': self.secret_mode,
'payload': self.payload}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
self.secret_req = {'name': self.name,
'payload_content_type':
' text/plain; charset=utf-8',
'algorithm': self.secret_algorithm,
'bit_length': self.secret_bit_length,
'mode': self.secret_mode,
'payload': self.payload}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
def test_create_secret_with_only_content_type(self):
# No payload just content_type
self.secret_req = {'payload_content_type':
'text/plain'}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req,
expect_errors=True
)
self.assertEqual(resp.status_int, 400)
self.secret_req = {'payload_content_type':
'text/plain',
'payload': 'somejunk'}
resp = self.app.post_json(
'/%s/secrets/' % self.keystone_id,
self.secret_req
)
self.assertEqual(resp.status_int, 201)
#TODO(jwood) These tests are integration-style unit tests of the REST -> Pecan
# resources, which are more painful to test now that we have a two-tier
# plugin lookup framework. These tests should instead be re-located to
# unit tests of the secret_store.py and store_crypto.py modules. A separate
# CR will add the additional unit tests.
# def test_create_secret_content_type_text_plain(self):
# # payload_content_type has trailing space
# self.secret_req = {'name': self.name,
# 'payload_content_type': 'text/plain ',
# 'algorithm': self.secret_algorithm,
# 'bit_length': self.secret_bit_length,
# 'mode': self.secret_mode,
# 'payload': self.payload}
#
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req
# )
# self.assertEqual(resp.status_int, 201)
#
# self.secret_req = {'name': self.name,
# 'payload_content_type': ' text/plain',
# 'algorithm': self.secret_algorithm,
# 'bit_length': self.secret_bit_length,
# 'mode': self.secret_mode,
# 'payload': self.payload}
#
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req
# )
# self.assertEqual(resp.status_int, 201)
#
# def test_create_secret_content_type_text_plain_space_charset_utf8(self):
# # payload_content_type has trailing space
# self.secret_req = {'name': self.name,
# 'payload_content_type':
# 'text/plain; charset=utf-8 ',
# 'algorithm': self.secret_algorithm,
# 'bit_length': self.secret_bit_length,
# 'mode': self.secret_mode,
# 'payload': self.payload}
#
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req
# )
# self.assertEqual(resp.status_int, 201)
#
# self.secret_req = {'name': self.name,
# 'payload_content_type':
# ' text/plain; charset=utf-8',
# 'algorithm': self.secret_algorithm,
# 'bit_length': self.secret_bit_length,
# 'mode': self.secret_mode,
# 'payload': self.payload}
#
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req
# )
# self.assertEqual(resp.status_int, 201)
#
# def test_create_secret_with_only_content_type(self):
# # No payload just content_type
# self.secret_req = {'payload_content_type':
# 'text/plain'}
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req,
# expect_errors=True
# )
# self.assertEqual(resp.status_int, 400)
#
# self.secret_req = {'payload_content_type':
# 'text/plain',
# 'payload': 'somejunk'}
# resp = self.app.post_json(
# '/%s/secrets/' % self.keystone_id,
# self.secret_req
# )
# self.assertEqual(resp.status_int, 201)
class WhenCreatingBinarySecretsUsingSecretsResource(BaseSecretsResource):
@ -506,7 +526,7 @@ class WhenCreatingBinarySecretsUsingSecretsResource(BaseSecretsResource):
self._test_should_add_new_secret_metadata_without_payload()
def test_should_add_new_secret_payload_almost_too_large(self):
self._test_should_add_new_secret_payload_almost_too_large()
self._test_should_add_secret_payload_almost_too_large()
def test_should_fail_due_to_payload_too_large(self):
self._test_should_fail_due_to_payload_too_large()
@ -596,8 +616,9 @@ class WhenGettingSecretsListUsingSecretsResource(FunctionalTest):
class RootController(object):
secrets = controllers.secrets.SecretsController(
self.crypto_mgr, self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo
self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo,
self.secret_meta_repo
)
return RootController()
@ -641,10 +662,7 @@ class WhenGettingSecretsListUsingSecretsResource(FunctionalTest):
self.kek_repo = mock.MagicMock()
self.conf = mock.MagicMock()
self.conf.crypto.namespace = 'barbican.test.crypto.plugin'
self.conf.crypto.enabled_crypto_plugins = ['test_crypto']
self.crypto_mgr = em.CryptoExtensionManager(conf=self.conf)
self.secret_meta_repo = mock.MagicMock()
self.params = {'offset': self.offset,
'limit': self.limit,
@ -760,8 +778,9 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
class RootController(object):
secrets = controllers.secrets.SecretsController(
self.crypto_mgr, self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo
self.tenant_repo, self.secret_repo,
self.tenant_secret_repo, self.datum_repo, self.kek_repo,
self.secret_meta_repo
)
return RootController()
@ -784,8 +803,6 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.kek_tenant.active = True
self.kek_tenant.bind_completed = False
self.kek_tenant.kek_label = "kek_label"
self.kek_tenant.plugin_name = utils.generate_fullname_for(
ctp.TestCryptoPlugin())
self.datum = models.EncryptedDatum()
self.datum.id = datum_id
@ -807,6 +824,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.keystone_id = self.keystone_id
self.tenant_repo = mock.MagicMock()
self.tenant_repo.get.return_value = self.tenant
self.tenant_repo.find_by_keystone_id.return_value = self.tenant
self.secret_repo = mock.MagicMock()
self.secret_repo.get.return_value = self.secret
@ -819,10 +837,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.kek_repo = mock.MagicMock()
self.conf = mock.MagicMock()
self.conf.crypto.namespace = 'barbican.test.crypto.plugin'
self.conf.crypto.enabled_crypto_plugins = ['test_crypto']
self.crypto_mgr = em.CryptoExtensionManager(conf=self.conf)
self.secret_meta_repo = mock.MagicMock()
def test_should_get_secret_as_json(self):
resp = self.app.get(
@ -841,7 +856,11 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
resp.namespace['content_types'].itervalues())
self.assertNotIn('mime_type', resp.namespace)
def test_should_get_secret_as_plain(self):
@mock.patch('barbican.plugin.resources.get_secret')
def test_should_get_secret_as_plain(self, mock_get_secret):
data = 'unencrypted_data'
mock_get_secret.return_value = data
resp = self.app.get(
'/%s/secrets/%s/' % (self.keystone_id, self.secret.id),
headers={'Accept': 'text/plain'}
@ -853,7 +872,11 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
suppress_exception=True)
self.assertEqual(resp.status_int, 200)
self.assertIsNotNone(resp.body)
self.assertEqual(resp.body, data)
mock_get_secret\
.assert_called_once_with('text/plain',
self.secret,
self.tenant)
def test_should_get_secret_meta_for_binary(self):
self.datum.content_type = "application/octet-stream"
@ -876,7 +899,11 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.assertIn(self.datum.content_type,
resp.namespace['content_types'].itervalues())
def test_should_get_secret_as_binary(self):
@mock.patch('barbican.plugin.resources.get_secret')
def test_should_get_secret_as_binary(self, mock_get_secret):
data = 'unencrypted_data'
mock_get_secret.return_value = data
self.datum.content_type = "application/octet-stream"
self.datum.cypher_text = 'aaaa'
@ -888,7 +915,12 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
}
)
self.assertEqual(resp.body, 'unencrypted_data')
self.assertEqual(resp.body, data)
mock_get_secret\
.assert_called_once_with('application/octet-stream',
self.secret,
self.tenant)
def test_should_throw_exception_for_get_when_secret_not_found(self):
self.secret_repo.get.return_value = None
@ -908,17 +940,8 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
)
self.assertEqual(resp.status_int, 406)
def test_should_throw_exception_for_get_when_datum_not_available(self):
self.secret.encrypted_data = []
resp = self.app.get(
'/%s/secrets/%s/' % (self.keystone_id, self.secret.id),
headers={'Accept': 'text/plain'},
expect_errors=True
)
self.assertEqual(resp.status_int, 404)
def test_should_put_secret_as_plain(self):
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_secret_as_plain(self, mock_store_secret):
self.secret.encrypted_data = []
resp = self.app.put(
@ -929,14 +952,13 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.assertEqual(resp.status_int, 204)
args, kwargs = self.datum_repo.create_from.call_args
datum = args[0]
self.assertIsInstance(datum, models.EncryptedDatum)
self.assertEqual(base64.b64encode('cypher_text'), datum.cypher_text)
mock_store_secret\
.assert_called_once_with('plain text', 'text/plain',
None, self.secret.to_dict_fields,
self.secret, self.tenant, mock.ANY)
validate_datum(self, datum)
def test_should_put_secret_as_binary(self):
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_secret_as_binary(self, mock_store_secret):
self.secret.encrypted_data = []
resp = self.app.put(
@ -950,15 +972,18 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.assertEqual(resp.status_int, 204)
args, kwargs = self.datum_repo.create_from.call_args
datum = args[0]
self.assertIsInstance(datum, models.EncryptedDatum)
mock_store_secret\
.assert_called_once_with('plain text', 'application/octet-stream',
None, self.secret.to_dict_fields,
self.secret, self.tenant, mock.ANY)
def test_should_put_encoded_secret_as_binary(self):
@mock.patch('barbican.plugin.resources.store_secret')
def test_should_put_encoded_secret_as_binary(self, mock_store_secret):
self.secret.encrypted_data = []
payload = base64.b64encode('plain text')
resp = self.app.put(
'/%s/secrets/%s/' % (self.keystone_id, self.secret.id),
base64.b64encode('plain text'),
payload,
headers={
'Accept': 'text/plain',
'Content-Type': 'application/octet-stream',
@ -968,6 +993,11 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
self.assertEqual(resp.status_int, 204)
mock_store_secret\
.assert_called_once_with(payload, 'application/octet-stream',
'base64', self.secret.to_dict_fields,
self.secret, self.tenant, mock.ANY)
def test_should_fail_to_put_secret_with_unsupported_encoding(self):
self.secret.encrypted_data = []
resp = self.app.put(
@ -1059,17 +1089,17 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
)
self.assertEqual(resp.status_int, 413)
def test_should_delete_secret(self):
@mock.patch('barbican.plugin.resources.delete_secret')
def test_should_delete_secret(self, mock_delete_secret):
self.app.delete(
'/%s/secrets/%s/' % (self.keystone_id, self.secret.id)
)
self.secret_repo.delete_entity_by_id \
.assert_called_once_with(entity_id=self.secret.id,
keystone_id=self.keystone_id)
mock_delete_secret\
.assert_called_once_with(self.secret, self.keystone_id, mock.ANY)
def test_should_throw_exception_for_delete_when_secret_not_found(self):
self.secret_repo.delete_entity_by_id.side_effect = excep.NotFound(
"Test not found exception")
self.secret_repo.get.return_value = None
resp = self.app.delete(
'/%s/secrets/%s/' % (self.keystone_id, self.secret.id),

View File

@ -235,13 +235,13 @@ class WhenTestingSecretsResource(BaseTestCase):
._generate_get_error())
self.secret_repo.get_by_create_date = get_by_create_date
self.resource = SecretsResource(crypto_manager=mock.MagicMock(),
tenant_repo=mock.MagicMock(),
self.resource = SecretsResource(tenant_repo=mock.MagicMock(),
secret_repo=self.secret_repo,
tenant_secret_repo=mock
.MagicMock(),
datum_repo=mock.MagicMock(),
kek_repo=mock.MagicMock())
kek_repo=mock.MagicMock(),
secret_meta_repo=mock.MagicMock())
def test_rules_should_be_loaded(self):
self.assertIsNotNone(self.policy_enforcer.rules)
@ -285,7 +285,6 @@ class WhenTestingSecretResource(BaseTestCase):
self.secret_repo.delete_entity_by_id = fail_method
self.resource = SecretResource(self.secret_id,
crypto_manager=mock.MagicMock(),
tenant_repo=mock.MagicMock(),
secret_repo=self.secret_repo,
datum_repo=mock.MagicMock(),

View File

@ -1,203 +0,0 @@
# Copyright (c) 2014 Red Hat, Inc.
#
# 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.
import mock
import os
import tempfile
import testtools
try:
import barbican.crypto.dogtag_crypto as dogtag_import
from barbican.crypto import plugin as plugin_import
from barbican.model import models
imports_ok = True
except ImportError:
# dogtag imports probably not available
imports_ok = False
class WhenTestingDogtagCryptoPlugin(testtools.TestCase):
def setUp(self):
super(WhenTestingDogtagCryptoPlugin, self).setUp()
if not imports_ok:
return
self.keyclient_mock = mock.MagicMock(name="KeyClient mock")
self.patcher = mock.patch('pki.cryptoutil.NSSCryptoUtil')
self.patcher.start()
# create nss db for test only
self.nss_dir = tempfile.mkdtemp()
self.cfg_mock = mock.MagicMock(name='config mock')
self.cfg_mock.dogtag_crypto_plugin = mock.MagicMock(
nss_db_path=self.nss_dir)
self.plugin = dogtag_import.DogtagCryptoPlugin(self.cfg_mock)
self.plugin.keyclient = self.keyclient_mock
def tearDown(self):
super(WhenTestingDogtagCryptoPlugin, self).tearDown()
if not imports_ok:
return
self.patcher.stop()
os.rmdir(self.nss_dir)
def test_generate(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
secret = models.Secret()
secret.bit_length = 128
secret.algorithm = "AES"
generate_dto = plugin_import.GenerateDTO(
secret.algorithm,
secret.bit_length,
None,
None)
self.plugin.generate_symmetric(
generate_dto,
mock.MagicMock(),
mock.MagicMock()
)
self.keyclient_mock.generate_symmetric_key.assert_called_once_with(
mock.ANY,
secret.algorithm.upper(),
secret.bit_length,
mock.ANY)
def test_generate_non_supported_algorithm(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
secret = models.Secret()
secret.bit_length = 128
secret.algorithm = "hmacsha256"
generate_dto = plugin_import.GenerateDTO(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
secret.algorithm,
secret.bit_length,
None)
self.assertRaises(
dogtag_import.DogtagPluginAlgorithmException,
self.plugin.generate_symmetric,
generate_dto,
mock.MagicMock(),
mock.MagicMock()
)
def test_raises_error_with_no_pem_path(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
m = mock.MagicMock()
m.dogtag_crypto_plugin = mock.MagicMock(pem_path=None)
self.assertRaises(
ValueError,
dogtag_import.DogtagCryptoPlugin,
m,
)
def test_raises_error_with_no_pem_password(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
m = mock.MagicMock()
m.dogtag_crypto_plugin = mock.MagicMock(pem_password=None)
self.assertRaises(
ValueError,
dogtag_import.DogtagCryptoPlugin,
m,
)
def test_raises_error_with_no_nss_password(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
m = mock.MagicMock()
m.dogtag_crypto_plugin = mock.MagicMock(nss_password=None)
self.assertRaises(
ValueError,
dogtag_import.DogtagCryptoPlugin,
m,
)
def test_encrypt(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
payload = 'encrypt me!!'
encrypt_dto = plugin_import.EncryptDTO(payload)
self.plugin.encrypt(encrypt_dto,
mock.MagicMock(),
mock.MagicMock())
self.keyclient_mock.archive_key.assert_called_once_with(
mock.ANY,
"passPhrase",
payload,
key_algorithm=None,
key_size=None)
def test_decrypt(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
key_id = 'key1'
decrypt_dto = plugin_import.DecryptDTO(key_id)
self.plugin.decrypt(decrypt_dto,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock())
self.keyclient_mock.retrieve_key.assert_called_once_with(key_id)
def test_supports_encrypt_decrypt(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
self.assertTrue(
self.plugin.supports(
plugin_import.PluginSupportTypes.ENCRYPT_DECRYPT
)
)
def test_supports_symmetric_key_generation(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
self.assertTrue(
self.plugin.supports(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
'aes', 256
)
)
def test_supports_symmetric_hmacsha256_key_generation(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
self.assertFalse(
self.plugin.supports(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION,
'hmacsha256', 128
)
)
def test_supports_asymmetric_key_generation(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
self.assertFalse(
self.plugin.supports(
plugin_import.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
)
)
def test_does_not_support_unknown_type(self):
if not imports_ok:
self.skipTest("Dogtag imports not available")
self.assertFalse(
self.plugin.supports("SOMETHING_RANDOM")
)

View File

@ -1,477 +0,0 @@
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# 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.
import base64
import mock
import testtools
from barbican.crypto import extension_manager as em
from barbican.crypto import mime_types as mt
from barbican.crypto import plugin
def get_mocked_kek_repo():
# For SimpleCryptoPlugin, per-tenant KEKs are stored in
# kek_meta_dto.plugin_meta. SimpleCryptoPlugin does a get-or-create
# on the plugin_meta field, so plugin_meta should be None initially.
kek_datum = mock.MagicMock()
kek_datum.plugin_meta = None
kek_datum.bind_completed = False
kek_repo = mock.MagicMock(name='kek_repo')
kek_repo.find_or_create_kek_datum = mock.MagicMock()
kek_repo.find_or_create_kek_datum.return_value = kek_datum
return kek_repo
class TestSupportsCryptoPlugin(plugin.CryptoPluginBase):
"""Crypto plugin for testing supports."""
def encrypt(self, encrypt_dto, kek_meta_dto, tenant):
raise NotImplementedError()
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 generate_symmetric(self, generate_dto,
kek_meta_dto, keystone_id):
raise NotImplementedError()
def generate_asymmetric(self, generate_dto,
kek_meta_dto, keystone_id):
raise NotImplementedError("Feature not implemented for PKCS11")
def supports(self, type_enum, algorithm=None, bit_length=None, mode=None):
return False
class WhenTestingNormalizeBeforeEncryptionForBinary(testtools.TestCase):
def setUp(self):
super(WhenTestingNormalizeBeforeEncryptionForBinary, self).setUp()
self.unencrypted = 'AAAAAAAA'
self.content_type = 'application/octet-stream'
self.content_encoding = 'base64'
self.enforce_text_only = False
def test_encrypt_binary_from_base64(self):
unenc, content = em.normalize_before_encryption(self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only)
self.assertEqual(self.content_type, content)
self.assertEqual(base64.b64decode(self.unencrypted), unenc)
def test_encrypt_binary_directly(self):
self.content_encoding = None
unenc, content = em.normalize_before_encryption(self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only)
self.assertEqual(self.content_type, content)
self.assertEqual(self.unencrypted, unenc)
def test_encrypt_fail_binary_unknown_encoding(self):
self.content_encoding = 'gzip'
ex = self.assertRaises(
em.CryptoContentEncodingNotSupportedException,
em.normalize_before_encryption,
self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only,
)
self.assertEqual(self.content_encoding, ex.content_encoding)
def test_encrypt_fail_binary_force_text_based_no_encoding(self):
self.content_encoding = None
self.enforce_text_only = True
self.assertRaises(
em.CryptoContentEncodingMustBeBase64,
em.normalize_before_encryption,
self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only,
)
def test_encrypt_fail_unknown_content_type(self):
self.content_type = 'bogus'
ex = self.assertRaises(
em.CryptoContentTypeNotSupportedException,
em.normalize_before_encryption,
self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only,
)
self.assertEqual(self.content_type, ex.content_type)
class WhenTestingNormalizeBeforeEncryptionForText(testtools.TestCase):
def setUp(self):
super(WhenTestingNormalizeBeforeEncryptionForText, self).setUp()
self.unencrypted = 'AAAAAAAA'
self.content_type = 'text/plain'
self.content_encoding = 'base64'
self.enforce_text_only = False
def test_encrypt_text_ignore_encoding(self):
unenc, content = em.normalize_before_encryption(self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only)
self.assertEqual(self.content_type, content)
self.assertEqual(self.unencrypted, unenc)
def test_encrypt_text_not_normalized_ignore_encoding(self):
self.content_type = 'text/plain;charset=utf-8'
unenc, content = em.normalize_before_encryption(self.unencrypted,
self.content_type,
self.content_encoding,
self.enforce_text_only)
self.assertEqual(mt.normalize_content_type(self.content_type),
content)
self.assertEqual(self.unencrypted.encode('utf-8'), unenc)
def test_raises_on_bogus_content_type(self):
content_type = 'text/plain; charset=ISO-8859-1'
self.assertRaises(
em.CryptoContentTypeNotSupportedException,
em.normalize_before_encryption,
self.unencrypted,
content_type,
self.content_encoding,
self.enforce_text_only
)
def test_raises_on_no_payload(self):
content_type = 'text/plain; charset=ISO-8859-1'
self.assertRaises(
em.CryptoNoPayloadProvidedException,
em.normalize_before_encryption,
None,
content_type,
self.content_encoding,
self.enforce_text_only
)
class WhenTestingAnalyzeBeforeDecryption(testtools.TestCase):
def setUp(self):
super(WhenTestingAnalyzeBeforeDecryption, self).setUp()
self.content_type = 'application/octet-stream'
def test_decrypt_fail_bogus_content_type(self):
self.content_type = 'bogus'
ex = self.assertRaises(
em.CryptoAcceptNotSupportedException,
em.analyze_before_decryption,
self.content_type,
)
self.assertEqual(self.content_type, ex.accept)
class WhenTestingDenormalizeAfterDecryption(testtools.TestCase):
def setUp(self):
super(WhenTestingDenormalizeAfterDecryption, self).setUp()
self.unencrypted = 'AAAAAAAA'
self.content_type = 'application/octet-stream'
def test_decrypt_fail_binary(self):
unenc = em.denormalize_after_decryption(self.unencrypted,
self.content_type)
self.assertEqual(self.unencrypted, unenc)
def test_decrypt_text(self):
self.content_type = 'text/plain'
unenc = em.denormalize_after_decryption(self.unencrypted,
self.content_type)
self.assertEqual(self.unencrypted.decode('utf-8'), unenc)
def test_decrypt_fail_unknown_content_type(self):
self.content_type = 'bogus'
self.assertRaises(
em.CryptoGeneralException,
em.denormalize_after_decryption,
self.unencrypted,
self.content_type,
)
def test_decrypt_fail_binary_as_plain(self):
self.unencrypted = '\xff'
self.content_type = 'text/plain'
self.assertRaises(
em.CryptoAcceptNotSupportedException,
em.denormalize_after_decryption,
self.unencrypted,
self.content_type,
)
class WhenTestingCryptoExtensionManager(testtools.TestCase):
def setUp(self):
super(WhenTestingCryptoExtensionManager, self).setUp()
self.manager = em.CryptoExtensionManager()
def test_create_supported_algorithm(self):
skg = plugin.PluginSupportTypes.SYMMETRIC_KEY_GENERATION
self.assertEqual(skg, self.manager._determine_type('AES'))
self.assertEqual(skg, self.manager._determine_type('aes'))
self.assertEqual(skg, self.manager._determine_type('DES'))
self.assertEqual(skg, self.manager._determine_type('des'))
def test_create_unsupported_algorithm(self):
self.assertRaises(
em.CryptoAlgorithmNotSupportedException,
self.manager._determine_type,
'faux_alg',
)
def test_create_asymmetric_supported_algorithm(self):
skg = plugin.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
self.assertEqual(skg, self.manager._determine_type('RSA'))
self.assertEqual(skg, self.manager._determine_type('rsa'))
self.assertEqual(skg, self.manager._determine_type('DSA'))
self.assertEqual(skg, self.manager._determine_type('dsa'))
def test_encrypt_no_plugin_found(self):
self.manager.extensions = []
self.assertRaises(
em.CryptoPluginNotFound,
self.manager.encrypt,
'payload',
'content_type',
'content_encoding',
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
)
def test_encrypt_no_supported_plugin(self):
plugin = TestSupportsCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plugin)
self.manager.extensions = [plugin_mock]
self.assertRaises(
em.CryptoSupportedPluginNotFound,
self.manager.encrypt,
'payload',
'content_type',
'content_encoding',
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
)
def test_encrypt_response_dto(self):
plg = plugin.SimpleCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plg)
self.manager.extensions = [plugin_mock]
kek_repo = get_mocked_kek_repo()
response_dto = self.manager.encrypt(
'payload', 'text/plain', None, mock.MagicMock(), mock.MagicMock(),
kek_repo, False
)
self.assertIsNotNone(response_dto)
def test_decrypt_no_plugin_found(self):
"""Passing mocks here causes CryptoPluginNotFound because the mock
won't match any of the available plugins.
"""
self.assertRaises(
em.CryptoPluginNotFound,
self.manager.decrypt,
'text/plain',
mock.MagicMock(),
mock.MagicMock(),
)
def test_decrypt_no_supported_plugin_found(self):
"""Similar to test_decrypt_no_plugin_found, but in this case
no plugin can be found that supports the specified secret's
encrypted data.
"""
fake_secret = mock.MagicMock()
fake_datum = mock.MagicMock()
fake_datum.kek_meta_tenant = mock.MagicMock()
fake_secret.encrypted_data = [fake_datum]
self.assertRaises(
em.CryptoPluginNotFound,
self.manager.decrypt,
'text/plain',
fake_secret,
mock.MagicMock(),
)
def test_generate_data_encryption_key_no_plugin_found(self):
self.manager.extensions = []
self.assertRaises(
em.CryptoPluginNotFound,
self.manager.generate_symmetric_encryption_key,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
)
def test_generate_symmetric_encryption_key(self):
secret = mock.MagicMock(algorithm='aes', bit_length=128)
content_type = 'application/octet-stream'
tenant = mock.MagicMock()
kek_repo = get_mocked_kek_repo()
plg = plugin.SimpleCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plg)
self.manager.extensions = [plugin_mock]
datum = self.manager.generate_symmetric_encryption_key(
secret, content_type, tenant, kek_repo
)
self.assertIsNotNone(datum)
def test_generate_data_encryption_key_no_supported_plugin(self):
plugin = TestSupportsCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plugin)
self.manager.extensions = [plugin_mock]
self.assertRaises(
em.CryptoSupportedPluginNotFound,
self.manager.generate_symmetric_encryption_key,
mock.MagicMock(algorithm='AES'),
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
)
def test_find_or_create_kek_objects_bind_returns_none(self):
plugin = TestSupportsCryptoPlugin()
kek_repo = mock.MagicMock(name='kek_repo')
bind_completed = mock.MagicMock(bind_completed=False)
kek_repo.find_or_create_kek_datum.return_value = bind_completed
self.assertRaises(
em.CryptoKEKBindingException,
self.manager._find_or_create_kek_objects,
plugin,
mock.MagicMock(),
kek_repo,
)
def test_find_or_create_kek_objects_saves_to_repo(self):
kek_repo = mock.MagicMock(name='kek_repo')
bind_completed = mock.MagicMock(bind_completed=False)
kek_repo.find_or_create_kek_datum.return_value = bind_completed
self.manager._find_or_create_kek_objects(
mock.MagicMock(),
mock.MagicMock(),
kek_repo
)
self.assertEqual(1, kek_repo.save.call_count)
def generate_asymmetric_encryption_keys_no_plugin_found(self):
self.manager.extensions = []
self.assertRaises(
em.CryptoPluginNotFound,
self.manager.generate_asymmetric_encryption_keys,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock(),
)
def generate_asymmetric_encryption_keys_no_supported_plugin(self):
plugin = TestSupportsCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plugin)
self.manager.extensions = [plugin_mock]
self.assertRaises(
em.CryptoSupportedPluginNotFound,
self.manager.generate_asymmetric_encryption_keys,
mock.MagicMock(algorithm='DSA'),
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock()
)
def generate_asymmetric_encryption_rsa_keys_ensure_encoding(self):
plg = plugin.SimpleCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plg)
self.manager.extensions = [plugin_mock]
meta = mock.MagicMock(algorithm='rsa',
bit_length=1024,
passphrase=None)
private_datum, public_datum, passphrase_datum = \
self.manager.generate_asymmetric_encryption_keys(meta,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock())
self.assertIsNotNone(private_datum)
self.assertIsNotNone(public_datum)
self.assertIsNone(passphrase_datum)
try:
base64.b64decode(private_datum.cypher_text)
base64.b64decode(public_datum.cypher_text)
if passphrase_datum:
base64.b64decode(passphrase_datum.cypher_text)
isB64Encoding = True
except Exception:
isB64Encoding = False
self.assertTrue(isB64Encoding)
def generate_asymmetric_encryption_dsa_keys_ensure_encoding(self):
plg = plugin.SimpleCryptoPlugin()
plugin_mock = mock.MagicMock(obj=plg)
self.manager.extensions = [plugin_mock]
meta = mock.MagicMock(algorithm='rsa',
bit_length=1024,
passphrase=None)
private_datum, public_datum, passphrase_datum = \
self.manager.generate_asymmetric_encryption_keys(meta,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock())
self.assertIsNotNone(private_datum)
self.assertIsNotNone(public_datum)
self.assertIsNone(passphrase_datum)
try:
base64.b64decode(private_datum.cypher_text)
base64.b64decode(public_datum.cypher_text)
if passphrase_datum:
base64.b64decode(passphrase_datum.cypher_text)
isB64Encoding = True
except Exception:
isB64Encoding = False
self.assertTrue(isB64Encoding)

View File

View File

@ -24,8 +24,9 @@ import mock
import six
import testtools
from barbican.crypto import plugin
from barbican.model import models
from barbican.plugin.crypto import crypto as plugin
from barbican.plugin.crypto import simple_crypto as simple
class TestCryptoPlugin(plugin.CryptoPluginBase):
@ -67,7 +68,7 @@ class WhenTestingSimpleCryptoPlugin(testtools.TestCase):
def setUp(self):
super(WhenTestingSimpleCryptoPlugin, self).setUp()
self.plugin = plugin.SimpleCryptoPlugin()
self.plugin = simple.SimpleCryptoPlugin()
def _get_mocked_kek_meta_dto(self):
# For SimpleCryptoPlugin, per-tenant KEKs are stored in

View File

@ -17,9 +17,9 @@ import mock
import testtools
from barbican.crypto import p11_crypto
from barbican.crypto import plugin as plugin_import
from barbican.model import models
from barbican.plugin.crypto import crypto as plugin_import
from barbican.plugin.crypto import p11_crypto
class WhenTestingP11CryptoPlugin(testtools.TestCase):
@ -29,7 +29,7 @@ class WhenTestingP11CryptoPlugin(testtools.TestCase):
self.p11_mock = mock.MagicMock(CKR_OK=0, CKF_RW_SESSION='RW',
name='PyKCS11 mock')
self.patcher = mock.patch('barbican.crypto.p11_crypto.PyKCS11',
self.patcher = mock.patch('barbican.plugin.crypto.p11_crypto.PyKCS11',
new=self.p11_mock)
self.patcher.start()
self.pkcs11 = self.p11_mock.PyKCS11Lib()

View File

View File

@ -15,8 +15,8 @@
import testtools
from barbican.crypto import mime_types
from barbican.model import models
from barbican.plugin.util import mime_types
class WhenTestingIsBase64ProcessingNeeded(testtools.TestCase):

View File

@ -16,7 +16,6 @@
import mock
import testtools
from barbican.crypto import extension_manager as em
from barbican.model import models
from barbican.openstack.common import gettextutils as u
from barbican.openstack.common import timeutils
@ -61,6 +60,8 @@ class WhenBeginningOrder(testtools.TestCase):
self.order_repo = mock.MagicMock()
self.order_repo.get.return_value = self.order
self.secret = models.Secret()
self.secret_repo = mock.MagicMock()
self.secret_repo.create_from.return_value = None
@ -72,18 +73,19 @@ class WhenBeginningOrder(testtools.TestCase):
self.kek_repo = mock.MagicMock()
self.conf = mock.MagicMock()
self.conf.crypto.namespace = 'barbican.test.crypto.plugin'
self.conf.crypto.enabled_crypto_plugins = ['test_crypto']
self.crypto_mgr = em.CryptoExtensionManager(conf=self.conf)
self.secret_meta_repo = mock.MagicMock()
self.resource = resources.BeginOrder(self.crypto_mgr,
self.tenant_repo, self.order_repo,
self.resource = resources.BeginOrder(self.tenant_repo, self.order_repo,
self.secret_repo,
self.tenant_secret_repo,
self.datum_repo, self.kek_repo)
self.datum_repo, self.kek_repo,
self.secret_meta_repo
)
@mock.patch('barbican.plugin.resources.generate_secret')
def test_should_process_order(self, mock_generate_secret):
mock_generate_secret.return_value = self.secret
def test_should_process_order(self):
self.resource.process(self.order.id, self.keystone_id)
self.order_repo.get \
@ -91,28 +93,14 @@ class WhenBeginningOrder(testtools.TestCase):
keystone_id=self.keystone_id)
self.assertEqual(self.order.status, models.States.ACTIVE)
args, kwargs = self.secret_repo.create_from.call_args
secret = args[0]
self.assertIsInstance(secret, models.Secret)
self.assertEqual(secret.name, self.secret_name)
self.assertEqual(secret.expiration, self.secret_expiration.isoformat())
args, kwargs = self.tenant_secret_repo.create_from.call_args
tenant_secret = args[0]
self.assertIsInstance(tenant_secret, models.TenantSecret)
self.assertEqual(tenant_secret.tenant_id, self.tenant_id)
self.assertEqual(tenant_secret.secret_id, secret.id)
args, kwargs = self.datum_repo.create_from.call_args
datum = args[0]
self.assertIsInstance(datum, models.EncryptedDatum)
self.assertIsNotNone(datum.cypher_text)
self.assertIsNone(datum.kek_meta_extended)
self.assertIsNotNone(datum.kek_meta_tenant)
self.assertTrue(datum.kek_meta_tenant.bind_completed)
self.assertIsNotNone(datum.kek_meta_tenant.plugin_name)
self.assertIsNotNone(datum.kek_meta_tenant.kek_label)
secret_info = self.order.to_dict_fields()['secret']
mock_generate_secret\
.assert_called_once_with(
secret_info,
secret_info.get('payload_content_type',
'application/octet-stream'),
self.tenant, mock.ANY
)
def test_should_fail_during_retrieval(self):
# Force an error during the order retrieval phase.
@ -146,7 +134,11 @@ class WhenBeginningOrder(testtools.TestCase):
self.assertEqual(u._('Create Secret failure seen - please contact '
'site administrator.'), self.order.error_reason)
def test_should_fail_during_success_report_fail(self):
@mock.patch('barbican.plugin.resources.generate_secret')
def test_should_fail_during_success_report_fail(self,
mock_generate_secret):
mock_generate_secret.return_value = self.secret
# Force an error during the processing handler phase.
self.order_repo.save = mock.MagicMock(return_value=None,
side_effect=ValueError())

View File

@ -27,14 +27,14 @@ scripts =
bin/barbican-db-manage.py
[entry_points]
barbican.secretstore.plugin =
store_crypto = barbican.plugin.store_crypto:StoreCryptoAdapterPlugin
dogtag_crypto = barbican.plugin.dogtag:DogtagPlugin
barbican.crypto.plugin =
p11_crypto = barbican.crypto.p11_crypto:P11CryptoPlugin
simple_crypto = barbican.crypto.plugin:SimpleCryptoPlugin
dogtag_crypto = barbican.crypto.dogtag_crypto:DogtagCryptoPlugin
p11_crypto = barbican.plugin.crypto.p11_crypto:P11CryptoPlugin
simple_crypto = barbican.plugin.crypto.simple_crypto:SimpleCryptoPlugin
barbican.test.crypto.plugin =
test_crypto = barbican.tests.crypto.test_plugin:TestCryptoPlugin
barbican.secretstore.plugin =
dogtag_crypto = barbican.plugin.dogtag:DogtagPlugin
[build_sphinx]
all_files = 1