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