mercurial/sslutil.py
changeset 29260 70bc9912d83d
parent 29259 ec247e8595f9
child 29262 dfc4f08aa160
equal deleted inserted replaced
29259:ec247e8595f9 29260:70bc9912d83d
   112     Returns a dict of settings relevant to that hostname.
   112     Returns a dict of settings relevant to that hostname.
   113     """
   113     """
   114     s = {
   114     s = {
   115         # List of 2-tuple of (hash algorithm, hash).
   115         # List of 2-tuple of (hash algorithm, hash).
   116         'certfingerprints': [],
   116         'certfingerprints': [],
       
   117         # Path to file containing concatenated CA certs. Used by
       
   118         # SSLContext.load_verify_locations().
       
   119         'cafile': None,
   117         # ssl.CERT_* constant used by SSLContext.verify_mode.
   120         # ssl.CERT_* constant used by SSLContext.verify_mode.
   118         'verifymode': None,
   121         'verifymode': None,
   119     }
   122     }
   120 
   123 
   121     # Fingerprints from [hostfingerprints] are always SHA-1.
   124     # Fingerprints from [hostfingerprints] are always SHA-1.
   130 
   133 
   131     # If --insecure is used, don't take CAs into consideration.
   134     # If --insecure is used, don't take CAs into consideration.
   132     elif ui.insecureconnections:
   135     elif ui.insecureconnections:
   133         s['verifymode'] = ssl.CERT_NONE
   136         s['verifymode'] = ssl.CERT_NONE
   134 
   137 
   135     # TODO assert verifymode is not None once we integrate cacert
   138     # Try to hook up CA certificate validation unless something above
   136     # checking in this function.
   139     # makes it not necessary.
       
   140     if s['verifymode'] is None:
       
   141         # Find global certificates file in config.
       
   142         cafile = ui.config('web', 'cacerts')
       
   143 
       
   144         if cafile:
       
   145             cafile = util.expandpath(cafile)
       
   146             if not os.path.exists(cafile):
       
   147                 raise error.Abort(_('could not find web.cacerts: %s') % cafile)
       
   148         else:
       
   149             # No global CA certs. See if we can load defaults.
       
   150             cafile = _defaultcacerts()
       
   151             if cafile:
       
   152                 ui.debug('using %s to enable OS X system CA\n' % cafile)
       
   153 
       
   154         s['cafile'] = cafile
       
   155 
       
   156         # Require certificate validation if CA certs are being loaded and
       
   157         # verification hasn't been disabled above.
       
   158         if cafile or _canloaddefaultcerts:
       
   159             s['verifymode'] = ssl.CERT_REQUIRED
       
   160         else:
       
   161             # At this point we don't have a fingerprint, aren't being
       
   162             # explicitly insecure, and can't load CA certs. Connecting
       
   163             # at this point is insecure. But we do it for BC reasons.
       
   164             # TODO abort here to make secure by default.
       
   165             s['verifymode'] = ssl.CERT_NONE
       
   166 
       
   167     assert s['verifymode'] is not None
   137 
   168 
   138     return s
   169     return s
   139 
       
   140 def _determinecertoptions(ui, settings):
       
   141     """Determine certificate options for a connections.
       
   142 
       
   143     Returns a tuple of (cert_reqs, ca_certs).
       
   144     """
       
   145     if settings['verifymode'] == ssl.CERT_NONE:
       
   146         return ssl.CERT_NONE, None
       
   147 
       
   148     cacerts = ui.config('web', 'cacerts')
       
   149 
       
   150     # If a value is set in the config, validate against a path and load
       
   151     # and require those certs.
       
   152     if cacerts:
       
   153         cacerts = util.expandpath(cacerts)
       
   154         if not os.path.exists(cacerts):
       
   155             raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
       
   156 
       
   157         return ssl.CERT_REQUIRED, cacerts
       
   158 
       
   159     # No CAs in config. See if we can load defaults.
       
   160     cacerts = _defaultcacerts()
       
   161 
       
   162     # We found an alternate CA bundle to use. Load it.
       
   163     if cacerts:
       
   164         ui.debug('using %s to enable OS X system CA\n' % cacerts)
       
   165         ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
       
   166         return ssl.CERT_REQUIRED, cacerts
       
   167 
       
   168     # FUTURE this can disappear once wrapsocket() is secure by default.
       
   169     if _canloaddefaultcerts:
       
   170         return ssl.CERT_REQUIRED, None
       
   171 
       
   172     return ssl.CERT_NONE, None
       
   173 
   170 
   174 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
   171 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
   175     """Add SSL/TLS to a socket.
   172     """Add SSL/TLS to a socket.
   176 
   173 
   177     This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
   174     This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
   186     """
   183     """
   187     if not serverhostname:
   184     if not serverhostname:
   188         raise error.Abort('serverhostname argument is required')
   185         raise error.Abort('serverhostname argument is required')
   189 
   186 
   190     settings = _hostsettings(ui, serverhostname)
   187     settings = _hostsettings(ui, serverhostname)
   191     cert_reqs, ca_certs = _determinecertoptions(ui, settings)
       
   192 
   188 
   193     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
   189     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
   194     # that both ends support, including TLS protocols. On legacy stacks,
   190     # that both ends support, including TLS protocols. On legacy stacks,
   195     # the highest it likely goes in TLS 1.0. On modern stacks, it can
   191     # the highest it likely goes in TLS 1.0. On modern stacks, it can
   196     # support TLS 1.2.
   192     # support TLS 1.2.
   213 
   209 
   214     # This is a no-op on old Python.
   210     # This is a no-op on old Python.
   215     sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
   211     sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
   216 
   212 
   217     # This still works on our fake SSLContext.
   213     # This still works on our fake SSLContext.
   218     sslcontext.verify_mode = cert_reqs
   214     sslcontext.verify_mode = settings['verifymode']
   219 
   215 
   220     if certfile is not None:
   216     if certfile is not None:
   221         def password():
   217         def password():
   222             f = keyfile or certfile
   218             f = keyfile or certfile
   223             return ui.getpass(_('passphrase for %s: ') % f, '')
   219             return ui.getpass(_('passphrase for %s: ') % f, '')
   224         sslcontext.load_cert_chain(certfile, keyfile, password)
   220         sslcontext.load_cert_chain(certfile, keyfile, password)
   225 
   221 
   226     if ca_certs is not None:
   222     if settings['cafile'] is not None:
   227         sslcontext.load_verify_locations(cafile=ca_certs)
   223         sslcontext.load_verify_locations(cafile=settings['cafile'])
   228         caloaded = True
   224         caloaded = True
   229     else:
   225     else:
   230         # This is a no-op on old Python.
   226         # This is a no-op on old Python.
   231         sslcontext.load_default_certs()
   227         sslcontext.load_default_certs()
   232         caloaded = _canloaddefaultcerts
   228         caloaded = _canloaddefaultcerts