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 681 2008-12-05 07:18:41Z jajcus $"
26 __docformat__="restructuredtext en"
27
28 import socket
29 import sys
30 import errno
31 import logging
32
33 from pyxmpp.streambase import StreamBase,STREAM_NS
34 from pyxmpp.streambase import FatalStreamError,StreamEncryptionRequired
35 from pyxmpp.exceptions import TLSNegotiationFailed, TLSError, TLSNegotiatedButNotAvailableError
36
37 try:
38 import M2Crypto
39 if M2Crypto.version_info < (0, 16):
40 tls_available = 0
41 else:
42 from M2Crypto import SSL
43 from M2Crypto.SSL import SSLError
44 import M2Crypto.SSL.cb
45 tls_available = 1
46 except ImportError:
47 tls_available = 0
48
49 if not tls_available:
53
54 TLS_NS="urn:ietf:params:xml:ns:xmpp-tls"
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. See M2Crypto documentation for details."""
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 two arguments:
82 'ok' and 'store_context' and return True if a certificate is accepted.
83 The verification callback should call Stream.tls_is_certificate_valid()
84 to check if certificate subject name matches stream peer JID.
85 See M2Crypto documentation for details. If no verify_callback is provided,
86 then default `Stream.tls_default_verify_callback` will be used."""
87 self.require=require
88 self.ctx=ctx
89 self.verify_peer=verify_peer
90 self.cert_file=cert_file
91 self.cacert_file=cacert_file
92 self.key_file=key_file
93 self.verify_callback=verify_callback
94
96 """Mix-in class providing TLS support for an XMPP stream.
97
98 :Ivariables:
99 - `tls`: TLS connection object.
100 """
102 """Initialize TLS support of a Stream object
103
104 :Parameters:
105 - `tls_settings`: settings for StartTLS.
106 :Types:
107 - `tls_settings`: `TLSSettings`
108 """
109 self.tls_settings=tls_settings
110 self.__logger=logging.getLogger("pyxmpp.StreamTLSMixIn")
111
113 """Reset `StreamTLSMixIn` object state making it ready to handle new
114 connections."""
115 self.tls=None
116 self.tls_requested=False
117
119 """Update the <features/> with StartTLS feature.
120
121 [receving entity only]
122
123 :Parameters:
124 - `features`: the <features/> element of the stream.
125 :Types:
126 - `features`: `libxml2.xmlNode`
127
128 :returns: updated <features/> element node.
129 :returntype: `libxml2.xmlNode`"""
130 if self.tls_settings and not self.tls:
131 tls=features.newChild(None,"starttls",None)
132 ns=tls.newNs(TLS_NS,None)
133 tls.setNs(ns)
134 if self.tls_settings.require:
135 tls.newChild(None,"required",None)
136 return features
137
139 """Same as `Stream.write_raw` but assume `self.lock` is acquired."""
140 logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data)
141 try:
142 if self.tls:
143 self.tls.setblocking(True)
144 if self.socket:
145 self.socket.send(data)
146 if self.tls:
147 self.tls.setblocking(False)
148 except (IOError,OSError,socket.error),e:
149 raise FatalStreamError("IO Error: "+str(e))
150 except SSLError,e:
151 raise TLSError("TLS Error: "+str(e))
152
154 """Read data pending on the stream socket and pass it to the parser."""
155 if self.eof:
156 return
157 while self.socket:
158 try:
159 r=self.socket.read()
160 if r is None:
161 return
162 except socket.error,e:
163 if e.args[0]!=errno.EINTR:
164 raise
165 return
166 self._feed_reader(r)
167
169 """Read data pending on the stream socket and pass it to the parser."""
170 self.__logger.debug("StreamTLSMixIn._read(), socket: %r",self.socket)
171 if self.tls:
172 self._read_tls()
173 else:
174 StreamBase._read(self)
175
177 """Same as `Stream.process` but assume `self.lock` is acquired."""
178 try:
179 StreamBase._process(self)
180 except SSLError,e:
181 self.close()
182 raise TLSError("TLS Error: "+str(e))
183
185 """Process incoming stream element. Pass it to _process_tls_node
186 if it is in TLS namespace.
187
188 :raise StreamEncryptionRequired: if encryption is required by current
189 configuration, it is not active and the element is not in the TLS
190 namespace nor in the stream namespace.
191
192 :return: `True` when the node was recognized as TLS element.
193 :returntype: `bool`"""
194 ns_uri=xmlnode.ns().getContent()
195 if ns_uri==STREAM_NS:
196 return False
197 elif ns_uri==TLS_NS:
198 self._process_tls_node(xmlnode)
199 return True
200 if self.tls_settings and self.tls_settings.require and not self.tls:
201 raise StreamEncryptionRequired,"TLS encryption required and not started yet"
202 return False
203
205 """Process incoming StartTLS related element of <stream:features/>.
206
207 [initiating entity only]
208
209 The received features node is available in `self.features`."""
210 ctxt = self.doc_in.xpathNewContext()
211 ctxt.setContextNode(self.features)
212 ctxt.xpathRegisterNs("tls",TLS_NS)
213 try:
214 tls_n=ctxt.xpathEval("tls:starttls")
215 tls_required_n=ctxt.xpathEval("tls:starttls/tls:required")
216 finally:
217 ctxt.xpathFreeContext()
218
219 if not self.tls:
220 if tls_required_n and not self.tls_settings:
221 raise FatalStreamError,"StartTLS support disabled, but required by peer"
222 if self.tls_settings and self.tls_settings.require and not tls_n:
223 raise FatalStreamError,"StartTLS required, but not supported by peer"
224 if self.tls_settings and tls_n:
225 self.__logger.debug("StartTLS negotiated")
226 if not tls_available:
227 raise TLSNegotiatedButNotAvailableError,("StartTLS negotiated, but not available"
228 " (M2Crypto >= 0.16 module required)")
229 if self.initiator:
230 self._request_tls()
231 else:
232 self.__logger.debug("StartTLS not negotiated")
233
235 """Request a TLS-encrypted connection.
236
237 [initiating entity only]"""
238 self.tls_requested=1
239 self.features=None
240 root=self.doc_out.getRootElement()
241 xmlnode=root.newChild(None,"starttls",None)
242 ns=xmlnode.newNs(TLS_NS,None)
243 xmlnode.setNs(ns)
244 self._write_raw(xmlnode.serialize(encoding="UTF-8"))
245 xmlnode.unlinkNode()
246 xmlnode.freeNode()
247
249 """Process stream element in the TLS namespace.
250
251 :Parameters:
252 - `xmlnode`: the XML node received
253 """
254 if not self.tls_settings or not tls_available:
255 self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize()))
256 return False
257 if self.initiator:
258 if xmlnode.name=="failure":
259 raise TLSNegotiationFailed,"Peer failed to initialize TLS connection"
260 elif xmlnode.name!="proceed" or not self.tls_requested:
261 self.__logger.debug("Unexpected TLS node: %r" % (xmlnode.serialize()))
262 return False
263 try:
264 self.tls_requested=0
265 self._make_tls_connection()
266 self.socket=self.tls
267 except SSLError,e:
268 self.tls=None
269 raise TLSError("TLS Error: "+str(e))
270 self.__logger.debug("Restarting XMPP stream")
271 self._restart_stream()
272 return True
273 else:
274 raise FatalStreamError,"TLS not implemented for the receiving side yet"
275
277 """Initiate TLS connection.
278
279 [initiating entity only]"""
280 if not tls_available or not self.tls_settings:
281 raise TLSError,"TLS is not available"
282
283 self.state_change("tls connecting",self.peer)
284 self.__logger.debug("Creating TLS context")
285 if self.tls_settings.ctx:
286 ctx=self.tls_settings.ctx
287 else:
288 ctx=SSL.Context('tlsv1')
289
290 verify_callback = self.tls_settings.verify_callback
291 if not verify_callback:
292 verify_callback = self.tls_default_verify_callback
293
294
295 if self.tls_settings.verify_peer:
296 self.__logger.debug("verify_peer, verify_callback: %r", verify_callback)
297 ctx.set_verify(SSL.verify_peer, 10, verify_callback)
298 else:
299 ctx.set_verify(SSL.verify_none, 10)
300
301 if self.tls_settings.cert_file:
302 ctx.use_certificate_chain_file(self.tls_settings.cert_file)
303 if self.tls_settings.key_file:
304 ctx.use_PrivateKey_file(self.tls_settings.key_file)
305 else:
306 ctx.use_PrivateKey_file(self.tls_settings.cert_file)
307 ctx.check_private_key()
308 if self.tls_settings.cacert_file:
309 try:
310 ctx.load_verify_location(self.tls_settings.cacert_file)
311 except AttributeError:
312 ctx.load_verify_locations(self.tls_settings.cacert_file)
313 self.__logger.debug("Creating TLS connection")
314 self.tls=SSL.Connection(ctx,self.socket)
315 self.socket=None
316 self.__logger.debug("Setting up TLS connection")
317 self.tls.setup_ssl()
318 self.__logger.debug("Setting TLS connect state")
319 self.tls.set_connect_state()
320 self.__logger.debug("Starting TLS handshake")
321 self.tls.connect_ssl()
322 self.state_change("tls connected",self.peer)
323 self.tls.setblocking(0)
324
325
326 try:
327 raise Exception
328 except:
329 pass
330
332 """Check subject name of the certificate and return True when
333 it is valid.
334
335 Only the certificate at depth 0 in the certificate chain (peer
336 certificate) is checked.
337
338 Currently only the Common Name is checked and certificate is considered
339 valid if CN is the same as the peer JID.
340
341 :Parameters:
342 - `store_context`: certificate store context, as passed to the
343 verification callback.
344
345 :returns: verification result. `True` if certificate subject name is valid.
346 """
347 depth = store_context.get_error_depth()
348 if depth > 0:
349 return True
350 cert = store_context.get_current_cert()
351 cn = cert.get_subject().CN
352 if str(cn) != self.peer.as_utf8():
353 return False
354 return True
355
357 """Default certificate verification callback for TLS connections.
358
359 Will reject connection (return `False`) if M2Crypto finds any error
360 or when certificate CommonName doesn't match peer JID.
361
362 TODO: check otherName/idOnXMPP (or what it is called)
363
364 :Parameters:
365 - `ok`: current verification result (as decided by OpenSSL).
366 - `store_context`: certificate store context
367
368 :return: computed verification result."""
369 try:
370 self.__logger.debug("tls_default_verify_callback(ok=%i, store=%r)" % (ok, store_context))
371 from M2Crypto import X509,m2
372
373 depth = store_context.get_error_depth()
374 cert = store_context.get_current_cert()
375 cn = cert.get_subject().CN
376
377 self.__logger.debug(" depth: %i cert CN: %r" % (depth, cn))
378 if ok and not self.tls_is_certificate_valid(store_context):
379 self.__logger.debug(u"Common name does not match peer name (%s != %s)" % (cn, self.peer.as_utf8))
380 return False
381 return ok
382 except:
383 self.__logger.exception("Exception caught")
384 raise
385
387 """Get the TLS connection object for the stream.
388
389 :return: `self.tls`"""
390 return self.tls
391
392
393