changeset 8847:7951f385fcb7

url: support client certificate files over HTTPS (issue643) This extends the httpshandler with the means to utilise the auth section to provide it with a PEM encoded certificate key file and certificate chain file. This works also with sites that both require client certificate authentication and basic or digest password authentication, although the latter situation may require the user to enter the PEM password multiple times.
author Henrik Stuart <hg@hstuart.dk>
date Sat, 20 Jun 2009 10:58:57 +0200
parents b30775386d40
children 89b71acdac9a
files doc/hgrc.5.txt mercurial/url.py
diffstat 2 files changed, 46 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/doc/hgrc.5.txt	Sun Jun 07 20:31:38 2009 +0200
+++ b/doc/hgrc.5.txt	Sat Jun 20 10:58:57 2009 +0200
@@ -142,6 +142,11 @@
     foo.password = bar
     foo.schemes = http https
 
+    bar.prefix = secure.example.org
+    bar.key = path/to/file.key
+    bar.cert = path/to/file.cert
+    bar.schemes = https
+
 Supported arguments:
 
   prefix;;
@@ -152,10 +157,17 @@
     against the URI with its scheme stripped as well, and the schemes
     argument, q.v., is then subsequently consulted.
   username;;
-    Username to authenticate with.
+    Optional. Username to authenticate with. If not given, and the
+    remote site requires basic or digest authentication, the user
+    will be prompted for it.
   password;;
-    Optional. Password to authenticate with. If not given the user
+    Optional. Password to authenticate with. If not given, and the
+    remote site requires basic or digest authentication, the user
     will be prompted for it.
+  key;;
+    Optional. PEM encoded client certificate key file.
+  cert;;
+    Optional. PEM encoded client certificate chain file.
   schemes;;
     Optional. Space separated list of URI schemes to use this
     authentication entry with. Only used if the prefix doesn't include
--- a/mercurial/url.py	Sun Jun 07 20:31:38 2009 +0200
+++ b/mercurial/url.py	Sat Jun 20 10:58:57 2009 +0200
@@ -109,7 +109,9 @@
             return (user, passwd)
 
         if not user:
-            user, passwd = self._readauthtoken(authuri)
+            auth = self.readauthtoken(authuri)
+            if auth:
+                user, passwd = auth.get('username'), auth.get('password')
         if not user or not passwd:
             if not self.ui.interactive():
                 raise util.Abort(_('http authorization required'))
@@ -132,7 +134,7 @@
         msg = _('http auth: user %s, password %s\n')
         self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
 
-    def _readauthtoken(self, uri):
+    def readauthtoken(self, uri):
         # Read configuration
         config = dict()
         for key, val in self.ui.configitems('auth'):
@@ -143,7 +145,7 @@
         # Find the best match
         scheme, hostpath = uri.split('://', 1)
         bestlen = 0
-        bestauth = None, None
+        bestauth = None
         for auth in config.itervalues():
             prefix = auth.get('prefix')
             if not prefix: continue
@@ -155,7 +157,7 @@
             if (prefix == '*' or hostpath.startswith(prefix)) and \
                 len(prefix) > bestlen and scheme in schemes:
                 bestlen = len(prefix)
-                bestauth = auth.get('username'), auth.get('password')
+                bestauth = auth
         return bestauth
 
 class proxyhandler(urllib2.ProxyHandler):
@@ -411,8 +413,32 @@
         send = _gen_sendfile(httplib.HTTPSConnection)
 
     class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
+        def __init__(self, ui):
+            keepalive.KeepAliveHandler.__init__(self)
+            urllib2.HTTPSHandler.__init__(self)
+            self.ui = ui
+            self.pwmgr = passwordmgr(self.ui)
+
         def https_open(self, req):
-            return self.do_open(httpsconnection, req)
+            self.auth = self.pwmgr.readauthtoken(req.get_full_url())
+            return self.do_open(self._makeconnection, req)
+
+        def _makeconnection(self, host, port=443, *args, **kwargs):
+            keyfile = None
+            certfile = None
+
+            if args: # key_file
+                keyfile = args.pop(0)
+            if args: # cert_file
+                certfile = args.pop(0)
+
+            # if the user has specified different key/cert files in
+            # hgrc, we prefer these
+            if self.auth and 'key' in self.auth and 'cert' in self.auth:
+                keyfile = self.auth['key']
+                certfile = self.auth['cert']
+
+            return httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
 
 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
 # it doesn't know about the auth type requested.  This can happen if
@@ -460,7 +486,7 @@
     '''
     handlers = [httphandler()]
     if has_https:
-        handlers.append(httpshandler())
+        handlers.append(httpshandler(ui))
 
     handlers.append(proxyhandler(ui))