URL: https://github.com/freeipa/freeipa/pull/695
Author: MartinBasti
 Title: #695: Fix PKCS11 helper
Action: opened

PR body:
"""
Slots in HSM are not assigned statically, we have to chose proper
slot from token label.

Softhsm i2.2.0 changed this behavior and now slots can change over
time (it is allowed by pkcs11 standard).

Changelog:
* created method get_slot() that returns slot number from
  used label
* replaces usage of slot in __init__ method of P11_Helper
  with label
* slot is dynamically detected from token label before
  session is opened
* pkcs11-util --init-token now uses '--free' instead '--slot'
  which uses first free slot (we don't care about slot numbers
  anymore)

https://pagure.io/freeipa/issue/6692
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/695/head:pr695
git checkout pr695
From 43eb5208abfa5b4e67b10d4fa14b59c5d4e66f31 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 29 Mar 2017 18:53:11 +0200
Subject: [PATCH] Fix PKCS11 helper

Slots in HSM are not assigned statically, we have to chose proper
slot from token label.

Softhsm i2.2.0 changed this behavior and now slots can change over
time (it is allowed by pkcs11 standard).

Changelog:
* created method get_slot() that returns slot number from
  used label
* replaces usage of slot in __init__ method of P11_Helper
  with label
* slot is dynamically detected from token label before
  session is opened
* pkcs11-util --init-token now uses '--free' instead '--slot'
  which uses first free slot (we don't care about slot numbers
  anymore)

https://pagure.io/freeipa/issue/6692
---
 daemons/dnssec/ipa-dnskeysync-replica        |   4 +-
 daemons/dnssec/ipa-ods-exporter              |   3 +-
 ipalib/constants.py                          |   2 +
 ipapython/dnssec/localhsm.py                 |   5 +-
 ipapython/p11helper.py                       | 106 ++++++++++++++++++++++++---
 ipaserver/install/dnskeysyncinstance.py      |  10 +--
 ipaserver/install/opendnssecinstance.py      |   9 ++-
 ipatests/test_ipapython/test_ipap11helper.py |   6 +-
 8 files changed, 119 insertions(+), 26 deletions(-)

diff --git a/daemons/dnssec/ipa-dnskeysync-replica b/daemons/dnssec/ipa-dnskeysync-replica
index 69a3a68..3714163 100755
--- a/daemons/dnssec/ipa-dnskeysync-replica
+++ b/daemons/dnssec/ipa-dnskeysync-replica
@@ -15,6 +15,7 @@ import os
 import sys
 
 import ipalib
+from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL
 from ipapython.dn import DN
 from ipapython.ipa_log_manager import root_logger, standard_logging_setup
 from ipapython import ipaldap
@@ -154,8 +155,7 @@ ldapkeydb = LdapKeyDB(log, ldap,
         DN(('cn', 'keys'), ('cn', 'sec'), ipalib.api.env.container_dns,
            ipalib.api.env.basedn))
 
-# TODO: slot number could be configurable
-localhsm = LocalHSM(paths.LIBSOFTHSM2_SO, 0,
+localhsm = LocalHSM(paths.LIBSOFTHSM2_SO, SOFTHSM_DNSSEC_TOKEN_LABEL,
         open(paths.DNSSEC_SOFTHSM_PIN).read())
 
 ldap2replica_master_keys_sync(log, ldapkeydb, localhsm)
diff --git a/daemons/dnssec/ipa-ods-exporter b/daemons/dnssec/ipa-ods-exporter
index 385764a..77f8c4d 100755
--- a/daemons/dnssec/ipa-ods-exporter
+++ b/daemons/dnssec/ipa-ods-exporter
@@ -32,6 +32,7 @@ import sqlite3
 import traceback
 
 import ipalib
+from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL
 from ipapython.dn import DN
 from ipapython import ipaldap
 from ipapython import ipautil
@@ -645,7 +646,7 @@ log.debug('Connected')
 ldapkeydb = LdapKeyDB(log, ldap, DN(('cn', 'keys'), ('cn', 'sec'),
                                     ipalib.api.env.container_dns,
                                     ipalib.api.env.basedn))
-localhsm = LocalHSM(paths.LIBSOFTHSM2_SO, 0,
+localhsm = LocalHSM(paths.LIBSOFTHSM2_SO, SOFTHSM_DNSSEC_TOKEN_LABEL,
         open(paths.DNSSEC_SOFTHSM_PIN).read())
 
 ldap2master_replica_keys_sync(log, ldapkeydb, localhsm)
diff --git a/ipalib/constants.py b/ipalib/constants.py
index c423117..43f1f3c 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -279,3 +279,5 @@
 
 # regexp definitions
 PATTERN_GROUPUSER_NAME = '^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*[a-zA-Z0-9_.$-]?$'
+
+SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC'
diff --git a/ipapython/dnssec/localhsm.py b/ipapython/dnssec/localhsm.py
index 8f18a45..73511e9 100755
--- a/ipapython/dnssec/localhsm.py
+++ b/ipapython/dnssec/localhsm.py
@@ -89,10 +89,11 @@ def __str__(self):
     def __repr__(self):
         return self.__str__()
 
+
 class LocalHSM(AbstractHSM):
-    def __init__(self, library, slot, pin):
+    def __init__(self, library, label, pin):
         self.cache_replica_pubkeys = None
-        self.p11 = _ipap11helper.P11_Helper(slot, pin, library)
+        self.p11 = _ipap11helper.P11_Helper(label, pin, library)
         self.log = logging.getLogger()
 
     def __del__(self):
diff --git a/ipapython/p11helper.py b/ipapython/p11helper.py
index 5ff9ccc..f193ea7 100644
--- a/ipapython/p11helper.py
+++ b/ipapython/p11helper.py
@@ -30,6 +30,7 @@
 };
 
 typedef unsigned long CK_SLOT_ID;
+typedef CK_SLOT_ID *CK_SLOT_ID_PTR;
 
 typedef unsigned long CK_SESSION_HANDLE;
 
@@ -43,6 +44,13 @@
 
 typedef unsigned long CK_ATTRIBUTE_TYPE;
 
+typedef unsigned long ck_flags_t;
+
+typedef unsigned char CK_BBOOL;
+
+typedef unsigned long int CK_ULONG;
+typedef CK_ULONG *CK_ULONG_PTR;
+
 struct _CK_ATTRIBUTE
 {
   CK_ATTRIBUTE_TYPE type;
@@ -59,6 +67,31 @@
   unsigned long ulParameterLen;
 };
 
+struct _CK_TOKEN_INFO
+{
+  unsigned char label[32];
+  unsigned char manufacturer_id[32];
+  unsigned char model[16];
+  unsigned char serial_number[16];
+  ck_flags_t flags;
+  unsigned long max_session_count;
+  unsigned long session_count;
+  unsigned long max_rw_session_count;
+  unsigned long rw_session_count;
+  unsigned long max_pin_len;
+  unsigned long min_pin_len;
+  unsigned long total_public_memory;
+  unsigned long free_public_memory;
+  unsigned long total_private_memory;
+  unsigned long free_private_memory;
+  struct _CK_VERSION hardware_version;
+  struct _CK_VERSION firmware_version;
+  unsigned char utc_time[16];
+};
+
+typedef struct _CK_TOKEN_INFO CK_TOKEN_INFO;
+typedef CK_TOKEN_INFO *CK_TOKEN_INFO_PTR;
+
 typedef unsigned long CK_RV;
 
 typedef ... *CK_NOTIFY;
@@ -70,9 +103,12 @@
 typedef ... *CK_C_GetInfo;
 typedef ... *CK_C_GetFunctionList;
 CK_RV C_GetFunctionList (struct _CK_FUNCTION_LIST **function_list);
-typedef ... *CK_C_GetSlotList;
+typedef CK_RV (*CK_C_GetSlotList) (CK_BBOOL tokenPresent,
+                                   CK_SLOT_ID_PTR pSlotList,
+                                   CK_ULONG_PTR pulCount);
 typedef ... *CK_C_GetSlotInfo;
-typedef ... *CK_C_GetTokenInfo;
+typedef CK_RV (*CK_C_GetTokenInfo) (CK_SLOT_ID slotID,
+                                    CK_TOKEN_INFO_PTR pInfo);
 typedef ... *CK_C_WaitForSlotEvent;
 typedef ... *CK_C_GetMechanismList;
 typedef ... *CK_C_GetMechanismInfo;
@@ -255,10 +291,7 @@
 
 typedef unsigned char CK_BYTE;
 typedef unsigned char CK_UTF8CHAR;
-typedef unsigned char CK_BBOOL;
-typedef unsigned long int CK_ULONG;
 typedef CK_BYTE *CK_BYTE_PTR;
-typedef CK_ULONG *CK_ULONG_PTR;
 
 typedef CK_OBJECT_HANDLE *CK_OBJECT_HANDLE_PTR;
 
@@ -387,6 +420,7 @@ def new_array(ctype, *args):
 CKR_OK = 0
 CKR_ATTRIBUTE_TYPE_INVALID = 0x12
 CKR_USER_NOT_LOGGED_IN = 0x101
+CKR_BUFFER_TOO_SMALL = 0x150
 
 CK_BYTE = _ffi.typeof('CK_BYTE')
 CK_BBOOL = _ffi.typeof('CK_BBOOL')
@@ -403,6 +437,10 @@ def new_array(ctype, *args):
 
 CK_FUNCTION_LIST_PTR = _ffi.typeof('CK_FUNCTION_LIST_PTR')
 
+CK_SLOT_ID = _ffi.typeof('CK_SLOT_ID')
+
+CK_TOKEN_INFO = _ffi.typeof('CK_TOKEN_INFO')
+
 NULL_PTR = NULL
 
 
@@ -796,11 +834,10 @@ def _id_exists(self, id, id_len, class_):
         # Object not found
         return False
 
-    def __init__(self, slot, user_pin, library_path):
+    def __init__(self, token_label, user_pin, library_path):
         self.p11_ptr = new_ptr(CK_FUNCTION_LIST_PTR)
         self.session_ptr = new_ptr(CK_SESSION_HANDLE)
 
-        self.slot = 0
         self.session_ptr[0] = 0
         self.p11_ptr[0] = NULL
         self.module_handle = None
@@ -808,7 +845,7 @@ def __init__(self, slot, user_pin, library_path):
         # Parse method args
         if isinstance(user_pin, unicode):
             user_pin = user_pin.encode()
-        self.slot = slot
+        self.token_label = token_label
 
         try:
             pGetFunctionList, module_handle = loadLibrary(library_path)
@@ -829,9 +866,16 @@ def __init__(self, slot, user_pin, library_path):
         check_return_value(rv, "initialize")
 
         #
+        # Get Slot
+        #
+        slot = self.get_slot()
+        if slot is None:
+            raise Error("No slot for label {} found".format(self.token_label))
+
+        #
         # Start session
         #
-        rv = self.p11.C_OpenSession(self.slot,
+        rv = self.p11.C_OpenSession(slot,
                                     CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL,
                                     NULL, self.session_ptr)
         check_return_value(rv, "open session")
@@ -842,6 +886,49 @@ def __init__(self, slot, user_pin, library_path):
         rv = self.p11.C_Login(self.session, CKU_USER, user_pin, len(user_pin))
         check_return_value(rv, "log in")
 
+    def get_slot(self):
+        """Get slot where then token is located
+        :return: slot number or None when slot not found
+        """
+        object_count_ptr = new_ptr(CK_ULONG)
+
+        # get slots ID
+        slots = None
+        for _i in range(0, 10):
+            # try max N times, then die to avoid infinite iteration
+            rv = self.p11.C_GetSlotList(CK_TRUE, NULL, object_count_ptr)
+            check_return_value(rv, "get slots IDs - prepare")
+
+            result_ids_ptr = new_array(CK_SLOT_ID, object_count_ptr[0])
+
+            rv = self.p11.C_GetSlotList(
+                CK_TRUE, result_ids_ptr, object_count_ptr)
+            if rv == CKR_BUFFER_TOO_SMALL:
+                continue
+            check_return_value(rv, "get slots IDs")
+            slots = result_ids_ptr
+            break  # we have slots !!!
+
+        if slots is None:
+            raise Error("Failed to get slots")
+
+        for slot in slots:
+            token_info_ptr = new_ptr(CK_TOKEN_INFO)
+            rv = self.p11.C_GetTokenInfo(slot, token_info_ptr)
+            check_return_value(rv, 'get token info')
+
+            # softhsm always returns label 32 bytes long with padding made of
+            # white spaces (#32), so we have to rstrip() padding and compare
+            # Label was created by softhsm-util so it is not our fault that
+            # there are #32 as padding (cffi initializes structures with
+            # zeroes)
+            # In case that this is not valid anymore, keep in mind backward
+            # compatibility
+
+            if self.token_label == char_array_to_unicode(
+                    token_info_ptr[0].label, 32).rstrip():
+                return slot
+
     def finalize(self):
         """
         Finalize operations with pkcs11 library
