From 69527ef02c4883d4b8bf08a8b7e74212514153b2 Mon Sep 17 00:00:00 2001 From: idziubenko Date: Wed, 23 Jun 2021 14:55:59 +0300 Subject: [PATCH] swaped sudsds for suds --- pym/console/application/cl_client.py | 15 +- pym/console/application/client_class.py | 161 ++++++++- pym/console/application/function.py | 4 +- pym/console/application/pyopenssl_wrapper.py | 352 +++++++++++++++++++ 4 files changed, 511 insertions(+), 21 deletions(-) create mode 100644 pym/console/application/pyopenssl_wrapper.py diff --git a/pym/console/application/cl_client.py b/pym/console/application/cl_client.py index d57820e..76bd081 100644 --- a/pym/console/application/cl_client.py +++ b/pym/console/application/cl_client.py @@ -16,9 +16,10 @@ from calculate.core.server.local_call import Display, Methods, has_force_arg from calculate.lib.utils.tools import unpack_single_opts -from sudsds import WebFault -from sudsds.transport import TransportError - +# from sudsds import WebFault +from suds import WebFault +# from sudsds.transport import TransportError +from suds.transport import TransportError from client_class import Client_suds import traceback as tb import time @@ -383,10 +384,10 @@ def main(wait_thread): list(unpack_single_opts(sys.argv[1:]))) logging.basicConfig(level=logging.FATAL) - logging.getLogger('sudsds.client').setLevel(logging.FATAL) - logging.getLogger('sudsds.transport').setLevel(logging.FATAL) - logging.getLogger('sudsds.transport.http').setLevel(logging.FATAL) - logging.getLogger('sudsds.umx.typed').setLevel(logging.ERROR) + logging.getLogger('suds.client').setLevel(logging.FATAL) + logging.getLogger('suds.transport').setLevel(logging.FATAL) + logging.getLogger('suds.transport.http').setLevel(logging.FATAL) + logging.getLogger('suds.umx.typed').setLevel(logging.ERROR) clVarsCore = DataVarsCore() clVarsCore.importCore() diff --git a/pym/console/application/client_class.py b/pym/console/application/client_class.py index e4fc255..fc4511d 100644 --- a/pym/console/application/client_class.py +++ b/pym/console/application/client_class.py @@ -27,14 +27,26 @@ import hashlib import M2Crypto from calculate.core.datavars import DataVarsCore from calculate.lib.datavars import DataVars -from sudsds.client import Client +# from sudsds.client import Client +from suds.client import Client from cert_verify import verify, get_CRL -from sudsds.transport.http import (HttpTransport, SUDSHTTPRedirectHandler, - CheckingHTTPSConnection, - CheckingHTTPSHandler, - PYOPENSSL_AVAILABLE, PyOpenSSLSocket) -from sudsds.transport import Transport -from sudsds.properties import Unskin +# from sudsds.transport.http import (HttpTransport, SUDSHTTPRedirectHandler, +# CheckingHTTPSConnection, +# CheckingHTTPSHandler, +# PYOPENSSL_AVAILABLE, PyOpenSSLSocket) +import httplib +from suds.transport.http import HttpTransport +try: + from pyopenssl_wrapper import PyOpenSSLSocket +except ImportError: + PYOPENSSL_AVAILABLE = False +else: + PYOPENSSL_AVAILABLE = True +# from sudsds.transport import Transport +# from sudsds.properties import Unskin +from suds.transport import Transport +from suds.properties import Unskin + from cookielib import CookieJar, DefaultCookiePolicy from logging import getLogger from calculate.console.datavars import DataVarsConsole @@ -47,6 +59,122 @@ setLocalTranslate('cl_console3', sys.modules[__name__]) log = getLogger(__name__) flag = 0 +class SUDSHTTPRedirectHandler(u2.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + """Return a Request or None in response to a redirect. + + This is called by the http_error_30x methods, + it was taken from the original Python version and modified + to use POST when redirection takes place. + This allows a SOAP message to be redirected without a loss + of content. + """ + m = req.get_method() + if (code in (301, 302, 303, 307) and m in ("GET", "HEAD") + or code in (301, 302, 303) and m == "POST"): + newurl = newurl.replace(' ', '%20') + newheaders = dict((k,v) for k,v in req.headers.items() + if k.lower() not in ("content-length", "content-type") + ) + log.debug("Redirecting to %s", newurl) + return u2.Request(newurl, + data=req.data, # here we pass the original data + headers=newheaders, + origin_req_host=req.get_origin_req_host(), + unverifiable=True, + ) + else: + raise u2.HTTPError(req.get_full_url(), code, msg, headers, fp) + +class MyHTTPResponse(httplib.HTTPResponse): + + def __init__(self, sock, debuglevel=0, strict=0, method=None): + + httplib.HTTPResponse.__init__(self, sock, debuglevel, strict, method) + +class CheckingHTTPSConnection(httplib.HTTPSConnection): + """based on httplib.HTTPSConnection code - extended to support + server certificate verification and client certificate authorization""" + + response_class = MyHTTPResponse + + FORCE_SSL_VERSION = None + SERVER_CERT_CHECK = True # might be turned off when a workaround is needed + + + def __init__(self, host, ca_certs=None, cert_verifier=None, + keyobj=None, certobj=None, **kw): + """cert_verifier is a function returning either True or False + based on whether the certificate was found to be OK, + keyobj and certobj represent internal PyOpenSSL structures holding + the key and certificate respectively. + """ + httplib.HTTPSConnection.__init__(self, host, **kw) + self.ca_certs = ca_certs + self.cert_verifier = cert_verifier + self.keyobj = keyobj + self.certobj = certobj + + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if hasattr(self, '_tunnel_host') and self._tunnel_host: + self.sock = sock + self._tunnel() + if self.FORCE_SSL_VERSION: + add = {'ssl_version': self.FORCE_SSL_VERSION} + else: + add = {} + if self.SERVER_CERT_CHECK and self.ca_certs: + add['cert_reqs'] = ssl.CERT_REQUIRED + else: + add['cert_reqs'] = ssl.CERT_NONE + # try to use PyOpenSSL by default + if PYOPENSSL_AVAILABLE: + wrap_class = PyOpenSSLSocket + add['keyobj'] = self.keyobj + add['certobj'] = self.certobj + add['keyfile'] = self.key_file + add['certfile'] = self.cert_file + else: + wrap_class = ssl.SSLSocket + self.sock = wrap_class(sock, ca_certs=self.ca_certs, **add) + #if self.cert_verifier and self.SERVER_CERT_CHECK: + # if not self.cert_verifier(self.sock.getpeercert()): + # raise Exception("Server certificate did not pass security check.", + # self.sock.getpeercert()) + +class CheckingHTTPSHandler(u2.HTTPSHandler): + + def __init__(self, ca_certs=None, cert_verifier=None, + client_certfile=None, client_keyfile=None, + client_keyobj=None, client_certobj=None, + *args, **kw): + """cert_verifier is a function returning either True or False + based on whether the certificate was found to be OK""" + u2.HTTPSHandler.__init__(self, *args, **kw) + self.ca_certs = ca_certs + self.cert_verifier = cert_verifier + self.client_keyfile = client_keyfile # filename + self.client_certfile = client_certfile # filename + self.keyobj = client_keyobj + self.certobj = client_certobj + # FOR DEBUG + # self.set_http_debuglevel(100) + + def https_open(self, req): + def open(*args, **kw): + new_kw = dict(ca_certs=self.ca_certs, + cert_verifier=self.cert_verifier, + cert_file=self.client_certfile, + key_file=self.client_keyfile, + keyobj=self.keyobj, + certobj=self.certobj) + new_kw.update(kw) + return CheckingHTTPSConnection(*args, **new_kw) + return self.do_open(open, req) + + https_request = u2.AbstractHTTPHandler.do_request_ + class Client_suds(SessionId, Client): def set_parameters(self, path_to_cert, CERT_FILE, PKEY_FILE, HOST): @@ -505,11 +633,20 @@ class HTTPSClientCertTransport(HttpTransport): self.cookie_callback = cookie_callback self.user_agent_string = user_agent_string log.debug("Proxy: %s", self.options.proxy) - from dslib.network import ProxyManager - - proxy_handler = ProxyManager.HTTPS_PROXY.create_proxy_handler() - proxy_auth_handler = \ - ProxyManager.HTTPS_PROXY.create_proxy_auth_handler() + #TODO to be removed: + # artifacts from old times: + + # from dslib.network import ProxyManager + # proxy_handler = ProxyManager.HTTPS_PROXY.create_proxy_handler() + # proxy_auth_handler = ProxyManager.HTTPS_PROXY.create_proxy_auth_handler() + + # apparently, dslib simply returned None on create_proxy_auth_handler + # if this is ever needed, probably use urllib2.ProxyBasicAuthHandler + proxy_auth_handler = None + # and create_proxy_handler SHOULD HAVE eval'd to this: + # proxy_handler = urllib2.ProxyHandler({"https" : "https://hostname"}) + # but because no hostname was given, it also just returned None + proxy_handler = None if (ca_certs or (client_keyfile and client_certfile) or (client_keyobj and client_certobj)): https_handler = CheckingClientHTTPSHandler( diff --git a/pym/console/application/function.py b/pym/console/application/function.py index 96bb78f..1561795 100644 --- a/pym/console/application/function.py +++ b/pym/console/application/function.py @@ -27,8 +27,8 @@ from calculate.core.server.cert_cmd import getHwAddr, getIpLocal from calculate.core.server.local_call import print_brief_group from calculate.lib.cl_lang import setLocalTranslate from calculate.lib.utils.files import readFile -from sudsds import MethodNotFound - +# from sudsds import MethodNotFound +from suds import MethodNotFound _ = lambda x: x setLocalTranslate('cl_console3', sys.modules[__name__]) diff --git a/pym/console/application/pyopenssl_wrapper.py b/pym/console/application/pyopenssl_wrapper.py new file mode 100644 index 0000000..35fd8bd --- /dev/null +++ b/pym/console/application/pyopenssl_wrapper.py @@ -0,0 +1,352 @@ +""" +This is just a simple copy of the ssl.py module contained in the Python +standard library. It was modified to work with PyOpenSSL and only to the +extent that it works with the DS server. It might not work for any other +purpose. +""" + +import textwrap + +import _ssl # if we can't import it, let the error propagate + +from _ssl import SSLError +from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED +from _ssl import PROTOCOL_SSLv23, PROTOCOL_TLSv1 +from _ssl import RAND_status, RAND_add +from _ssl import \ + SSL_ERROR_ZERO_RETURN, \ + SSL_ERROR_WANT_READ, \ + SSL_ERROR_WANT_WRITE, \ + SSL_ERROR_WANT_X509_LOOKUP, \ + SSL_ERROR_SYSCALL, \ + SSL_ERROR_SSL, \ + SSL_ERROR_WANT_CONNECT, \ + SSL_ERROR_EOF, \ + SSL_ERROR_INVALID_ERROR_CODE + +from socket import socket, _fileobject +from socket import getnameinfo as _getnameinfo +import base64 # for DER-to-PEM translation + +# the OpenSSL stuff + +import OpenSSL + +_ssl_to_openssl_cert_op_remap = { + CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER|OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT + } + +_ssl_to_openssl_version_remap = { + PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, + } + +class PyOpenSSLSocket (socket): + + """This class implements a subtype of socket.socket that wraps + the underlying OS socket in an SSL context when necessary, and + provides read and write methods over that channel.""" + + def __init__(self, sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + keyobj=None, certobj=None): + socket.__init__(self, _sock=sock._sock) + # the initializer for socket trashes the methods (tsk, tsk), so... + self.send = lambda data, flags=0: PyOpenSSLSocket.send(self, data, flags) + self.sendto = lambda data, addr, flags=0: PyOpenSSLSocket.sendto(self, data, addr, flags) + self.recv = lambda buflen=1024, flags=0: PyOpenSSLSocket.recv(self, buflen, flags) + self.recvfrom = lambda addr, buflen=1024, flags=0: PyOpenSSLSocket.recvfrom(self, addr, buflen, flags) + self.recv_into = lambda buffer, nbytes=None, flags=0: PyOpenSSLSocket.recv_into(self, buffer, nbytes, flags) + self.recvfrom_into = lambda buffer, nbytes=None, flags=0: PyOpenSSLSocket.recvfrom_into(self, buffer, nbytes, flags) + + if certfile and not keyfile: + keyfile = certfile + # see if it's connected + try: + socket.getpeername(self) + except: + # no, no connection yet + self._sslobj = None + else: + # yes, create the SSL object + self._sslobj = sslwrap(self._sock, server_side, + keyfile, certfile, + cert_reqs, ssl_version, ca_certs, + keyobj=keyobj, certobj=certobj) + if do_handshake_on_connect: + timeout = self.gettimeout() + try: + self.settimeout(None) + self.do_handshake() + finally: + self.settimeout(timeout) + self.keyfile = keyfile + self.certfile = certfile + self.cert_reqs = cert_reqs + self.ssl_version = ssl_version + self.ca_certs = ca_certs + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self.keyobj = keyobj + self.certobj = certobj + + def read(self, len=1024): + + """Read up to LEN bytes and return them. + Return zero-length string on EOF.""" + + try: + return self._sslobj.read(len) + except SSLError, x: + if x.args[0] == SSL_ERROR_EOF and self.suppress_ragged_eofs: + return '' + else: + raise + + def write(self, data): + + """Write DATA to the underlying SSL channel. Returns + number of bytes of DATA actually transmitted.""" + + return self._sslobj.write(data) + + def getpeercert(self, binary_form=False): + + """Returns a formatted version of the data in the + certificate provided by the other end of the SSL channel. + Return None if no certificate was provided, {} if a + certificate was provided, but not validated.""" + + return self._sslobj.get_peer_certificate() + + def cipher (self): + + if not self._sslobj: + return None + else: + return self._sslobj.cipher() + + def send (self, data, flags=0): + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to send() on %s" % + self.__class__) + while True: + try: + v = self._sslobj.write(data) + except SSLError, x: + if x.args[0] == SSL_ERROR_WANT_READ: + return 0 + elif x.args[0] == SSL_ERROR_WANT_WRITE: + return 0 + else: + raise + else: + return v + else: + return socket.send(self, data, flags) + + def sendto (self, data, addr, flags=0): + if self._sslobj: + raise ValueError("sendto not allowed on instances of %s" % + self.__class__) + else: + return socket.sendto(self, data, addr, flags) + + def sendall (self, data, flags=0): + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to sendall() on %s" % + self.__class__) + amount = len(data) + count = 0 + while (count < amount): + v = self.send(data[count:]) + count += v + return amount + else: + return socket.sendall(self, data, flags) + + def recv (self, buflen=1024, flags=0): + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to sendall() on %s" % + self.__class__) + while True: + try: + return self.read(buflen) + except SSLError, x: + if x.args[0] == SSL_ERROR_WANT_READ: + continue + else: + raise x + else: + return socket.recv(self, buflen, flags) + + def recv_into (self, buffer, nbytes=None, flags=0): + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to recv_into() on %s" % + self.__class__) + while True: + try: + tmp_buffer = self.read(nbytes) + v = len(tmp_buffer) + buffer[:v] = tmp_buffer + return v + except SSLError, x: + if x.args[0] == SSL_ERROR_WANT_READ: + continue + else: + raise x + else: + return socket.recv_into(self, buffer, nbytes, flags) + + def recvfrom (self, addr, buflen=1024, flags=0): + if self._sslobj: + raise ValueError("recvfrom not allowed on instances of %s" % + self.__class__) + else: + return socket.recvfrom(self, addr, buflen, flags) + + def recvfrom_into (self, buffer, nbytes=None, flags=0): + if self._sslobj: + raise ValueError("recvfrom_into not allowed on instances of %s" % + self.__class__) + else: + return socket.recvfrom_into(self, buffer, nbytes, flags) + + def pending (self): + if self._sslobj: + return self._sslobj.pending() + else: + return 0 + + def unwrap (self): + if self._sslobj: + s = self._sslobj.shutdown() + self._sslobj = None + return s + else: + raise ValueError("No SSL wrapper around " + str(self)) + + def shutdown (self, how): + self._sslobj = None + socket.shutdown(self, how) + + def close (self): + if self._makefile_refs < 1: + self._sslobj = None + socket.close(self) + else: + self._makefile_refs -= 1 + + def do_handshake (self): + + """Perform a TLS/SSL handshake.""" + + self._sslobj.do_handshake() + + def connect(self, addr): + + """Connects to remote ADDR, and then wraps the connection in + an SSL channel.""" + + # Here we assume that the socket is client-side, and not + # connected at the time of the call. We connect it, then wrap it. + if self._sslobj: + raise ValueError("attempt to connect already-connected PyOpenSSLSocket!") + socket.connect(self, addr) + self._sslobj = sslwrap(self._sock, False, self.keyfile, self.certfile, + self.cert_reqs, self.ssl_version, + self.ca_certs, + keyobj=self.keyobj, certobj=self.certobj) + if self.do_handshake_on_connect: + self.do_handshake() + + def accept(self): + + """Accepts a new connection from a remote client, and returns + a tuple containing that new connection wrapped with a server-side + SSL channel, and the address of the remote client.""" + + newsock, addr = socket.accept(self) + return (PyOpenSSLSocket(newsock, + keyfile=self.keyfile, + certfile=self.certfile, + server_side=True, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ca_certs=self.ca_certs, + do_handshake_on_connect=self.do_handshake_on_connect, + suppress_ragged_eofs=self.suppress_ragged_eofs), + addr) + + def makefile(self, mode='r', bufsize=-1): + + """Make and return a file-like object that + works with the SSL connection. Just use the code + from the socket module.""" + + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize) + + + +def wrap_socket(sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True): + + return PyOpenSSLSocket(sock, keyfile=keyfile, certfile=certfile, + server_side=server_side, cert_reqs=cert_reqs, + ssl_version=ssl_version, ca_certs=ca_certs, + do_handshake_on_connect=do_handshake_on_connect, + suppress_ragged_eofs=suppress_ragged_eofs) + + +def verify_connection(conn, x509, error_code, depth, ret_code): + # no extra validation - just return whatever OpenSSL already + # decided during its check + return bool(ret_code) + +def sslwrap(sock, server_side=False, keyfile=None, certfile=None, + cert_reqs=CERT_NONE, ssl_version=PROTOCOL_SSLv23, + ca_certs=None, keyobj=None, certobj=None): + """this is modification of _ssl.sslwrap that uses PyOpenSSL, + keyobj and certobj are new parameters allowing setting the + key and cert not by filename, but from internal PyOpenSSL + structures. + """ + ctx = OpenSSL.SSL.Context(_ssl_to_openssl_version_remap[ssl_version]) + if ca_certs: + ctx.load_verify_locations(ca_certs) + ctx.set_verify(_ssl_to_openssl_cert_op_remap[cert_reqs], verify_connection) + if keyobj: + ctx.use_privatekey(keyobj) + elif keyfile: + ctx.use_privatekey_file(keyfile) + if certobj: + ctx.use_certificate(certobj) + elif certfile: + ctx.use_certificate_file(certfile) + ctx.set_options(0x4000) # THIS IS THE KEY TO SUCCESS OF DS + ssl_sock = OpenSSL.SSL.Connection(ctx, sock) + ssl_sock.setblocking(True) + ssl_sock.set_connect_state() + return ssl_sock