"""
Mechanism base class, as well as helper functions for parsing Mechanism arguments
to pycryptoki functions.
"""
import logging
from ctypes import c_void_p, cast, pointer, POINTER, sizeof, create_string_buffer, c_char
from six import integer_types, binary_type
from pycryptoki.string_helpers import _decode
from pycryptoki.lookup_dicts import MECH_NAME_LOOKUP
from ..cryptoki import CK_AES_CBC_PAD_EXTRACT_PARAMS, CK_MECHANISM, \
CK_ULONG, CK_ULONG_PTR, CK_AES_CBC_PAD_INSERT_PARAMS, CK_BYTE, CK_BYTE_PTR, CK_MECHANISM_TYPE
from ..defines import *
LOG = logging.getLogger(__name__)
CK_AES_CBC_PAD_EXTRACT_PARAMS_TEMP = {'mechanism': CKM_AES_CBC_PAD_EXTRACT_DOMAIN_CTRL,
'ulType': CK_CRYPTOKI_ELEMENT,
'ulHandle': 5,
'ulDeleteAfterExtract': 0,
'pBuffer': 0,
'pulBufferLen': 0,
'ulStorage': CK_STORAGE_HOST,
'pedId': 0,
'pbFileName': 0,
'ctxID': 3
}
CK_AES_CBC_PAD_INSERT_PARAMS_TEMP = {'mechanism': CKM_AES_CBC_PAD_INSERT_DOMAIN_CTRL,
'ulType': CK_CRYPTOKI_ELEMENT,
'ulContainerState': 0,
'pBuffer': 0,
'pulBufferLen': 0,
'ulStorageType': CK_STORAGE_HOST,
'pulType': 0,
'pulHandle': 0,
'ctxID': 3,
'pedID': 3,
'pbFileName': 0,
'ulStorage': CK_STORAGE_HOST,
}
supported_parameters = {'CK_AES_CBC_PAD_EXTRACT_PARAMS': CK_AES_CBC_PAD_EXTRACT_PARAMS,
'CK_AES_CBC_PAD_INSERT_PARAMS': CK_AES_CBC_PAD_INSERT_PARAMS}
[docs]class MechanismException(Exception):
"""
Exception raised for mechanism errors. Ex: required parameters are missing
"""
pass
[docs]class Mechanism(object):
"""
Base class for pycryptoki mechanisms.
Performs checks for missing parameters w/ created mechs, and
creates the base Mechanism Struct for conversion to ctypes.
"""
REQUIRED_PARAMS = []
def __new__(cls, mech_type="UNKNOWN", params=None):
"""
Factory for mechs.
"""
from . import MECH_LOOKUP, NullMech
if cls == Mechanism:
mech_cls = MECH_LOOKUP.get(mech_type, NullMech)
return super(Mechanism, cls).__new__(mech_cls)
else:
return super(Mechanism, cls).__new__(cls)
def __init__(self, mech_type="UNKNOWN", params=None):
self.mech_type = mech_type
if params is None:
params = {}
self.params = params
missing_params = []
for req in self.REQUIRED_PARAMS:
if req not in params:
missing_params.append(req)
if missing_params:
raise MechanismException("Cannot create {}, "
"Missing required parameters:\n\t"
"{}".format(self.__class__,
"\n\t".join(missing_params)))
def __repr__(self):
"""
Return a human-readable string of the mechanism data.
"""
# todo: lookup dict for the mechanism name.
return "{}(mech_type: {}," \
" {})".format(self.__class__.__name__,
MECH_NAME_LOOKUP.get(self.mech_type, "UNKNOWN"),
", ".join("{}: {}".format(k, v) for k, v in self.params.items()))
def __str__(self):
"""
Formatted string representation of a mechanism.
"""
nice_params = []
msg = "{}(mech_type: {}".format(self.__class__.__name__,
MECH_NAME_LOOKUP.get(self.mech_type, "UNKNOWN"))
# +1 for the opening (
msg_buff = len(self.__class__.__name__) + 1
for key, value in self.params.items():
if isinstance(value, binary_type):
value = _decode(value)
nice_params.append("{}{}: {}".format(" " * msg_buff, key, value))
if self.params:
msg += ",\n" + "\n".join(nice_params)
msg += ")"
return msg
[docs] def to_c_mech(self):
"""
Create the Mechanism structure & set the mech type to the passed-in flavor.
:return: :class:`~pycryptoki.cryptoki.CK_MECHANISM`
"""
self.mech = CK_MECHANISM()
self.mech.mechanism = CK_MECHANISM_TYPE(self.mech_type)
return self.mech
[docs]def get_c_struct_from_mechanism(python_dictionary, params_type_string):
"""Gets a c struct from a python dictionary representing that struct
:param python_dictionary: The python dictionary representing the C struct,
see :class:`CK_AES_CBC_PAD_EXTRACT_PARAMS` for an example
:param params_type_string: A string representing the parameter struct.
ex. for :class:`~pycryptoki.cryptoki.CK_AES_CBC_PAD_EXTRACT_PARAMS` use the
string ``CK_AES_CBC_PAD_EXTRACT_PARAMS``
:returns: A C struct
"""
params_type = supported_parameters[params_type_string]
params = params_type()
mech = CK_MECHANISM()
mech.mechanism = python_dictionary['mechanism']
mech.pParameter = cast(pointer(params), c_void_p)
mech.usParameterLen = CK_ULONG(sizeof(params_type))
# Automatically handle the simpler fields
for entry in params_type._fields_:
key_name = entry[0]
key_type = entry[1]
if key_type == CK_ULONG:
setattr(params, key_name, CK_ULONG(python_dictionary[key_name]))
elif key_type == CK_ULONG_PTR:
setattr(params, key_name, pointer(CK_ULONG(python_dictionary[key_name])))
else:
continue
# Explicitly handle the more complex fields
if params_type == CK_AES_CBC_PAD_EXTRACT_PARAMS:
if len(python_dictionary['pBuffer']) == 0:
params.pBuffer = None
else:
params.pBuffer = (CK_BYTE * len(python_dictionary['pBuffer']))()
# params.pbFileName = 0 #TODO convert byte pointer to serializable type
pass
elif params_type == CK_AES_CBC_PAD_INSERT_PARAMS:
# params.pbFileName = TODO
params.pBuffer = cast(create_string_buffer(python_dictionary['pBuffer']), CK_BYTE_PTR)
params.ulBufferLen = len(python_dictionary['pBuffer'])
pass
else:
raise Exception("Unsupported parameter type, pycryptoki can be extended to make it work")
return mech
[docs]def get_python_dict_from_c_mechanism(c_mechanism, params_type_string):
"""Gets a python dictionary from a c mechanism's struct for serialization
and easier test case writing
:param c_mechanism: The c mechanism to convert to a python dictionary
:param params_type_string: A string representing the parameter struct.
ex. for :class:`~pycryptoki.cryptoki.CK_AES_CBC_PAD_EXTRACT_PARAMS` use the
string ``CK_AES_CBC_PAD_EXTRACT_PARAMS``
:returns: A python dictionary representing the c struct
"""
python_dictionary = {}
python_dictionary['mechanism'] = c_mechanism.mechanism
params_type = supported_parameters[params_type_string]
params_struct = cast(c_mechanism.pParameter, POINTER(params_type)).contents
# Automatically handle the simpler fields
for entry in params_type._fields_:
key_name = entry[0]
key_type = entry[1]
if key_type == CK_ULONG:
python_dictionary[key_name] = getattr(params_struct, key_name)
elif key_type == CK_ULONG_PTR:
python_dictionary[key_name] = getattr(params_struct, key_name).contents.value
else:
continue
# Explicitly handle the more complex fields
if params_type == CK_AES_CBC_PAD_EXTRACT_PARAMS:
bufferLength = params_struct.pulBufferLen.contents.value
if params_struct.pBuffer is None:
bufferString = None
else:
char_p_string = cast(params_struct.pBuffer, POINTER(c_char))
if char_p_string is not None:
bufferString = char_p_string[0:bufferLength]
else:
bufferString = None
python_dictionary['pBuffer'] = bufferString
python_dictionary['pbFileName'] = 0 # TODO
elif params_type == CK_AES_CBC_PAD_INSERT_PARAMS:
python_dictionary['pbFileName'] = 0 # TODO
python_dictionary['pBuffer'] = 0 # TODO
else:
raise Exception("Unsupported parameter type, pycryptoki can be extended to make it work")
return python_dictionary
[docs]def parse_mechanism(mechanism_param):
"""
Designed for use with any function call that takes in a mechanism,
this will handle a mechanism parameter that is one of the following:
1. ``CKM_`` integer constant -- will create a :class:`~pycryptoki.cryptoki.CK_MECHANISM`
with only mech_type set.
.. code-block :: python
parse_mechanism(CKM_RSA_PKCS)
# Results in:
mech = CK_MECHANISM()
mech.mechanism = CK_MECHANISM_TYPE(CKM_RSA_PKCS)
mech.pParameter = None
mech.usParameterLen = 0
2. Dictionary with ``mech_type`` as a mandatory key, and ``params`` as an optional key. This
will be passed into the :class:`Mechanism` class for conversion to
a :class:`~pycryptoki.cryptoki.CK_MECHANISM`.
.. code-block :: python
parse_mechanism({'mech_type': CKM_AES_CBC,
'params': {'iv': list(range(8))}})
# Results in:
mech = CK_MECHANISM()
mech.mechanism = CK_MECHANISM_TYPE(CKM_AES_CBC)
iv_ba, iv_len = to_byte_array(list(range(8)))
mech.pParameter = iv_ba
mech.usParameterLen = iv_len
3. :class:`~pycryptoki.cryptoki.CK_MECHANISM` struct -- passed directly into the raw C Call.
4. Mechanism class -- will call to_c_mech() on the class, and use the results.
.. warning:: If you're using this with rpyc, you need to make sure the call `to_c_mech` occurs
on the *server* (the machine with the HSM)! If you pass in a :py:class:`Mechanism` class
that was created on the client, the resulting call into `to_c_mech()` will *also* be on
the client side!
.. note:: You can look at ``REQUIRED_PARAMS`` on each mechanism class to see what parameters are
required.
:param mechanism_param: Parameter to convert to a C Mechanism.
:return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` struct.
"""
if isinstance(mechanism_param, dict):
mech = Mechanism(**mechanism_param).to_c_mech()
elif isinstance(mechanism_param, CK_MECHANISM):
mech = mechanism_param
elif isinstance(mechanism_param, integer_types):
mech = Mechanism(mech_type=mechanism_param).to_c_mech()
elif isinstance(mechanism_param, Mechanism):
mech = mechanism_param.to_c_mech()
else:
raise TypeError("Invalid mechanism type {}, should be CK_MECHANISM, dictionary with "
"kwargs to be passed to `Mechanism`, integer constant, or a "
"Mechanism() class.".format(type(mechanism_param)))
return mech