Patchwork [3,of,3] ssl: prompt passphrase of client key file via ui.getpass() (issue4648)

login
register
mail settings
Submitter Yuya Nishihara
Date June 2, 2015, 2:28 p.m.
Message ID <1a1aa2f8a37bd2afec41.1433255335@mimosa>
Download mbox | patch
Permalink /patch/9443/
State Accepted
Headers show

Comments

Yuya Nishihara - June 2, 2015, 2:28 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1430986524 -32400
#      Thu May 07 17:15:24 2015 +0900
# Node ID 1a1aa2f8a37bd2afec4126a525e4196832518a7e
# Parent  6c7323bd82293bbd74758bea5e54538c82564872
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() ?
Matt Mackall - June 3, 2015, 8:37 p.m.
On Tue, 2015-06-02 at 23:28 +0900, Yuya Nishihara wrote:
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1430986524 -32400
> #      Thu May 07 17:15:24 2015 +0900
> # Node ID 1a1aa2f8a37bd2afec4126a525e4196832518a7e
> # Parent  6c7323bd82293bbd74758bea5e54538c82564872
> ssl: prompt passphrase of client key file via ui.getpass() (issue4648)

These are queued for default, thanks! 

> BTW, test-check-commit-hg.t complains that ssl_wrap_socket() has foo_bar
> naming. Should I bulk-replace it to sslwrapsocket() ?

Sure.

Patch

diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -21,7 +21,8 @@  try:
         _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 @@  try:
             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 @@  try:
                 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 @@  except ImportError:
 
     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 @@  def _defaultcacerts():
     return '!'
 
 def sslkwargs(ui, host):
-    kws = {}
+    kws = {'ui': ui}
     hostfingerprint = ui.config('hostfingerprints', host)
     if hostfingerprint:
         return kws
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -175,7 +175,7 @@  class httpconnection(keepalive.HTTPConne
             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)
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -385,10 +385,19 @@  with client certificate:
   > [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