Я хочу иметь возможность передать сертификат в библиотеку ssl Python, не требуя временного файла. Кажется, что модуль ssl Python не может этого сделать.
Чтобы обойти эту проблему, я хочу получить базовую структуру SSL_CTX
, хранящуюся в классе ssl._ssl._SSLContext
, из собственного _ssl
модуля. Используя ctypes, я мог бы вручную вызвать соответствующие функции SSL_CTX_*
из libssl с этим контекстом. Как это сделать на C показано здесь, и я сделал бы то же самое через ctypes.
К сожалению, я застрял в точке, где мне удалось подключиться к функции load_verify_locations
из ssl._ssl._SSLContext
, но, похоже, не удалось получить правильный адрес памяти экземпляра структуры ssl._ssl._SSLContext
. Вся функция load_verify_locations
видит родительский объект ssl.SSLContext
.
Мой вопрос: как я могу получить из экземпляра объекта ssl.SSLContext
в память собственного базового класса ssl._ssl._SSLContext
? Если бы у меня было это, я мог бы легко получить доступ к его члену ctx
.
Вот мой код. Кредиты на то, как monkeypatch на родном модуле Python перейдите в проект запрещенных фруктов Линкольна Клэрета
Py_ssize_t = hasattr(ctypes.pythonapi, 'Py_InitModule4_64') and ctypes.c_int64 or ctypes.c_int
class PyObject(ctypes.Structure):
pass
PyObject._fields_ = [
('ob_refcnt', Py_ssize_t),
('ob_type', ctypes.POINTER(PyObject)),
]
class SlotsProxy(PyObject):
_fields_ = [('dict', ctypes.POINTER(PyObject))]
class PySSLContext(ctypes.Structure):
pass
PySSLContext._fields_ = [
('ob_refcnt', Py_ssize_t),
('ob_type', ctypes.POINTER(PySSLContext)),
('ctx', ctypes.c_void_p),
]
name = ssl._ssl._SSLContext.__name__
target = ssl._ssl._SSLContext.__dict__
proxy_dict = SlotsProxy.from_address(id(target))
namespace = {}
ctypes.pythonapi.PyDict_SetItem(
ctypes.py_object(namespace),
ctypes.py_object(name),
proxy_dict.dict,
)
patchable = namespace[name]
old_value = patchable["load_verify_locations"]
libssl = ctypes.cdll.LoadLibrary("libssl.so.1.0.0")
libssl.SSL_CTX_set_verify.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p)
libssl.SSL_CTX_get_verify_mode.argtypes = (ctypes.c_void_p,)
def load_verify_locations(self, cafile, capath, cadata):
print(self)
print(self.verify_mode)
addr = PySSLContext.from_address(id(self)).ctx
libssl.SSL_CTX_set_verify(addr, 1337, None)
print(libssl.SSL_CTX_get_verify_mode(addr))
print(self.verify_mode)
return old_value(self, cafile, capath, cadata)
patchable["load_verify_locations"] = load_verify_locations
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
Вывод:
<ssl.SSLContext object at 0x7f4b81304ba8>
2
1337
2
Это говорит о том, что независимо от того, что я изменяю, это не контекст ssl, о котором знает Python, а какая-то другая случайная ячейка памяти.
Чтобы опробовать код сверху, вам нужно запустить https-сервер. Создайте самоподписанный сертификат SSL, используя:
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost' -nodes
И запустите сервер, используя следующий код:
import http.server, http.server
import ssl
httpd = http.server.HTTPServer(('localhost', 4443), http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='cert.pem', keyfile='key.pem', server_side=True)
httpd.serve_forever()
И затем добавьте следующую строку в конец моего примера выше:
urllib.request.urlopen("https://localhost:4443", context=context)