@@ -868,7 +955,6 @@ def finalize(self):
 
         self.p11_ptr[0] = NULL
         self.session_ptr[0] = 0
-        self.slot = 0
         self.module_handle = None
 
     #################################################################
diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py
index fadaf21..d9dfda9 100644
--- a/ipaserver/install/dnskeysyncinstance.py
+++ b/ipaserver/install/dnskeysyncinstance.py
@@ -26,10 +26,9 @@
 from ipaplatform.paths import paths
 from ipalib import errors, api
 from ipalib.constants import CACERT
+from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL
 from ipaserver.install.bindinstance import dns_container_exists
 
-softhsm_token_label = u'ipaDNSSEC'
-softhsm_slot = 0
 replica_keylabel_template = u"dnssec-replica:%s"
 
 
@@ -289,8 +288,8 @@ def __setup_softhsm(self):
         command = [
             paths.SOFTHSM2_UTIL,
             '--init-token',
-            '--slot', str(softhsm_slot),
-            '--label', softhsm_token_label,
+            '--free',  # use random free slot
+            '--label', SOFTHSM_DNSSEC_TOKEN_LABEL,
             '--pin', pin,
             '--so-pin', pin_so,
         ]
@@ -309,7 +308,8 @@ def __setup_replica_keys(self):
                 pin = f.read()
 
         os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
