Patchwork [7,of,8] sslutil: allow fingerprints to be specified in [hostsecurity]

login
register
mail settings
Submitter Gregory Szorc
Date May 28, 2016, 8:04 p.m.
Message ID <969eddf5985326d5c4fd.1464465869@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/15235/
State Accepted
Headers show

Comments

Gregory Szorc - May 28, 2016, 8:04 p.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1464464256 25200
#      Sat May 28 12:37:36 2016 -0700
# Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
# Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
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.
timeless - May 29, 2016, 2:14 a.m.
Please provide an example of a host with two fingerprints (load balancing
or incremental rollout)
On May 28, 2016 4:05 PM, "Gregory Szorc" <gregory.szorc@gmail.com> wrote:

> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1464464256 25200
> #      Sat May 28 12:37:36 2016 -0700
> # Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
> # Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
> 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.
>
> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
> --- a/mercurial/help/config.txt
> +++ b/mercurial/help/config.txt
> @@ -971,16 +971,18 @@ environment variables above are passed a
>
>  If a Python hook returns a "true" value or raises an exception, this
>  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
>  only succeed if the servers certificate matches the fingerprint.
>  This is very similar to how ssh known hosts works.
>
>  The fingerprint is the SHA-1 hash value of the DER encoded certificate.
>  Multiple values can be specified (separated by spaces or commas). This can
> @@ -990,16 +992,49 @@ to a new certificate.
>  The CA chain and web.cacerts is not used for servers with a fingerprint.
>
>  For example::
>
>      [hostfingerprints]
>      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``
>  --------------
>
>  Used to access web-based Mercurial repositories through a HTTP
>  proxy.
>
>  ``host``
>      Host name and (optional) port of the proxy server, for example
> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
> --- a/mercurial/sslutil.py
> +++ b/mercurial/sslutil.py
> @@ -116,16 +116,31 @@ def _hostsettings(ui, hostname):
>          'certfingerprints': [],
>          # Path to file containing concatenated CA certs. Used by
>          # SSLContext.load_verify_locations().
>          'cafile': None,
>          # ssl.CERT_* constant used by SSLContext.verify_mode.
>          '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()
>          s['certfingerprints'].append(('sha1', fingerprint))
>
>      # If a host cert fingerprint is defined, it is the only thing that
>      # matters. No need to validate CA certs.
>      if s['certfingerprints']:
> diff --git a/tests/test-https.t b/tests/test-https.t
> --- a/tests/test-https.t
> +++ b/tests/test-https.t
> @@ -277,35 +277,53 @@ Test server cert which no longer is vali
>    $ cat hg2.pid >> $DAEMON_PIDS
>    $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem
> https://localhost:$HGPORT2/
>    pulling from https://localhost:$HGPORT2/
>    abort: error: *certificate verify failed* (glob)
>    [255]
>
>  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
>    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]
>
> +  $ 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
>    (check hostfingerprint configuration)
>    [255]
>
>
>  - ignores that certificate doesn't match hostname
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>
Gregory Szorc - May 29, 2016, 4:48 p.m.
On Sat, May 28, 2016 at 7:14 PM, timeless <timeless@gmail.com> wrote:

> Please provide an example of a host with two fingerprints (load balancing
> or incremental rollout)
>

The last added line in config.txt does this.


> On May 28, 2016 4:05 PM, "Gregory Szorc" <gregory.szorc@gmail.com> wrote:
>
>> # HG changeset patch
>> # User Gregory Szorc <gregory.szorc@gmail.com>
>> # Date 1464464256 25200
>> #      Sat May 28 12:37:36 2016 -0700
>> # Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
>> # Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
>> 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.
>>
>> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
>> --- a/mercurial/help/config.txt
>> +++ b/mercurial/help/config.txt
>> @@ -971,16 +971,18 @@ environment variables above are passed a
>>
>>  If a Python hook returns a "true" value or raises an exception, this
>>  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
>>  only succeed if the servers certificate matches the fingerprint.
>>  This is very similar to how ssh known hosts works.
>>
>>  The fingerprint is the SHA-1 hash value of the DER encoded certificate.
>>  Multiple values can be specified (separated by spaces or commas). This
>> can
>> @@ -990,16 +992,49 @@ to a new certificate.
>>  The CA chain and web.cacerts is not used for servers with a fingerprint.
>>
>>  For example::
>>
>>      [hostfingerprints]
>>      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``
>>  --------------
>>
>>  Used to access web-based Mercurial repositories through a HTTP
>>  proxy.
>>
>>  ``host``
>>      Host name and (optional) port of the proxy server, for example
>> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
>> --- a/mercurial/sslutil.py
>> +++ b/mercurial/sslutil.py
>> @@ -116,16 +116,31 @@ def _hostsettings(ui, hostname):
>>          'certfingerprints': [],
>>          # Path to file containing concatenated CA certs. Used by
>>          # SSLContext.load_verify_locations().
>>          'cafile': None,
>>          # ssl.CERT_* constant used by SSLContext.verify_mode.
>>          '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()
>>          s['certfingerprints'].append(('sha1', fingerprint))
>>
>>      # If a host cert fingerprint is defined, it is the only thing that
>>      # matters. No need to validate CA certs.
>>      if s['certfingerprints']:
>> diff --git a/tests/test-https.t b/tests/test-https.t
>> --- a/tests/test-https.t
>> +++ b/tests/test-https.t
>> @@ -277,35 +277,53 @@ Test server cert which no longer is vali
>>    $ cat hg2.pid >> $DAEMON_PIDS
>>    $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem
>> https://localhost:$HGPORT2/
>>    pulling from https://localhost:$HGPORT2/
>>    abort: error: *certificate verify failed* (glob)
>>    [255]
>>
>>  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
>>    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]
>>
>> +  $ 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
>>    (check hostfingerprint configuration)
>>    [255]
>>
>>
>>  - ignores that certificate doesn't match hostname
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel@mercurial-scm.org
>> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>>
>
Augie Fackler - May 29, 2016, 9:01 p.m.
On Sat, May 28, 2016 at 01:04:29PM -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1464464256 25200
> #      Sat May 28 12:37:36 2016 -0700
> # Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
> # Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
> sslutil: allow fingerprints to be specified in [hostsecurity]

I'm a fan of this overall, but I'm a little hesitant to introduce a
whole new section rather than figuring out how to sneak the new
behavior into the existing [hostfingerprints] section. Anyone want to
try and convince me one way or the other?

I've gone ahead and queued patches 1-6, since they seem to be on the
right track in any case.

>
> 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.
>
> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
> --- a/mercurial/help/config.txt
> +++ b/mercurial/help/config.txt
> @@ -971,16 +971,18 @@ environment variables above are passed a
>
>  If a Python hook returns a "true" value or raises an exception, this
>  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
>  only succeed if the servers certificate matches the fingerprint.
>  This is very similar to how ssh known hosts works.
>
>  The fingerprint is the SHA-1 hash value of the DER encoded certificate.
>  Multiple values can be specified (separated by spaces or commas). This can
> @@ -990,16 +992,49 @@ to a new certificate.
>  The CA chain and web.cacerts is not used for servers with a fingerprint.
>
>  For example::
>
>      [hostfingerprints]
>      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``
>  --------------
>
>  Used to access web-based Mercurial repositories through a HTTP
>  proxy.
>
>  ``host``
>      Host name and (optional) port of the proxy server, for example
> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
> --- a/mercurial/sslutil.py
> +++ b/mercurial/sslutil.py
> @@ -116,16 +116,31 @@ def _hostsettings(ui, hostname):
>          'certfingerprints': [],
>          # Path to file containing concatenated CA certs. Used by
>          # SSLContext.load_verify_locations().
>          'cafile': None,
>          # ssl.CERT_* constant used by SSLContext.verify_mode.
>          '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()
>          s['certfingerprints'].append(('sha1', fingerprint))
>
>      # If a host cert fingerprint is defined, it is the only thing that
>      # matters. No need to validate CA certs.
>      if s['certfingerprints']:
> diff --git a/tests/test-https.t b/tests/test-https.t
> --- a/tests/test-https.t
> +++ b/tests/test-https.t
> @@ -277,35 +277,53 @@ Test server cert which no longer is vali
>    $ cat hg2.pid >> $DAEMON_PIDS
>    $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
>    pulling from https://localhost:$HGPORT2/
>    abort: error: *certificate verify failed* (glob)
>    [255]
>
>  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
>    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]
>
> +  $ 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
>    (check hostfingerprint configuration)
>    [255]
>
>
>  - ignores that certificate doesn't match hostname
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Gregory Szorc - May 29, 2016, 9:07 p.m.
> On May 29, 2016, at 14:01, Augie Fackler <raf@durin42.com> wrote:
> 
>> On Sat, May 28, 2016 at 01:04:29PM -0700, Gregory Szorc wrote:
>> # HG changeset patch
>> # User Gregory Szorc <gregory.szorc@gmail.com>
>> # Date 1464464256 25200
>> #      Sat May 28 12:37:36 2016 -0700
>> # Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
>> # Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
>> sslutil: allow fingerprints to be specified in [hostsecurity]
> 
> I'm a fan of this overall, but I'm a little hesitant to introduce a
> whole new section rather than figuring out how to sneak the new
> behavior into the existing [hostfingerprints] section. Anyone want to
> try and convince me one way or the other?
> 

I plan on introducing a handful of additional per-host config options. CA file, SSL/TLS protocol version, etc. I would have shoehorned the fingerprint prefixing into [hostfingerprints] if that's all it was.

> I've gone ahead and queued patches 1-6, since they seem to be on the
> right track in any case.
> 
>> 
>> 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.
>> 
>> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
>> --- a/mercurial/help/config.txt
>> +++ b/mercurial/help/config.txt
>> @@ -971,16 +971,18 @@ environment variables above are passed a
>> 
>> If a Python hook returns a "true" value or raises an exception, this
>> 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
>> only succeed if the servers certificate matches the fingerprint.
>> This is very similar to how ssh known hosts works.
>> 
>> The fingerprint is the SHA-1 hash value of the DER encoded certificate.
>> Multiple values can be specified (separated by spaces or commas). This can
>> @@ -990,16 +992,49 @@ to a new certificate.
>> The CA chain and web.cacerts is not used for servers with a fingerprint.
>> 
>> For example::
>> 
>>     [hostfingerprints]
>>     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``
>> --------------
>> 
>> Used to access web-based Mercurial repositories through a HTTP
>> proxy.
>> 
>> ``host``
>>     Host name and (optional) port of the proxy server, for example
>> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
>> --- a/mercurial/sslutil.py
>> +++ b/mercurial/sslutil.py
>> @@ -116,16 +116,31 @@ def _hostsettings(ui, hostname):
>>         'certfingerprints': [],
>>         # Path to file containing concatenated CA certs. Used by
>>         # SSLContext.load_verify_locations().
>>         'cafile': None,
>>         # ssl.CERT_* constant used by SSLContext.verify_mode.
>>         '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()
>>         s['certfingerprints'].append(('sha1', fingerprint))
>> 
>>     # If a host cert fingerprint is defined, it is the only thing that
>>     # matters. No need to validate CA certs.
>>     if s['certfingerprints']:
>> diff --git a/tests/test-https.t b/tests/test-https.t
>> --- a/tests/test-https.t
>> +++ b/tests/test-https.t
>> @@ -277,35 +277,53 @@ Test server cert which no longer is vali
>>   $ cat hg2.pid >> $DAEMON_PIDS
>>   $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
>>   pulling from https://localhost:$HGPORT2/
>>   abort: error: *certificate verify failed* (glob)
>>   [255]
>> 
>> 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
>>   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]
>> 
>> +  $ 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
>>   (check hostfingerprint configuration)
>>   [255]
>> 
>> 
>> - ignores that certificate doesn't match hostname
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel@mercurial-scm.org
>> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Augie Fackler - May 29, 2016, 9:08 p.m.
> On May 29, 2016, at 14:07, Gregory Szorc <gregory.szorc@gmail.com> wrote:
> 
> 
> 
>> On May 29, 2016, at 14:01, Augie Fackler <raf@durin42.com> wrote:
>> 
>>> On Sat, May 28, 2016 at 01:04:29PM -0700, Gregory Szorc wrote:
>>> # HG changeset patch
>>> # User Gregory Szorc <gregory.szorc@gmail.com>
>>> # Date 1464464256 25200
>>> #      Sat May 28 12:37:36 2016 -0700
>>> # Node ID 969eddf5985326d5c4fd26da95fa19247339e6a0
>>> # Parent  fb7b49629e9c961517f576e8edfdfdc2bdb6d0e9
>>> sslutil: allow fingerprints to be specified in [hostsecurity]
>> 
>> I'm a fan of this overall, but I'm a little hesitant to introduce a
>> whole new section rather than figuring out how to sneak the new
>> behavior into the existing [hostfingerprints] section. Anyone want to
>> try and convince me one way or the other?
>> 
> 
> I plan on introducing a handful of additional per-host config options. CA file, SSL/TLS protocol version, etc. I would have shoehorned the fingerprint prefixing into [hostfingerprints] if that's all it was.

Sold. Thanks for the quick response. :)

> 
>> I've gone ahead and queued patches 1-6, since they seem to be on the
>> right track in any case.
>> 
>>> 
>>> 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.
>>> 
>>> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
>>> --- a/mercurial/help/config.txt
>>> +++ b/mercurial/help/config.txt
>>> @@ -971,16 +971,18 @@ environment variables above are passed a
>>> 
>>> If a Python hook returns a "true" value or raises an exception, this
>>> 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
>>> only succeed if the servers certificate matches the fingerprint.
>>> This is very similar to how ssh known hosts works.
>>> 
>>> The fingerprint is the SHA-1 hash value of the DER encoded certificate.
>>> Multiple values can be specified (separated by spaces or commas). This can
>>> @@ -990,16 +992,49 @@ to a new certificate.
>>> The CA chain and web.cacerts is not used for servers with a fingerprint.
>>> 
>>> For example::
>>> 
>>>    [hostfingerprints]
>>>    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``
>>> --------------
>>> 
>>> Used to access web-based Mercurial repositories through a HTTP
>>> proxy.
>>> 
>>> ``host``
>>>    Host name and (optional) port of the proxy server, for example
>>> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
>>> --- a/mercurial/sslutil.py
>>> +++ b/mercurial/sslutil.py
>>> @@ -116,16 +116,31 @@ def _hostsettings(ui, hostname):
>>>        'certfingerprints': [],
>>>        # Path to file containing concatenated CA certs. Used by
>>>        # SSLContext.load_verify_locations().
>>>        'cafile': None,
>>>        # ssl.CERT_* constant used by SSLContext.verify_mode.
>>>        '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()
>>>        s['certfingerprints'].append(('sha1', fingerprint))
>>> 
>>>    # If a host cert fingerprint is defined, it is the only thing that
>>>    # matters. No need to validate CA certs.
>>>    if s['certfingerprints']:
>>> diff --git a/tests/test-https.t b/tests/test-https.t
>>> --- a/tests/test-https.t
>>> +++ b/tests/test-https.t
>>> @@ -277,35 +277,53 @@ Test server cert which no longer is vali
>>>  $ cat hg2.pid >> $DAEMON_PIDS
>>>  $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
>>>  pulling from https://localhost:$HGPORT2/
>>>  abort: error: *certificate verify failed* (glob)
>>>  [255]
>>> 
>>> 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
>>>  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]
>>> 
>>> +  $ 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
>>>  (check hostfingerprint configuration)
>>>  [255]
>>> 
>>> 
>>> - ignores that certificate doesn't match hostname
>>> _______________________________________________
>>> Mercurial-devel mailing list
>>> Mercurial-devel@mercurial-scm.org
>>> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -971,16 +971,18 @@  environment variables above are passed a
 
 If a Python hook returns a "true" value or raises an exception, this
 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
 only succeed if the servers certificate matches the fingerprint.
 This is very similar to how ssh known hosts works.
 
 The fingerprint is the SHA-1 hash value of the DER encoded certificate.
 Multiple values can be specified (separated by spaces or commas). This can
@@ -990,16 +992,49 @@  to a new certificate.
 The CA chain and web.cacerts is not used for servers with a fingerprint.
 
 For example::
 
     [hostfingerprints]
     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``
 --------------
 
 Used to access web-based Mercurial repositories through a HTTP
 proxy.
 
 ``host``
     Host name and (optional) port of the proxy server, for example
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -116,16 +116,31 @@  def _hostsettings(ui, hostname):
         'certfingerprints': [],
         # Path to file containing concatenated CA certs. Used by
         # SSLContext.load_verify_locations().
         'cafile': None,
         # ssl.CERT_* constant used by SSLContext.verify_mode.
         '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()
         s['certfingerprints'].append(('sha1', fingerprint))
 
     # If a host cert fingerprint is defined, it is the only thing that
     # matters. No need to validate CA certs.
     if s['certfingerprints']:
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -277,35 +277,53 @@  Test server cert which no longer is vali
   $ cat hg2.pid >> $DAEMON_PIDS
   $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
   pulling from https://localhost:$HGPORT2/
   abort: error: *certificate verify failed* (glob)
   [255]
 
 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
   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]
 
+  $ 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
   (check hostfingerprint configuration)
   [255]
 
 
 - ignores that certificate doesn't match hostname