diff --git a/barbican/plugin/crypto/p11_crypto.py b/barbican/plugin/crypto/p11_crypto.py index cb2614a43..48056087c 100644 --- a/barbican/plugin/crypto/p11_crypto.py +++ b/barbican/plugin/crypto/p11_crypto.py @@ -36,6 +36,14 @@ p11_crypto_plugin_group = cfg.OptGroup(name='p11_crypto_plugin', p11_crypto_plugin_opts = [ cfg.StrOpt('library_path', help=u._('Path to vendor PKCS11 library')), + cfg.StrOpt('token_serial_number', + help=u._('Token serial number used to identify the token to be ' + 'used. Required when the device has multiple tokens ' + 'with the same label.')), + cfg.StrOpt('token_label', + help=u._('Token label used to identify the token to ' + 'be used. Required when token_serial_number is ' + 'not specified.')), cfg.StrOpt('login', help=u._('Password to login to PKCS11 session'), secret=True), @@ -46,7 +54,8 @@ p11_crypto_plugin_opts = [ cfg.StrOpt('hmac_label', help=u._('Master HMAC Key label (as stored in the HSM)')), cfg.IntOpt('slot_id', - help=u._('HSM Slot ID'), + help=u._('(Optional) HSM Slot ID that contains the token ' + 'device to be used.'), default=1), cfg.BoolOpt('rw_session', help=u._('Flag for Read/Write Sessions'), @@ -310,7 +319,9 @@ class P11CryptoPlugin(plugin.CryptoPluginBase): seed_random_buffer=seed_random_buffer, generate_iv=plugin_conf.aes_gcm_generate_iv, always_set_cka_sensitive=plugin_conf.always_set_cka_sensitive, - hmac_keywrap_mechanism=plugin_conf.hmac_keywrap_mechanism + hmac_keywrap_mechanism=plugin_conf.hmac_keywrap_mechanism, + token_serial_number=plugin_conf.token_serial_number, + token_label=plugin_conf.token_label ) def _reinitialize_pkcs11(self): diff --git a/barbican/plugin/crypto/pkcs11.py b/barbican/plugin/crypto/pkcs11.py index dbe4289be..e851a609e 100644 --- a/barbican/plugin/crypto/pkcs11.py +++ b/barbican/plugin/crypto/pkcs11.py @@ -12,6 +12,7 @@ # limitations under the License. import collections +import itertools import textwrap import cffi @@ -27,8 +28,10 @@ LOG = utils.getLogger(__name__) Attribute = collections.namedtuple("Attribute", ["type", "value"]) CKAttributes = collections.namedtuple("CKAttributes", ["template", "cffivals"]) CKMechanism = collections.namedtuple("CKMechanism", ["mech", "cffivals"]) +Token = collections.namedtuple("Token", ["slot_id", "label", "serial_number"]) CKR_OK = 0 +CK_TRUE = 1 CKF_RW_SESSION = (1 << 1) CKF_SERIAL_SESSION = (1 << 2) CKU_SO = 0 @@ -263,11 +266,15 @@ def build_ffi(): ffi = cffi.FFI() ffi.cdef(textwrap.dedent(""" typedef unsigned char CK_BYTE; + typedef CK_BYTE CK_CHAR; + typedef CK_BYTE CK_UTF8CHAR; + typedef CK_BYTE CK_BBOOL; typedef unsigned long CK_ULONG; typedef unsigned long CK_RV; typedef unsigned long CK_SESSION_HANDLE; typedef unsigned long CK_OBJECT_HANDLE; typedef unsigned long CK_SLOT_ID; + typedef CK_SLOT_ID * CK_SLOT_ID_PTR; typedef unsigned long CK_FLAGS; typedef unsigned long CK_STATE; typedef unsigned long CK_USER_TYPE; @@ -294,6 +301,44 @@ def build_ffi(): typedef CK_BYTE *CK_BYTE_PTR; typedef CK_ULONG *CK_ULONG_PTR; + typedef struct CK_VERSION { + CK_BYTE major; + CK_BYTE minor; + } CK_VERSION; + + typedef struct CK_SLOT_INFO { + CK_UTF8CHAR slotDescription[64]; + CK_UTF8CHAR manufacturerID[32]; + CK_FLAGS flags; + + CK_VERSION hardwareVersion; + CK_VERSION firmwareVersion; + } CK_SLOT_INFO; + typedef CK_SLOT_INFO * CK_SLOT_INFO_PTR; + + typedef struct CK_TOKEN_INFO { + CK_UTF8CHAR label[32]; + CK_UTF8CHAR manufacturerID[32]; + CK_UTF8CHAR model[16]; + CK_CHAR serialNumber[16]; + CK_FLAGS flags; + + CK_ULONG ulMaxSessionCount; + CK_ULONG ulSessionCount; + CK_ULONG ulMaxRwSessionCount; + CK_ULONG ulRwSessionCount; + CK_ULONG ulMaxPinLen; + CK_ULONG ulMinPinLen; + CK_ULONG ulTotalPublicMemory; + CK_ULONG ulFreePublicMemory; + CK_ULONG ulTotalPrivateMemory; + CK_ULONG ulFreePrivateMemory; + CK_VERSION hardwareVersion; + CK_VERSION firmwareVersion; + CK_CHAR utcTime[16]; + } CK_TOKEN_INFO; + typedef CK_TOKEN_INFO * CK_TOKEN_INFO_PTR; + typedef struct ck_session_info { CK_SLOT_ID slot_id; CK_STATE state; @@ -321,6 +366,9 @@ 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_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); CK_RV C_GetAttributeValue(CK_SESSION_HANDLE, CK_OBJECT_HANDLE, CK_ATTRIBUTE *, CK_ULONG); CK_RV C_SetAttributeValue(CK_SESSION_HANDLE, CK_OBJECT_HANDLE, @@ -365,7 +413,9 @@ class PKCS11(object): ffi=None, algorithm=None, seed_random_buffer=None, generate_iv=None, always_set_cka_sensitive=None, - hmac_keywrap_mechanism='CKM_SHA256_HMAC'): + hmac_keywrap_mechanism='CKM_SHA256_HMAC', + token_serial_number=None, + token_label=None): if algorithm: LOG.warning("WARNING: Using deprecated 'algorithm' argument.") encryption_mechanism = encryption_mechanism or algorithm @@ -389,7 +439,10 @@ class PKCS11(object): # Session options self.login_passphrase = _to_bytes(login_passphrase) self.rw_session = rw_session - self.slot_id = slot_id + self.slot_id = self._get_slot_id( + token_serial_number, + token_label, + slot_id) # Algorithm options self.algorithm = CKM_NAMES[encryption_mechanism] @@ -406,6 +459,72 @@ class PKCS11(object): self._seed_random(session, seed_random_buffer) self._rng_self_test(session) self.return_session(session) + LOG.debug("Connected to PCKS11 sn: %s label: %s slot: %s", + token_serial_number, token_label, self.slot_id) + + def _get_slot_id(self, token_serial_number, token_label, slot_id): + # First find out how many slots with tokens are available + slots_ptr = self.ffi.new("CK_ULONG_PTR") + rv = self.lib.C_GetSlotList(CK_TRUE, self.ffi.NULL, slots_ptr) + self._check_error(rv) + + # Next get the Slot IDs for each of the available slots + slot_ids_ptr = self.ffi.new("CK_SLOT_ID[{}]".format(slots_ptr[0])) + rv = self.lib.C_GetSlotList(CK_TRUE, slot_ids_ptr, slots_ptr) + self._check_error(rv) + + # Gather details from each token + tokens = list() + for id in slot_ids_ptr: + token_info_ptr = self.ffi.new("CK_TOKEN_INFO_PTR") + rv = self.lib.C_GetTokenInfo(id, token_info_ptr) + self._check_error(rv) + tokens.append(Token( + id, + self.ffi.string(token_info_ptr.label).decode("UTF-8").strip(), + self.ffi.string( + token_info_ptr.serialNumber + ).decode("UTF-8").strip() + )) + + # Matching serial number gets highest priority + if token_serial_number: + for token in tokens: + if token.serial_number == token_serial_number: + LOG.debug("Found token sn: %s in slot %s", + token.serial_number, + token.slot_id) + if token_label: + LOG.warning( + "Ignoring token_label: %s from barbican.conf", + token_label + ) + if slot_id: + LOG.warning("Ignoring slot_id: %s from barbican.conf", + slot_id) + return token.slot_id + raise ValueError("Token Serial Number not found.") + + # Label match is next, raises an error if there's not exactly one match + if token_label: + matched = list(itertools.dropwhile( + lambda x: x.label != token_label, + tokens + )) + if len(matched) > 1: + raise ValueError("More than one matching token label found") + if len(matched) < 1: + raise ValueError("Token Label not found.") + + token = matched.pop() + LOG.debug("Found token label: %s in slot %s", token.label, + token.slot_id) + if slot_id: + LOG.warning("Ignoring slot_id: %s from barbican.conf", slot_id) + return token.slot_id + + # If we got this far, slot_id was the only param given, so we return it + return slot_id def get_session(self): session = self._open_session(self.slot_id) diff --git a/barbican/tests/plugin/crypto/test_p11_crypto.py b/barbican/tests/plugin/crypto/test_p11_crypto.py index afae19cc6..f9044c0ea 100644 --- a/barbican/tests/plugin/crypto/test_p11_crypto.py +++ b/barbican/tests/plugin/crypto/test_p11_crypto.py @@ -54,6 +54,8 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): self.cfg_mock.p11_crypto_plugin.hmac_label = 'hmac_label' self.cfg_mock.p11_crypto_plugin.mkek_length = 32 self.cfg_mock.p11_crypto_plugin.slot_id = 1 + self.cfg_mock.p11_crypto_plugin.token_serial_number = None + self.cfg_mock.p11_crypto_plugin.token_label = None self.cfg_mock.p11_crypto_plugin.rw_session = True self.cfg_mock.p11_crypto_plugin.pkek_length = 32 self.cfg_mock.p11_crypto_plugin.pkek_cache_ttl = 900 @@ -286,6 +288,8 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): return pkcs11.CKR_OK lib = mock.Mock() lib.C_Initialize.return_value = pkcs11.CKR_OK + lib.C_GetSlotList.return_value = pkcs11.CKR_OK + lib.C_GetTokenInfo.return_value = pkcs11.CKR_OK lib.C_OpenSession.return_value = pkcs11.CKR_OK lib.C_CloseSession.return_value = pkcs11.CKR_OK lib.C_GetSessionInfo.return_value = pkcs11.CKR_OK diff --git a/barbican/tests/plugin/crypto/test_pkcs11.py b/barbican/tests/plugin/crypto/test_pkcs11.py index fbc01fbb3..f0a74a742 100644 --- a/barbican/tests/plugin/crypto/test_pkcs11.py +++ b/barbican/tests/plugin/crypto/test_pkcs11.py @@ -27,6 +27,8 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.lib = mock.Mock() self.lib.C_Initialize.return_value = pkcs11.CKR_OK self.lib.C_Finalize.return_value = pkcs11.CKR_OK + self.lib.C_GetSlotList.side_effect = self._get_slot_list + self.lib.C_GetTokenInfo.side_effect = self._get_token_info self.lib.C_OpenSession.side_effect = self._open_session self.lib.C_CloseSession.return_value = pkcs11.CKR_OK self.lib.C_GetSessionInfo.side_effect = self._get_session_user @@ -59,6 +61,10 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.cfg_mock.encryption_mechanism = 'CKM_AES_CBC' self.cfg_mock.hmac_keywrap_mechanism = 'CKM_SHA256_HMAC' + 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, @@ -71,6 +77,31 @@ class WhenTestingPKCS11(utils.BaseTestCase): self.ffi.buffer(buf)[:] = b'0' * length return pkcs11.CKR_OK + def _get_slot_list(self, token_present, slot_ids_ptr, slots_ptr): + # default to mocking only one slot (ID: 1) + if slot_ids_ptr is not self.ffi.NULL: + slot_ids_ptr[0] = 1 + slots_ptr[0] = 1 + return pkcs11.CKR_OK + + def _get_token_info(self, id, token_info_ptr): + token_info_ptr.serialNumber = self.token_mock.serial_number + token_info_ptr.label = self.token_mock.label + return pkcs11.CKR_OK + + def _get_two_slot_list(self, token_present, slot_ids_ptr, slots_ptr): + # mock two slots (IDs: 1, 2) + if slot_ids_ptr is not self.ffi.NULL: + slot_ids_ptr[0] = 1 + slot_ids_ptr[1] = 2 + slots_ptr[0] = 2 + return pkcs11.CKR_OK + + def _get_two_token_info_same_label(self, id, token_info_ptr): + token_info_ptr.serialNumber = (str(id) * 6).encode('UTF-8') + token_info_ptr.label = self.token_mock.label + return pkcs11.CKR_OK + def _get_session_public(self, session, session_info_ptr): if self.cfg_mock.rw_session: session_info_ptr[0].state = pkcs11.CKS_RW_PUBLIC_SESSION @@ -143,6 +174,45 @@ class WhenTestingPKCS11(utils.BaseTestCase): def _verify(self, *args, **kwargs): return pkcs11.CKR_OK + def test_get_slot_id_from_serial_number(self): + slot_id = self.pkcs11._get_slot_id('111111', None, 1) + self.assertEqual(1, slot_id) + + def test_get_slot_id_from_label(self): + slot_id = self.pkcs11._get_slot_id(None, 'myLabel', 1) + self.assertEqual(1, slot_id) + + def test_get_slot_id_backwards_compatibility(self): + slot_id = self.pkcs11._get_slot_id(None, None, 5) + self.assertEqual(5, slot_id) + + def test_get_slot_id_from_serial_ignores_label(self): + slot_id = self.pkcs11._get_slot_id('111111', 'badLabel', 1) + self.assertEqual(1, slot_id) + + def test_get_slot_id_from_serial_ignores_given_slot(self): + slot_id = self.pkcs11._get_slot_id('111111', None, 3) + self.assertEqual(1, slot_id) + + def test_get_slot_id_from_label_ignores_given_slot(self): + slot_id = self.pkcs11._get_slot_id(None, 'myLabel', 3) + self.assertEqual(1, slot_id) + + def test_get_slot_id_serial_not_found(self): + self.assertRaises(ValueError, + self.pkcs11._get_slot_id, '222222', None, 1) + + def test_get_slot_id_label_not_found(self): + self.assertRaises(ValueError, + self.pkcs11._get_slot_id, None, 'badLabel', 1) + + def test_get_slot_id_two_tokens_same_label(self): + self.lib.C_GetSlotList.side_effect = self._get_two_slot_list + self.lib.C_GetTokenInfo.side_effect = \ + self._get_two_token_info_same_label + self.assertRaises(ValueError, + self.pkcs11._get_slot_id, None, 'myLabel', 1) + def test_public_get_session(self): self.lib.C_GetSessionInfo.side_effect = self._get_session_public sess = self.pkcs11.get_session() diff --git a/doc/source/install/barbican-backend.rst b/doc/source/install/barbican-backend.rst index f67df033c..81e0ebb79 100644 --- a/doc/source/install/barbican-backend.rst +++ b/doc/source/install/barbican-backend.rst @@ -98,6 +98,15 @@ The PKCS#11 plugin configuration looks like: # 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 = 12345678 + + # Token label used to identify the token to be used. Required when + # token_serial_number is not specified. (string value) + #token_label = + # Password to login to PKCS11 session login = 'mypassword' @@ -110,8 +119,10 @@ The PKCS#11 plugin configuration looks like: # Label to identify HMAC key in the HSM (must not be the same as MKEK label) hmac_label = 'my_hmac_label' - # HSM Slot id (Should correspond to a configured PKCS11 slot). Default: 1 - # slot_id = 1 + # (Optional) HSM Slot ID that contains the token device to be used. + # (integer value) + #slot_id = 1 + # Enable Read/Write session with the HSM? # rw_session = True @@ -141,6 +152,15 @@ For a nCipher nShield Connect XC, the plugin configuration looks like: # Path to vendor PKCS11 library library_path = '/opt/nfast/toolkits/pkcs11/libcknfast.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 = 12345678 + + # Token label used to identify the token to be used. Required when + # token_serial_number is not specified. (string value) + #token_label = + # Password to login to PKCS11 session login = 'XXX' @@ -153,7 +173,8 @@ For a nCipher nShield Connect XC, the plugin configuration looks like: # Label to identify HMAC key in the HSM (must not be the same as MKEK label) hmac_label = 'thales_hmac_0' - # HSM Slot id (Should correspond to a configured PKCS11 slot). Default: 1 + # (Optional) HSM Slot ID that contains the token device to be used. + # (integer value) # slot_id = 1 # Enable Read/Write session with the HSM? @@ -220,6 +241,15 @@ For an ATOS Bull HSM, the plugin configuration looks like: # Path to vendor PKCS11 library library_path = '/usr/lib64/libnethsm.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 = 12345678 + + # Token label used to identify the token to be used. Required when + # token_serial_number is not specified. (string value) + #token_label = + # Password to login to PKCS11 session login = 'XXX' @@ -232,7 +262,8 @@ For an ATOS Bull HSM, the plugin configuration looks like: # Label to identify HMAC key in the HSM (must not be the same as MKEK label) hmac_label = 'atos_hmac_0' - # HSM Slot id (Should correspond to a configured PKCS11 slot). Default: 1 + # (Optional) HSM Slot ID that contains the token device to be used. + # (integer value) # slot_id = 1 # Enable Read/Write session with the HSM? @@ -293,6 +324,15 @@ The PKCS#11 plugin configuration looks like: # Path to vendor PKCS11 library (string value) library_path = '/opt/utimaco/lib/libcs_pkcs11_R2.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 = 12345678 + + # Token label used to identify the token to be used. Required when + # token_serial_number is not specified. (string value) + #token_label = + # Password to login to PKCS11 session (string value) login = '$up3r$e