-        p11 = _ipap11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO)
+        p11 = _ipap11helper.P11_Helper(
+            SOFTHSM_DNSSEC_TOKEN_LABEL, pin, paths.LIBSOFTHSM2_SO)
 
         try:
             # generate replica keypair
diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py
index f0c512b..8a5068b 100644
--- a/ipaserver/install/opendnssecinstance.py
+++ b/ipaserver/install/opendnssecinstance.py
@@ -18,10 +18,10 @@
 from ipaplatform.constants import constants
 from ipaplatform.paths import paths
 from ipalib import errors, api
-from ipaserver.install import dnskeysyncinstance
+from ipaserver import p11helper
+from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL
 
 KEYMASTER = u'dnssecKeyMaster'
-softhsm_slot = 0
 
 
 def get_dnssec_key_masters(conn):
@@ -72,7 +72,7 @@ def __init__(self, fstore=None, dm_password=None, ldapi=False,
         self.ods_gid = None
         self.conf_file_dict = {
             'SOFTHSM_LIB': paths.LIBSOFTHSM2_SO,
-            'TOKEN_LABEL': dnskeysyncinstance.softhsm_token_label,
+            'TOKEN_LABEL': SOFTHSM_DNSSEC_TOKEN_LABEL,
             'KASP_DB': paths.OPENDNSSEC_KASP_DB,
             'ODS_USER': constants.ODS_USER,
             'ODS_GROUP': constants.ODS_GROUP,
@@ -247,7 +247,8 @@ def __generate_master_key(self):
             pin = f.read()
 
         os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF
-        p11 = p11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO)
+        p11 = p11helper.P11_Helper(
+            SOFTHSM_DNSSEC_TOKEN_LABEL, pin, paths.LIBSOFTHSM2_SO)
         try:
             # generate master key
             root_logger.debug("Creating master key")
diff --git a/ipatests/test_ipapython/test_ipap11helper.py b/ipatests/test_ipapython/test_ipap11helper.py
index 2c8fd28..5659d60 100644
--- a/ipatests/test_ipapython/test_ipap11helper.py
+++ b/ipatests/test_ipapython/test_ipap11helper.py
@@ -55,12 +55,12 @@ def p11(request):
     with open('softhsm2.conf', 'w') as cfg:
         cfg.write(CONFIG_DATA % token_path)
     os.environ['SOFTHSM2_CONF'] = os.path.join(token_path, 'softhsm2.conf')
-    subprocess.check_call([SOFTHSM2_UTIL, '--init-token', '--slot', '0',
+    subprocess.check_call([SOFTHSM2_UTIL, '--init-token', '--free',
                            '--label', 'test', '--pin', '1234', '--so-pin',
                            '1234'])
 
     try:
-        p11 = _ipap11helper.P11_Helper(0, "1234", LIBSOFTHSM)
+        p11 = _ipap11helper.P11_Helper('test', "1234", LIBSOFTHSM)
     except _ipap11helper.Error:
         pytest.fail('Failed to initialize the helper object.', pytrace=False)
 
@@ -70,6 +70,8 @@ def fin():
         except _ipap11helper.Error:
             pytest.fail('Failed to finalize the helper object.', pytrace=False)
         finally:
+            subprocess.call(
+                [SOFTHSM2_UTIL, '--delete-token', '--label', 'test'])
             del os.environ['SOFTHSM2_CONF']
 
     request.addfinalizer(fin)
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to