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