Package pyxmpp :: Module streamtls
[hide private]

Source Code for Module pyxmpp.streamtls

  1  # 
  2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 16  # 
 17  # pylint: disable-msg=W0201 
 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   
47 -def _count_args(callable):
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
57 -class TLSSettings:
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
103 -class StreamTLSMixIn:
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
120 - def _reset_tls(self):
121 """Reset `StreamTLSMixIn` object state making it ready to handle new 122 connections.""" 123 self.tls = None 124 self.tls_requested = False
125
126 - def _make_stream_tls_features(self, features):
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
146 - def _write_raw(self,data):
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
163 - def _read_tls(self):
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() # .recv() blocks in python 2.6.4 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
182 - def _read(self):
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
190 - def _process(self):
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
198 - def _process_node_tls(self,xmlnode):
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
218 - def _handle_tls_features(self):
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
245 - def _request_tls(self):
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
259 - def _process_tls_node(self,xmlnode):
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
287 - def _make_tls_connection(self):
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
327 - def tls_is_certificate_valid(self, cert):
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
358 - def get_tls_connection(self):
359 """Get the TLS connection object for the stream. 360 361 :return: `self.tls`""" 362 return self.tls
363 364 # vi: sts=4 et sw=4 365