url: SSL server certificate verification using web.cacerts file (issue1174)
authorHenrik Stuart <hg@hstuart.dk>
Wed, 10 Feb 2010 20:27:46 +0100
changeset 10409 4c94a3df4b10
parent 10408 50fb1fe143ff
child 10410 b59fba37e5e6
url: SSL server certificate verification using web.cacerts file (issue1174)
doc/hgrc.5.txt
mercurial/url.py
--- a/doc/hgrc.5.txt	Wed Feb 10 20:08:18 2010 +0100
+++ b/doc/hgrc.5.txt	Wed Feb 10 20:27:46 2010 +0100
@@ -873,6 +873,26 @@
     Base URL to use when publishing URLs in other locations, so
     third-party tools like email notification hooks can construct
     URLs. Example: ``http://hgserver/repos/``.
+``cacerts``
+    Path to file containing a list of PEM encoded certificate authorities
+    that may be used to verify an SSL server's identity. The form must be
+    as follows::
+
+        -----BEGIN CERTIFICATE-----
+        ... (certificate in base64 PEM encoding) ...
+        -----END CERTIFICATE-----
+        -----BEGIN CERTIFICATE-----
+        ... (certificate in base64 PEM encoding) ...
+        -----END CERTIFICATE-----
+
+    This feature is only supported when using Python 2.6. If you wish to
+    use it with earlier versions of Python, install the backported
+    version of the ssl library that is available from
+    ``http://pypi.python.org``.
+
+    You can use OpenSSL's CA certificate file if your platform has one.
+    On most Linux systems this will be ``/etc/ssl/certs/ca-certificates.crt``.
+    Otherwise you will have to generate this file manually.
 ``contact``
     Name or email address of the person in charge of the repository.
     Defaults to ui.username or ``$EMAIL`` or "unknown" if unset or empty.
--- a/mercurial/url.py	Wed Feb 10 20:08:18 2010 +0100
+++ b/mercurial/url.py	Wed Feb 10 20:27:46 2010 +0100
@@ -255,11 +255,48 @@
         # avoid using deprecated/broken FakeSocket in python 2.6
         import ssl
         _ssl_wrap_socket = ssl.wrap_socket
+        CERT_REQUIRED = ssl.CERT_REQUIRED
     except ImportError:
-        def _ssl_wrap_socket(sock, key_file, cert_file):
+        CERT_REQUIRED = 2
+
+        def _ssl_wrap_socket(sock, key_file, cert_file,
+                             cert_reqs=CERT_REQUIRED, ca_certs=None):
+            if ca_certs:
+                raise util.Abort(_(
+                    'certificate checking requires Python 2.6'))
+
             ssl = socket.ssl(sock, key_file, cert_file)
             return httplib.FakeSocket(sock, ssl)
 
+        _GLOBAL_DEFAULT_TIMEOUT = object()
+
+    try:
+        _create_connection = socket.create_connection
+    except ImportError:
+        def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
+                               source_address=None):
+            # lifted from Python 2.6
+
+            msg = "getaddrinfo returns an empty list"
+            host, port = address
+            for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
+                af, socktype, proto, canonname, sa = res
+                sock = None
+                try:
+                    sock = socket.socket(af, socktype, proto)
+                    if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
+                        sock.settimeout(timeout)
+                    if source_address:
+                        sock.bind(source_address)
+                    sock.connect(sa)
+                    return sock
+
+                except socket.error, msg:
+                    if sock is not None:
+                        sock.close()
+
+            raise socket.error, msg
+
 class httpconnection(keepalive.HTTPConnection):
     # must be able to send big bundle as stream.
     send = _gen_sendfile(keepalive.HTTPConnection)
@@ -427,6 +464,21 @@
     class BetterHTTPS(httplib.HTTPSConnection):
         send = keepalive.safesend
 
+        def connect(self):
+            if hasattr(self, 'ui'):
+                cacerts = self.ui.config('web', 'cacerts')
+            else:
+                cacerts = None
+
+            if cacerts:
+                sock = _create_connection((self.host, self.port))
+                self.sock = _ssl_wrap_socket(sock, self.key_file,
+                        self.cert_file, cert_reqs=CERT_REQUIRED,
+                        ca_certs=cacerts)
+                self.ui.debug(_('server identity verification succeeded\n'))
+            else:
+                httplib.HTTPSConnection.connect(self)
+
     class httpsconnection(BetterHTTPS):
         response_class = keepalive.HTTPResponse
         # must be able to send big bundle as stream.
@@ -473,7 +525,9 @@
                 keyfile = self.auth['key']
                 certfile = self.auth['cert']
 
-            return httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
+            conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
+            conn.ui = self.ui
+            return conn
 
 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
 # it doesn't know about the auth type requested.  This can happen if