Patchwork [2,of,3] sslutil: move context options flags to _hostsettings

login
register
mail settings
Submitter Gregory Szorc
Date July 7, 2016, 6:31 a.m.
Message ID <66a5adf42ac7bc0031e7.1467873096@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/15767/
State Accepted
Headers show

Comments

Gregory Szorc - July 7, 2016, 6:31 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1467870802 25200
#      Wed Jul 06 22:53:22 2016 -0700
# Node ID 66a5adf42ac7bc0031e772b407403e809e67fb6e
# Parent  f72b68901929352cd54e5d0c3b7e47b0d01c9313
sslutil: move context options flags to _hostsettings

Again, moving configuration determination to a single location.

Patch

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -125,16 +125,18 @@  def _hostsettings(ui, hostname):
         # Whether certificate verification should be disabled.
         'disablecertverification': False,
         # Whether the legacy [hostfingerprints] section has data for this host.
         'legacyfingerprint': False,
         # PROTOCOL_* constant to use for SSLContext.__init__.
         'protocol': None,
         # ssl.CERT_* constant used by SSLContext.verify_mode.
         'verifymode': None,
+        # Defines extra ssl.OP* bitwise options to set.
+        'ctxoptions': None,
     }
 
     # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
     # that both ends support, including TLS protocols. On legacy stacks,
     # the highest it likely goes in TLS 1.0. On modern stacks, it can
     # support TLS 1.2.
     #
     # The PROTOCOL_TLSv* constants select a specific TLS version
@@ -143,16 +145,21 @@  def _hostsettings(ui, hostname):
     # disable protocols via SSLContext.options and OP_NO_* constants.
     # However, SSLContext.options doesn't work unless we have the
     # full/real SSLContext available to us.
     if modernssl:
         s['protocol'] = ssl.PROTOCOL_SSLv23
     else:
         s['protocol'] = ssl.PROTOCOL_TLSv1
 
+    # SSLv2 and SSLv3 are broken. We ban them outright.
+    # WARNING: ctxoptions doesn't have an effect unless the modern ssl module
+    # is available. Be careful when adding flags!
+    s['ctxoptions'] = OP_NO_SSLv2 | OP_NO_SSLv3
+
     # Look for fingerprints in [hostsecurity] section. Value is a list
     # of <alg>:<fingerprint> strings.
     fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
                                  [])
     for fingerprint in fingerprints:
         if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
             raise error.Abort(_('invalid fingerprint for %s: %s') % (
                                 hostname, fingerprint),
@@ -229,16 +236,17 @@  def _hostsettings(ui, hostname):
             # At this point we don't have a fingerprint, aren't being
             # explicitly insecure, and can't load CA certs. Connecting
             # is insecure. We allow the connection and abort during
             # validation (once we have the fingerprint to print to the
             # user).
             s['verifymode'] = ssl.CERT_NONE
 
     assert s['protocol'] is not None
+    assert s['ctxoptions'] is not None
     assert s['verifymode'] is not None
 
     return s
 
 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
     """Add SSL/TLS to a socket.
 
     This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
@@ -254,19 +262,18 @@  def wrapsocket(sock, keyfile, certfile, 
     if not serverhostname:
         raise error.Abort(_('serverhostname argument is required'))
 
     settings = _hostsettings(ui, serverhostname)
 
     # TODO use ssl.create_default_context() on modernssl.
     sslcontext = SSLContext(settings['protocol'])
 
-    # SSLv2 and SSLv3 are broken. We ban them outright.
-    # This is a no-op on old Python.
-    sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
+    # This is a no-op unless using modern ssl.
+    sslcontext.options |= settings['ctxoptions']
 
     # This still works on our fake SSLContext.
     sslcontext.verify_mode = settings['verifymode']
 
     if certfile is not None:
         def password():
             f = keyfile or certfile
             return ui.getpass(_('passphrase for %s: ') % f, '')