# HG changeset patch # User Henrik Stuart # Date 2009-06-20 08:58:57 # Node ID 7951f385fcb71f9b684ceb467d37aa43ddecefda # Parent b30775386d4064a22a52b35cd5449a2623cec27a 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. diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -142,6 +142,11 @@ Example: 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 @@ Supported arguments: 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 diff --git a/mercurial/url.py b/mercurial/url.py --- a/mercurial/url.py +++ b/mercurial/url.py @@ -109,7 +109,9 @@ class passwordmgr(urllib2.HTTPPasswordMg 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 @@ class passwordmgr(urllib2.HTTPPasswordMg 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 @@ class passwordmgr(urllib2.HTTPPasswordMg # 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 @@ class passwordmgr(urllib2.HTTPPasswordMg 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 @@ if has_https: 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 @@ def opener(ui, authinfo=None): ''' handlers = [httphandler()] if has_https: - handlers.append(httpshandler()) + handlers.append(httpshandler(ui)) handlers.append(proxyhandler(ui))