Patchwork [3,of,7] sslutil: implement SSLContext class

login
register
mail settings
Submitter Gregory Szorc
Date March 28, 2016, 4:28 a.m.
Message ID <fe7f05150f59648c65c7.1459139312@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/14094/
State Accepted
Headers show

Comments

Gregory Szorc - March 28, 2016, 4:28 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1459111834 25200
#      Sun Mar 27 13:50:34 2016 -0700
# Node ID fe7f05150f59648c65c7e2c68c13981ab64a495f
# Parent  29af463b9e5e61d0609587c37b5059ecb80a4f85
sslutil: implement SSLContext class

Python <2.7.9 doesn't have a ssl.SSLContext class. In this patch,
we implement the interface to the class so we can have a unified
code path for all supported versions of Python.

This is similar to the approach that urllib3 takes.

Patch

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -31,16 +31,84 @@  hassni = getattr(ssl, 'HAS_SNI', False)
 
 try:
     OP_NO_SSLv2 = ssl.OP_NO_SSLv2
     OP_NO_SSLv3 = ssl.OP_NO_SSLv3
 except AttributeError:
     OP_NO_SSLv2 = 0x1000000
     OP_NO_SSLv3 = 0x2000000
 
+try:
+    # ssl.SSLContext was added in 2.7.9 and presence indicates modern
+    # SSL/TLS features are available.
+    SSLContext = ssl.SSLContext
+    modernssl = True
+except AttributeError:
+    modernssl = False
+
+    # We implement SSLContext using the interface from the standard library.
+    class SSLContext(object):
+        # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
+        _supportsciphers = sys.version_info >= (2, 7)
+
+        def __init__(self, protocol):
+            # From the public interface of SSLContext
+            self.protocol = protocol
+            self.check_hostname = False
+            self.options = 0
+            self.verify_mode = ssl.CERT_NONE
+
+            # Used by our implementation.
+            self._certfile = None
+            self._keyfile = None
+            self._certpassword = None
+            self._cacerts = None
+            self._ciphers = None
+
+        def load_cert_chain(self, certfile, keyfile=None, password=None):
+            self._certfile = certfile
+            self._keyfile = keyfile
+            self._certpassword = password
+
+        def load_default_certs(self, purpose=None):
+            pass
+
+        def load_verify_locations(self, cafile=None, capath=None, cadata=None):
+            if capath:
+                raise error.Abort('capath not supported')
+            if cadata:
+                raise error.Abort('cadata not supported')
+
+            self._cacerts = cafile
+
+        def set_ciphers(self, ciphers):
+            if not self._supportsciphers:
+                raise error.Abort('setting ciphers not supported')
+
+            self._ciphers = ciphers
+
+        def wrap_socket(self, socket, server_hostname=None, server_side=False):
+            # server_hostname is unique to SSLContext.wrap_socket and is used
+            # for SNI in that context. So there's nothing for us to do with it
+            # in this legacy code since we don't support SNI.
+
+            args = {
+                'keyfile': self._keyfile,
+                'certfile': self._certfile,
+                'server_side': server_side,
+                'cert_reqs': self.verify_mode,
+                'ssl_version': self.protocol,
+                'ca_certs': self._cacerts,
+            }
+
+            if self._supportsciphers:
+                args['ciphers'] = self._ciphers
+
+            return ssl.wrap_socket(socket, **args)
+
 _canloaddefaultcerts = False
 try:
     # ssl.SSLContext was added in 2.7.9 and presence indicates modern
     # SSL/TLS features are available.
     ssl_context = ssl.SSLContext
     _canloaddefaultcerts = util.safehasattr(ssl_context, 'load_default_certs')
 
     def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,