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  __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   
46 -def _count_args(callable):
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
56 -class TLSSettings:
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
102 -class StreamTLSMixIn:
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
119 - def _reset_tls(self):
120 """Reset `StreamTLSMixIn` object state making it ready to handle new 121 connections.""" 122 self.tls = None 123 self.tls_requested = False
124
125 - def _make_stream_tls_features(self, features):
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
145 - def _write_raw(self,data):
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
164 - def _read_tls(self):
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() # .recv() blocks in python 2.6.4 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
183 - def _read(self):
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
191 - def _process(self):
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
199 - def _process_node_tls(self,xmlnode):
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
219 - def _handle_tls_features(self):
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
246 - def _request_tls(self):
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
260 - def _process_tls_node(self,xmlnode):
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
288 - def _make_tls_connection(self):
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
328 - def tls_is_certificate_valid(self, cert):
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
359 - def get_tls_connection(self):
360 """Get the TLS connection object for the stream. 361 362 :return: `self.tls`""" 363 return self.tls
364 365 # vi: sts=4 et sw=4 366