[PATCH 2 of 2] sslutil: try to find CA certficates in well-known locations

Gregory Szorc gregory.szorc at gmail.com
Thu Jul 7 00:19:37 EDT 2016


# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1467864960 25200
#      Wed Jul 06 21:16:00 2016 -0700
# Node ID af8b4f956c0599ebf3749fe9e7740bc35e4f02fe
# Parent  93723841473c4aa7bb794144e00a8377198c79f3
sslutil: try to find CA certficates in well-known locations

Many Linux distros and other Nixen have CA certificates in well-defined
locations. Rather than potentially fail to load any CA certificates at
all (which will always result in a certificate verification failure),
we scan for paths to known CA certificate files and load one if seen.
Because a proper Mercurial install will have the path to the CA
certificate file defined at install time, we print a warning that
the install isn't proper and provide a URL with instructions to
correct things.

We only perform path-based fallback on Pythons that don't know
how to call into OpenSSL to load the default verify locations. This
is because we trust that Python/OpenSSL is properly configured
and knows better than Mercurial. So this new code effectively only
runs on Python <2.7.9 (technically Pythons without the modern ssl
module).

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -425,22 +425,32 @@ def _plainapplepython():
       cacerts file
     """
     if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
         return False
     exe = os.path.realpath(sys.executable).lower()
     return (exe.startswith('/usr/bin/python') or
             exe.startswith('/system/library/frameworks/python.framework/'))
 
+_systemcacertpaths = [
+    # RHEL, CentOS, and Fedora
+    '/etc/pki/tls/certs/ca-bundle.trust.crt',
+    # Debian, Ubuntu, Gentoo
+    '/etc/ssl/certs/ca-certificates.crt',
+]
+
 def _defaultcacerts(ui):
     """return path to default CA certificates or None.
 
     It is assumed this function is called when the returned certificates
     file will actually be used to validate connections. Therefore this
     function may print warnings or debug messages assuming this usage.
+
+    We don't print a message when the Python is able to load default
+    CA certs because this scenario is detected at socket connect time.
     """
     # The "certifi" Python package provides certificates. If it is installed,
     # assume the user intends it to be used and use it.
     try:
         import certifi
         certs = certifi.where()
         ui.debug('using ca certificates from certifi\n')
         return certs
@@ -475,16 +485,38 @@ def _defaultcacerts(ui):
         # files. Also consider exporting the keychain certs to a file during
         # Mercurial install.
         if not _canloaddefaultcerts:
             ui.warn(_('(unable to load CA certificates; see '
                       'https://mercurial-scm.org/wiki/SecureConnections for '
                       'how to configure Mercurial to avoid this message)\n'))
         return None
 
+    # Try to find CA certificates in well-known locations. We print a warning
+    # when using a found file because we don't want too much silent magic
+    # for security settings. The expectation is that proper Mercurial
+    # installs will have the CA certs path defined at install time and the
+    # installer/packager will make an appropriate decision on the user's
+    # behalf. We only get here and perform this setting as a feature of
+    # last resort.
+    if not _canloaddefaultcerts:
+        for path in _systemcacertpaths:
+            if os.path.isfile(path):
+                ui.warn(_('(using CA certificates from %s; if you see this '
+                          'message, your Mercurial install is not properly '
+                          'configured; see '
+                          'https://mercurial-scm.org/wiki/SecureConnections '
+                          'for how to configure Mercurial to avoid this '
+                          'message)\n') % path)
+                return path
+
+        ui.warn(_('(unable to load CA certificates; see '
+                  'https://mercurial-scm.org/wiki/SecureConnections for '
+                  'how to configure Mercurial to avoid this message)\n'))
+
     return None
 
 def validatesocket(sock):
     """Validate a socket meets security requiremnets.
 
     The passed socket must have been created with ``wrapsocket()``.
     """
     host = sock._hgstate['hostname']
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -51,16 +51,17 @@ we are able to load CA certs.
   $ hg clone https://localhost:$HGPORT/ copy-pull
   (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
   abort: error: *certificate verify failed* (glob)
   [255]
 #endif
 
 #if no-sslcontext defaultcacerts
   $ hg clone https://localhost:$HGPORT/ copy-pull
+  (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
   abort: error: *certificate verify failed* (glob)
   [255]
 #endif
 
 #if no-sslcontext windows
   $ hg clone https://localhost:$HGPORT/ copy-pull
   (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
   abort: error: *certificate verify failed* (glob)
@@ -72,16 +73,17 @@ we are able to load CA certs.
   (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
   abort: localhost certificate error: no certificate received
   (set hostsecurity.localhost:certfingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 config setting or use --insecure to connect insecurely)
   [255]
 #endif
 
 #if defaultcacertsloaded
   $ hg clone https://localhost:$HGPORT/ copy-pull
+  (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
   abort: error: *certificate verify failed* (glob)
   [255]
 #endif
 
 #if no-defaultcacerts
   $ hg clone https://localhost:$HGPORT/ copy-pull
   (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
   abort: localhost certificate error: no certificate received
diff --git a/tests/test-patchbomb-tls.t b/tests/test-patchbomb-tls.t
--- a/tests/test-patchbomb-tls.t
+++ b/tests/test-patchbomb-tls.t
@@ -53,25 +53,27 @@ we are able to load CA certs:
   [255]
 #endif
 
 #if no-sslcontext defaultcacerts
   $ try
   this patch series consists of 1 patches.
   
   
+  (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
   (?i)abort: .*?certificate.verify.failed.* (re)
   [255]
 #endif
 
 #if defaultcacertsloaded
   $ try
   this patch series consists of 1 patches.
   
   
+  (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
   (?i)abort: .*?certificate.verify.failed.* (re)
   [255]
 
 #endif
 
 #if no-defaultcacerts
   $ try
   this patch series consists of 1 patches.


More information about the Mercurial-devel mailing list