sslutil: move CA file processing into _hostsettings()
authorGregory Szorc <gregory.szorc@gmail.com>
Sat, 28 May 2016 12:53:33 -0700
changeset 29260 70bc9912d83d
parent 29259 ec247e8595f9
child 29261 1eff545cef52
sslutil: move CA file processing into _hostsettings() The CA file processing code has been moved from _determinecertoptions into _hostsettings(). As part of the move, the logic has been changed slightly and the "cacerts" variable has been renamed to "cafile" to match the argument used by SSLContext.load_verify_locations(). Since _determinecertoptions() no longer contains any meaningful code, it has been removed.
mercurial/sslutil.py
--- a/mercurial/sslutil.py	Sat May 28 11:41:21 2016 -0700
+++ b/mercurial/sslutil.py	Sat May 28 12:53:33 2016 -0700
@@ -114,6 +114,9 @@
     s = {
         # List of 2-tuple of (hash algorithm, hash).
         'certfingerprints': [],
+        # Path to file containing concatenated CA certs. Used by
+        # SSLContext.load_verify_locations().
+        'cafile': None,
         # ssl.CERT_* constant used by SSLContext.verify_mode.
         'verifymode': None,
     }
@@ -132,45 +135,39 @@
     elif ui.insecureconnections:
         s['verifymode'] = ssl.CERT_NONE
 
-    # TODO assert verifymode is not None once we integrate cacert
-    # checking in this function.
+    # Try to hook up CA certificate validation unless something above
+    # makes it not necessary.
+    if s['verifymode'] is None:
+        # Find global certificates file in config.
+        cafile = ui.config('web', 'cacerts')
+
+        if cafile:
+            cafile = util.expandpath(cafile)
+            if not os.path.exists(cafile):
+                raise error.Abort(_('could not find web.cacerts: %s') % cafile)
+        else:
+            # No global CA certs. See if we can load defaults.
+            cafile = _defaultcacerts()
+            if cafile:
+                ui.debug('using %s to enable OS X system CA\n' % cafile)
+
+        s['cafile'] = cafile
+
+        # Require certificate validation if CA certs are being loaded and
+        # verification hasn't been disabled above.
+        if cafile or _canloaddefaultcerts:
+            s['verifymode'] = ssl.CERT_REQUIRED
+        else:
+            # At this point we don't have a fingerprint, aren't being
+            # explicitly insecure, and can't load CA certs. Connecting
+            # at this point is insecure. But we do it for BC reasons.
+            # TODO abort here to make secure by default.
+            s['verifymode'] = ssl.CERT_NONE
+
+    assert s['verifymode'] is not None
 
     return s
 
-def _determinecertoptions(ui, settings):
-    """Determine certificate options for a connections.
-
-    Returns a tuple of (cert_reqs, ca_certs).
-    """
-    if settings['verifymode'] == ssl.CERT_NONE:
-        return ssl.CERT_NONE, None
-
-    cacerts = ui.config('web', 'cacerts')
-
-    # If a value is set in the config, validate against a path and load
-    # and require those certs.
-    if cacerts:
-        cacerts = util.expandpath(cacerts)
-        if not os.path.exists(cacerts):
-            raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
-
-        return ssl.CERT_REQUIRED, cacerts
-
-    # No CAs in config. See if we can load defaults.
-    cacerts = _defaultcacerts()
-
-    # We found an alternate CA bundle to use. Load it.
-    if cacerts:
-        ui.debug('using %s to enable OS X system CA\n' % cacerts)
-        ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
-        return ssl.CERT_REQUIRED, cacerts
-
-    # FUTURE this can disappear once wrapsocket() is secure by default.
-    if _canloaddefaultcerts:
-        return ssl.CERT_REQUIRED, None
-
-    return ssl.CERT_NONE, None
-
 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
     """Add SSL/TLS to a socket.
 
@@ -188,7 +185,6 @@
         raise error.Abort('serverhostname argument is required')
 
     settings = _hostsettings(ui, serverhostname)
-    cert_reqs, ca_certs = _determinecertoptions(ui, settings)
 
     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
     # that both ends support, including TLS protocols. On legacy stacks,
@@ -215,7 +211,7 @@
     sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
 
     # This still works on our fake SSLContext.
-    sslcontext.verify_mode = cert_reqs
+    sslcontext.verify_mode = settings['verifymode']
 
     if certfile is not None:
         def password():
@@ -223,8 +219,8 @@
             return ui.getpass(_('passphrase for %s: ') % f, '')
         sslcontext.load_cert_chain(certfile, keyfile, password)
 
-    if ca_certs is not None:
-        sslcontext.load_verify_locations(cafile=ca_certs)
+    if settings['cafile'] is not None:
+        sslcontext.load_verify_locations(cafile=settings['cafile'])
         caloaded = True
     else:
         # This is a no-op on old Python.