[PATCH 5 of 6] sslutil: convert socket validation from a class to a function (API)

Gregory Szorc gregory.szorc at gmail.com
Sun May 15 14:57:22 EDT 2016


# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1463337518 25200
#      Sun May 15 11:38:38 2016 -0700
# Node ID 21acf5e61add50991012edaa707441548a83986c
# Parent  af91b3c6355344f173606b676c7b675e77520898
sslutil: convert socket validation from a class to a function (API)

Now that the socket validator doesn't have any instance state,
we can make it a generic function.

The "validator" class has been converted into the "validatesocket"
function and all consumers have been updated.

diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -280,11 +280,11 @@ class http2handler(urlreq.httphandler, u
         kwargs['keyfile'] = keyfile
         kwargs['certfile'] = certfile
         kwargs['serverhostname'] = host
 
         kwargs.update(sslutil.sslkwargs(self.ui, host))
 
         con = HTTPConnection(host, port, use_ssl=True,
                              ssl_wrap_socket=sslutil.wrapsocket,
-                             ssl_validator=sslutil.validator(self.ui, host),
+                             ssl_validator=sslutil.validatesocket,
                              **kwargs)
         return con
diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -134,17 +134,17 @@ def _smtp(ui):
     s.connect(host=mailhost, port=mailport)
     if starttls:
         ui.note(_('(using starttls)\n'))
         s.ehlo()
         s.starttls()
         s.ehlo()
     if (starttls or smtps) and verifycert:
         ui.note(_('(verifying remote certificate)\n'))
-        sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
+        sslutil.validatesocket(s.sock, verifycert == 'strict')
     username = ui.config('smtp', 'username')
     password = ui.config('smtp', 'password')
     if username and not password:
         password = ui.getpass()
     if username and password:
         ui.note(_('(authenticating to mail server as %s)\n') %
                   (username))
         try:
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -286,82 +286,82 @@ def sslkwargs(ui, host):
 
     # FUTURE this can disappear once wrapsocket() is secure by default.
     if _canloaddefaultcerts:
         kws['cert_reqs'] = ssl.CERT_REQUIRED
         return kws
 
     return kws
 
-class validator(object):
-    def __init__(self, ui=None, host=None):
-        pass
-
-    def __call__(self, sock, strict=False):
-        host = sock._hgstate['hostname']
-        ui = sock._hgstate['ui']
-
-        if not sock.cipher(): # work around http://bugs.python.org/issue13721
-            raise error.Abort(_('%s ssl connection error') % host)
-        try:
-            peercert = sock.getpeercert(True)
-            peercert2 = sock.getpeercert()
-        except AttributeError:
-            raise error.Abort(_('%s ssl connection error') % host)
-
-        if not peercert:
-            raise error.Abort(_('%s certificate error: '
-                               'no certificate received') % host)
+def validatesocket(sock, strict=False):
+    """Validate a socket meets security requiremnets.
 
-        # If a certificate fingerprint is pinned, use it and only it to
-        # validate the remote cert.
-        hostfingerprints = ui.configlist('hostfingerprints', host)
-        peerfingerprint = util.sha1(peercert).hexdigest()
-        nicefingerprint = ":".join([peerfingerprint[x:x + 2]
-            for x in xrange(0, len(peerfingerprint), 2)])
-        if hostfingerprints:
-            fingerprintmatch = False
-            for hostfingerprint in hostfingerprints:
-                if peerfingerprint.lower() == \
-                        hostfingerprint.replace(':', '').lower():
-                    fingerprintmatch = True
-                    break
-            if not fingerprintmatch:
-                raise error.Abort(_('certificate for %s has unexpected '
-                                   'fingerprint %s') % (host, nicefingerprint),
-                                 hint=_('check hostfingerprint configuration'))
-            ui.debug('%s certificate matched fingerprint %s\n' %
-                     (host, nicefingerprint))
-            return
+    The passed socket must have been created with ``wrapsocket()``.
+    """
+    host = sock._hgstate['hostname']
+    ui = sock._hgstate['ui']
 
-        # If insecure connections were explicitly requested via --insecure,
-        # print a warning and do no verification.
-        #
-        # It may seem odd that this is checked *after* host fingerprint pinning.
-        # This is for backwards compatibility (for now). The message is also
-        # the same as below for BC.
-        if ui.insecureconnections:
-            ui.warn(_('warning: %s certificate with fingerprint %s not '
-                      'verified (check hostfingerprints or web.cacerts '
-                      'config setting)\n') %
-                    (host, nicefingerprint))
-            return
+    if not sock.cipher(): # work around http://bugs.python.org/issue13721
+        raise error.Abort(_('%s ssl connection error') % host)
+    try:
+        peercert = sock.getpeercert(True)
+        peercert2 = sock.getpeercert()
+    except AttributeError:
+        raise error.Abort(_('%s ssl connection error') % host)
 
-        if not sock._hgstate['caloaded']:
-            if strict:
-                raise error.Abort(_('%s certificate with fingerprint %s not '
-                                    'verified') % (host, nicefingerprint),
-                                  hint=_('check hostfingerprints or '
-                                         'web.cacerts config setting'))
-            else:
-                ui.warn(_('warning: %s certificate with fingerprint %s '
-                          'not verified (check hostfingerprints or '
-                          'web.cacerts config setting)\n') %
-                        (host, nicefingerprint))
+    if not peercert:
+        raise error.Abort(_('%s certificate error: '
+                           'no certificate received') % host)
 
-            return
+    # If a certificate fingerprint is pinned, use it and only it to
+    # validate the remote cert.
+    hostfingerprints = ui.configlist('hostfingerprints', host)
+    peerfingerprint = util.sha1(peercert).hexdigest()
+    nicefingerprint = ":".join([peerfingerprint[x:x + 2]
+        for x in xrange(0, len(peerfingerprint), 2)])
+    if hostfingerprints:
+        fingerprintmatch = False
+        for hostfingerprint in hostfingerprints:
+            if peerfingerprint.lower() == \
+                    hostfingerprint.replace(':', '').lower():
+                fingerprintmatch = True
+                break
+        if not fingerprintmatch:
+            raise error.Abort(_('certificate for %s has unexpected '
+                               'fingerprint %s') % (host, nicefingerprint),
+                             hint=_('check hostfingerprint configuration'))
+        ui.debug('%s certificate matched fingerprint %s\n' %
+                 (host, nicefingerprint))
+        return
 
-        msg = _verifycert(peercert2, host)
-        if msg:
-            raise error.Abort(_('%s certificate error: %s') % (host, msg),
-                             hint=_('configure hostfingerprint %s or use '
-                                    '--insecure to connect insecurely') %
-                                  nicefingerprint)
+    # If insecure connections were explicitly requested via --insecure,
+    # print a warning and do no verification.
+    #
+    # It may seem odd that this is checked *after* host fingerprint pinning.
+    # This is for backwards compatibility (for now). The message is also
+    # the same as below for BC.
+    if ui.insecureconnections:
+        ui.warn(_('warning: %s certificate with fingerprint %s not '
+                  'verified (check hostfingerprints or web.cacerts '
+                  'config setting)\n') %
+                (host, nicefingerprint))
+        return
+
+    if not sock._hgstate['caloaded']:
+        if strict:
+            raise error.Abort(_('%s certificate with fingerprint %s not '
+                                'verified') % (host, nicefingerprint),
+                              hint=_('check hostfingerprints or '
+                                     'web.cacerts config setting'))
+        else:
+            ui.warn(_('warning: %s certificate with fingerprint %s '
+                      'not verified (check hostfingerprints or '
+                      'web.cacerts config setting)\n') %
+                    (host, nicefingerprint))
+
+        return
+
+    msg = _verifycert(peercert2, host)
+    if msg:
+        raise error.Abort(_('%s certificate error: %s') % (host, msg),
+                         hint=_('configure hostfingerprint %s or use '
+                                '--insecure to connect insecurely') %
+                              nicefingerprint)
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -351,17 +351,17 @@ if has_https:
 
             host = self.host
             if self.realhostport: # use CONNECT proxy
                 _generic_proxytunnel(self)
                 host = self.realhostport.rsplit(':', 1)[0]
             self.sock = sslutil.wrapsocket(
                 self.sock, self.key_file, self.cert_file, serverhostname=host,
                 **sslutil.sslkwargs(self.ui, host))
-            sslutil.validator(self.ui, host)(self.sock)
+            sslutil.validatesocket(self.sock)
 
     class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
         def __init__(self, ui):
             keepalive.KeepAliveHandler.__init__(self)
             urlreq.httpshandler.__init__(self)
             self.ui = ui
             self.pwmgr = passwordmgr(self.ui)
 


More information about the Mercurial-devel mailing list