# HG changeset patch # User Gregory Szorc # Date 1464459122 25200 # Node ID 6315c1e14f751c526fc1d49b4c6902c254d06c44 # Parent 9da137faaa9c9593f821923189316e7fcb38cf67 sslutil: introduce a function for determining host-specific settings This patch marks the beginning of a series that introduces a new, more configurable, per-host security settings mechanism. Currently, we have global settings (like web.cacerts and the --insecure argument). We also have per-host settings via [hostfingerprints]. Global security settings are good for defaults, but they don't provide the amount of control often wanted. For example, an organization may want to require a particular CA is used for a particular hostname. [hostfingerprints] is nice. But it currently assumes SHA-1. Furthermore, there is no obvious place to put additional per-host settings. Subsequent patches will be introducing new mechanisms for defining security settings, some on a per-host basis. This commits starts the transition to that world by introducing the _hostsettings function. It takes a ui and hostname and returns a dict of security settings. Currently, it limits itself to returning host fingerprint info. We foreshadow the future support of non-SHA1 hashing algorithms for verifying the host fingerprint by making the "certfingerprints" key a list of tuples instead of a list of hashes. We add this dict to the hgstate property on the socket and use it during socket validation for checking fingerprints. There should be no change in behavior. diff -r 9da137faaa9c -r 6315c1e14f75 mercurial/sslutil.py --- a/mercurial/sslutil.py Wed May 25 19:57:31 2016 -0700 +++ b/mercurial/sslutil.py Sat May 28 11:12:02 2016 -0700 @@ -106,6 +106,23 @@ return ssl.wrap_socket(socket, **args) +def _hostsettings(ui, hostname): + """Obtain security settings for a hostname. + + Returns a dict of settings relevant to that hostname. + """ + s = { + # List of 2-tuple of (hash algorithm, hash). + 'certfingerprints': [], + } + + # Fingerprints from [hostfingerprints] are always SHA-1. + for fingerprint in ui.configlist('hostfingerprints', hostname, []): + fingerprint = fingerprint.replace(':', '').lower() + s['certfingerprints'].append(('sha1', fingerprint)) + + return s + def _determinecertoptions(ui, host): """Determine certificate options for a connections. @@ -217,6 +234,7 @@ sslsocket._hgstate = { 'caloaded': caloaded, 'hostname': serverhostname, + 'settings': _hostsettings(ui, serverhostname), 'ui': ui, } @@ -292,6 +310,7 @@ """ host = sock._hgstate['hostname'] ui = sock._hgstate['ui'] + settings = sock._hgstate['settings'] try: peercert = sock.getpeercert(True) @@ -305,15 +324,13 @@ # If a certificate fingerprint is pinned, use it and only it to # validate the remote cert. - hostfingerprints = ui.configlist('hostfingerprints', host) peerfingerprint = util.sha1(peercert).hexdigest() nicefingerprint = ":".join([peerfingerprint[x:x + 2] for x in xrange(0, len(peerfingerprint), 2)]) - if hostfingerprints: + if settings['certfingerprints']: fingerprintmatch = False - for hostfingerprint in hostfingerprints: - if peerfingerprint.lower() == \ - hostfingerprint.replace(':', '').lower(): + for hash, fingerprint in settings['certfingerprints']: + if peerfingerprint.lower() == fingerprint: fingerprintmatch = True break if not fingerprintmatch: