X Tutup
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.
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)
X Tutup