1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """TLS support for XMPP streams.
20
21 Normative reference:
22 - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
23 """
24
25 __revision__="$Id: streamtls.py 701 2010-04-03 15:43:50Z jajcus $"
26 __docformat__="restructuredtext en"
27
28 import socket
29 import sys
30 import errno
31 import logging
32 import ssl
33 import warnings
34 import inspect
35 from ssl import SSLError
36
37 from pyxmpp.streambase import StreamBase,STREAM_NS
38 from pyxmpp.streambase import FatalStreamError,StreamEncryptionRequired
39 from pyxmpp.exceptions import TLSNegotiationFailed, TLSError, TLSNegotiatedButNotAvailableError
40 from pyxmpp.jid import JID
41
42 TLS_NS="urn:ietf:params:xml:ns:xmpp-tls"
43
44 tls_available = True
45
46
48 """Utility function to count expected arguments of a callable"""
49 vc_args = inspect.getargspec(callable)
50 count = len(vc_args.args)
51 if not hasattr(callable, "im_self"):
52 return count
53 if callable.im_self is None:
54 return count
55 return count - 1
56
58 """Storage for TLS-related settings of an XMPP stream.
59
60 :Ivariables:
61 - `require`: is TLS required
62 - `verify_peer`: should the peer's certificate be verified
63 - `cert_file`: path to own X.509 certificate
64 - `key_file`: path to the private key for own X.509 certificate
65 - `cacert_file`: path to a file with trusted CA certificates
66 - `verify_callback`: callback function for certificate
67 verification."""
68
69 - def __init__(self,
70 require = False, verify_peer = True,
71 cert_file = None, key_file = None, cacert_file = None,
72 verify_callback = None, ctx = None):
73 """Initialize the TLSSettings object.
74
75 :Parameters:
76 - `require`: is TLS required
77 - `verify_peer`: should the peer's certificate be verified
78 - `cert_file`: path to own X.509 certificate
79 - `key_file`: path to the private key for own X.509 certificate
80 - `cacert_file`: path to a file with trusted CA certificates
81 - `verify_callback`: callback function for certificate
82 verification. The callback function must accept a single
83 argument: the certificate to verify, as returned by
84 `ssl.SSLSocket.getpeercert()` and return True if a certificate is
85 accepted. The verification callback should call
86 Stream.tls_is_certificate_valid() to check if certificate subject
87 name or alt subject name matches stream peer JID."""
88 if ctx is not None:
89 warnings.warn("ctx argument of TLSSettings is deprecated",
90 DeprecationWarning)
91 self.require = require
92 self.verify_peer = verify_peer
93 self.cert_file = cert_file
94 self.cacert_file = cacert_file
95 self.key_file = key_file
96 if verify_callback:
97 if _count_args(verify_callback) > 1 :
98 warnings.warn("Two-argument TLS verify callback is deprecated",
99 DeprecationWarning)
100 verify_callback = None
101 self.verify_callback = verify_callback
102
104 """Mix-in class providing TLS support for an XMPP stream.
105
106 :Ivariables:
107 - `tls`: TLS connection object.
108 """
109 - def __init__(self, tls_settings = None):
110 """Initialize TLS support of a Stream object
111
112 :Parameters:
113 - `tls_settings`: settings for StartTLS.
114 :Types:
115 - `tls_settings`: `TLSSettings`
116 """
117 self.tls_settings = tls_settings
118 self.__logger = logging.getLogger("pyxmpp.StreamTLSMixIn")
119
121 """Reset `StreamTLSMixIn` object state making it ready to handle new
122 connections."""
123 self.tls = None
124 self.tls_requested = False
125
127 """Update the <features/> with StartTLS feature.
128
129 [receving entity only]
130
131 :Parameters:
132 - `features`: the <features/> element of the stream.
133 :Types:
134 - `features`: `libxml2.xmlNode`
135
136 :returns: updated <features/> element node.
137 :returntype: `libxml2.xmlNode`"""
138 if self.tls_settings and not self.tls:
139 tls = features.newChild(None, "starttls", None)
140 ns = tls.newNs(TLS_NS, None)
141 tls.setNs(ns)
142 if self.tls_settings.require:
143 tls.newChild(None, "required", None)
144 return features
145
147 """Same as `Stream.write_raw` but assume `self.lock` is acquired."""
148 logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data)
149 try:
150 while self.socket:
151 try:
152 self.socket.send(data)
153 except SSLError, err:
154 if err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
155 continue
156 raise
157 break
158 except (IOError, OSError, socket.error),e:
159 raise FatalStreamError("IO Error: "+str(e))
160 except SSLError,e:
161 raise TLSError("TLS Error: "+str(e))
162
164 """Read data pending on the stream socket and pass it to the parser."""
165 if self.eof:
166 return
167 while self.socket:
168 try:
169 r = self.socket.read()
170 if r is None:
171 return
172 except SSLError, err:
173 if err.args[0] == ssl.SSL_ERROR_WANT_READ:
174 return
175 raise
176 except socket.error, err:
177 if err.args[0] != errno.EINTR:
178 raise
179 return
180 self._feed_reader(r)
181
183 """Read data pending on the stream socket and pass it to the parser."""
184 self.__logger.debug("StreamTLSMixIn._read(), socket: %r",self.socket)
185 if self.tls:
186 self._read_tls()
187 else:
188 StreamBase._read(self)
189
191 """Same as `Stream.process` but assume `self.lock` is acquired."""
192 try:
193 StreamBase._process(self)
194 except SSLError,e:
195 self.close()
196 raise TLSError("TLS Error: "+str(e))
197
199 """Process incoming stream element. Pass it to _process_tls_node
200 if it is in TLS namespace.
201
202 :raise StreamEncryptionRequired: if encryption is required by current
203 configuration, it is not active and the element is not in the TLS
204 namespace nor in the stream namespace.
205
206 :return: `True` when the node was recognized as TLS element.
207 :returntype: `bool`"""
208 ns_uri=xmlnode.ns().getContent()
209 if ns_uri==STREAM_NS:
210 return False
211 elif ns_uri==TLS_NS:
212 self._process_tls_node(xmlnode)
213 return True
214 if self.tls_settings and self.tls_settings.require and not self.tls:
215 raise StreamEncryptionRequired,"TLS encryption required and not started yet"
216 return False
217
219 """Process incoming StartTLS related element of <stream:features/>.
220
221 [initiating entity only]
222
223 The received features node is available in `self.features`."""
224 ctxt = self.doc_in.xpathNewContext()
225 ctxt.setContextNode(self.features)
226 ctxt.xpathRegisterNs("tls",TLS_NS)
227 try:
228 tls_n=ctxt.xpathEval("tls:starttls")
229 tls_required_n=ctxt.xpathEval("tls:starttls/tls:required")
230 finally:
231 ctxt.xpathFreeContext()
232
233 if not self.tls:
234 if tls_required_n and not self.tls_settings:
235 raise FatalStreamError,"StartTLS support disabled, but required by peer"
236 if self.tls_settings and self.tls_settings.require and not tls_n:
237 raise FatalStreamError,"StartTLS required, but not supported by peer"
238 if self.tls_settings and tls_n:
239 self.__logger.debug("StartTLS negotiated")
240 if self.initiator:
241 self._request_tls()
242 else:
243 self.__logger.debug("StartTLS not negotiated")
244
246 """Request a TLS-encrypted connection.
247
248 [initiating entity only]"""
249 self.tls_requested=1
250 self.features=None
251 root=self.doc_out.getRootElement()
252 xmlnode=root.newChild(None,"starttls",None)
253 ns=xmlnode.newNs(TLS_NS,None)
254 xmlnode.setNs(ns)
255 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
256 xmlnode.unlinkNode()
257 xmlnode.freeNode()
258
260 """Process stream element in the TLS namespace.
261
262 :Parameters:
263 - `xmlnode`: the XML node received
264 """
265 if not self.tls_settings or not tls_available:
266 self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize()))
267 return False
268 if self.initiator:
269 if xmlnode.name=="failure":
270 raise TLSNegotiationFailed,"Peer failed to initialize TLS connection"
271 elif xmlnode.name!="proceed" or not self.tls_requested:
272 self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize()))
273 return False
274 try:
275 self.tls_requested=0
276 self._make_tls_connection()
277 self.socket=self.tls
278 except SSLError,e:
279 self.tls=None
280 raise TLSError("TLS Error: "+str(e))
281 self.__logger.debug("Restarting XMPP stream")
282 self._restart_stream()
283 return True
284 else:
285 raise FatalStreamError,"TLS not implemented for the receiving side yet"
286
288 """Initiate TLS connection.
289
290 [initiating entity only]"""
291 if not tls_available or not self.tls_settings:
292 raise TLSError,"TLS is not available"
293
294 self.state_change("tls connecting",self.peer)
295
296 if not self.tls_settings.verify_callback:
297 self.tls_settings.verify_callback = self.tls_is_certificate_valid
298
299 self.__logger.debug("tls_settings: {0!r}".format(self.tls_settings.__dict__))
300 self.__logger.debug("Creating TLS connection")
301
302 if self.tls_settings.verify_peer:
303 cert_reqs = ssl.CERT_REQUIRED
304 else:
305 cert_reqs = ssl.CERT_NONE
306
307 self.tls = ssl.wrap_socket(self.socket,
308 keyfile = self.tls_settings.key_file,
309 certfile = self.tls_settings.cert_file,
310 server_side = not self.initiator,
311 cert_reqs = cert_reqs,
312 ssl_version = ssl.PROTOCOL_TLSv1,
313 ca_certs = self.tls_settings.cacert_file,
314 do_handshake_on_connect = False,
315 )
316 self.socket = None
317 self.__logger.debug("Starting TLS handshake")
318 self.tls.do_handshake()
319 self.tls.setblocking(False)
320 if self.tls_settings.verify_peer:
321 valid = self.tls_settings.verify_callback(self.tls.getpeercert())
322 if not valid:
323 raise SSLError, "Certificate verification failed"
324 self.socket = self.tls
325 self.state_change("tls connected", self.peer)
326
328 """Default certificate verification callback for TLS connections.
329
330 :Parameters:
331 - `cert`: certificate information, as returned by `ssl.SSLSocket.getpeercert`
332
333 :return: computed verification result."""
334 try:
335 self.__logger.debug("tls_is_certificate_valid(cert = %r)" % (
336 cert,))
337 if not cert:
338 self.__logger.warning("No TLS certificate information received.")
339 return False
340 valid_hostname_found = False
341 if 'subject' in cert:
342 for rdns in cert['subject']:
343 for key, value in rdns:
344 if key == 'commonName' and JID(value) == self.peer:
345 self.__logger.debug(" good commonName: {0}".format(value))
346 valid_hostname_found = True
347 if 'subjectAltName' in cert:
348 for key, value in cert['subjectAltName']:
349 if key == 'DNS' and JID(value) == self.peer:
350 self.__logger.debug(" good subjectAltName({0}): {1}"
351 .format(key, value))
352 valid_hostname_found = True
353 return valid_hostname_found
354 except:
355 self.__logger.exception("Exception caught")
356 raise
357
359 """Get the TLS connection object for the stream.
360
361 :return: `self.tls`"""
362 return self.tls
363
364
365