55import logging
66import socket
77import ssl
8+ import sys
9+ import traceback
810
911from SimpleXMLRPCServer import SimpleXMLRPCServer , SimpleXMLRPCRequestHandler
1012from xmlrpclib import ServerProxy , Error , Transport
1113
12- # PyOpenSSL
13- from OpenSSL import SSL
14-
1514# Spring Python
16- from springpython .util import TRACE1
15+ from springpython .remoting . http import CAValidatingHTTPS
1716
18- __all__ = ["VerificationException" , "SSLXMLRPCServer " , "SSLXMLRPCClient " ]
17+ __all__ = ["VerificationException" , "SSLServer " , "SSLClient " ]
1918
2019class VerificationException (Exception ):
2120 """ Raised when the verification of a certificate's fields fails.
@@ -25,37 +24,6 @@ class VerificationException(Exception):
2524# Server
2625# ##############################################################################
2726
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-
5927class RequestHandler (SimpleXMLRPCRequestHandler ):
6028 rpc_paths = ("/" , "/RPC2" ,)
6129
@@ -64,154 +32,154 @@ def setup(self):
6432 self .rfile = socket ._fileobject (self .request , "rb" , self .rbufsize )
6533 self .wfile = socket ._fileobject (self .request , "wb" , self .wbufsize )
6634
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 ):
35+ class SSLServer (object , SimpleXMLRPCServer ):
36+ def __init__ (self , host = None , port = None , ca_certs = None , keyfile = None , certfile = None ,
37+ cert_reqs = ssl .CERT_OPTIONAL , ssl_version = ssl .PROTOCOL_TLSv1 ,
38+ do_handshake_on_connect = True , suppress_ragged_eofs = True , ciphers = None , ** kwargs ):
7339
7440 SimpleXMLRPCServer .__init__ (self , (host , port ), requestHandler = RequestHandler )
7541 self .logger = logging .getLogger (self .__class__ .__name__ )
76- self .register_functions ()
7742
78- ctx = SSL .Context (ssl_method )
79- ctx .set_options (ctx_options )
43+ self .ca_certs = ca_certs
44+ self .keyfile = keyfile
45+ self .certfile = certfile
46+ self .cert_reqs = cert_reqs
47+ self .ssl_version = ssl_version
48+ self .do_handshake_on_connect = do_handshake_on_connect
49+ self .suppress_ragged_eofs = suppress_ragged_eofs
50+ self .ciphers = ciphers
8051
81- ctx .use_privatekey_file (key_file )
52+ # 'verify_fields' is taken from kwargs to allow for adding more keywords
53+ # in future versions.
54+ self .verify_fields = kwargs .get ("verify_fields" )
8255
83- if cert_file :
84- ctx .use_certificate_file (cert_file )
56+ self .register_functions ()
8557
86- if ca_certs :
87- ctx .load_verify_locations (ca_certs )
58+ def get_request (self ):
59+ """ Overridden from Socket.TCPServer.get_request, wraps the socket in
60+ an SSL context.
61+ """
62+ sock , from_addr = self .socket .accept ()
8863
89- ctx .set_cipher_list (cipher_list )
64+ # 'ciphers' argument is new in 2.7 and we must support 2.6 so add it
65+ # to kwargs conditionally, depending on the Python version.
9066
91- ctx .set_verify_depth (ssl_verify_depth )
92- ctx .set_verify (verify_options , self .on_verify_peer )
93- self .verify_fields = verify_fields
67+ kwargs = {"keyfile" :self .keyfile , "certfile" :self .certfile ,
68+ "server_side" :True , "cert_reqs" :self .cert_reqs , "ssl_version" :self .ssl_version ,
69+ "ca_certs" :self .ca_certs , "do_handshake_on_connect" :self .do_handshake_on_connect ,
70+ "suppress_ragged_eofs" :self .suppress_ragged_eofs }
9471
95- self . socket = SSLSocketWrapper ( SSL . Connection ( ctx ,
96- socket . socket ( self .address_family , self . socket_type )))
72+ if sys . version_info >= ( 2 , 7 ):
73+ kwargs [ "ciphers" ] = self .ciphers
9774
98- self . server_bind ( )
99- self . server_activate ()
75+ sock = ssl . wrap_socket ( sock , ** kwargs )
76+ return sock , from_addr
10077
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 .
78+ def verify_request (self , sock , from_addr ):
79+ """ Overridden from Socket.TCPServer.verify_request, adds validation of the
80+ other side's certificate fields .
10481 """
82+ try :
83+ if self .verify_fields :
10584
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 )
85+ cert = sock .getpeercert ()
86+ if not cert :
87+ msg = "Couldn't verify fields, peer didn't send the certificate, from_addr='%s'" % (from_addr ,)
88+ raise VerificationException (msg )
11689
117- if self .logger .isEnabledFor (TRACE1 ):
118- self .logger .log (TRACE1 , "components received '%s'" % components )
90+ allow_peer , reason = self .verify_peer (cert )
91+ if not allow_peer :
92+ self .logger .error (reason )
93+ sock .close ()
94+ return False
11995
120- for verify_field in self . verify_fields :
96+ except Exception , e :
12197
122- expected_value = self .verify_fields [verify_field ]
123- cert_value = components .get (verify_field , None )
98+ # It was either an error on our side or the client didn't send the
99+ # certificate even though self.cert_reqs was CERT_OPTIONAL (it couldn't
100+ # have been CERT_REQUIRED because we wouldn't have got so far, the
101+ # session would've been terminated much earlier in ssl.wrap_socket call).
102+ # Regardless of the reason we cannot accept the client in that case.
124103
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 )
104+ msg = "Verification error='%s', cert='%s', from_addr='%s'" % (
105+ traceback .format_exc (e ), sock .getpeercert (), from_addr )
106+ self .logger .error (msg )
129107
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 )
108+ sock .close ()
109+ return False
134110
135111 return True
136112
137- def register_functions (self ):
138- raise NotImplementedError ("Must be overridden by subclasses" )
139-
140- # ##############################################################################
141- # Client
142- # ##############################################################################
113+ def verify_peer (self , cert ):
114+ """ Verifies the other side's certificate. May be overridden in subclasses
115+ if the verification process needs to be customized.
116+ """
143117
118+ if self .logger .isEnabledFor (logging .DEBUG ):
119+ self .logger .debug ("verify_peer cert='%s'" % (cert ))
144120
145- class CAValidatingHTTPSConnection (httplib .HTTPConnection ):
146- """ This class allows communication via SSL and takes the CAs into account.
147- """
121+ subject = cert .get ("subject" )
122+ if not subject :
123+ msg = "Peer certificate doesn't have the 'subject' field, cert='%s'" % cert
124+ raise VerificationException (msg )
148125
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 )
126+ subject = dict (elem [0 ] for elem in subject )
153127
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
128+ for verify_field in self .verify_fields :
159129
160- def connect (self ):
161- """ Connect to a host on a given (SSL) port.
162- """
130+ expected_value = self .verify_fields [verify_field ]
131+ cert_value = subject .get (verify_field , None )
163132
164- sock = socket . create_connection (( self . host , self . port ), self . timeout )
165- if self . _tunnel_host :
166- self . sock = sock
167- self . _tunnel ()
133+ if not cert_value :
134+ reason = "Peer didn't send the '%s' field, subject fields received '%s'" % (
135+ verify_field , subject )
136+ return False , reason
168137
169- self .sock = self .wrap_socket (sock )
138+ if expected_value != cert_value :
139+ reason = "Expected the subject field '%s' to have value '%s' instead of '%s'" % (
140+ verify_field , expected_value , subject )
141+ return False , reason
170142
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 )
143+ return True , None
178144
179- class CAHTTPS ( httplib . HTTP ):
180- _connection_class = CAValidatingHTTPSConnection
145+ def register_functions ( self ):
146+ raise NotImplementedError ( "Must be overridden by subclasses" )
181147
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 ))
148+ # ##############################################################################
149+ # Client
150+ # ##############################################################################
186151
187152class SSLClientTransport (Transport ):
188153 """ Handles an HTTPS transaction to an XML-RPC server.
189154 """
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
155+ def __init__ (self , keyfile = None , certfile = None , ca_certs = None , cert_reqs = None ,
156+ ssl_version = None , timeout = None , strict = None ):
157+ self .keyfile = keyfile
158+ self .certfile = certfile
194159 self .ca_certs = ca_certs
195160 self .cert_reqs = cert_reqs
196161 self .ssl_version = ssl_version
197162 self .timeout = timeout
163+ self .strict = strict
198164
199165 Transport .__init__ (self )
200166
201167 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 )
168+ return CAValidatingHTTPS (host , strict = self .strict , keyfile = self .keyfile ,
169+ certfile = self . certfile , ca_certs = self .ca_certs , cert_reqs = self .cert_reqs ,
170+ ssl_version = self .ssl_version , timeout = self .timeout )
205171
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 ):
172+ class SSLClient (ServerProxy ):
173+ def __init__ (self , uri = None , ca_certs = None , keyfile = None , certfile = None ,
174+ cert_reqs = ssl . CERT_OPTIONAL , ssl_version = ssl . PROTOCOL_TLSv1 ,
175+ transport = None , encoding = None , verbose = 0 , allow_none = 0 , use_datetime = 0 ,
176+ timeout = socket ._GLOBAL_DEFAULT_TIMEOUT , strict = None ):
211177
212178 if not transport :
213- transport = SSLClientTransport (key_file , cert_file , ca_certs , cert_reqs ,
214- ssl_version , timeout )
179+ transport = SSLClientTransport (keyfile , certfile , ca_certs , cert_reqs ,
180+ ssl_version , timeout , strict )
215181
216182 ServerProxy .__init__ (self , uri , transport , encoding , verbose ,
217- allow_none , use_datetime )
183+ allow_none , use_datetime )
184+
185+ self .logger = logging .getLogger (self .__class__ .__name__ )
0 commit comments