2 ## based on transports_nb.py
4 ## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
5 ## modified by Dimitur Kirov <dkirov@gmail.com>
6 ## modified by Tomas Karasek <tom.to.the.k@gmail.com>
8 ## This program is free software; you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation; either version 2, or (at your option)
13 ## This program is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
19 from plugin
import PlugIn
28 log
= logging
.getLogger('gajim.c.x.tls_nb')
32 PYOPENSSL
= 'PYOPENSSL'
36 #raise ImportError("Manually disabled PyOpenSSL")
40 log
.info("PyOpenSSL loaded")
42 log
.debug("Import of PyOpenSSL failed:", exc_info
=True)
44 # FIXME: Remove these prints before release, replace with a warning dialog.
45 print >> sys
.stderr
, "=" * 79
46 print >> sys
.stderr
, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
47 print >> sys
.stderr
, "=" * 79
49 def gattr(obj
, attr
, default
=None):
51 return getattr(obj
, attr
)
52 except AttributeError:
58 Abstract SSLWrapper base class
63 Generic SSL Error Wrapper
66 def __init__(self
, sock
=None, exc
=None, errno
=None, strerror
=None,
70 errno
= errno
or gattr(exc
, 'errno') or exc
[0]
71 strerror
= strerror
or gattr(exc
, 'strerror') or gattr(exc
, 'args')
72 if not isinstance(strerror
, basestring
):
73 strerror
= repr(strerror
)
83 if self
.exc
is not None:
84 self
.exc_name
= str(self
.exc
.__class
__)
85 self
.exc_args
= gattr(self
.exc
, 'args')
86 self
.exc_str
= str(self
.exc
)
87 self
.exc_repr
= repr(self
.exc
)
90 if isinstance(exc
, OpenSSL
.SSL
.SysCallError
):
91 if self
.exc_args
[0] > 0:
92 errno
= self
.exc_args
[0]
93 strerror
= self
.exc_args
[1]
96 self
.parent
.__init
__(self
, errno
, strerror
)
98 if self
.peer
is None and sock
is not None:
100 ppeer
= self
.sock
.getpeername()
101 if len(ppeer
) == 2 and isinstance(ppeer
[0], basestring
) \
102 and isinstance(ppeer
[1], int):
108 s
= str(self
.__class
__)
110 s
+= " for %s:%d" % self
.peer
111 if self
.errno
is not None:
112 s
+= ": [Errno: %d]" % self
.errno
114 s
+= " (%s)" % self
.strerror
116 s
+= ", Caused by %s" % self
.exc_name
119 s
+= "(%s)" % self
.exc_str
120 else: s
+= "(%s)" % str(self
.exc_args
)
123 def __init__(self
, sslobj
, sock
=None):
126 log
.debug("%s.__init__ called with %s", self
.__class
__, sslobj
)
128 def recv(self
, data
, flags
=None):
130 Receive wrapper for SSL object
132 We can return None out of this function to signal that no data is
133 available right now. Better than an exception, which differs
134 depending on which SSL lib we're using. Unfortunately returning ''
135 can indicate that the socket has been closed, so to be sure, we avoid
136 this by returning None.
138 raise NotImplementedError
140 def send(self
, data
, flags
=None, now
=False):
142 Send wrapper for SSL object
144 raise NotImplementedError
147 class PyOpenSSLWrapper(SSLWrapper
):
149 Wrapper class for PyOpenSSL's recv() and send() methods
152 def __init__(self
, *args
):
153 self
.parent
= SSLWrapper
154 self
.parent
.__init
__(self
, *args
)
156 def is_numtoolarge(self
, e
):
157 ''' Magic methods don't need documentation '''
158 t
= ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
159 return (isinstance(e
.args
, (list, tuple)) and len(e
.args
) == 1 and
160 isinstance(e
.args
[0], (list, tuple)) and len(e
.args
[0]) == 2 and
161 e
.args
[0][0] == e
.args
[0][1] == t
)
163 def recv(self
, bufsize
, flags
=None):
167 retval
= self
.sslobj
.recv(bufsize
)
169 retval
= self
.sslobj
.recv(bufsize
, flags
)
170 except (OpenSSL
.SSL
.WantReadError
, OpenSSL
.SSL
.WantWriteError
), e
:
171 log
.debug("Recv: Want-error: " + repr(e
))
172 except OpenSSL
.SSL
.SysCallError
, e
:
173 log
.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e
),
175 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
176 except OpenSSL
.SSL
.ZeroReturnError
, e
:
177 # end-of-connection raises ZeroReturnError instead of having the
178 # connection's .recv() method return a zero-sized result.
179 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
, -1)
180 except OpenSSL
.SSL
.Error
, e
:
181 if self
.is_numtoolarge(e
):
182 # warn, but ignore this exception
183 log
.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
185 log
.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info
=True)
186 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
189 def send(self
, data
, flags
=None, now
=False):
192 return self
.sslobj
.send(data
)
194 return self
.sslobj
.send(data
, flags
)
195 except (OpenSSL
.SSL
.WantReadError
, OpenSSL
.SSL
.WantWriteError
), e
:
196 #log.debug("Send: " + repr(e))
197 time
.sleep(0.1) # prevent 100% CPU usage
198 except OpenSSL
.SSL
.SysCallError
, e
:
199 log
.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e
),
201 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
202 except OpenSSL
.SSL
.Error
, e
:
203 if self
.is_numtoolarge(e
):
204 # warn, but ignore this exception
205 log
.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
207 log
.error("Send: Caught OpenSSL.SSL.Error:", exc_info
=True)
208 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
212 class StdlibSSLWrapper(SSLWrapper
):
214 Wrapper class for Python socket.ssl read() and write() methods
217 def __init__(self
, *args
):
218 self
.parent
= SSLWrapper
219 self
.parent
.__init
__(self
, *args
)
221 def recv(self
, bufsize
, flags
=None):
222 # we simply ignore flags since ssl object doesn't support it
224 return self
.sslobj
.read(bufsize
)
225 except socket
.sslerror
, e
:
226 log
.debug("Recv: Caught socket.sslerror: " + repr(e
), exc_info
=True)
227 if e
.args
[0] not in (socket
.SSL_ERROR_WANT_READ
, socket
.SSL_ERROR_WANT_WRITE
):
228 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
231 def send(self
, data
, flags
=None, now
=False):
232 # we simply ignore flags since ssl object doesn't support it
234 return self
.sslobj
.write(data
)
235 except socket
.sslerror
, e
:
236 log
.debug("Send: Caught socket.sslerror:", exc_info
=True)
237 if e
.args
[0] not in (socket
.SSL_ERROR_WANT_READ
, socket
.SSL_ERROR_WANT_WRITE
):
238 raise SSLWrapper
.Error(self
.sock
or self
.sslobj
, e
)
242 class NonBlockingTLS(PlugIn
):
244 TLS connection used to encrypts already estabilished tcp connection
246 Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or
250 def __init__(self
, cacerts
, mycerts
):
252 :param cacerts: path to pem file with certificates of known XMPP servers
253 :param mycerts: path to pem file with certificates of user trusted servers
255 PlugIn
.__init
__(self
)
256 self
.cacerts
= cacerts
257 self
.mycerts
= mycerts
259 # from ssl.h (partial extract)
260 ssl_h_bits
= { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
261 "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
262 "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
263 "SSL_CB_ALERT": 0x4000,
264 "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
266 def plugin(self
, owner
):
268 Use to PlugIn TLS into transport and start establishing immediately.
269 Returns True if TLS/SSL was established correctly, otherwise False
271 log
.info('Starting TLS estabilishing')
273 res
= self
._startSSL
()
275 log
.error("PlugIn: while trying _startSSL():", exc_info
=True)
279 def _dumpX509(self
, cert
, stream
=sys
.stderr
):
280 print >> stream
, "Digest (SHA-1):", cert
.digest("sha1")
281 print >> stream
, "Digest (MD5):", cert
.digest("md5")
282 print >> stream
, "Serial #:", cert
.get_serial_number()
283 print >> stream
, "Version:", cert
.get_version()
284 print >> stream
, "Expired:", ("Yes" if cert
.has_expired() else "No")
285 print >> stream
, "Subject:"
286 self
._dumpX
509Name
(cert
.get_subject(), stream
)
287 print >> stream
, "Issuer:"
288 self
._dumpX
509Name
(cert
.get_issuer(), stream
)
289 self
._dumpPKey
(cert
.get_pubkey(), stream
)
291 def _dumpX509Name(self
, name
, stream
=sys
.stderr
):
292 print >> stream
, "X509Name:", str(name
)
294 def _dumpPKey(self
, pkey
, stream
=sys
.stderr
):
295 typedict
= {OpenSSL
.crypto
.TYPE_RSA
: "RSA",
296 OpenSSL
.crypto
.TYPE_DSA
: "DSA"}
297 print >> stream
, "PKey bits:", pkey
.bits()
298 print >> stream
, "PKey type: %s (%d)" % (typedict
.get(pkey
.type(),
299 "Unknown"), pkey
.type())
303 Immediatedly switch socket to TLS mode. Used internally
305 log
.debug("_startSSL called")
308 result
= self
._startSSL
_pyOpenSSL
()
310 result
= self
._startSSL
_stdlib
()
313 log
.debug('Synchronous handshake completed')
318 def _load_cert_file(self
, cert_path
, cert_store
, logg
=True):
319 if not os
.path
.isfile(cert_path
):
324 log
.warning('Unable to open certificate file %s: %s' % \
327 lines
= f
.readlines()
331 if 'BEGIN CERTIFICATE' in line
:
333 elif 'END CERTIFICATE' in line
and begin
> -1:
334 cert
= ''.join(lines
[begin
:i
+2])
336 x509cert
= OpenSSL
.crypto
.load_certificate(
337 OpenSSL
.crypto
.FILETYPE_PEM
, cert
)
338 cert_store
.add_cert(x509cert
)
339 except OpenSSL
.crypto
.Error
, exception_obj
:
341 log
.warning('Unable to load a certificate from file %s: %s' %\
342 (cert_path
, exception_obj
.args
[0][0][2]))
344 log
.warning('Unknown error while loading certificate from file '
349 def _startSSL_pyOpenSSL(self
):
350 log
.debug("_startSSL_pyOpenSSL called")
351 tcpsock
= self
._owner
352 # NonBlockingHTTPBOSH instance has no attribute _owner
353 if hasattr(tcpsock
, '_owner') and tcpsock
._owner
._caller
.client_cert \
354 and os
.path
.exists(tcpsock
._owner
._caller
.client_cert
):
355 conn
= tcpsock
._owner
._caller
356 # FIXME make a checkbox for Client Cert / SSLv23 / TLSv1
357 # If we are going to use a client cert/key pair for authentication,
358 # we choose TLSv1 method.
359 tcpsock
._sslContext
= OpenSSL
.SSL
.Context(OpenSSL
.SSL
.TLSv1_METHOD
)
360 log
.debug('Using client cert and key from %s' % conn
.client_cert
)
362 p12
= OpenSSL
.crypto
.load_pkcs12(open(conn
.client_cert
).read(),
363 conn
.client_cert_passphrase
)
364 except OpenSSL
.crypto
.Error
, exception_obj
:
365 log
.warning('Unable to load client pkcs12 certificate from '
366 'file %s: %s ... Is it a valid PKCS12 cert?' % \
367 (conn
.client_cert
, exception_obj
.args
))
369 log
.warning('Unknown error while loading certificate from file '
370 '%s' % conn
.client_cert
)
372 log
.info('PKCS12 Client cert loaded OK')
374 tcpsock
._sslContext
.use_certificate(p12
.get_certificate())
375 tcpsock
._sslContext
.use_privatekey(p12
.get_privatekey())
376 log
.info('p12 cert and key loaded')
377 except OpenSSL
.crypto
.Error
, exception_obj
:
378 log
.warning('Unable to extract client certificate from '
379 'file %s' % conn
.client_cert
)
380 except Exception, msg
:
381 log
.warning('Unknown error extracting client certificate '
382 'from file %s: %s' % (conn
.client_cert
, msg
))
384 log
.info('client cert and key loaded OK')
386 # See http://docs.python.org/dev/library/ssl.html
387 tcpsock
._sslContext
= OpenSSL
.SSL
.Context(OpenSSL
.SSL
.SSLv23_METHOD
)
388 flags
= OpenSSL
.SSL
.OP_NO_SSLv2
390 flags |
= OpenSSL
.SSL
.OP_NO_TICKET
391 except AttributeError, e
:
392 # py-OpenSSL < 0.9 or old OpenSSL
394 tcpsock
._sslContext
.set_options(flags
)
396 tcpsock
.ssl_errnum
= 0
397 tcpsock
._sslContext
.set_verify(OpenSSL
.SSL
.VERIFY_PEER
,
398 self
._ssl
_verify
_callback
)
400 tcpsock
._sslContext
.load_verify_locations(self
.cacerts
)
402 log
.warning('Unable to load SSL certificates from file %s' % \
403 os
.path
.abspath(self
.cacerts
))
404 store
= tcpsock
._sslContext
.get_cert_store()
405 self
._load
_cert
_file
(self
.mycerts
, store
)
406 if os
.path
.isdir('/etc/ssl/certs'):
407 for f
in os
.listdir('/etc/ssl/certs'):
408 # We don't logg because there is a lot a duplicated certs in this
410 self
._load
_cert
_file
(os
.path
.join('/etc/ssl/certs', f
), store
,
413 tcpsock
._sslObj
= OpenSSL
.SSL
.Connection(tcpsock
._sslContext
,
415 tcpsock
._sslObj
.set_connect_state() # set to client mode
416 wrapper
= PyOpenSSLWrapper(tcpsock
._sslObj
)
417 tcpsock
._recv
= wrapper
.recv
418 tcpsock
._send
= wrapper
.send
420 log
.debug("Initiating handshake...")
421 tcpsock
._sslObj
.setblocking(True)
423 tcpsock
._sslObj
.do_handshake()
425 log
.error('Error while TLS handshake: ', exc_info
=True)
427 tcpsock
._sslObj
.setblocking(False)
428 self
._owner
.ssl_lib
= PYOPENSSL
431 def _startSSL_stdlib(self
):
432 log
.debug("_startSSL_stdlib called")
435 tcpsock
._sock
.setblocking(True)
436 tcpsock
._sslObj
= socket
.ssl(tcpsock
._sock
, None, None)
437 tcpsock
._sock
.setblocking(False)
438 tcpsock
._sslIssuer
= tcpsock
._sslObj
.issuer()
439 tcpsock
._sslServer
= tcpsock
._sslObj
.server()
440 wrapper
= StdlibSSLWrapper(tcpsock
._sslObj
, tcpsock
._sock
)
441 tcpsock
._recv
= wrapper
.recv
442 tcpsock
._send
= wrapper
.send
444 log
.error("Exception caught in _startSSL_stdlib:", exc_info
=True)
446 self
._owner
.ssl_lib
= PYSTDLIB
449 def _ssl_verify_callback(self
, sslconn
, cert
, errnum
, depth
, ok
):
450 # Exceptions can't propagate up through this callback, so print them here.
452 self
._owner
.ssl_fingerprint_sha1
= cert
.digest('sha1')
453 self
._owner
.ssl_certificate
= cert
456 self
._owner
.ssl_errnum
= errnum
457 self
._owner
.ssl_cert_pem
= OpenSSL
.crypto
.dump_certificate(
458 OpenSSL
.crypto
.FILETYPE_PEM
, cert
)
461 log
.error("Exception caught in _ssl_info_callback:", exc_info
=True)
462 # Make sure something is printed, even if log is disabled.
463 traceback
.print_exc()