changeset 25415:21b536f01eda

ssl: prompt passphrase of client key file via ui.getpass() (issue4648) This is necessary to communicate with third-party tools through command-server channel. This requires SSLContext backported to Python 2.7.9+. It doesn't look nice to pass ui by sslkwargs, but I think it is the only way to do without touching various client codes including httpclient (aka http2). ui is mandatory if certfile is specified, so it has no default value. BTW, test-check-commit-hg.t complains that ssl_wrap_socket() has foo_bar naming. Should I bulk-replace it to sslwrapsocket() ?
author Yuya Nishihara <yuya@tcha.org>
date Thu, 07 May 2015 17:15:24 +0900
parents f7ccbc2776b7
children c4a92867c048
files mercurial/sslutil.py mercurial/url.py tests/test-https.t
diffstat 3 files changed, 21 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/sslutil.py	Thu May 07 17:02:20 2015 +0900
+++ b/mercurial/sslutil.py	Thu May 07 17:15:24 2015 +0900
@@ -21,7 +21,8 @@
         _canloaddefaultcerts = util.safehasattr(ssl_context,
                                                 'load_default_certs')
 
-        def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
+        def ssl_wrap_socket(sock, keyfile, certfile, ui,
+                            cert_reqs=ssl.CERT_NONE,
                             ca_certs=None, serverhostname=None):
             # Allow any version of SSL starting with TLSv1 and
             # up. Note that specifying TLSv1 here prohibits use of
@@ -35,7 +36,10 @@
             sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
             sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
             if certfile is not None:
-                sslcontext.load_cert_chain(certfile, keyfile)
+                def password():
+                    f = keyfile or certfile
+                    return ui.getpass(_('passphrase for %s: ') % f, '')
+                sslcontext.load_cert_chain(certfile, keyfile, password)
             sslcontext.verify_mode = cert_reqs
             if ca_certs is not None:
                 sslcontext.load_verify_locations(cafile=ca_certs)
@@ -51,7 +55,8 @@
                 raise util.Abort(_('ssl connection failed'))
             return sslsocket
     except AttributeError:
-        def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
+        def ssl_wrap_socket(sock, keyfile, certfile, ui,
+                            cert_reqs=ssl.CERT_NONE,
                             ca_certs=None, serverhostname=None):
             sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
                                         cert_reqs=cert_reqs, ca_certs=ca_certs,
@@ -67,7 +72,8 @@
 
     import socket, httplib
 
-    def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
+    def ssl_wrap_socket(sock, keyfile, certfile, ui,
+                        cert_reqs=CERT_REQUIRED,
                         ca_certs=None, serverhostname=None):
         if not util.safehasattr(socket, 'ssl'):
             raise util.Abort(_('Python SSL support not found'))
@@ -146,7 +152,7 @@
     return '!'
 
 def sslkwargs(ui, host):
-    kws = {}
+    kws = {'ui': ui}
     hostfingerprint = ui.config('hostfingerprints', host)
     if hostfingerprint:
         return kws
--- a/mercurial/url.py	Thu May 07 17:02:20 2015 +0900
+++ b/mercurial/url.py	Thu May 07 17:15:24 2015 +0900
@@ -175,7 +175,7 @@
             self.sock.connect((self.host, self.port))
             if _generic_proxytunnel(self):
                 # we do not support client X.509 certificates
-                self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
+                self.sock = sslutil.ssl_wrap_socket(self.sock, None, None, None,
                                                     serverhostname=self.host)
         else:
             keepalive.HTTPConnection.connect(self)
--- a/tests/test-https.t	Thu May 07 17:02:20 2015 +0900
+++ b/tests/test-https.t	Thu May 07 17:15:24 2015 +0900
@@ -385,10 +385,19 @@
   > [auth]
   > l.prefix = localhost
   > l.cert = client-cert.pem
+  > l.key = client-key.pem
   > EOT
 
   $ P=`pwd` hg id https://localhost:$HGPORT/ \
   > --config auth.l.key=client-key-decrypted.pem
   5fed3813f7f5
 
+  $ printf '1234\n' | env P=`pwd` hg id https://localhost:$HGPORT/ \
+  > --config ui.interactive=True --config ui.nontty=True
+  passphrase for client-key.pem: 5fed3813f7f5
+
+  $ env P=`pwd` hg id https://localhost:$HGPORT/
+  abort: error: * (glob)
+  [255]
+
 #endif