barbican/barbican/plugin/store_crypto.py

337 lines
13 KiB
Python

# 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.common import config
from barbican.common import utils
from barbican.model import models
from barbican.model import repositories
from barbican.plugin.crypto import base
from barbican.plugin.crypto import manager
from barbican.plugin.interface import secret_store as sstore
CONF = config.new_config()
config.parse_args(CONF)
class StoreCryptoContext(object):
"""Context for crypto-adapter secret store plugins.
This context object allows access to core Barbican resources such as
datastore models.
"""
def __init__(
self,
project_model,
secret_model=None,
private_secret_model=None,
public_secret_model=None,
passphrase_secret_model=None,
content_type=None):
self.secret_model = secret_model
self.private_secret_model = private_secret_model
self.public_secret_model = public_secret_model
self.passphrase_secret_model = passphrase_secret_model
self.project_model = project_model
self.content_type = content_type
class StoreCryptoAdapterPlugin(object):
"""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.
Note that this class does not inherit from SecretStoreBase, as it also
requires access to lower-level datastore entities such as KEKDatum. This
additional information is passed in via the 'context' parameter.
"""
def __init__(self):
super(StoreCryptoAdapterPlugin, self).__init__()
def store_secret(self, secret_dto, context):
"""Store a secret.
:param secret_dto: SecretDTO for secret
:param context: StoreCryptoContext for secret
:returns: an optional dictionary containing metadata about the secret
"""
# Find HSM-style 'crypto' plugin.
encrypting_plugin = manager.get_manager().get_plugin_store_generate(
base.PluginSupportTypes.ENCRYPT_DECRYPT,
project_id=context.project_model.id
)
# Find or create a key encryption key metadata.
kek_datum_model, kek_meta_dto = _find_or_create_kek_objects(
encrypting_plugin, context.project_model)
# Secrets are base64 encoded before being passed to the secret stores.
secret_bytes = base64.b64decode(secret_dto.secret)
encrypt_dto = base.EncryptDTO(secret_bytes)
# Enhance the context with content_type, This is needed to build
# datum_model to store
if not context.content_type:
context.content_type = secret_dto.content_type
# Create an encrypted datum instance and add the encrypted cyphertext.
response_dto = encrypting_plugin.encrypt(
encrypt_dto, kek_meta_dto, context.project_model.external_id
)
# Convert binary data into a text-based format.
_store_secret_and_datum(
context, context.secret_model, kek_datum_model, response_dto)
return None
def get_secret(self, secret_type, metadata, context):
"""Retrieve a secret.
:param secret_type: secret type
:param metadata: secret metadata
:param context: StoreCryptoContext for secret
:returns: SecretDTO that contains 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 = manager.get_manager().get_plugin_retrieve(
datum_model.kek_meta_project.plugin_name)
# wrap the KEKDatum instance in our DTO
kek_meta_dto = base.KEKMetaDTO(datum_model.kek_meta_project)
# Convert from text-based storage format to binary.
encrypted = base64.b64decode(datum_model.cypher_text)
decrypt_dto = base.DecryptDTO(encrypted)
# Decrypt the secret.
secret = decrypting_plugin.decrypt(decrypt_dto,
kek_meta_dto,
datum_model.kek_meta_extended,
context.project_model.external_id)
secret = base64.b64encode(secret)
key_spec = sstore.KeySpec(alg=context.secret_model.algorithm,
bit_length=context.secret_model.bit_length,
mode=context.secret_model.mode)
return sstore.SecretDTO(secret_type,
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.
:param key_spec: KeySpec that contains details on the type of key to
generate
:param context: StoreCryptoContext for secret
:returns: a dictionary that contains metadata about the key
"""
# Find HSM-style 'crypto' plugin.
plugin_type = _determine_generation_type(key_spec.alg)
if base.PluginSupportTypes.SYMMETRIC_KEY_GENERATION != plugin_type:
raise sstore.SecretAlgorithmNotSupportedException(key_spec.alg)
generating_plugin = manager.get_manager().get_plugin_store_generate(
plugin_type,
key_spec.alg,
key_spec.bit_length,
key_spec.mode,
project_id=context.project_model.id)
# Find or create a key encryption key metadata.
kek_datum_model, kek_meta_dto = _find_or_create_kek_objects(
generating_plugin, context.project_model)
# Create an encrypted datum instance and add the created cypher text.
generate_dto = base.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.project_model.external_id)
# Convert binary data into a text-based format.
_store_secret_and_datum(
context, context.secret_model, kek_datum_model, response_dto)
return None
def generate_asymmetric_key(self, key_spec, context):
"""Generates an asymmetric key.
Returns a AsymmetricKeyMetadataDTO object containing
metadata(s) for asymmetric key components. The metadata
can be used to retrieve individual components of
asymmetric key pair.
"""
plugin_type = _determine_generation_type(key_spec.alg)
if base.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION != plugin_type:
raise sstore.SecretAlgorithmNotSupportedException(key_spec.alg)
generating_plugin = manager.get_manager().get_plugin_store_generate(
plugin_type, key_spec.alg, key_spec.bit_length,
project_id=context.project_model.id)
# Find or create a key encryption key metadata.
kek_datum_model, kek_meta_dto = _find_or_create_kek_objects(
generating_plugin, context.project_model)
generate_dto = base.GenerateDTO(key_spec.alg,
key_spec.bit_length,
None, key_spec.passphrase)
# Create the encrypted meta.
private_key_dto, public_key_dto, passwd_dto = (
generating_plugin.generate_asymmetric(
generate_dto, kek_meta_dto, context.project_model.external_id
)
)
_store_secret_and_datum(
context,
context.private_secret_model,
kek_datum_model,
private_key_dto)
_store_secret_and_datum(
context,
context.public_secret_model,
kek_datum_model,
public_key_dto)
if key_spec.passphrase and passwd_dto:
_store_secret_and_datum(
context,
context.passphrase_secret_model,
kek_datum_model,
passwd_dto)
return sstore.AsymmetricKeyMetadataDTO()
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
(key_spec.alg.lower() in
sstore.KeyAlgorithm.ASYMMETRIC_ALGORITHMS
or key_spec.alg.lower() in
sstore.KeyAlgorithm.SYMMETRIC_ALGORITHMS))
def store_secret_supports(self, key_spec):
"""Key storage supported?
Specifies whether the plugin supports storage of the secret given
the attributes included in the KeySpec
"""
return True
def _determine_generation_type(algorithm):
"""Determines the type based on algorithm."""
if not algorithm:
raise sstore.SecretAlgorithmNotSupportedException(algorithm)
symmetric_algs = base.PluginSupportTypes.SYMMETRIC_ALGORITHMS
asymmetric_algs = base.PluginSupportTypes.ASYMMETRIC_ALGORITHMS
if algorithm.lower() in symmetric_algs:
return base.PluginSupportTypes.SYMMETRIC_KEY_GENERATION
elif algorithm.lower() in asymmetric_algs:
return base.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
else:
raise sstore.SecretAlgorithmNotSupportedException(algorithm)
def _find_or_create_kek_objects(plugin_inst, project_model):
kek_repo = repositories.get_kek_datum_repository()
# 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(project_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 = base.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 base.CryptoKEKBindingException(full_plugin_name)
_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(
context, secret_model, kek_datum_model, generated_dto):
# Create Secret entities in data store.
if not secret_model.id:
secret_model.project_id = context.project_model.id
repositories.get_secret_repository().create_from(secret_model)
# setup and store encrypted datum
datum_model = models.EncryptedDatum(secret_model, kek_datum_model)
datum_model.content_type = context.content_type
datum_model.cypher_text = base64.b64encode(generated_dto.cypher_text)
datum_model.kek_meta_extended = generated_dto.kek_meta_extended
repositories.get_encrypted_datum_repository().create_from(
datum_model)
def _indicate_bind_completed(kek_meta_dto, kek_datum):
"""Updates the supplied kek_datum instance
Updates 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