Update P11 plugin to function & match new plugin contract.

add plugin to entry_points and more cleanup

Change-Id: Ie3c742e3071912f3a22d7460cfb79b1a383efb6f
Implementing: blueprint crypto-implement-hsm-plugin
This commit is contained in:
Paul Kehrer 2013-08-08 23:00:10 -05:00
parent 5efef9fbce
commit 7d7e918953
2 changed files with 84 additions and 98 deletions
barbican/crypto
setup.py

@ -2,6 +2,8 @@
# This code is disabled just enough to pass tox tests, but once full
# integration into Barbican is achieved, this code should re-enabled.
import base64
from oslo.config import cfg
from barbican.common import exception
@ -14,9 +16,28 @@ from barbican.openstack.common.gettextutils import _
# TODO: Remove this:
PyKCS11 = {}
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',
default=None,
help=_('Path to vendor PKCS11 library')),
cfg.StrOpt('login',
default=None,
help=_('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 = _("More than one key found for label")
class P11CryptoPluginException(exception.BarbicanException):
message = _("TODO") # TODO
message = _("General exception")
class P11CryptoPlugin(CryptoPluginBase):
@ -26,24 +47,29 @@ class P11CryptoPlugin(CryptoPluginBase):
This implementation currently relies on an unreleased fork of PyKCS11.
"""
def __init__(self, conf=cfg.CONF):
self.block_size = 16
self.kek_key_length = 32
self.pkcs11 = PyKCS11.PyKCS11Lib()
# TODO check if conf crypto path is none
#self.pkcs11.load(conf.crypto.p11_crypto_plugin_lib_path)
self.pkcs11.load('/usr/lib/libCryptoki2_64.so') # TODO: load from conf
def __init__(self, conf=cfg.CONF, library=None):
self.block_size = 16 # in bytes
self.kek_key_length = 32 # in bytes (256-bit)
self.algorithm = 0x8000011c # CKM_AES_GCM vendor prefixed.
if library is not None:
self.pkcs11 = library
else:
self.pkcs11 = PyKCS11.PyKCS11Lib()
if conf.p11_crypto_plugin.library_path is None:
raise ValueError(_("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())
# TODO: check if session stays open/reopen when closed
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 _pad(self, unencrypted):
"""Adds padding to unencrypted byte string."""
pad_length = self.block_size - (
len(unencrypted) % self.block_size
)
pad_length = self.block_size - (len(unencrypted) % self.block_size)
return unencrypted + (chr(pad_length) * pad_length)
def _strip_pad(self, unencrypted):
@ -65,110 +91,65 @@ class P11CryptoPlugin(CryptoPluginBase):
if len(keys) == 1:
return keys[0]
elif len(keys) == 0:
return None, None
return None
elif len(keys) > 1:
raise P11CryptoPluginException() # TODO:make this a mega exception
def _get_current_key_label_for_tenant(self, tenant):
key_label = self.repo.get_key(tenant) # TODO
return key_label
# TODO: jwood: No longer needed...see bind_kek_metadata() below...
# def _generate_key_for_tenant(self, tenant):
# # TODO: uuid generation from sufficient entropy?
# key_label = "tenant-{0}-key-{1}".format(tenant.tenant_id,
# uuid.uuid4())
# 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, key_label),
# (PyKCS11.CKA_PRIVATE, True),
# (PyKCS11.CKA_SENSITIVE, True),
# (PyKCS11.CKA_ENCRYPT, True),
# (PyKCS11.CKA_DECRYPT, True),
# #(PyKCS11.CKA_TOKEN, True), # TODO: enable this (saves to HSM)
# (PyKCS11.CKA_WRAP, True),
# (PyKCS11.CKA_UNWRAP, True),
# # TODO: make these unextractable if feasible
# (PyKCS11.CKA_EXTRACTABLE, True))
# ckattr = self.session._template2ckattrlist(template)
#
# m = PyKCS11.Mechanism(PyKCS11.CKM_AES_KEY_GEN, None)
# key = PyKCS11.CK_OBJECT_HANDLE()
# self._check_error(
# self.pkcs11.lib.C_GenerateKey(self.session.session, m, ckattr,
# key)
# )
# self.repo.write_key(key_label, tenant) # TODO: write key
# return (key, key_label)
def _build_kek_metadata(self, mechanism, key_label, iv):
# TODO: CBC, default (exception?)
encryption_type = {
PyKCS11.CKM_AES_ECB: 'AES ECB',
PyKCS11.CKM_AES_CBC_PAD: 'AES CBC PAD',
# TODO: determine if PKCS11 GCM pads automatically
PyKCS11.CKM_AES_GCM: 'AES GCM'
}[mechanism.mechanism]
kek_metadata = json.dumps({
'plugin': 'P11CryptoPlugin',
'encryption': encryption_type,
'kek_length': self.kek_key_length,
'kek': key_label
})
return kek_metadata
def encrypt(self, unencrypted, kek_metadata, tenant):
padded_data = self._pad(unencrypted)
key = self._get_key_by_label(kek_metadata.kek_label)
# TODO: jwood: No longer needed:
# key_label = self._get_current_key_label_for_tenant(tenant)
# if key_label:
# key = self._get_key_by_label(key_label)
# else:
# key, key_label = self._generate_key_for_tenant(tenant)
raise P11CryptoPluginKeyException()
def _generate_iv(self):
iv = self.session.generateRandom(self.block_size)
mech = PyKCS11.Mechanism(PyKCS11.CKM_AES_CBC_PAD, iv)
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 encrypt(self, unencrypted, kek_meta_tenant, tenant):
# TODO: GCM should not require padding.
padded_data = self._pad(unencrypted)
key = self._get_key_by_label(kek_meta_tenant.kek_label)
iv = self.generate_iv()
gcm = self._build_gcm_params(iv)
mech = PyKCS11.Mechanism(self.algorithm, gcm)
encrypted = self.session.encrypt(key, padded_data, mech)
cyphertext = b''.join(chr(i) for i in encrypted)
kek_meta_extended = json.dumps({
'iv': base64.b64encode(iv)
})
# TODO: jwood No longer needed???: kek_metadata = self
# ._build_kek_metadata(mech, key_label, iv)
return cyphertext, None # TODO: jwood kek_metadata return not needed?
return cyphertext, kek_meta_extended
def decrypt(self, encrypted, kek_meta_tenant, kek_meta_extended, tenant):
# TODO: jwood Metadata coming in now...kek_info = json.loads(
# kek_metadata)
key, iv = self._get_key_by_label(
kek_meta_tenant.kek_label) # TODO: jwood
# kek_info['kek']) # TODO: get IV
mech = PyKCS11.Mechanism(PyKCS11.CKM_AES_CBC_PAD, iv)
key = self._get_key_by_label(kek_meta_tenant.kek_label)
meta_extended = json.loads(kek_meta_extended)
iv = base64.b64decode(meta_extended['iv'])
gcm = self._build_gcm_params(iv)
mech = PyKCS11.Mechanism(self.algorithm, gcm)
decrypted = self.session.decrypt(key, encrypted, mech)
padded_secret = b''.join(chr(i) for i in decrypted)
return self._strip_pad(padded_secret)
# TODO: jwood: This is a new method, to generate a key in the HSM for the
# metadata kek_label.
def bind_kek_metadata(self, kek_metadata):
# Enforce idempotency: If we've already generated a key for the
# kek_label, leave now.
key, iv = self._get_key_by_label(kek_metadata.kek_label)
key = self._get_key_by_label(kek_metadata.kek_label)
if key:
return
# To be persisted by Barbican:
kek_metadata.algorithm = 'AES CBC PAD'
kek_metadata.bit_length = self.kek_key_length
kek_metadata.mode = None
kek_metadata.algorithm = 'AES'
kek_metadata.bit_length = self.kek_key_length * 8
kek_metadata.mode = 'GCM'
kek_metadata.plugin_meta = None
# Generate the key.
# TODO: review template to ensure it's what we want
template = (
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
@ -178,17 +159,21 @@ class P11CryptoPlugin(CryptoPluginBase):
(PyKCS11.CKA_SENSITIVE, True),
(PyKCS11.CKA_ENCRYPT, True),
(PyKCS11.CKA_DECRYPT, True),
#(PyKCS11.CKA_TOKEN, True), # TODO: enable this (saves to HSM)
(PyKCS11.CKA_TOKEN, True),
(PyKCS11.CKA_WRAP, True),
(PyKCS11.CKA_UNWRAP, True),
# TODO: make these unextractable if feasible
(PyKCS11.CKA_EXTRACTABLE, True))
(PyKCS11.CKA_EXTRACTABLE, False))
ckattr = self.session._template2ckattrlist(template)
m = PyKCS11.Mechanism(PyKCS11.CKM_AES_KEY_GEN, None)
key = PyKCS11.CK_OBJECT_HANDLE()
self._check_error(
self.pkcs11.lib.C_GenerateKey(self.session.session, m, ckattr, key)
self.pkcs11.lib.C_GenerateKey(
self.rw_session.session,
m,
ckattr,
key
)
)
def create(self, algorithm, bit_length):
@ -197,7 +182,7 @@ class P11CryptoPlugin(CryptoPluginBase):
byte_length = bit_length / 8
rand = self.session.generateRandom(byte_length)
if len(rand) != byte_length:
raise P11CryptoPluginException() # TODO: revisit exceptions
raise P11CryptoPluginException()
return rand
def supports(self, secret_type):

@ -75,6 +75,7 @@ setup(
py_modules=[],
entry_points="""
[barbican.crypto.plugin]
p11_crypto = barbican.crypto.p11plugin:P11CryptoPlugin
simple_crypto = barbican.crypto.plugin:SimpleCryptoPlugin
[barbican.test.crypto.plugin]