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