[PATCH] sslutil: per-host config option to define certificates

Augie Fackler raf at durin42.com
Thu Jun 9 23:32:38 EDT 2016


On Tue, Jun 07, 2016 at 09:03:44PM -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc at gmail.com>
> # Date 1465356594 25200
> #      Tue Jun 07 20:29:54 2016 -0700
> # Node ID 323f0c9c91e02be86bde60620cec5f38020f4c86
> # Parent  1b3a0b0c414faa3d6d4dbcf4c5abbbe18aa9efd4
> sslutil: per-host config option to define certificates

sure, queued

>
> Recent work has introduced the [hostsecurity] config section for
> defining per-host security settings. This patch builds on top
> of this foundation and implements the ability to define a per-host
> path to a file containing certificates used for verifying the server
> certificate. It is logically a per-host web.cacerts setting.
>
> This patch also introduces a warning when both per-host
> certificates and fingerprints are defined. These are mutually
> exclusive for host verification and I think the user should be
> alerted when security settings are ambiguous because, well,
> security is important.
>
> Tests validating the new behavior have been added.
>
> I decided against putting "ca" in the option name because a
> non-CA certificate can be specified and used to validate the server
> certificate (commonly this will be the exact public certificate
> used by the server). It's worth noting that the underlying
> Python API used is load_verify_locations(cafile=X) and it calls
> into OpenSSL's SSL_CTX_load_verify_locations(). Even OpenSSL's
> documentation seems to omit that the file can contain a non-CA
> certificate if it matches the server's certificate exactly. I
> thought a CA certificate was a special kind of x509 certificate.
> Perhaps I'm wrong and any x509 certificate can be used as a
> CA certificate [as far as OpenSSL is concerned]. In any case,
> I thought it best to drop "ca" from the name because this reflects
> reality.
>
> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
> --- a/mercurial/help/config.txt
> +++ b/mercurial/help/config.txt
> @@ -1019,21 +1019,49 @@ The following per-host settings can be d
>
>      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.
>
> +    This option takes precedence over ``verifycertsfile``.
> +
> +``verifycertsfile``
> +    Path to file a containing a list of PEM encoded certificates used to
> +    verify the server certificate. Environment variables and ``~user``
> +    constructs are expanded in the filename.
> +
> +    The server certificate or the certificate's certificate authority (CA)
> +    must match a certificate from this file or certificate verification
> +    will fail and connections to the server will be refused.
> +
> +    If defined, only certificates provided by this file will be used:
> +    ``web.cacerts`` and any system/default certificates will not be
> +    used.
> +
> +    This option has no effect if the per-host ``fingerprints`` option
> +    is set.
> +
> +    The format of the file is as follows:
> +
> +        -----BEGIN CERTIFICATE-----
> +        ... (certificate in base64 PEM encoding) ...
> +        -----END CERTIFICATE-----
> +        -----BEGIN CERTIFICATE-----
> +        ... (certificate in base64 PEM encoding) ...
> +        -----END CERTIFICATE-----
> +
>  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
> +    foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem
>
>  ``http_proxy``
>  --------------
>
>  Used to access web-based Mercurial repositories through a HTTP
>  proxy.
>
>  ``host``
> diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
> --- a/mercurial/sslutil.py
> +++ b/mercurial/sslutil.py
> @@ -157,33 +157,52 @@ def _hostsettings(ui, hostname):
>      # If --insecure is used, don't take CAs into consideration.
>      elif ui.insecureconnections:
>          s['disablecertverification'] = True
>          s['verifymode'] = ssl.CERT_NONE
>
>      if ui.configbool('devel', 'disableloaddefaultcerts'):
>          s['allowloaddefaultcerts'] = False
>
> +    # If both fingerprints and a per-host ca file are specified, issue a warning
> +    # because users should not be surprised about what security is or isn't
> +    # being performed.
> +    cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
> +    if s['certfingerprints'] and cafile:
> +        ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
> +                  'fingerprints defined; using host fingerprints for '
> +                  'verification)\n') % hostname)
> +
>      # Try to hook up CA certificate validation unless something above
>      # makes it not necessary.
>      if s['verifymode'] is None:
> -        # Find global certificates file in config.
> -        cafile = ui.config('web', 'cacerts')
> -
> +        # Look at per-host ca file first.
>          if cafile:
>              cafile = util.expandpath(cafile)
>              if not os.path.exists(cafile):
> -                raise error.Abort(_('could not find web.cacerts: %s') % cafile)
> +                raise error.Abort(_('path specified by %s does not exist: %s') %
> +                                  ('hostsecurity.%s:verifycertsfile' % hostname,
> +                                   cafile))
> +            s['cafile'] = cafile
>          else:
> -            # No global CA certs. See if we can load defaults.
> -            cafile = _defaultcacerts()
> +            # Find global certificates file in config.
> +            cafile = ui.config('web', 'cacerts')
> +
>              if cafile:
> -                ui.debug('using %s to enable OS X system CA\n' % cafile)
> +                cafile = util.expandpath(cafile)
> +                if not os.path.exists(cafile):
> +                    raise error.Abort(_('could not find web.cacerts: %s') %
> +                                      cafile)
> +            else:
> +                # No global CA certs. See if we can load defaults.
> +                cafile = _defaultcacerts()
> +                if cafile:
> +                    ui.debug('using %s to enable OS X system CA\n' % cafile)
>
> -        s['cafile'] = cafile
> +            s['cafile'] = cafile
>
>          # Require certificate validation if CA certs are being loaded and
>          # verification hasn't been disabled above.
>          if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
>              s['verifymode'] = ssl.CERT_REQUIRED
>          else:
>              # At this point we don't have a fingerprint, aren't being
>              # explicitly insecure, and can't load CA certs. Connecting
> diff --git a/tests/test-https.t b/tests/test-https.t
> --- a/tests/test-https.t
> +++ b/tests/test-https.t
> @@ -166,16 +166,64 @@ Our test cert is not signed by a trusted
>  we are able to load CA certs.
>
>  #if defaultcacerts
>    $ hg clone https://localhost:$HGPORT/ copy-pull
>    abort: error: *certificate verify failed* (glob)
>    [255]
>  #endif
>
> +Specifying a per-host certificate file that doesn't exist will abort
> +
> +  $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
> +  abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: /does/not/exist
> +  [255]
> +
> +A malformed per-host certificate file will raise an error
> +
> +  $ echo baddata > badca.pem
> +  $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
> +  abort: error: unknown error* (glob)
> +  [255]
> +
> +A per-host certificate mismatching the server will fail verification
> +
> +  $ hg --config hostsecurity.localhost:verifycertsfile=client-cert.pem clone https://localhost:$HGPORT/
> +  abort: error: *certificate verify failed* (glob)
> +  [255]
> +
> +A per-host certificate matching the server's cert will be accepted
> +
> +  $ hg --config hostsecurity.localhost:verifycertsfile=pub.pem clone -U https://localhost:$HGPORT/ perhostgood1
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 4 changes to 4 files
> +
> +A per-host certificate with multiple certs and one matching will be accepted
> +
> +  $ cat client-cert.pem pub.pem > perhost.pem
> +  $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 4 changes to 4 files
> +
> +Defining both per-host certificate and a fingerprint will print a warning
> +
> +  $ hg --config hostsecurity.localhost:verifycertsfile=pub.pem --config hostsecurity.localhost:fingerprints=sha1:914f1aff87249c09b6859b88b1906d30756491ca clone -U https://localhost:$HGPORT/ caandfingerwarning
> +  (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 4 changes to 4 files
> +
>    $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
>
>  clone via pull
>
>    $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
>    warning: certificate for localhost not verified (set hostsecurity.localhost:certfingerprints=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 or web.cacerts config settings)
>    requesting all changes
>    adding changesets
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


More information about the Mercurial-devel mailing list