Repository: privacyidea/privacyidea

Issue:
Generate AES key via pkcs11
We could ask the user to use pkcs11-tool or pkcs11-data.
But maybe we should also provide the possibility to create all three encryption keys used for privacyIDEA and label them correctly.
Such a tool/script could also directly output the necessary format for ``pi.cfg``.

Generating an AES key using PyKCS11:

~~~~python
from PyKCS11 import *
pkcs11 = PyKCS11Lib()
pkcs11.load()
slot = pkcs11.getSlotList(tokenPresent=True)[0]
session = pkcs11.openSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION)
session.login("1234")

aesTemplate = [
    (CKA_CLASS, CKO_SECRET_KEY),
    (CKA_TOKEN, CK_TRUE),
    (CKA_SENSITIVE, CK_TRUE),
    (CKA_VALUE_LEN, 32),
    (CKA_KEY_TYPE, CKK_AES),
    (CKA_LABEL, "tokenkey")
]

aesKey = session.generateKey(aesTemplate)
session.logout()
session.closeSession()
~~~~


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/crypto.py b/privacyidea/lib/crypto.py
--- a/privacyidea/lib/crypto.py
+++ b/privacyidea/lib/crypto.py
@@ -295,35 +295,7 @@ def init_hsm():
     """
     config = current_app.config
     if "pi_hsm" not in config or not isinstance(config["pi_hsm"], dict):
-        hsm_module_name = config.get("PI_HSM_MODULE",
-                                     "privacyidea.lib.security.default.DefaultSecurityModule")
-        module_parts = hsm_module_name.split(".")
-        package_name = ".".join(module_parts[:-1])
-        class_name = module_parts[-1]
-        mod = __import__(package_name, globals(), locals(), [class_name])
-        hsm_class = getattr(mod, class_name)
-        log.info("initializing HSM class: {0!s}".format(hsm_class))
-        if not hasattr(hsm_class, "setup_module"):  # pragma: no cover
-            raise NameError("Security Module AttributeError: " + package_name + "." +
-                            class_name+ " instance has no attribute 'setup_module'")
-
-        hsm_parameters = {}
-        if class_name == "DefaultSecurityModule":
-            hsm_parameters = {"file": config.get("PI_ENCFILE")}
-        else:
-            # get all parameters by splitting every config entry starting with PI_HSM_MODULE_
-            # and pass this as a config object to hsm_class.
-            hsm_parameters = {}
-            for key in config.keys():
-                if key.startswith("PI_HSM_MODULE_"):
-                    param = key[len("PI_HSM_MODULE_"):].lower()
-                    hsm_parameters[param] = config.get(key)
-            logging_params = dict(hsm_parameters)
-            if "password" in logging_params:
-                logging_params["password"] = "XXXX"
-            log.info("calling HSM module with parameters {0}".format(logging_params))
-
-        HSM_config = {"obj": hsm_class(hsm_parameters)}
+        HSM_config = {"obj": create_hsm_object(config)}
         current_app.config["pi_hsm"] = HSM_config
         log.info("Initialized HSM object {0}".format(HSM_config))
     hsm = current_app.config.get("pi_hsm").get('obj')
@@ -739,3 +711,45 @@ def verify(self, s, signature):
             log.error("Failed to verify signature: {0!r}".format(s))
             log.debug("{0!s}".format(traceback.format_exc()))
         return r
+
+
+def create_hsm_object(config):
+    """
+    This creates an HSM object from the given config dictionary.
+    The config dictionary are the values that appear in pi.cfg.
+
+    It is needed PI_HSM_MODULE and all other values
+    PI_HSM_MODULE_* depending on the module implementation.
+
+    :param config: A configuration dictionary
+    :return: A HSM object
+    """
+    hsm_module_name = config.get("PI_HSM_MODULE",
+                                 "privacyidea.lib.security.default.DefaultSecurityModule")
+    module_parts = hsm_module_name.split(".")
+    package_name = ".".join(module_parts[:-1])
+    class_name = module_parts[-1]
+    mod = __import__(package_name, globals(), locals(), [class_name])
+    hsm_class = getattr(mod, class_name)
+    log.info("initializing HSM class: {0!s}".format(hsm_class))
+    if not hasattr(hsm_class, "setup_module"):  # pragma: no cover
+        raise NameError("Security Module AttributeError: " + package_name + "." +
+                        class_name + " instance has no attribute 'setup_module'")
+
+    hsm_parameters = {}
+    if class_name == "DefaultSecurityModule":
+        hsm_parameters = {"file": config.get("PI_ENCFILE")}
+    else:
+        # get all parameters by splitting every config entry starting with PI_HSM_MODULE_
+        # and pass this as a config object to hsm_class.
+        hsm_parameters = {}
+        for key in config.keys():
+            if key.startswith("PI_HSM_MODULE_"):
+                param = key[len("PI_HSM_MODULE_"):].lower()
+                hsm_parameters[param] = config.get(key)
+        logging_params = dict(hsm_parameters)
+        if "password" in logging_params:
+            logging_params["password"] = "XXXX"
+        log.info("calling HSM module with parameters {0}".format(logging_params))
+
+    return hsm_class(hsm_parameters)
diff --git a/privacyidea/lib/security/aeshsm.py b/privacyidea/lib/security/aeshsm.py
--- a/privacyidea/lib/security/aeshsm.py
+++ b/privacyidea/lib/security/aeshsm.py
@@ -24,6 +24,7 @@
 import logging
 from privacyidea.lib.security.default import SecurityModule
 from privacyidea.lib.error import HSMException
+from privacyidea.lib.crypto import get_alphanum_str
 
 __doc__ = """
 This is a PKCS11 Security module that encrypts and decrypts the data on a
