diff mercurial/sslutil.py @ 29334:ecc9b788fd69

sslutil: per-host config option to define certificates Recent work has introduced the [hostsecurity] config section for defining per-host security settings. This patch builds on top of this foundation and implements the ability to define a per-host path to a file containing certificates used for verifying the server certificate. It is logically a per-host web.cacerts setting. This patch also introduces a warning when both per-host certificates and fingerprints are defined. These are mutually exclusive for host verification and I think the user should be alerted when security settings are ambiguous because, well, security is important. Tests validating the new behavior have been added. I decided against putting "ca" in the option name because a non-CA certificate can be specified and used to validate the server certificate (commonly this will be the exact public certificate used by the server). It's worth noting that the underlying Python API used is load_verify_locations(cafile=X) and it calls into OpenSSL's SSL_CTX_load_verify_locations(). Even OpenSSL's documentation seems to omit that the file can contain a non-CA certificate if it matches the server's certificate exactly. I thought a CA certificate was a special kind of x509 certificate. Perhaps I'm wrong and any x509 certificate can be used as a CA certificate [as far as OpenSSL is concerned]. In any case, I thought it best to drop "ca" from the name because this reflects reality.
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 07 Jun 2016 20:29:54 -0700
parents 1b3a0b0c414f
children 0d83ad967bf8
line wrap: on
line diff
--- a/mercurial/sslutil.py	Fri May 27 23:18:38 2016 +0900
+++ b/mercurial/sslutil.py	Tue Jun 07 20:29:54 2016 -0700
@@ -162,23 +162,42 @@
     if ui.configbool('devel', 'disableloaddefaultcerts'):
         s['allowloaddefaultcerts'] = False
 
+    # If both fingerprints and a per-host ca file are specified, issue a warning
+    # because users should not be surprised about what security is or isn't
+    # being performed.
+    cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
+    if s['certfingerprints'] and cafile:
+        ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
+                  'fingerprints defined; using host fingerprints for '
+                  'verification)\n') % hostname)
+
     # 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')
-
+        # Look at per-host ca file first.
         if cafile:
             cafile = util.expandpath(cafile)
             if not os.path.exists(cafile):
-                raise error.Abort(_('could not find web.cacerts: %s') % cafile)
+                raise error.Abort(_('path specified by %s does not exist: %s') %
+                                  ('hostsecurity.%s:verifycertsfile' % hostname,
+                                   cafile))
+            s['cafile'] = cafile
         else:
-            # No global CA certs. See if we can load defaults.
-            cafile = _defaultcacerts()
+            # Find global certificates file in config.
+            cafile = ui.config('web', 'cacerts')
+
             if cafile:
-                ui.debug('using %s to enable OS X system CA\n' % 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
+            s['cafile'] = cafile
 
         # Require certificate validation if CA certs are being loaded and
         # verification hasn't been disabled above.