From 20e4946cb8ae5c3465ee033696ca5180b4c50c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Douglas=20Mendiz=C3=A1bal?= Date: Fri, 25 Oct 2024 16:45:58 -0400 Subject: [PATCH] Configure mechanism for wrapping pKEKs The PKCS#11 backend key-wraps (encrypts) the project-specific Key Encryption Keys (pKEKs) using the master encryption key (MKEK). The mechanism for wrapping/unwrapping the keys was hard-coded to use CKM_AES_CBC_PAD. This patch refactors the pkcs11 module to make this mechanism configurable. This is necessary to fix Bug #2036506 because some PKCS#11 devices and software implementations no longer allow CKM_AES_CBC_PAD to be used for key wrapping. Supported key wrap mechanisms now include: * CKM_AES_CBC_PAD * CKM_AES_KEY_WRAP_PAD * CKM_AES_KEY_WRAP_KWP This patch also includes two additional patches so they can all be tested at the same time: Fix typo in wrap_key function This patch fixes a typo in one of the mechanisms in the PKCS11.wrap_key() function in the pkcs11 module. and Increase unit testing coverage for PKCS#11 This patch adds a few tests to increase the test coverage for the PKCS#11 backend. Closes-Bug: #2036506 Change-Id: Ic2009a2a55622bb707e884d6a960c044b2248f52 (cherry picked from commit 0d4101fa5da52f242ab0a52955f67769b23485a1) (cherry picked from commit 7b36764cd12781bdb1acc37dcd52dd4e6637171e) (cherry picked from commit bae6737cb33ebe47c0655a704ff434539db3dc00) (cherry picked from commit b5841df387e5ab38caf173950a1d98ab37a51453) (cherry picked from commit 6945564c4c3c8203f779d17b41e4c38d30664d84) --- barbican/cmd/barbican_manage.py | 13 +- barbican/plugin/crypto/p11_crypto.py | 105 +++++++----- barbican/plugin/crypto/pkcs11.py | 153 +++++++++++------- .../tests/plugin/crypto/test_p11_crypto.py | 64 +++++++- barbican/tests/plugin/crypto/test_pkcs11.py | 67 ++++++-- doc/source/install/barbican-backend.rst | 69 ++++---- .../fix-bug-2036506-bf171b5949495457.yaml | 22 +++ 7 files changed, 344 insertions(+), 149 deletions(-) create mode 100644 releasenotes/notes/fix-bug-2036506-bf171b5949495457.yaml diff --git a/barbican/cmd/barbican_manage.py b/barbican/cmd/barbican_manage.py index 513541707..f4e105d7e 100644 --- a/barbican/cmd/barbican_manage.py +++ b/barbican/cmd/barbican_manage.py @@ -326,13 +326,16 @@ class HSMCommands(object): elif type(slotid) is not int: slotid = int(slotid) if hmacwrap is None: - hmacwrap = conf.p11_crypto_plugin.hmac_keywrap_mechanism + hmacwrap = conf.p11_crypto_plugin.hmac_mechanism self.pkcs11 = pkcs11.PKCS11( - library_path=libpath, login_passphrase=passphrase, - rw_session=True, slot_id=slotid, - encryption_mechanism='CKM_AES_CBC', - hmac_keywrap_mechanism=hmacwrap, + library_path=libpath, + login_passphrase=passphrase, + rw_session=conf.p11_crypto_plugin.rw_session, + slot_id=slotid, + encryption_mechanism=conf.p11_crypto_plugin.encryption_mechanism, + hmac_mechanism=hmacwrap, + key_wrap_mechanism=conf.p11_crypto_plugin.key_wrap_mechanism, token_serial_number=conf.p11_crypto_plugin.token_serial_number, token_labels=conf.p11_crypto_plugin.token_labels ) diff --git a/barbican/plugin/crypto/p11_crypto.py b/barbican/plugin/crypto/p11_crypto.py index 90b421b3e..84153a1de 100644 --- a/barbican/plugin/crypto/p11_crypto.py +++ b/barbican/plugin/crypto/p11_crypto.py @@ -50,7 +50,7 @@ p11_crypto_plugin_opts = [ 'devices may require more than one label for Load ' 'Balancing or High Availability configurations.')), cfg.StrOpt('login', - help=u._('Password to login to PKCS11 session'), + help=u._('Password (PIN) to login to PKCS11 session'), secret=True), cfg.StrOpt('mkek_label', help=u._('Master KEK label (as stored in the HSM)')), @@ -81,11 +81,19 @@ p11_crypto_plugin_opts = [ help=u._('HMAC Key Type'), default='CKK_AES'), cfg.StrOpt('hmac_keygen_mechanism', - help=u._('HMAC Key Generation Algorithm'), + help=u._('HMAC Key Generation Algorithm used to create the ' + 'master HMAC Key.'), default='CKM_AES_KEY_GEN'), - cfg.StrOpt('hmac_keywrap_mechanism', - help=u._('HMAC key wrap mechanism'), - default='CKM_SHA256_HMAC'), + cfg.StrOpt('hmac_mechanism', + help=u._('HMAC algorithm used to sign encrypted data.'), + default='CKM_SHA256_HMAC', + deprecated_name='hmac_keywrap_mechanism'), + cfg.StrOpt('key_wrap_mechanism', + help=u._('Key Wrapping algorithm used to wrap Project KEKs.'), + default='CKM_AES_CBC_PAD'), + cfg.BoolOpt('key_wrap_generate_iv', + help=u._('Generate IVs for Key Wrapping mechanism.'), + default=True), cfg.StrOpt('seed_file', help=u._('File to pull entropy for seeding RNG'), default=''), @@ -138,33 +146,31 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): if plugin_conf.library_path is None: raise ValueError(u._("library_path is required")) self.library_path = plugin_conf.library_path + self.login = plugin_conf.login + + self.token_serial_number = plugin_conf.token_serial_number + self.token_labels = plugin_conf.token_labels + self.slot_id = plugin_conf.slot_id + + self.rw_session = plugin_conf.rw_session + self.seed_file = plugin_conf.seed_file + self.seed_length = plugin_conf.seed_length self.encryption_mechanism = plugin_conf.encryption_mechanism - self.generate_iv = plugin_conf.aes_gcm_generate_iv + self.encryption_gen_iv = plugin_conf.aes_gcm_generate_iv self.cka_sensitive = plugin_conf.always_set_cka_sensitive self.mkek_key_type = 'CKK_AES' self.mkek_length = plugin_conf.mkek_length self.mkek_label = plugin_conf.mkek_label - self.hmac_label = plugin_conf.hmac_label self.hmac_key_type = plugin_conf.hmac_key_type - self.hmac_keygen_mechanism = plugin_conf.hmac_keygen_mechanism - self.hmac_keywrap_mechanism = plugin_conf.hmac_keywrap_mechanism + self.hmac_label = plugin_conf.hmac_label + self.hmac_mechanism = plugin_conf.hmac_mechanism + self.key_wrap_mechanism = plugin_conf.key_wrap_mechanism + self.key_wrap_gen_iv = plugin_conf.key_wrap_generate_iv self.os_locking_ok = plugin_conf.os_locking_ok self.pkek_length = plugin_conf.pkek_length self.pkek_cache_ttl = plugin_conf.pkek_cache_ttl self.pkek_cache_limit = plugin_conf.pkek_cache_limit - self.rw_session = plugin_conf.rw_session - self.seed_file = plugin_conf.seed_file - self.seed_length = plugin_conf.seed_length - self.slot_id = plugin_conf.slot_id - self.login = plugin_conf.login - self.token_serial_number = plugin_conf.token_serial_number - self.token_labels = plugin_conf.token_labels or list() - if plugin_conf.token_label: - LOG.warning('Using deprecated option "token_label". Please update ' - 'your configuration file.') - if plugin_conf.token_label not in self.token_labels: - self.token_labels.append(plugin_conf.token_label) # Use specified or create new pkcs11 object self.pkcs11 = pkcs11 or self._create_pkcs11(ffi) @@ -346,17 +352,19 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): return pkcs11.PKCS11( library_path=self.library_path, login_passphrase=self.login, - rw_session=self.rw_session, - slot_id=self.slot_id, - encryption_mechanism=self.encryption_mechanism, - ffi=ffi, - seed_random_buffer=seed_random_buffer, - generate_iv=self.generate_iv, - always_set_cka_sensitive=self.cka_sensitive, - hmac_keywrap_mechanism=self.hmac_keywrap_mechanism, token_serial_number=self.token_serial_number, token_labels=self.token_labels, - os_locking_ok=self.os_locking_ok + slot_id=self.slot_id, + rw_session=self.rw_session, + seed_random_buffer=seed_random_buffer, + encryption_mechanism=self.encryption_mechanism, + encryption_gen_iv=self.encryption_gen_iv, + always_set_cka_sensitive=self.cka_sensitive, + hmac_mechanism=self.hmac_mechanism, + key_wrap_mechanism=self.key_wrap_mechanism, + key_wrap_gen_iv=self.key_wrap_gen_iv, + os_locking_ok=self.os_locking_ok, + ffi=ffi ) def _reinitialize_pkcs11(self): @@ -397,23 +405,33 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): return key def _load_kek_from_meta_dto(self, kek_meta_dto): + # If plugin_meta is missing the keywrap_mechanism, we default + # to the previously hard-coded CKM_AES_CBC_PAD + _DEFAULT_KEYWRAP_MECHANISM = 'CKM_AES_CBC_PAD' meta = json.loads(kek_meta_dto.plugin_meta) + keywrap_mechanism = meta.get('key_wrap_mechanism', + _DEFAULT_KEYWRAP_MECHANISM) + LOG.debug("Key Wrap mechanism: %s", keywrap_mechanism) kek = self._load_kek( kek_meta_dto.kek_label, meta['iv'], meta['wrapped_key'], - meta['hmac'], meta['mkek_label'], meta['hmac_label'] + meta['hmac'], meta['mkek_label'], meta['hmac_label'], + keywrap_mechanism ) return kek def _load_kek(self, key_label, iv, wrapped_key, hmac, - mkek_label, hmac_label): + mkek_label, hmac_label, keywrap_mechanism): with self.pkek_cache_lock: kek = self._pkek_cache_get(key_label) if kek is None: # Decode data - iv = base64.b64decode(iv) wrapped_key = base64.b64decode(wrapped_key) + if iv is None: + kek_data = wrapped_key + else: + iv = base64.b64decode(iv) + kek_data = iv + wrapped_key hmac = base64.b64decode(hmac) - kek_data = iv + wrapped_key with self.caching_session_lock: session = self.caching_session @@ -425,8 +443,12 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): self.pkcs11.verify_hmac(mkhk, hmac, kek_data, session) # Unwrap KEK - kek = self.pkcs11.unwrap_key(mkek, iv, wrapped_key, - session) + kek = self.pkcs11.unwrap_key( + keywrap_mechanism, + mkek, + iv, + wrapped_key, + session) self._pkek_cache_add(kek, key_label) @@ -448,16 +470,21 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): wkek = self.pkcs11.wrap_key(mkek, kek, session) # HMAC Wrapped KEK - wkek_data = wkek['iv'] + wkek['wrapped_key'] + if wkek['iv'] is None: + wkek_data = wkek['wrapped_key'] + else: + wkek_data = wkek['iv'] + wkek['wrapped_key'] + wkek_hmac = self.pkcs11.compute_hmac(mkhk, wkek_data, session) # Cache KEK self._pkek_cache_add(kek, key_label) return { - 'iv': base64.b64encode(wkek['iv']), + 'iv': wkek['iv'] and base64.b64encode(wkek['iv']), 'wrapped_key': base64.b64encode(wkek['wrapped_key']), 'hmac': base64.b64encode(wkek_hmac), 'mkek_label': self.mkek_label, - 'hmac_label': self.hmac_label + 'hmac_label': self.hmac_label, + 'key_wrap_mechanism': wkek['key_wrap_mechanism'] } diff --git a/barbican/plugin/crypto/pkcs11.py b/barbican/plugin/crypto/pkcs11.py index 823b54639..0341fabc9 100644 --- a/barbican/plugin/crypto/pkcs11.py +++ b/barbican/plugin/crypto/pkcs11.py @@ -141,7 +141,8 @@ CKM_AES_CBC = 0x1082 CKM_AES_MAC = 0x1083 CKM_AES_CBC_PAD = 0x1085 CKM_AES_GCM = 0x1087 -CKM_AES_KEY_WRAP = 0x1090 +CKM_AES_KEY_WRAP_PAD = 0x210A +CKM_AES_KEY_WRAP_KWP = 0x210B CKM_GENERIC_SECRET_KEY_GEN = 0x350 VENDOR_SAFENET_CKM_AES_GCM = 0x8000011c @@ -157,20 +158,30 @@ _ENCRYPTION_MECHANISMS = { _CBC_IV_SIZE = 16 # bytes _CBC_BLOCK_SIZE = 128 # bits +# ----- Supported Mechanisms ----- +# Barbican only supports the PKCS#11 mechanisms below + _KEY_GEN_MECHANISMS = { 'CKM_AES_KEY_GEN': CKM_AES_KEY_GEN, 'CKM_NC_SHA256_HMAC_KEY_GEN': CKM_NC_SHA256_HMAC_KEY_GEN, 'CKM_GENERIC_SECRET_KEY_GEN': CKM_GENERIC_SECRET_KEY_GEN, } -_KEY_WRAP_MECHANISMS = { +_HMAC_MECHANISMS = { 'CKM_SHA256_HMAC': CKM_SHA256_HMAC, 'CKM_AES_MAC': CKM_AES_MAC } +_KEY_WRAP_MECHANISMS = { + 'CKM_AES_CBC_PAD': CKM_AES_CBC_PAD, + 'CKM_AES_KEY_WRAP_PAD': CKM_AES_KEY_WRAP_PAD, + 'CKM_AES_KEY_WRAP_KWP': CKM_AES_KEY_WRAP_KWP +} + CKM_NAMES = dict() CKM_NAMES.update(_ENCRYPTION_MECHANISMS) CKM_NAMES.update(_KEY_GEN_MECHANISMS) +CKM_NAMES.update(_HMAC_MECHANISMS) CKM_NAMES.update(_KEY_WRAP_MECHANISMS) ERROR_CODES = { @@ -324,6 +335,16 @@ def build_ffi(): CK_BYTE minor; } CK_VERSION; + typedef struct CK_INFO { + CK_VERSION cryptokiVersion; + CK_UTF8CHAR manufacturerID[32]; + CK_FLAGS flags; + CK_UTF8CHAR libraryDescription[32]; + CK_VERSION libraryVersion; + } CK_INFO; + + typedef CK_INFO * CK_INFO_PTR; + typedef struct CK_SLOT_INFO { CK_UTF8CHAR slotDescription[64]; CK_UTF8CHAR manufacturerID[32]; @@ -384,6 +405,7 @@ def build_ffi(): CK_RV C_GetSessionInfo(CK_SESSION_HANDLE, CK_SESSION_INFO_PTR); CK_RV C_Login(CK_SESSION_HANDLE, CK_USER_TYPE, CK_UTF8CHAR_PTR, CK_ULONG); + CK_RV C_GetInfo(CK_INFO_PTR); CK_RV C_GetSlotList(CK_BBOOL, CK_SLOT_ID_PTR, CK_ULONG_PTR); CK_RV C_GetSlotInfo(CK_SLOT_ID, CK_SLOT_INFO_PTR); CK_RV C_GetTokenInfo(CK_SLOT_ID, CK_TOKEN_INFO_PTR); @@ -426,41 +448,31 @@ def build_ffi(): class PKCS11(object): - def __init__(self, library_path, login_passphrase, rw_session, slot_id, - encryption_mechanism=None, - ffi=None, algorithm=None, - seed_random_buffer=None, - generate_iv=None, always_set_cka_sensitive=None, - hmac_keywrap_mechanism='CKM_SHA256_HMAC', + def __init__(self, library_path, login_passphrase, token_serial_number=None, token_labels=None, - os_locking_ok=False): - if algorithm: - LOG.warning("WARNING: Using deprecated 'algorithm' argument.") - encryption_mechanism = encryption_mechanism or algorithm - + slot_id=None, + rw_session=True, + seed_random_buffer=None, + encryption_mechanism=None, + encryption_gen_iv=True, + always_set_cka_sensitive=True, + hmac_mechanism=None, + key_wrap_mechanism=None, + key_wrap_gen_iv=False, + os_locking_ok=False, + ffi=None): + # Validate all mechanisms are supported if encryption_mechanism not in _ENCRYPTION_MECHANISMS: - raise ValueError("Invalid encryption_mechanism.") - self.encrypt_mech = _ENCRYPTION_MECHANISMS[encryption_mechanism] - self.encrypt = getattr( - self, - '_{}_encrypt'.format(encryption_mechanism) - ) - - if hmac_keywrap_mechanism not in _KEY_WRAP_MECHANISMS: - raise ValueError("Invalid HMAC keywrap mechanism") + raise ValueError("Invalid Encryption mechanism.") + if hmac_mechanism not in _HMAC_MECHANISMS: + raise ValueError("Invalid HMAC signing mechanism.") + if key_wrap_mechanism not in _KEY_WRAP_MECHANISMS: + raise ValueError("Invalid Key Wrapping mechanism.") self.ffi = ffi or build_ffi() self.lib = self.ffi.dlopen(library_path) - - if os_locking_ok: - init_arg_pt = self.ffi.new("CK_C_INITIALIZE_ARGS *") - init_arg_pt.flags = CKF_OS_LOCKING_OK - else: - init_arg_pt = self.ffi.NULL - - rv = self.lib.C_Initialize(init_arg_pt) - self._check_error(rv) + self._initialize_library(os_locking_ok) # Session options self.login_passphrase = _to_bytes(login_passphrase) @@ -471,13 +483,19 @@ class PKCS11(object): slot_id) # Algorithm options - self.algorithm = CKM_NAMES[encryption_mechanism] + self.encrypt_mech = CKM_NAMES[encryption_mechanism] + self.encrypt = getattr( + self, + '_{}_encrypt'.format(encryption_mechanism) + ) + self.encrypt_gen_iv = encryption_gen_iv self.blocksize = 16 self.noncesize = 12 self.gcmtagsize = 16 - self.generate_iv = generate_iv self.always_set_cka_sensitive = always_set_cka_sensitive - self.hmac_keywrap_mechanism = CKM_NAMES[hmac_keywrap_mechanism] + self.hmac_mechanism = CKM_NAMES[hmac_mechanism] + self.key_wrap_mechanism = key_wrap_mechanism + self.key_wrap_gen_iv = key_wrap_gen_iv # Validate configuration and RNG session = self.get_session() @@ -487,6 +505,16 @@ class PKCS11(object): self.return_session(session) LOG.debug("Connected to PCKS#11 Token in Slot %s", self.slot_id) + def _initialize_library(self, os_locking_ok): + if os_locking_ok: + init_arg_pt = self.ffi.new("CK_C_INITIALIZE_ARGS *") + init_arg_pt.flags = CKF_OS_LOCKING_OK + else: + init_arg_pt = self.ffi.NULL + + rv = self.lib.C_Initialize(init_arg_pt) + self._check_error(rv) + def _get_slot_id(self, token_serial_number, token_labels, slot_id): # First find out how many slots with tokens are available slots_ptr = self.ffi.new("CK_ULONG_PTR") @@ -640,14 +668,14 @@ class PKCS11(object): def _VENDOR_SAFENET_CKM_AES_GCM_encrypt(self, key, pt_data, session): iv = None - if self.generate_iv: + if self.encrypt_gen_iv: iv = self._generate_random(self.noncesize, session) ck_mechanism = self._build_gcm_mechanism(iv) rv = self.lib.C_EncryptInit(session, ck_mechanism.mech, key) self._check_error(rv) pt_len = len(pt_data) - if self.generate_iv: + if self.encrypt_gen_iv: ct_len = self.ffi.new("CK_ULONG *", pt_len + self.gcmtagsize) else: ct_len = self.ffi.new("CK_ULONG *", pt_len + self.gcmtagsize * 2) @@ -655,7 +683,7 @@ class PKCS11(object): rv = self.lib.C_Encrypt(session, pt_data, pt_len, ct, ct_len) self._check_error(rv) - if self.generate_iv: + if self.encrypt_gen_iv: return { "iv": self.ffi.buffer(iv)[:], "ct": self.ffi.buffer(ct, ct_len[0])[:] @@ -669,7 +697,7 @@ class PKCS11(object): def _build_gcm_mechanism(self, iv=None): mech = self.ffi.new("CK_MECHANISM *") - mech.mechanism = self.algorithm + mech.mechanism = self.encrypt_mech gcm = self.ffi.new("CK_AES_GCM_PARAMS *") if iv: @@ -774,11 +802,19 @@ class PKCS11(object): return obj_handle_ptr[0] def wrap_key(self, wrapping_key, key_to_wrap, session): - mech = self.ffi.new("CK_MECHANISM *") - mech.mechanism = CKM_AES_CBC_PAD - iv = self._generate_random(16, session) - mech.parameter = iv - mech.parameter_len = 16 + if self.key_wrap_gen_iv: + iv_len = { + 'CKM_AES_CBC_PAD': 16, # bytes + 'CKM_AES_KEY_WRAP_PAD': 8, # bytes + 'CKM_AES_KEY_WRAP_KWP': 4 # bytes + }.get(self.key_wrap_mechanism) + iv = self._generate_random(iv_len, session) + else: + iv = None + mech = self._build_key_wrap_mechanism( + CKM_NAMES[self.key_wrap_mechanism], + iv + ) # Ask for length of the wrapped key wrapped_key_len = self.ffi.new("CK_ULONG *") @@ -797,18 +833,27 @@ class PKCS11(object): self._check_error(rv) return { - 'iv': self.ffi.buffer(iv)[:], - 'wrapped_key': self.ffi.buffer(wrapped_key, wrapped_key_len[0])[:] + 'iv': iv and self.ffi.buffer(iv)[:], + 'wrapped_key': self.ffi.buffer(wrapped_key, wrapped_key_len[0])[:], + 'key_wrap_mechanism': self.key_wrap_mechanism } - def unwrap_key(self, wrapping_key, iv, wrapped_key, session): - ck_iv = self.ffi.new("CK_BYTE[]", iv) - ck_wrapped_key = self.ffi.new("CK_BYTE[]", wrapped_key) - unwrapped_key = self.ffi.new("CK_OBJECT_HANDLE *") + def _build_key_wrap_mechanism(self, mechanism, iv): mech = self.ffi.new("CK_MECHANISM *") - mech.mechanism = CKM_AES_CBC_PAD - mech.parameter = ck_iv - mech.parameter_len = len(iv) + mech.mechanism = mechanism + if iv is not None: + mech.parameter = iv + mech.parameter_len = len(iv) + return mech + + def unwrap_key(self, mechanism, wrapping_key, iv, wrapped_key, session): + ck_wrapped_key = self.ffi.new("CK_BYTE[]", wrapped_key) + ck_iv = iv and self.ffi.new("CK_BYTE[{}]".format(len(iv)), iv) + unwrapped_key = self.ffi.new("CK_OBJECT_HANDLE *") + mech = self._build_key_wrap_mechanism( + CKM_NAMES[mechanism], + ck_iv + ) ck_attributes = self._build_attributes([ Attribute(CKA_CLASS, CKO_SECRET_KEY), @@ -830,7 +875,7 @@ class PKCS11(object): def compute_hmac(self, hmac_key, data, session): mech = self.ffi.new("CK_MECHANISM *") - mech.mechanism = self.hmac_keywrap_mechanism + mech.mechanism = self.hmac_mechanism rv = self.lib.C_SignInit(session, mech, hmac_key) self._check_error(rv) @@ -843,7 +888,7 @@ class PKCS11(object): def verify_hmac(self, hmac_key, sig, data, session): mech = self.ffi.new("CK_MECHANISM *") - mech.mechanism = self.hmac_keywrap_mechanism + mech.mechanism = self.hmac_mechanism rv = self.lib.C_VerifyInit(session, mech, hmac_key) self._check_error(rv) diff --git a/barbican/tests/plugin/crypto/test_p11_crypto.py b/barbican/tests/plugin/crypto/test_p11_crypto.py index 1890ff2cd..3ee1fd3c6 100644 --- a/barbican/tests/plugin/crypto/test_p11_crypto.py +++ b/barbican/tests/plugin/crypto/test_p11_crypto.py @@ -13,7 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import builtins +import collections +import os from unittest import mock from barbican.common import exception as ex @@ -24,6 +27,11 @@ from barbican.plugin.crypto import pkcs11 from barbican.tests import utils +FakeKEKMetaDTO = collections.namedtuple( + 'FakeKEKMetaDTO', 'kek_label, plugin_meta' +) + + def generate_random_effect(length, session): return b'0' * length @@ -41,7 +49,10 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): self.pkcs11.encrypt.return_value = {'iv': b'0', 'ct': b'0'} self.pkcs11.decrypt.return_value = b'0' self.pkcs11.generate_key.return_value = int(3) - self.pkcs11.wrap_key.return_value = {'iv': b'1', 'wrapped_key': b'1'} + self.pkcs11.wrap_key.return_value = { + 'iv': b'1', + 'wrapped_key': b'1', + 'key_wrap_mechanism': 'CKM_AES_CBC_PAD'} self.pkcs11.unwrap_key.return_value = int(4) self.pkcs11.compute_hmac.return_value = b'1' self.pkcs11.verify_hmac.return_value = None @@ -63,8 +74,11 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): self.cfg_mock.p11_crypto_plugin.encryption_mechanism = 'CKM_AES_CBC' self.cfg_mock.p11_crypto_plugin.seed_file = '' self.cfg_mock.p11_crypto_plugin.seed_length = 32 - self.cfg_mock.p11_crypto_plugin.hmac_keywrap_mechanism = \ + self.cfg_mock.p11_crypto_plugin.hmac_mechanism = \ 'CKM_SHA256_HMAC' + self.cfg_mock.p11_crypto_plugin.key_wrap_mechanism = \ + 'CKM_AES_CBC_PAD' + self.cfg_mock.p11_crypto_plugin.key_wrap_gen_iv = True self.plugin_name = 'Test PKCS11 plugin' self.cfg_mock.p11_crypto_plugin.plugin_name = self.plugin_name @@ -157,7 +171,9 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): def test_decrypt(self): ct = b'ctct' - kek_meta_extended = '{"iv":"AAAA","mechanism":"CKM_AES_CBC"}' + kek_meta_extended = ('{"iv":"AAAA",' + '"mechanism":"CKM_AES_CBC",' + '"key_wrap_mechanism":"CKM_AES_CBC_PAD"}') decrypt_dto = plugin_import.DecryptDTO(ct) kek_meta = mock.MagicMock() kek_meta.kek_label = 'pkek' @@ -355,3 +371,45 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): def test_get_plugin_name(self): self.assertEqual(self.plugin_name, self.plugin.get_plugin_name()) + + def test_load_kek_from_meta_dto_no_key_wrap_mechanism(self): + key = base64.b64encode(os.urandom(32)).decode('UTF-8') + hmac = base64.b64encode(os.urandom(16)).decode('UTF-8') + fake_dto = FakeKEKMetaDTO('test_kek', p11_crypto.json_dumps_compact({ + 'iv': None, + 'wrapped_key': key, + 'hmac': hmac, + 'mkek_label': 'test_mkek', + 'hmac_label': 'test_hmac' + })) + load_mock = mock.MagicMock() + self.plugin._load_kek = load_mock + + self.plugin._load_kek_from_meta_dto(fake_dto) + + # key_wrap_mechanism should default to 'CKM_AES_CBC_PAD' + load_mock.assert_called_with( + 'test_kek', None, key, hmac, + 'test_mkek', 'test_hmac', 'CKM_AES_CBC_PAD') + + def test_load_kek_no_iv(self): + key = os.urandom(32) + wrapped = base64.b64encode(key).decode('UTF-8') + hmac = base64.b64encode(os.urandom(16)).decode('UTF-8') + + self.plugin._load_kek('test_key', None, wrapped, hmac, 'mkek_label', + 'hmac_label', 'CKM_AES_KEY_WRAP_KWP') + + key in self.pkcs11.verify_hmac.call_args.args + + def test_generate_wrapped_kek_no_iv(self): + wrapped = base64.b64encode(os.urandom(32)) + self.pkcs11.wrap_key.return_value = { + 'iv': None, + 'wrapped_key': wrapped, + 'key_wrap_mechanism': 'CKM_AES_KEY_WRAP_KWP' + } + + _ = self.plugin._generate_wrapped_kek(32, 'test_kek') + + wrapped in self.pkcs11.compute_hmac.call_args.args diff --git a/barbican/tests/plugin/crypto/test_pkcs11.py b/barbican/tests/plugin/crypto/test_pkcs11.py index 37ea3502c..d7dcb8713 100644 --- a/barbican/tests/plugin/crypto/test_pkcs11.py +++ b/barbican/tests/plugin/crypto/test_pkcs11.py @@ -59,18 +59,22 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.cfg_mock.rw_session = False self.cfg_mock.slot_id = 1 self.cfg_mock.encryption_mechanism = 'CKM_AES_CBC' - self.cfg_mock.hmac_keywrap_mechanism = 'CKM_SHA256_HMAC' + self.cfg_mock.hmac_mechanism = 'CKM_SHA256_HMAC' + self.cfg_mock.key_wrap_mechanism = 'CKM_AES_KEY_WRAP_KWP' self.token_mock = mock.MagicMock() self.token_mock.label = b'myLabel' self.token_mock.serial_number = b'111111' self.pkcs11 = pkcs11.PKCS11( - self.cfg_mock.library_path, self.cfg_mock.login_passphrase, - self.cfg_mock.rw_session, self.cfg_mock.slot_id, - self.cfg_mock.encryption_mechanism, + self.cfg_mock.library_path, + self.cfg_mock.login_passphrase, + slot_id=self.cfg_mock.slot_id, + rw_session=self.cfg_mock.rw_session, + encryption_mechanism=self.cfg_mock.encryption_mechanism, + hmac_mechanism=self.cfg_mock.hmac_mechanism, + key_wrap_mechanism=self.cfg_mock.key_wrap_mechanism, ffi=self.ffi, - hmac_keywrap_mechanism=self.cfg_mock.hmac_keywrap_mechanism ) def _generate_random(self, session, buf, length): @@ -140,7 +144,7 @@ class WhenTestingPKCS11(utils.BaseTestCase): return pkcs11.CKR_OK def _encrypt(self, session, pt, pt_len, ct, ct_len): - if self.pkcs11.generate_iv: + if self.pkcs11.encrypt_gen_iv: self.ffi.buffer(ct)[:] = pt[::-1] + b'0' * self.pkcs11.gcmtagsize else: self.ffi.buffer(ct)[:] = pt[::-1] + b'0' * (self.pkcs11.gcmtagsize @@ -174,6 +178,33 @@ class WhenTestingPKCS11(utils.BaseTestCase): def _verify(self, *args, **kwargs): return pkcs11.CKR_OK + def test_init_raises_invalid_encryption_mechanism(self): + self.assertRaises( + ValueError, + pkcs11.PKCS11, + self.cfg_mock.library_path, + self.cfg_mock.login_passphrase, + encryption_mechanism='CKM_BOGUS') + + def test_init_raises_invalid_hmac_mechanism(self): + self.assertRaises( + ValueError, + pkcs11.PKCS11, + self.cfg_mock.library_path, + self.cfg_mock.login_passphrase, + encryption_mechanism='CKM_AES_GCM', + hmac_mechanism='CKM_BOGUS') + + def test_init_raises_invalid_key_wrap_mechanism(self): + self.assertRaises( + ValueError, + pkcs11.PKCS11, + self.cfg_mock.library_path, + self.cfg_mock.login_passphrase, + encryption_mechanism='CKM_AES_GCM', + hmac_mechanism='CKM_SHA256_HMAC', + key_wrap_mechanism='CKM_BOGUS') + def test_get_slot_id_from_serial_number(self): slot_id = self.pkcs11._get_slot_id('111111', None, 2) self.assertEqual(1, slot_id) @@ -313,7 +344,7 @@ class WhenTestingPKCS11(utils.BaseTestCase): def test_encrypt_with_no_iv_generation(self): pt = b'0123456789ABCDEF' - self.pkcs11.generate_iv = False + self.pkcs11.encrypt_gen_iv = False ct = self.pkcs11._VENDOR_SAFENET_CKM_AES_GCM_encrypt( mock.MagicMock(), pt, mock.MagicMock() @@ -328,7 +359,7 @@ class WhenTestingPKCS11(utils.BaseTestCase): def test_encrypt_with_iv_generation(self): pt = b'0123456789ABCDEF' - self.pkcs11.generate_iv = True + self.pkcs11.encrypt_gen_iv = True ct = self.pkcs11._VENDOR_SAFENET_CKM_AES_GCM_encrypt( mock.MagicMock(), pt, mock.MagicMock() ) @@ -412,7 +443,8 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.assertEqual(1, self.lib.C_DecryptInit.call_count) self.assertEqual(1, self.lib.C_Decrypt.call_count) - def test_wrap_key(self): + def test_wrap_key_with_iv_generation(self): + self.pkcs11.key_wrap_gen_iv = True wkek = self.pkcs11.wrap_key(mock.Mock(), mock.Mock(), mock.Mock()) self.assertGreater(len(wkek['iv']), 0) self.assertEqual(b'0' * 16, wkek['wrapped_key']) @@ -420,9 +452,22 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.assertEqual(2, self.lib.C_GenerateRandom.call_count) self.assertEqual(2, self.lib.C_WrapKey.call_count) + def test_wrap_key_no_iv_generation(self): + self.pkcs11.key_wrap_gen_iv = False + wkek = self.pkcs11.wrap_key(mock.Mock(), mock.Mock(), mock.Mock()) + self.assertIsNone(wkek['iv']) + self.assertEqual(b'0' * 16, wkek['wrapped_key']) + + self.assertEqual(1, self.lib.C_GenerateRandom.call_count) + self.assertEqual(2, self.lib.C_WrapKey.call_count) + def test_unwrap_key(self): - kek = self.pkcs11.unwrap_key(mock.Mock(), b'0' * 16, - b'0' * 16, mock.Mock()) + kek = self.pkcs11.unwrap_key( + 'CKM_AES_CBC_PAD', + b'0' * 16, + b'0' * 16, + b'0' * 16, + mock.Mock()) self.assertEqual(1, kek) self.assertEqual(self.lib.C_UnwrapKey.call_count, 1) diff --git a/doc/source/install/barbican-backend.rst b/doc/source/install/barbican-backend.rst index 4fc737d48..11be29539 100644 --- a/doc/source/install/barbican-backend.rst +++ b/doc/source/install/barbican-backend.rst @@ -91,70 +91,65 @@ and signed with HMAC key. Both MKEK and HMAC resides in the HSM. The configuration for this plugin in ``/etc/barbican/barbican.conf``. Settings for some different HSMs are provided below: -Thales Luna Network HSM (Safenet) -+++++++++++++++++++++++++++++++++ +Thales Luna Network HSM ++++++++++++++++++++++++ The PKCS#11 plugin configuration for Luna Network HSM looks like: .. code-block:: ini - # ================= Secret Store Plugin =================== [secretstore] - .. - enabled_secretstore_plugins = store_crypto + enable_multiple_secret_stores = True + stores_lookup_suffix = luna + + # ========== Secret Store configuration ========== + [secretstore:luna] + secret_store_plugin = store_crypto + crypto_plugin = p11_crypto # ================= Crypto plugin =================== - [crypto] - .. - enabled_crypto_plugins = p11_crypto - [p11_crypto_plugin] # Path to vendor PKCS11 library library_path = '/usr/lib/libCryptoki2_64.so' - # Token serial number used to identify the token to be used. Required - # when the device has multiple tokens with the same label. (string - # value) + # Token serial number for the token to be used. Required + # when the device has multiple tokens with the same label. + # (string value) #token_serial_number = 12345678 - # Token label used to identify the token to be used. Required when + # Token label for the token to be used. Required when # token_serial_number is not specified. (string value) - #token_label = + token_labels = myPCKS11Token - # Password to login to PKCS11 session + # (Optional) HSM Slot ID that contains the token device to be used. + # Required when token_serial_number and token_labels are not specified. + # (integer value) + #slot_id = 0 + + # Password (PIN) to login to PKCS11 session login = 'mypassword' + # Encryption algorithm used to encrypt secrets + encryption_mechanism = CKM_AES_CBC_GCM + # Label to identify master KEK in the HSM (must not be the same as HMAC label) mkek_label = 'my_mkek_label' - # Length in bytes of master KEK - mkek_length = 32 - - # Label to identify HMAC key in the HSM (must not be the same as MKEK label) + # Label to identify master HMAC key in the HSM (must not be the same as MKEK label) hmac_label = 'my_hmac_label' - # (Optional) HSM Slot ID that contains the token device to be used. - # (integer value) - slot_id = 1 + # Key Type for the master HMAC key + hmac_key_type = CKK_GENERIC_SECRET + # HMAC Key Generation Algorithm used to create the master HMAC Key + hmac_keygen_mechanism = CKM_GENERIC_SECRET_KEY_GEN - # Enable Read/Write session with the HSM? - # rw_session = True + # HMAC algorith used to sign ecnrypted data + hmac_mechanism = CKM_SHA256_HMAC - # Length of Project KEKs to create - # pkek_length = 32 + # Key Wrap algorithm used to wrap Project KEKs + key_wrap_mechanism = CKM_AES_KEY_WRAP_KWP - # How long to cache unwrapped Project KEKs - # pkek_cache_ttl = 900 - - # Max number of items in pkek cache - # pkek_cache_limit = 100 - -.. note:: - - Barbican does not support FIPS mode enabled for SafeNet Luna HSM or - Data Protection on Demand HSM. Make sure that it's operating in non-FIPS - mode while integrating with Barbican. The HMAC and MKEK keys can be generated as follows: diff --git a/releasenotes/notes/fix-bug-2036506-bf171b5949495457.yaml b/releasenotes/notes/fix-bug-2036506-bf171b5949495457.yaml new file mode 100644 index 000000000..f1853b555 --- /dev/null +++ b/releasenotes/notes/fix-bug-2036506-bf171b5949495457.yaml @@ -0,0 +1,22 @@ +--- +deprecations: + - | + The `[p11_crypto_plugin]hmac_keywrap_mechanism` option has been replaced + by `[p11_crypto_plugin]hmac_mechanism`. This option was renamed to avoid + confusion since this mechanism is only used to sign encrypted data and + never used for key wrap encryption. +security: + - | + The PKCS#11 backend driver has been updated to support newer Key Wrap + mechanisms. New deployments should use CKM_AES_KEY_WRAP_KWP, but + CKM_AES_KEY_WRAP_PAD and CKM_AES_CBC_PAD are also supported for + compatibility with older devices that have not yet implemented PKCS#11 + Version 3.0. +fixes: + - | + Fixed Bug #2036506 - This patch replaces the hard-coded CKM_AES_CBC_PAD + mechanism used to wrap pKEKs with an option to configure this mechanism. + Two new options have been added to the [p11_crypto_plugin] section of the + configuration file: `key_wrap_mechanism` and `key_wrap_generate_iv`. These + options default to `CKM_AES_CBC_PAD` and `True` respectively to preserve + backwards compatibility.