diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -13,6 +13,7 @@ import operator import os import random import socket +import ssl import string import sys import tempfile @@ -2057,6 +2058,66 @@ def debugsetparents(ui, repo, rev1, rev2 with repo.wlock(): repo.setparents(r1, r2) +@command('debugssl', [], '[SOURCE]', optionalrepo=True) +def debugssl(ui, repo, source=None, **opts): + '''test a secure connection to a server + + This builds the certificate chain for the server on Windows, installing the + missing intermediates and trusted root via Windows Update if necessary. It + does nothing on other platforms. + + If SOURCE is omitted, the 'default' path will be used. If a URL is given, + that server is used. See :hg:`help urls` for more information. + + If the update succeeds, retry the original operation. Otherwise, the cause + of the SSL error is likely another issue. + ''' + if pycompat.osname != 'nt': + raise error.Abort(_('Certificate chain building is only possible on ' + 'Windows')) + + if not source: + source = "default" + elif not repo: + raise error.Abort(_("there is no Mercurial repository here, and no " + "server specified")) + + source, branches = hg.parseurl(ui.expandpath(source)) + url = util.url(source) + addr = None + + if url.scheme == 'https': + addr = (url.host, url.port or 443) + elif url.scheme == 'ssh': + addr = (url.host, url.port or 22) + else: + raise error.Abort(_("Only https and ssh connections are supported")) + + from . import win32 + + s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS, + cert_reqs=ssl.CERT_NONE, ca_certs=None) + + try: + s.connect(addr) + cert = s.getpeercert(True) + + ui.status(_('Checking the certificate chain for %s.\n') % url.host) + + complete = win32.checkcertificatechain(cert, build=False) + + if not complete: + ui.status(_('The certificate chain is incomplete. Updating... ')) + + if not win32.checkcertificatechain(cert): + ui.status(_('Failed.\n')) + else: + ui.status(_('Done.\n')) + else: + ui.status(_('The full certificate chain is available.\n')) + finally: + s.close() + @command('debugsub', [('r', 'rev', '', _('revision to check'), _('REV'))], diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -108,6 +108,7 @@ Show debug commands if there are no othe debugrevlog debugrevspec debugsetparents + debugssl debugsub debugsuccessorssets debugtemplate @@ -283,6 +284,7 @@ Show all commands + options debugrevlog: changelog, manifest, dir, dump debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized debugsetparents: + debugssl: debugsub: rev debugsuccessorssets: closest debugtemplate: rev, define diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -952,6 +952,7 @@ Test list of internal help commands debugrevspec parse and apply a revision specification debugsetparents manually set the parents of the current working directory + debugssl test a secure connection to a server debugsub (no help text available) debugsuccessorssets show set of successors for revision