Source code for pycryptoki.pycryptoki_client

from __future__ import print_function

from six import string_types, binary_type

from pycryptoki.string_helpers import _decode, _coerce_mech_to_str

"""
Contains both a local and remote pycryptoki client
"""
import inspect
import logging
import socket
from functools import wraps

import rpyc
import time

from rpyc.core.protocol import PingError

from .daemon import rpyc_pycryptoki
from .lookup_dicts import ATTR_NAME_LOOKUP, ret_vals_dictionary

LOG = logging.getLogger(__name__)


# from https://github.com/saltycrane/retry-decorator/blob/master/decorators.py
[docs]def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): """Retry calling the decorated function using an exponential backoff. http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry :param ExceptionToCheck: the exception to check. may be a tuple of exceptions to check :type ExceptionToCheck: Exception or tuple :param tries: number of times to try (not retry) before giving up :type tries: int :param delay: initial delay between retries in seconds :type delay: int :param backoff: backoff multiplier e.g. value of 2 will double the delay each retry :type backoff: int :param logger: logger to use. If None, print :type logger: logging.Logger instance """ def deco_retry(f): @wraps(f) def f_retry(*args, **kwargs): mtries, mdelay = tries, delay while mtries > 1: try: return f(*args, **kwargs) except ExceptionToCheck as e: msg = "%s, Retrying in %d seconds..." % (str(e), mdelay) if logger: logger.warning(msg) else: print(msg) time.sleep(mdelay) mtries -= 1 mdelay *= backoff return f(*args, **kwargs) return f_retry # true decorator return deco_retry
[docs]def connection_test(func): """ Decorator to check that the underlying rpyc connection is alive before sending commands across it. :param func: :return: """ @wraps(func) def wrapper(self, *args, **kwargs): """ Inner closure. """ if not self.started: self.start() return func(self, *args, **kwargs) return wrapper
[docs]def log_args(funcname, arg_dict): """ This will run through each of the key, value pairs of the argument spec passed into pycryptoki and perform the following checks: * if key is a template, format the template data through a dict lookup * if key is password, set the log data to be '*' * if value is longer than 40 characters, abbreviate it. :param arg_dict: :return: """ log_msg = "Remote pycryptoki command: {}()".format(funcname) if arg_dict: log_msg += " with args:" log_list = [log_msg] for key, value in arg_dict.items(): if "template" in key and isinstance(value, dict): # Means it's a template, so let's perform a lookup on all of the objects within # this. log_list.append("\t%s: " % key) for template_key, template_value in arg_dict[key].items(): log_list.append("\t\t%s: %s" % (ATTR_NAME_LOOKUP.get(template_key, template_key), template_value)) elif "password" in key: log_list.append("\t%s: *" % key) elif "mechanism" in key: log_list.append("\t%s: " % key) nice_mech = _coerce_mech_to_str(arg_dict[key]).splitlines() log_list.extend(["\t\t%s" % x for x in nice_mech]) else: if isinstance(value, binary_type): log_val = _decode(value) else: log_val = value if isinstance(log_val, (string_types, binary_type)) and len(log_val) > 40: msg = "\t%s: %s[...]%s" % (key, log_val[:20], log_val[-20:]) else: msg = "\t%s: %s" % (key, log_val) log_list.append(msg) LOG.debug("\n".join(log_list))
[docs]class RemotePycryptokiClient(object): """Class to handle connecting to a remote Pycryptoki RPYC daemon. After instantiation, you can use it directly to make calls to a remote cryptoki library via RPYC (no need to do any imports or anything like that, just use the direct pycryptoki call like client.c_initialize_ex() ) :param ip: IP Address of the client the remote daemon is running on. :param port: What Port the daemon is running on. """ def __init__(self, ip=None, port=None): self.ip = ip self.port = port self.connection = None self.server = None
[docs] def kill(self): """ Close out the local RPYC connection. """ # maybe we should be reloading cryptoki dll? if self.started and not self.connection.closed: LOG.info("Stopping remote pycryptoki connection.") self.connection.close()
[docs] @retry((socket.error, EOFError, PingError), logger=LOG) def start(self): """ Start the connection to the remote RPYC daemon. """ if not self.started: LOG.info("Starting remote pycryptoki connection") self.connection = rpyc.classic.connect(self.ip, port=self.port) self.connection.ping() self.server = self.connection.root
[docs] def cleanup(self): """ """ pass
@property def started(self): """ Check if the RPYC connection is alive. :return: boolean """ try: return (self.connection is not None and self.server is not None and self.connection.ping() is None) except (PingError, EOFError): self.connection = None self.server = None return False @connection_test def __getattr__(self, name): """ This is the python default attribute handler, if an attribute is not found it's probably a pycryptoki call that we forward automagically to the server """ if hasattr(self.server, name): def wrapper(*args, **kwargs): """ Closer to allow us to log the full args & keyword argument list of all calls. """ will_raise = False if name.endswith("_ex"): func = getattr(self.server, name.rsplit("_ex", 1)[0]) will_raise = True else: func = getattr(self.server, name) nice_args = inspect.getcallargs(func, *args, **kwargs) log_args(name, nice_args) ret = getattr(self.server, name)(*args, **kwargs) # Two major calling types for pycryptoki: # 1. with _ex appended, which will raise an exception if retcode != 0 # 2. without _ex, which will return either just the retcode, or a tuple where the # first item is the retcode. # We can assume the calls that could raise an exception will *also* log the retcode. if not will_raise: retcode = ret if isinstance(ret, tuple): retcode = ret[0] LOG.debug("Remote call '%s' returned %s (%s)", name, ret_vals_dictionary.get(retcode, "Unknown"), retcode) return ret return wrapper else: raise AttributeError(name)
[docs]class LocalPycryptokiClient(object): """Class forwards calls to pycryptoki to local client but looks identical to remote client """ def __init__(self): """Nothing really to do""" pass def __getattr__(self, name): """ Function that overrides python attribute lookup; automagically calls functions in pycryptoki if they're listed in the daemon """ LOG.info("Running local pycryptoki command: {0}".format(name)) return getattr(rpyc_pycryptoki, name)
[docs] def kill(self): """ """ # nothing to do here, maybe we should unload and reload the dll pass
[docs] def cleanup(self): """ """ # nothing to do here pass