diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -976,6 +976,8 @@ is treated as a failure. ``hostfingerprints`` -------------------- +(Deprecated. Use ``[hostsecurity]``'s ``fingerprints`` options instead.) + Fingerprints of the certificates of known HTTPS servers. A HTTPS connection to a server with a fingerprint configured here will @@ -995,6 +997,39 @@ For example:: hg.intevation.de = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33 hg.intevation.org = fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33 +``hostsecurity`` +---------------- + +Used to specify per-host security settings. + +Options in this section have the form ``hostname``:``setting``. This allows +multiple settings to be defined on a per-host basis. + +The following per-host settings can be defined. + +``fingerprints`` + A list of hashes of the DER encoded peer/remote certificate. Values have + the form ``algorithm``:``fingerprint``. e.g. + ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``. + + The following algorithms/prefixes are supported: ``sha1``, ``sha256``, + ``sha512``. + + Use of ``sha256`` or ``sha512`` is preferred. + + If a fingerprint is specified, the CA chain is not validated for this + host and Mercurial will require the remote certificate to match one + of the fingerprints specified. This means if the server updates its + certificate, Mercurial will abort until a new fingerprint is defined. + This can provide stronger security than traditional CA-based validation + at the expense of convenience. + +For example:: + + [hostsecurity] + hg.example.com:fingerprints = sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2 + hg2.example.com:fingerprints = sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33 + ``http_proxy`` -------------- diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py --- a/mercurial/sslutil.py +++ b/mercurial/sslutil.py @@ -121,6 +121,21 @@ def _hostsettings(ui, hostname): 'verifymode': None, } + # Look for fingerprints in [hostsecurity] section. Value is a list + # of : strings. + fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname, + []) + for fingerprint in fingerprints: + if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))): + raise error.Abort(_('invalid fingerprint for %s: %s') % ( + hostname, fingerprint), + hint=_('must begin with "sha1:", "sha256:", ' + 'or "sha512:"')) + + alg, fingerprint = fingerprint.split(':', 1) + fingerprint = fingerprint.replace(':', '').lower() + s['certfingerprints'].append((alg, fingerprint)) + # Fingerprints from [hostfingerprints] are always SHA-1. for fingerprint in ui.configlist('hostfingerprints', hostname, []): fingerprint = fingerprint.replace(':', '').lower() diff --git a/tests/test-https.t b/tests/test-https.t --- a/tests/test-https.t +++ b/tests/test-https.t @@ -282,18 +282,31 @@ Test server cert which no longer is vali Fingerprints -- works without cacerts +- works without cacerts (hostkeyfingerprints) $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca 5fed3813f7f5 +- works without cacerts (hostsecurity) + $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca + 5fed3813f7f5 + + $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:62:09:97:2f:97:60:e3:65:8f:12:5d:78:9e:35:a1:36:7a:65:4b:0e:9f:ac:db:c3:bc:6e:b6:a3:c0:16:e0:30 + 5fed3813f7f5 + - multiple fingerprints specified and first matches $ hg --config 'hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure 5fed3813f7f5 + $ hg --config 'hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ + 5fed3813f7f5 + - multiple fingerprints specified and last matches $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, 914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ --insecure 5fed3813f7f5 + $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:914f1aff87249c09b6859b88b1906d30756491ca' -R copy-pull id https://localhost:$HGPORT/ + 5fed3813f7f5 + - multiple fingerprints specified and none match $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure @@ -301,6 +314,11 @@ Fingerprints (check hostfingerprint configuration) [255] + $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ + abort: certificate for localhost has unexpected fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca + (check hostfingerprint configuration) + [255] + - fails when cert doesn't match hostname (port is ignored) $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=914f1aff87249c09b6859b88b1906d30756491ca abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b