"Fossies" - the Fresh Open Source Software Archive

Member "chandler-1.0.3/chandler/parcels/osaf/framework/certstore/ssl.py" (14 Apr 2009, 20235 Bytes) of archive /windows/misc/Chandler_src_1.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #   Copyright (c) 2005-2007 Open Source Applications Foundation
    2 #
    3 #   Licensed under the Apache License, Version 2.0 (the "License");
    4 #   you may not use this file except in compliance with the License.
    5 #   You may obtain a copy of the License at
    6 #
    7 #       http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #   Unless required by applicable law or agreed to in writing, software
   10 #   distributed under the License is distributed on an "AS IS" BASIS,
   11 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12 #   See the License for the specific language governing permissions and
   13 #   limitations under the License.
   14 
   15 """
   16 SSL/TLS.
   17 
   18 @var  trusted_until_shutdown_site_certs:         Certificates that should be
   19                                                  trusted until program exit.
   20                                                  The certificates are in PEM
   21                                                  format (str).
   22 @type trusted_until_shutdown_site_certs:         list
   23 @var  trusted_until_shutdown_invalid_site_certs: Ignore SSL errors with these
   24                                                  certificates until program
   25                                                  exit. The key is the
   26                                                  certificate in PEM format (str)
   27                                                  and the value is a list of the
   28                                                  errors to ignore.
   29 @type trusted_until_shutdown_invalid_site_certs: dict
   30 @var  unknown_issuer:                            Certificate verification error
   31                                                  codes in this list signal that
   32                                                  the certificate has been
   33                                                  issued by unknown authority
   34                                                  and that we should probably
   35                                                  ask the user if they would
   36                                                  like to trust this certificate.
   37 @type unknown_issuer:                            list
   38 """
   39 
   40 from __future__ import with_statement
   41 import logging
   42 import threading
   43 
   44 import wx
   45 import M2Crypto
   46 import M2Crypto.m2 as m2
   47 import M2Crypto.SSL as SSL
   48 import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
   49 import M2Crypto.SSL.Checker as Checker
   50 import M2Crypto.X509 as X509
   51 import twisted
   52 import twisted.protocols.policies as policies
   53 from i18n import ChandlerMessageFactory as _
   54 
   55 from application import schema, Utility
   56 from osaf.framework.certstore import constants, utils
   57 from osaf.framework.twisted import runInUIThread
   58 from osaf import messages
   59 from chandlerdb.persistence.RepositoryView import otherViewWins
   60 
   61 
   62 __all__ = ['loadCertificatesToContext', 'SSLContextError', 'getContext',
   63            'connectSSL', 'connectTCP', 'unknown_issuer',
   64            'trusted_until_shutdown_site_certs',
   65            'trusted_until_shutdown_invalid_site_certs',
   66            'askTrustServerCertificate',
   67            'askIgnoreSSLError', 'certificateCache']
   68 
   69 log = logging.getLogger(__name__)
   70 
   71 certificateCache = []
   72 
   73 _sslLock = threading.Lock()
   74 
   75 def _getSSLView(repo):
   76     """
   77     Return the SSL view for this repository, creating one if necessary.
   78     This function is internal to this module. Also, it is advisable to take
   79     out _sslLock when accessing or changing the returned view, or any of
   80     its Items.
   81     
   82     @param repo: The repository whose SSL view we want to return
   83     @type repo: L{chandlerdb.persistence.DBRepository.DBRepository}
   84     
   85     @return: A repository view with name 'SSL'
   86     @rtype: L{chandlerdb.persistence.DBRepositoryView.DBRepositoryView}
   87     """
   88     with _sslLock:
   89         for view in repo.views:
   90             if view.name == 'SSL':
   91                 return view
   92         
   93         return repo.createView('SSL', pruneSize=400, notify=False,
   94                                mergeFn=otherViewWins)
   95 
   96 
   97 def loadCertificatesToContext(repView, ctx):
   98     """
   99     Add certificates to SSL Context.
  100     
  101     @param repView: repository view
  102     @param ctx:     M2Crypto.SSL.Context
  103     """
  104     sslView = _getSSLView(repView.repository)
  105     store = ctx.get_cert_store()
  106     
  107     with _sslLock:
  108         if certificateCache:
  109             for x509 in certificateCache:
  110                 store.add_x509(x509)
  111         else:
  112             sslView.refresh()
  113             q = schema.ns('osaf.framework.certstore', sslView).sslCertificateQuery
  114             for cert in q:
  115                 x509 = cert.asX509()
  116                 store.add_x509(x509)
  117                 certificateCache.append(x509)
  118 
  119 class SSLContextError(utils.CertificateException):
  120     """
  121     Raised when an SSL Context could not be created. Currently happens
  122     only when cipher list cannot be set.
  123     """
  124 
  125 
  126 def getContext(repositoryView, protocol='sslv23', verify=True,
  127                verifyCallback=None):
  128     """
  129     Get an SSL context. You should use this method to get a context
  130     in Chandler rather than creating them directly.
  131 
  132     @param repositoryView: Repository View from which to get certificates.
  133     @type repositoryView:  RepositoryView
  134     @param protocol:       An SSL protocol version string.
  135     @type protocol:        str
  136     @param verify:         Verify SSL/TLS connection. True by default.
  137     @type verify:          boolean
  138     @param verifyCallback: Function to call for certificate verification.
  139     @type verifyCallback:  Callback function
  140     """
  141     ctx = SSL.Context(protocol)
  142 
  143     # XXX Sometimes we might want to accept any cert, and only use
  144     #     sslPostConnectionCheck. Need an extra arg in calling func.
  145 
  146     # XXX We might want to accept any cert, and store it among with info
  147     #     who the other user is, and if at any time in the future these
  148     #     don't match, alert the user (vulnerable in first connection)
  149     #     Need to expand API.
  150 
  151     if verify:
  152         loadCertificatesToContext(repositoryView, ctx)
  153         
  154         # XXX TODO In some cases, for example when connecting directly
  155         #          to P2P partner, we want to authenticate mutually using
  156         #          certificates so we need to load the "me" certificate.
  157         #          Do not do this for all contexts, however, because it can
  158         #          leak our identity when connecting to random SSL servers
  159         #          out there.
  160         #ctx.load_cert_chain('client.pem')
  161 
  162         ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
  163                        9, verifyCallback)
  164 
  165     # Do not allow SSLv2 because it has security issues
  166     ctx.set_options(SSL.op_all | SSL.op_no_sslv2)
  167 
  168     # Disable unsafe ciphers, and order the remaining so that strongest
  169     # comes first, which can help peers select the strongest common
  170     # cipher.
  171     if ctx.set_cipher_list('ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH') != 1:
  172         log.error('Could not set cipher list')
  173         raise SSLContextError(_(u'Could not set cipher list'))
  174 
  175     return ctx
  176 
  177 
  178 class ContextFactory(object):
  179     """
  180     This is internal class to this module and should not be used outside.
  181     """
  182     def __init__(self, repositoryView, protocol='sslv23', verify=True,
  183                  verifyCallback=None):
  184         self.repositoryView = repositoryView
  185         self.protocol = protocol
  186         self.verify = verify
  187         self.verifyCallback = verifyCallback
  188 
  189     def getContext(self):
  190         return getContext(self.repositoryView, self.protocol, self.verify,
  191                           self.verifyCallback)
  192 
  193 trusted_until_shutdown_site_certs = []
  194 trusted_until_shutdown_invalid_site_certs = {}
  195 
  196 # These errors will happen when
  197 # the certificate can't be verified because we don't have
  198 # the issuing certificate.
  199 unknown_issuer = [m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
  200                   m2.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
  201                   m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
  202                   m2.X509_V_ERR_CERT_UNTRUSTED,
  203                   m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE]
  204 
  205 
  206 class TwistedProtocolWrapper(wrapper.TLSProtocolWrapper):
  207     """
  208     This is internal class to this module and should not be used outside.
  209     """
  210     def __init__(self, repositoryView, protocol, factory, wrappedProtocol, 
  211                  startPassThrough, client):
  212 
  213         if __debug__:
  214             log.debug('TwistedProtocolWrapper.__init__')
  215 
  216         repositoryView = _getSSLView(repositoryView.repository)
  217             
  218         self.contextFactory = ContextFactory(repositoryView, protocol, 
  219                                             verifyCallback=self.verifyCallback)
  220         wrapper.TLSProtocolWrapper.__init__(self, factory, wrappedProtocol, 
  221                                             startPassThrough, client,
  222                                             self.contextFactory,
  223                                             self.postConnectionVerify)
  224         self.repositoryView = repositoryView
  225         # List for now, even though only first might be needed:
  226         self.untrustedCertificates = []
  227 
  228     def verifyCallback(self, ok, store):
  229         # Returning 1 means any error is ignored and SSL checking continues
  230         if __debug__:
  231             log.debug('TwistedProtocolWrapper.verifyCallback')
  232         global trusted_until_shutdown_site_certs, \
  233                trusted_until_shutdown_invalid_site_certs, \
  234                unknown_issuer
  235                         
  236         if not ok:
  237             try:
  238                 with _sslLock:
  239                     err = store.get_error()
  240         
  241                     # This would actually give us the certificate we are currently
  242                     # checking: 
  243                     #
  244                     #pem = store.get_current_cert().as_pem()
  245                     #
  246                     # However, we want to ask the user what to do about
  247                     # the actual server certificate, not the other certs in the
  248                     # chain. While this makes it a little annoying for experts
  249                     # (we say there was a problem in the chain, and show only the
  250                     # server cert which itself may have no problems), it makes it
  251                     # slightly safer because if the users decide to ignore the
  252                     # error, they ignore only the server certificate errors. If
  253                     # we showed the CA certificate, the user could accidentally
  254                     # ok any certificates issued by the bad CA. Now they will
  255                     # at least be faced with the warning dialog for each actual
  256                     # server certificate.
  257                     stack = store.get1_chain()
  258                     x509 = stack[0]
  259                     pem = x509.as_pem()
  260         
  261                     # Check temporarily trusted certificates
  262                     if err not in unknown_issuer:
  263                         # Check if we are temporarily ignoring errors with this
  264                         # certificate.
  265                         acceptedErrList = trusted_until_shutdown_invalid_site_certs.get(pem)
  266                         if acceptedErrList is not None and err in acceptedErrList:
  267                             if __debug__:
  268                                 log.debug('Ignoring certificate error %d' %err)
  269                             return 1
  270                         self.untrustedCertificates.append(pem)
  271                         return ok
  272         
  273                     if pem in trusted_until_shutdown_site_certs:
  274                         if __debug__:
  275                             log.debug('Found temporarily trusted site cert')
  276                         return 1
  277         
  278                     # Check permanently trusted certificates
  279                     self.repositoryView.refresh()
  280                     q = schema.ns('osaf.framework.certstore', 
  281                                   self.repositoryView).sslTrustedServerCertificatesQuery
  282                     for cert in q:
  283                         if cert.pemAsString() == pem:
  284                             if __debug__:
  285                                 log.debug('Found permanently trusted site cert')
  286                             return 1
  287         
  288                     self.untrustedCertificates.append(pem)
  289             except: # This is ok, we MUST return a value and not raise
  290                 log.exception('SSL verifyCallback raised exception')
  291         if __debug__:
  292             log.debug('Returning %d' % ok)
  293         return ok
  294 
  295     def dataReceived(self, data):
  296         if __debug__:
  297             log.debug('TwistedProtocolWrapper.dataReceived')
  298 
  299         utils.entropyInitialized = True
  300         try:
  301             wrapper.TLSProtocolWrapper.dataReceived(self, data)
  302         except M2Crypto.BIO.BIOError, e:
  303             if self.isClient:
  304                 host = self.transport.addr[0]
  305             else:
  306                 host = self.transport.getPeer().host
  307 
  308             if e.args[1] == 'certificate verify failed':
  309                 raise Utility.CertificateVerificationError(host,
  310                                                            e.args[0], e.args[1],
  311                                                            self.untrustedCertificates)
  312             raise
  313 
  314 
  315     def postConnectionVerify(self, peerX509, expectedHost):
  316         #Do a post connection check on an SSL connection. This is done just
  317         #after the SSL connection has been established, but before exchanging
  318         #any real application data like username and password.
  319         #
  320         #This implementation checks to make sure that the certificate that the
  321         #peer presented was issued for the host we tried to connect to, or in
  322         #other words, make sure that we are talking to the server we think we
  323         #should be talking to.
  324         #
  325         # TODO: We should report ALL errors from this post connection check
  326         #       so that users will only get one dialog, even if there are
  327         #       several errors. Obviously we need to record all errors in
  328         #       verifyCallback first.
  329         check = Checker.Checker()
  330         try:
  331             return check(peerX509, expectedHost)
  332         except Checker.WrongHost, e:
  333             e.pem = peerX509.as_pem()
  334     
  335             acceptedErrList = trusted_until_shutdown_invalid_site_certs.get(e.pem)
  336             err = messages.SSL_HOST_MISMATCH % {'actualHost': e.actualHost}
  337             if acceptedErrList is not None and err in acceptedErrList:
  338                 if __debug__:
  339                     log.debug('Ignoring post connection error %s' % err)
  340                 return 1
  341     
  342             raise e
  343 
  344 
  345 def connectSSL(host, port, factory, repositoryView, 
  346                protocol='sslv23',
  347                timeout=30,
  348                bindAddress=None,
  349                reactor=twisted.internet.reactor):
  350     """
  351     A convenience function to start an SSL/TLS connection using Twisted.
  352     
  353     See IReactorSSL interface in Twisted. 
  354     """
  355     if __debug__:
  356         log.debug('connectSSL(host=%s, port=%d)' %(host, port))
  357 
  358     wrappingFactory = policies.WrappingFactory(factory)
  359     wrappingFactory.protocol = lambda factory, wrappedProtocol: \
  360         TwistedProtocolWrapper(repositoryView,
  361                                protocol,
  362                                factory,
  363                                wrappedProtocol,
  364                                startPassThrough=0,
  365                                client=1)
  366     return reactor.connectTCP(host, port, wrappingFactory, timeout,
  367                               bindAddress)
  368     
  369 
  370 def connectTCP(host, port, factory, repositoryView, 
  371                protocol='tlsv1',
  372                timeout=30,
  373                bindAddress=None,
  374                reactor=twisted.internet.reactor):
  375     """
  376     A convenience function to start a TCP connection using Twisted.
  377     
  378     NOTE: You must call startTLS(ctx) to go into SSL/TLS mode.
  379     
  380     See IReactorSSL interface in Twisted. 
  381     """
  382     if __debug__:
  383         log.debug('connectTCP(host=%s, port=%d)' %(host, port))
  384 
  385     wrappingFactory = policies.WrappingFactory(factory)
  386     wrappingFactory.protocol = lambda factory, wrappedProtocol: \
  387         TwistedProtocolWrapper(repositoryView,
  388                                protocol,
  389                                factory,
  390                                wrappedProtocol,
  391                                startPassThrough=1,
  392                                client=1)
  393     return reactor.connectTCP(host, port, wrappingFactory, timeout,
  394                               bindAddress)    
  395 
  396 @runInUIThread
  397 def askTrustServerCertificate(host, pem, reconnect):
  398     """
  399     Ask user if they would like to trust the certificate that was returned by
  400     the server. This will only happen if the certificate is not already
  401     trusted, either by trust chain or explicitly.
  402 
  403     @note: If you want to reconnect on background thread, pass in a dummy
  404            reconnect and reconnect manually after receiving True.
  405     
  406     @param host:      The host we think we are connected with.
  407     @param pem:       The certificate in PEM format.
  408     @param reconnect: The reconnect callback that will be called if the
  409                       user chooses to trust the certificate.
  410     @return:          True if user chose to trust, False otherwise.
  411     """
  412     from osaf.framework.certstore import dialogs, certificate
  413     global trusted_until_shutdown_site_certs
  414 
  415     repositoryView = wx.GetApp().UIRepositoryView
  416     x509 = X509.load_cert_string(pem)
  417     untrustedCertificate = certificate.findCertificate(repositoryView, pem)
  418     dlg = dialogs.TrustServerCertificateDialog(wx.GetApp().mainFrame,
  419                                                x509,
  420                                                host,
  421                                                untrustedCertificate)
  422     try:
  423         if dlg.ShowModal() == wx.ID_OK:
  424             selection = dlg.GetSelection()
  425 
  426             if selection == 0:
  427                 trusted_until_shutdown_site_certs += [pem]
  428             else:
  429                 if untrustedCertificate is not None:
  430                     untrustedCertificate.trust |= constants.TRUST_AUTHENTICITY
  431                 else:
  432                     fingerprint = utils.fingerprint(x509)
  433                     certificate.importCertificate(x509, fingerprint,
  434                                        constants.TRUST_AUTHENTICITY,
  435                                        repositoryView)
  436                 # In either case here (a known, untrusted cert, or a
  437                 # completely untrusted cert), we have made a change
  438                 # and we need to commit so other views can see it.
  439                 repositoryView.commit()
  440             
  441             reconnect()
  442 
  443             return True
  444     finally:
  445         dlg.Destroy()
  446 
  447     return False
  448 
  449 
  450 @runInUIThread
  451 def askIgnoreSSLError(host, pem, err, reconnect):
  452     """
  453     Ask user if they would like to ignore an error with the SSL connection,
  454     and if so, reconnect automatically.
  455     
  456     Strictly speaking we should not ask and just fail. In practice that
  457     wouldn't be very helpful because there are lots of misconfigured servers
  458     out there.
  459     
  460     @note: If you want to reconnect on background thread, pass in a dummy
  461            reconnect and reconnect manually after receiving True.
  462     
  463     @param host:      The host we think we are connected with.
  464     @param pem:       The certificate with which we noticed the error (the
  465                       error could be in the certificate itself, or it could be
  466                       a mismatch between the certificate and the server).
  467     @param err:       Error.
  468     @param reconnect: The reconnect callback that will be called if the used
  469                       chooses to ignore the error.
  470     @return:          True if user chose to ignore, False otherwise.
  471     """
  472     from osaf.framework.certstore import dialogs
  473     x509 = X509.load_cert_string(pem)
  474     dlg = dialogs.IgnoreSSLErrorDialog(wx.GetApp().mainFrame,
  475                                        x509,
  476                                        host,
  477                                        err)
  478     try:
  479         if dlg.ShowModal() == wx.ID_OK:
  480             acceptedErrList = trusted_until_shutdown_invalid_site_certs.setdefault(pem, [])
  481             acceptedErrList.append(err)
  482             reconnect()
  483             
  484             return True
  485     finally:
  486         dlg.Destroy()
  487 
  488     return False