[PATCH 2 of 2] url: debug print ssl certificate if connection failed
Mads Kiilerich
mads at kiilerich.com
Sat Jan 8 21:27:10 CST 2011
Yuya Nishihara wrote, On 01/08/2011 02:08 PM:
> # 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,
More precisely: If a server provides certificate that can't be verified
by the configured cacerts ...
> 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
More precisely: "supported" or "official"
> 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.
Interesting ;-)
I guess this also could be used to show the content of cacerts files and
make them less opaque.
Code in this area is however not simple to maintain, and using these
internal functions will not improve the situation ...
> 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
We usually do imports at the top of the file. Demandimport makes it cheap.
> + 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:
It would be nice to have some tests for this. It might not be feasible
to do it in the test suite as long as we don't have conditional sections
inside .t files, but could you either add some tests to test-https.t and
disable them (for example by putting "#" at the beginning of the lines)
or provide a working fragment in the commit message?
With this patch it is possible to show much of the server certificate
info. Cool. But in which cases can that be used? Can you describe some
use cases? What do the user see and how do he understand it and what
will he then do?
(If the subject is the same as the issuer then the certificate is
self-signed and the certificate from get_server_certificate can thus
probably be used in cacerts. That case can however also just be detected
with two consecutive calls to get_server_certificate.)
/Mads
More information about the Mercurial-devel
mailing list