@@ -314,6 +315,40 @@ def _decrypt_value(self, crypt_value, key_id):
 
         return self.decrypt(data, iv, key_id)
 
+    def create_keys(self):
+        """
+        Connect to the HSM and create the encryption keys.
+        The HSM connection should already be configured in pi.cfg.
+
+        We will create new keys with new key labels
+        :return: a dictionary of the created key labels
+        """
+        # We need a new read/write session
+        session = self.pkcs11.openSession(self.slot, PyKCS11.CKF_SERIAL_SESSION | PyKCS11.CKF_RW_SESSION)
+        session.login(self.password)
+
+        key_labels = {"token": "",
+                      "config": "",
+                      "value": ""}
+
+        for kl in key_labels.keys():
+            label = "{0!s}_{1!s}".format(kl, get_alphanum_str())
+            aesTemplate = [
+                (PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
+                (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
+                (PyKCS11.CKA_SENSITIVE, PyKCS11.CK_TRUE),
+                (PyKCS11.CKA_VALUE_LEN, 32),
+                (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
+                (PyKCS11.CKA_LABEL, label)
+            ]
+            aesKey = session.generateKey(aesTemplate)
+            key_labels[kl] = label
+
+        session.logout()
+        session.closeSession()
+
+        return key_labels
+
 
 if __name__ == "__main__":
     logging.basicConfig()
diff --git a/privacyidea/lib/security/default.py b/privacyidea/lib/security/default.py
--- a/privacyidea/lib/security/default.py
+++ b/privacyidea/lib/security/default.py
@@ -130,6 +130,14 @@ def decrypt_pin(self, crypt_pin):
                   "the method : %s " % (fname,))
         raise NotImplementedError("Should have been implemented {0!s}".format(fname))
 
+    def create_keys(self):
+        """
+        This can be used to create the encryption keys
+        :return: Module dependent
+        """
+        fname = "create_keys"
+        raise NotImplementedError("Should have been implemented {0!s}".format(fname))
+
 
 class DefaultSecurityModule(SecurityModule):
 


Test patch: