sslutil: allow fingerprints to be specified in [hostsecurity]
We introduce the [hostsecurity] config section. It holds per-host
security settings.
Currently, the section only contains a "fingerprints" option,
which behaves like [hostfingerprints] but supports specifying the
hashing algorithm.
There is still some follow-up work, such as changing some error
messages.
--- a/mercurial/help/config.txt Wed Mar 09 19:55:45 2016 +0000
+++ b/mercurial/help/config.txt Sat May 28 12:37:36 2016 -0700
@@ -976,6 +976,8 @@
``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 @@
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``
--------------
--- a/mercurial/sslutil.py Wed Mar 09 19:55:45 2016 +0000
+++ b/mercurial/sslutil.py Sat May 28 12:37:36 2016 -0700
@@ -121,6 +121,21 @@
'verifymode': None,
}
+ # Look for fingerprints in [hostsecurity] section. Value is a list
+ # of <alg>:<fingerprint> 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()
--- a/tests/test-https.t Wed Mar 09 19:55:45 2016 +0000
+++ b/tests/test-https.t Sat May 28 12:37:36 2016 -0700
@@ -282,18 +282,31 @@
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 @@
(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