changeset 28649:7acab42ef184

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.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 27 Mar 2016 13:50:34 -0700
parents 7fc787e5d8ec
children 737863b01d9f
files mercurial/sslutil.py
diffstat 1 files changed, 68 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/sslutil.py	Sun Mar 27 10:47:24 2016 -0700
+++ b/mercurial/sslutil.py	Sun Mar 27 13:50:34 2016 -0700
@@ -36,6 +36,74 @@
     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