Source code for pycryptoki.cryptoki_helpers

"""
Helper functions to get us access to the PKCS11 library.
"""
import logging
import os
import re
import struct
import sys
from ctypes import CDLL

from six.moves import configparser

from .defaults import CHRYSTOKI_DLL_FILE, CHRYSTOKI_CONFIG_FILE
from .exceptions import LunaException

LOG = logging.getLogger(__name__)

IS_64B = 8 * struct.calcsize("P") == 64

CRYSTOKI_CONF_DLL = "CHRYSTOKI_CONF_DLL"


[docs]class CryptokiConfigException(LunaException): """ Exception raised when we fail to determine the PKCS11 library location """ pass
[docs]def parse_chrystoki_conf(): """Parse the crystoki.ini/Chrystoki.conf file to find the library .so/.dll file so that we can use it. """ env_conf_path = os.environ.get("ChrystokiConfigurationPath") conf_path = None if CHRYSTOKI_DLL_FILE is not None: # Use this value for the location of the DLL dll_path = CHRYSTOKI_DLL_FILE LOG.debug("Using DLL Path from defaults.py: %s", dll_path) return dll_path elif CHRYSTOKI_CONFIG_FILE is not None: conf_path = CHRYSTOKI_CONFIG_FILE LOG.debug("Using Chrystoki.conf location from defaults.py: %s", conf_path) elif env_conf_path is not None: if 'win' in sys.platform: env_conf_path = env_conf_path.replace('\\\\', '~').replace('~', '\\') + 'crystoki.ini' else: env_conf_path = os.path.join(env_conf_path, 'Chrystoki.conf') conf_path = env_conf_path LOG.debug("Using Chrystoki.conf location from environment variable " "ChrystokiConfigurationPath: %s", conf_path) if conf_path is None: conf_path = '/etc/Chrystoki.conf' LOG.warning("No DLL Path or Chyrstoki.conf path set in defaults.py " "looking up DLL path in %s", conf_path) LOG.debug("Searching %s for Chrystoki DLL path...", conf_path) dll_path = _search_for_dll_in_chrystoki_conf(conf_path) LOG.info("Using DLL at location: %s", dll_path) return dll_path
def _search_for_dll_in_chrystoki_conf(conf_path): """Parses the chrystoki configuration file for the section that specifies the location of the DLL and returns the DLL location. :param str conf_path: The path to the configuration file :returns: The path to the chrystoki DLL :rtype: str """ if 'win' in sys.platform: try: config = configparser.ConfigParser() config.read(conf_path) dll_path = config.get("Chrystoki2", "LibNT") except ValueError: LOG.exception("Failed to read DLL from crystoki.ini.") raise CryptokiConfigException("Failed to read DLL location crystoki.ini file!") else: if not os.path.isfile(dll_path): raise CryptokiConfigException("Cryptoki DLL does not exist at path {}! Check your " "crystoki.ini file.".format(dll_path)) else: with open(conf_path) as conf_file: chrystoki_conf_text = conf_file.read() chrystoki2_segments = re.findall("\s*Chrystoki2\s*=\s*\{([^\}]*)", chrystoki_conf_text) if len(chrystoki2_segments) > 1: raise CryptokiConfigException("Found %d Chrystoki2 sections in the config file: " "%s" % (len(chrystoki2_segments), conf_path)) elif len(chrystoki2_segments) < 1: raise CryptokiConfigException("Found no Chrystoki2 section in the config file:" " %s" % conf_path) chrystoki2 = chrystoki2_segments[0].split('\n') dll_path = "" for line in chrystoki2: is_64bits = sys.maxsize > 2 ** 32 if is_64bits: lib_unix_line = re.findall("^\s*Lib(?:UNIX64|HPUX)\s*=\s*([^\n]+)", line) else: lib_unix_line = re.findall("^\s*Lib(?:UNIX|HPUX)\s*=\s*([^\n]+)", line) if len(lib_unix_line) > 1: raise CryptokiConfigException("Found more than one" " LibUNIX pattern on the same line") elif len(lib_unix_line) == 1: if dll_path != "": raise CryptokiConfigException("Found more than one instance of" " LibUNIX in the file.") dll_path = lib_unix_line[0].strip().strip(';').strip().strip("'").strip('"') if dll_path == "": raise CryptokiConfigException("Error finding LibUNIX declaration in configuration file:" " %s" % conf_path) return dll_path
[docs]class CryptokiDLLException(Exception): """Custom exception class used to print an error when a call to the Cryptoki DLL failed. The late binding makes debugging a little bit more difficult because function calls have to pass through an additional layer of abstraction. This custom exception prints out a quick message detailing exactly what function failed. """ def __init__(self, additional_info, orig_error): self.msg = additional_info self.original_error = orig_error def __str__(self): return self.msg + "\n" + str(self.original_error)
[docs]class CryptokiDLLSingleton(object): """A singleton class which holds an instance of the loaded cryptoki DLL object.""" _instance_map = {} loaded_dll_library = None def __new__(cls, *args, **kwargs): if not cls._instance_map.get(CRYSTOKI_CONF_DLL): new_instance = super(CryptokiDLLSingleton, cls).__new__(cls, *args, **kwargs) dll_path = parse_chrystoki_conf() new_instance.dll_path = dll_path if 'win' in sys.platform and IS_64B: import ctypes new_instance.loaded_dll_library = ctypes.WinDLL(dll_path) else: new_instance.loaded_dll_library = CDLL(dll_path) cls._instance_map[CRYSTOKI_CONF_DLL] = new_instance return cls._instance_map[CRYSTOKI_CONF_DLL]
[docs] def get_dll(self): """Get the loaded library (parsed from crystoki.ini/Chrystoki.conf)""" if self.loaded_dll_library is None or self.loaded_dll_library == "": raise CryptokiConfigException( "DLL path not found:\n" "1. Is the Luna HSM Client installed?\n" "2. Can python read the Luna HSM Client config file?\n" "3. Is there a LibUNIX/LibNT field in the Luna HSM Client config file") return self.loaded_dll_library
[docs] @classmethod def from_path(cls, path): if not cls._instance_map.get(path): new_instance = super(CryptokiDLLSingleton, cls).__new__(cls) cls._instance_map[path] = new_instance new_instance.dll_path = path if 'win' in sys.platform and IS_64B: import ctypes new_instance.loaded_dll_library = ctypes.WinDLL(path) else: new_instance.loaded_dll_library = CDLL(path) return cls._instance_map[path]
[docs]def log_args(funcname, args): """Log function name & arguments for a cryptoki ctypes call. :param str funcname: Function name :param tuple args: Arguments to be passed to ctypes function. """ log_msg = "Cryptoki call: {}({})".format(funcname, ", ".join(str(arg) for arg in args)) LOG.debug(log_msg)
[docs]def make_late_binding_function(function_name): """A function factory for creating a function that will bind to the cryptoki DLL only when the function is called. :param function_name: """ def luna_function(*args): """ :param *args: :param **kwargs: """ late_binded_function = getattr(CryptokiDLLSingleton().get_dll(), function_name) late_binded_function.restype = luna_function.restype late_binded_function.argtypes = luna_function.argtypes log_args(function_name, args) try: return_value = late_binded_function(*args) return return_value except Exception as e: raise CryptokiDLLException("Call to '{}({})' " "failed.".format(function_name, ", ".join([str(arg) for arg in args])), e) luna_function.__name__ = function_name return luna_function