-
Notifications
You must be signed in to change notification settings - Fork 22
/
helpers.py
280 lines (226 loc) · 9.55 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
"""
Contains some helper functions for creating C Bindings.
"""
import logging
import os
import re
import struct
import sys
from ctypes import CDLL
from six.moves import configparser
from pycryptoki.cryptoki.c_defs import CK_RV
from pycryptoki.defaults import CHRYSTOKI_DLL_FILE, CHRYSTOKI_CONFIG_FILE
from pycryptoki.exceptions import LunaException
IS_WINDOWS = "win" in sys.platform
def struct_def(struct, fields):
"""
Defines the fields of a given structure as specified.
Checks if the system is Windows first, as that would need a different struct packing.
:param struct: Class definition of a struct.
:param fields: List of tuples defining the fields (see ctypes docs)
"""
if IS_WINDOWS:
struct._pack_ = 1
struct._fields_ = fields
LOG = logging.getLogger(__name__)
IS_64B = 8 * struct.calcsize("P") == 64
CRYSTOKI_CONF_DLL = "CHRYSTOKI_CONF_DLL"
class CryptokiConfigException(LunaException):
"""
Exception raised when we fail to determine the PKCS11 library location
"""
pass
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)
try:
dll_path = _search_for_dll_in_chrystoki_conf(conf_path)
except FileNotFoundError as e:
LOG.exception(
"Luna /etc/Chrystoki.conf not be found, pycryptoki switched to point to the library of different product"
)
return None
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(r"\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(r"^\s*Lib(?:UNIX64|HPUX)\s*=\s*([^\n]+)", line)
else:
lib_unix_line = re.findall(r"^\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("'\";")
if dll_path == "":
raise CryptokiConfigException(
"Error finding LibUNIX declaration in configuration file: %s" % conf_path
)
return dll_path
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)
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)
# depends on different product, lib path could be configured by pointing to path, or stored in a file
dll_path = os.environ.get(CRYSTOKI_CONF_DLL, 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]
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
@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]
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)
def make_late_binding_function(function_name, argtypes=None, restype=CK_RV):
"""A function factory for creating a function that will bind to the cryptoki
DLL only when the function is called.
:param function_name: Name of the function in the DLL.
:return: Late bound function.
"""
def luna_function(*args):
"""
Placeholder
"""
late_binded_function = getattr(CryptokiDLLSingleton().get_dll(), function_name)
late_binded_function.restype = luna_function.restype if restype is None else restype
late_binded_function.argtypes = luna_function.argtypes if argtypes is None else 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
luna_function.__doc__ = """Cryptoki DLL call to {}.
{}
:return: {}
""".format(
function_name,
"\n".join(
" :param arg{}: {}".format(i, x.__name__)
for i, x in enumerate(argtypes, 1)
if x is not None
)
if argtypes
else "unknown",
restype.__name__,
)
return luna_function