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