Skip to content

Commit 01ac92d

Browse files
author
Dariusz Suchojad
committed
SESPRINGPYTHONPY-155: Basic docs and implementation.
1 parent d3bcb90 commit 01ac92d

File tree

7 files changed

+217
-0
lines changed

7 files changed

+217
-0
lines changed
11.4 KB
Loading
15.3 KB
Loading
17.4 KB
Loading
39 KB
Binary file not shown.
39.5 KB
Binary file not shown.
40 KB
Binary file not shown.

src/springpython/remoting/xmlrpc.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# stdlib
4+
import httplib
5+
import logging
6+
import socket
7+
import ssl
8+
9+
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
10+
from xmlrpclib import ServerProxy, Error, Transport
11+
12+
# PyOpenSSL
13+
from OpenSSL import SSL
14+
15+
# Spring Python
16+
from springpython.util import TRACE1
17+
18+
__all__ = ["VerificationException", "SSLXMLRPCServer", "SSLXMLRPCClient"]
19+
20+
class VerificationException(Exception):
21+
""" Raised when the verification of a certificate's fields fails.
22+
"""
23+
24+
# ##############################################################################
25+
# Server
26+
# ##############################################################################
27+
28+
# A slightly modified version of the public-domain code from
29+
# http://skvidal.fedorapeople.org/SecureXMLRPCServer.py
30+
class SSLSocketWrapper(object):
31+
""" This whole class exists just to filter out a parameter
32+
passed in to the shutdown() method in SimpleXMLRPC.doPOST()
33+
"""
34+
def __init__(self, conn):
35+
""" Connection is not yet a new-style class, so I'm making a proxy
36+
instead of subclassing."""
37+
self.__dict__["conn"] = conn
38+
39+
def __getattr__(self,name):
40+
return getattr(self.__dict__["conn"], name)
41+
42+
def __setattr__(self,name, value):
43+
setattr(self.__dict__["conn"], name, value)
44+
45+
def shutdown(self, how=1):
46+
""" SimpleXMLRpcServer.doPOST calls shutdown(1), and Connection.shutdown()
47+
doesn't take an argument. So we just discard the argument.
48+
"""
49+
self.__dict__["conn"].shutdown()
50+
51+
def accept(self):
52+
""" This is the other part of the shutdown() workaround. Since servers
53+
create new sockets, we have to infect them with our magic.
54+
"""
55+
c, a = self.__dict__["conn"].accept()
56+
return (SSLSocketWrapper(c), a)
57+
58+
59+
class RequestHandler(SimpleXMLRPCRequestHandler):
60+
rpc_paths = ("/", "/RPC2",)
61+
62+
def setup(self):
63+
self.connection = self.request # for doPOST
64+
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
65+
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
66+
67+
class SSLXMLRPCServer(object, SimpleXMLRPCServer):
68+
def __init__(self, host=None, port=None, key_file=None, cert_file=None,
69+
ca_certs=None, cipher_list="DEFAULT", ssl_method=SSL.TLSv1_METHOD,
70+
ctx_options=SSL.OP_NO_SSLv2,
71+
verify_options=SSL.VERIFY_NONE,
72+
ssl_verify_depth=1, verify_fields=None):
73+
74+
SimpleXMLRPCServer.__init__(self, (host, port), requestHandler=RequestHandler)
75+
self.logger = logging.getLogger(self.__class__.__name__)
76+
self.register_functions()
77+
78+
ctx = SSL.Context(ssl_method)
79+
ctx.set_options(ctx_options)
80+
81+
ctx.use_privatekey_file(key_file)
82+
83+
if cert_file:
84+
ctx.use_certificate_file(cert_file)
85+
86+
if ca_certs:
87+
ctx.load_verify_locations(ca_certs)
88+
89+
ctx.set_cipher_list(cipher_list)
90+
91+
ctx.set_verify_depth(ssl_verify_depth)
92+
ctx.set_verify(verify_options, self.on_verify_peer)
93+
self.verify_fields = verify_fields
94+
95+
self.socket = SSLSocketWrapper(SSL.Connection(ctx,
96+
socket.socket(self.address_family, self.socket_type)))
97+
98+
self.server_bind()
99+
self.server_activate()
100+
101+
def on_verify_peer(self, conn, x509, error_number, error_depth, return_code):
102+
""" Verifies the other side's certificate. May be overridden in subclasses
103+
if the verification process needs to be customized.
104+
"""
105+
106+
if self.logger.isEnabledFor(TRACE1):
107+
self.logger.log(TRACE1, "on_verify_peer '%s', '%s', '%s', '%s'" % (
108+
error_number, error_depth, return_code))
109+
110+
# error_depth = 0 means we're dealing with the client's certificate
111+
# and not that of a CA.
112+
if self.verify_fields and error_depth == 0:
113+
114+
components = x509.get_subject().get_components()
115+
components = dict(components)
116+
117+
if self.logger.isEnabledFor(TRACE1):
118+
self.logger.log(TRACE1, "components received '%s'" % components)
119+
120+
for verify_field in self.verify_fields:
121+
122+
expected_value = self.verify_fields[verify_field]
123+
cert_value = components.get(verify_field, None)
124+
125+
if not cert_value:
126+
msg = "Peer didn't send the '%s' field, fields received '%s'" % (
127+
verify_field, components)
128+
raise VerificationException(msg)
129+
130+
if expected_value != cert_value:
131+
msg = "Expected the field '%s' to have value '%s' instead of '%s'" % (
132+
verify_field, expected_value, cert_value)
133+
raise VerificationException(msg)
134+
135+
return True
136+
137+
def register_functions(self):
138+
raise NotImplementedError("Must be overridden by subclasses")
139+
140+
# ##############################################################################
141+
# Client
142+
# ##############################################################################
143+
144+
145+
class CAValidatingHTTPSConnection(httplib.HTTPConnection):
146+
""" This class allows communication via SSL and takes the CAs into account.
147+
"""
148+
149+
def __init__(self, host, port=None, key_file=None, cert_file=None,
150+
ca_certs=None, cert_reqs=None, strict=None, ssl_version=None,
151+
timeout=None):
152+
httplib.HTTPConnection.__init__(self, host, port, strict, timeout)
153+
154+
self.key_file = key_file
155+
self.cert_file = cert_file
156+
self.ca_certs = ca_certs
157+
self.cert_reqs = cert_reqs
158+
self.ssl_version = ssl_version
159+
160+
def connect(self):
161+
""" Connect to a host on a given (SSL) port.
162+
"""
163+
164+
sock = socket.create_connection((self.host, self.port), self.timeout)
165+
if self._tunnel_host:
166+
self.sock = sock
167+
self._tunnel()
168+
169+
self.sock = self.wrap_socket(sock)
170+
171+
def wrap_socket(self, sock):
172+
""" Gets a socket object and wraps it into an SSL-aware one. May be
173+
overridden in subclasses if the wrapping process needs to be customized.
174+
"""
175+
return ssl.wrap_socket(sock, self.key_file, self.cert_file,
176+
ca_certs=self.ca_certs, cert_reqs=self.cert_reqs,
177+
ssl_version=self.ssl_version)
178+
179+
class CAHTTPS(httplib.HTTP):
180+
_connection_class = CAValidatingHTTPSConnection
181+
182+
def __init__(self, host=None, port=None, key_file=None, cert_file=None, ca_certs=None,
183+
cert_reqs=None, strict=None, ssl_version=None, timeout=None):
184+
self._setup(self._connection_class(host, port, key_file, cert_file, ca_certs,
185+
cert_reqs, strict, ssl_version, timeout))
186+
187+
class SSLClientTransport(Transport):
188+
""" Handles an HTTPS transaction to an XML-RPC server.
189+
"""
190+
def __init__(self, key_file=None, cert_file=None, ca_certs=None, cert_reqs=None,
191+
ssl_version=None, timeout=None):
192+
self.key_file = key_file
193+
self.cert_file = cert_file
194+
self.ca_certs = ca_certs
195+
self.cert_reqs = cert_reqs
196+
self.ssl_version = ssl_version
197+
self.timeout = timeout
198+
199+
Transport.__init__(self)
200+
201+
def make_connection(self, host):
202+
return CAHTTPS(host, key_file=self.key_file, cert_file=self.cert_file,
203+
ca_certs=self.ca_certs, cert_reqs=self.cert_reqs,
204+
ssl_version=self.ssl_version, timeout=self.timeout)
205+
206+
class SSLXMLRPCClient(ServerProxy):
207+
def __init__(self, uri=None, transport=None, encoding=None, verbose=0,
208+
allow_none=0, use_datetime=0, key_file=None, cert_file=None,
209+
ca_certs=None, cert_reqs=ssl.CERT_OPTIONAL, ssl_version=ssl.PROTOCOL_TLSv1,
210+
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
211+
212+
if not transport:
213+
transport=SSLClientTransport(key_file, cert_file, ca_certs, cert_reqs,
214+
ssl_version, timeout)
215+
216+
ServerProxy.__init__(self, uri, transport, encoding, verbose,
217+
allow_none, use_datetime)

0 commit comments

Comments
 (0)