[PATCH 2 of 2] url: debug print ssl certificate if connection failed

Yuya Nishihara yuya at tcha.org
Sat Jan 8 07:08:26 CST 2011


# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1294491295 -32400
# Branch stable
# Node ID e541b8d268395ece35aff45f8116c6041d22d91f
# Parent  160f24a7970a402d0f7df1912b92419d7acbd8f3
url: debug print ssl certificate if connection failed

If a server provides self-signed certificate, ssl.wrap_socket raises exception
like "routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed".

As of Python 2.6 or 2.7, it doesn't provide a legitimate way to decode
certificate to human-readable text without established ssl socket.
So _printservercert() tries to use _ssl._test_decode_cert(), which is available
at least on Python 2.6.6 or 2.7.1.

diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -290,8 +290,11 @@ if has_https:
         import ssl
         _ssl_wrap_socket = ssl.wrap_socket
         CERT_REQUIRED = ssl.CERT_REQUIRED
+        SSLError = ssl.SSLError
     except ImportError:
         CERT_REQUIRED = 2
+        class SSLError(Exception):
+            pass
 
         def _ssl_wrap_socket(sock, key_file, cert_file,
                              cert_reqs=CERT_REQUIRED, ca_certs=None):
@@ -521,6 +524,27 @@ def _printcert(cert, write):
     for key, value in cert.get('subjectAltName', []):
         write(' subjectAltName.%s: %s\n' % (key, value))
 
+def _printservercert(addr, write):
+    """Fetch server certificate and print human-readable details"""
+    import tempfile
+    try:  # needs Python 2.6
+        import ssl, _ssl
+        decode_certfile = _ssl._test_decode_cert
+    except (ImportError, AttributeError):
+        return
+    der = ssl.get_server_certificate(addr)
+    if not der:
+        return
+    fh, fn = tempfile.mkstemp(prefix='hg-servercert-')
+    try:
+        f = os.fdopen(fh, 'w')
+        f.write(der)
+        f.close()
+        cert = decode_certfile(fn)
+    finally:
+        os.unlink(fn)
+    _printcert(cert, write)
+
 if has_https:
     class BetterHTTPS(httplib.HTTPSConnection):
         send = keepalive.safesend
@@ -535,9 +559,15 @@ if has_https:
 
             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)
+                try:
+                    self.sock = _ssl_wrap_socket(sock, self.key_file,
+                            self.cert_file, cert_reqs=CERT_REQUIRED,
+                            ca_certs=cacerts)
+                except SSLError:
+                    if self.ui.debugflag:
+                        _printservercert((self.host, self.port),
+                                         self.ui.debug)
+                    raise
                 msg = _verifycert(self.sock.getpeercert(), self.host)
                 if msg:
                     if self.ui.debugflag:


More information about the Mercurial-devel mailing list