5
5
import logging
6
6
import socket
7
7
import ssl
8
+ import sys
9
+ import traceback
8
10
9
11
from SimpleXMLRPCServer import SimpleXMLRPCServer , SimpleXMLRPCRequestHandler
10
12
from xmlrpclib import ServerProxy , Error , Transport
11
13
12
- # PyOpenSSL
13
- from OpenSSL import SSL
14
-
15
14
# Spring Python
16
- from springpython .util import TRACE1
15
+ from springpython .remoting . http import CAValidatingHTTPS
17
16
18
- __all__ = ["VerificationException" , "SSLXMLRPCServer " , "SSLXMLRPCClient " ]
17
+ __all__ = ["VerificationException" , "SSLServer " , "SSLClient " ]
19
18
20
19
class VerificationException (Exception ):
21
20
""" Raised when the verification of a certificate's fields fails.
@@ -25,37 +24,6 @@ class VerificationException(Exception):
25
24
# Server
26
25
# ##############################################################################
27
26
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
27
class RequestHandler (SimpleXMLRPCRequestHandler ):
60
28
rpc_paths = ("/" , "/RPC2" ,)
61
29
@@ -64,154 +32,154 @@ def setup(self):
64
32
self .rfile = socket ._fileobject (self .request , "rb" , self .rbufsize )
65
33
self .wfile = socket ._fileobject (self .request , "wb" , self .wbufsize )
66
34
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 ):
73
39
74
40
SimpleXMLRPCServer .__init__ (self , (host , port ), requestHandler = RequestHandler )
75
41
self .logger = logging .getLogger (self .__class__ .__name__ )
76
- self .register_functions ()
77
42
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
80
51
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" )
82
55
83
- if cert_file :
84
- ctx .use_certificate_file (cert_file )
56
+ self .register_functions ()
85
57
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 ()
88
63
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.
90
66
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 }
94
71
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
97
74
98
- self . server_bind ( )
99
- self . server_activate ()
75
+ sock = ssl . wrap_socket ( sock , ** kwargs )
76
+ return sock , from_addr
100
77
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 .
104
81
"""
82
+ try :
83
+ if self .verify_fields :
105
84
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 )
116
89
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
119
95
120
- for verify_field in self . verify_fields :
96
+ except Exception , e :
121
97
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.
124
103
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 )
129
107
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
134
110
135
111
return True
136
112
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
+ """
143
117
118
+ if self .logger .isEnabledFor (logging .DEBUG ):
119
+ self .logger .debug ("verify_peer cert='%s'" % (cert ))
144
120
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 )
148
125
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 )
153
127
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 :
159
129
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 )
163
132
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
168
137
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
170
142
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
178
144
179
- class CAHTTPS ( httplib . HTTP ):
180
- _connection_class = CAValidatingHTTPSConnection
145
+ def register_functions ( self ):
146
+ raise NotImplementedError ( "Must be overridden by subclasses" )
181
147
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
+ # ##############################################################################
186
151
187
152
class SSLClientTransport (Transport ):
188
153
""" Handles an HTTPS transaction to an XML-RPC server.
189
154
"""
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
194
159
self .ca_certs = ca_certs
195
160
self .cert_reqs = cert_reqs
196
161
self .ssl_version = ssl_version
197
162
self .timeout = timeout
163
+ self .strict = strict
198
164
199
165
Transport .__init__ (self )
200
166
201
167
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 )
205
171
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 ):
211
177
212
178
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 )
215
181
216
182
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