Commit Graph

88 Commits

Author SHA1 Message Date
Gregory Szorc
aa33fbc73f sslutil: abort when unable to verify peer connection (BC)
Previously, when we connected to a server and were unable to verify
its certificate against a trusted certificate authority we would
issue a warning and continue to connect. This is obviously not
great behavior because the x509 certificate model is based upon
trust of specific CAs. Failure to enforce that trust erodes security.
This behavior was defined several years ago when Python did not
support loading the system trusted CA store (Python 2.7.9's
backports of Python 3's improvements to the "ssl" module enabled
this).

This commit changes behavior when connecting to abort if the peer
certificate can't be validated. With an empty/default Mercurial
configuration, the peer certificate can be validated if Python is
able to load the system trusted CA store. Environments able to load
the system trusted CA store include:

* Python 2.7.9+ on most platforms and installations
* Python 2.7 distributions with a modern ssl module (e.g. RHEL7's
  patched 2.7.5 package)
* Python shipped on OS X

Environments unable to load the system trusted CA store include:

* Python 2.6
* Python 2.7 on many existing Linux installs (because they don't
  ship 2.7.9+ or haven't backported modern ssl module)
* Python 2.7.9+ on some installs where Python is unable to locate
  the system CA store (this is hopefully rare)

Users of these Pythongs will need to configure Mercurial to load the
system CA store using web.cacerts. This should ideally be performed
by packagers (by setting web.cacerts in the global/system hgrc file).
Where Mercurial packagers aren't setting this, the linked URL in the
new abort message can contain instructions for users.

In the future, we may want to add more code for finding the system
CA store. For example, many Linux distributions have the CA store
at well-known locations (such as /etc/ssl/certs/ca-certificates.crt
in the case of Ubuntu). This will enable CA loading to "just work"
on more Python configurations and will be best for our users since
they won't have to change anything after upgrading to a Mercurial
with this patch.

We may also want to consider distributing a trusted CA store with
Mercurial. Although we should think long and hard about that because
most systems have a global CA store and Mercurial should almost
certainly use the same store used by everything else on the system.
2016-06-25 07:26:43 -07:00
Gregory Szorc
a838b9492a sslutil: remove out of place comment
This comment likely got orphaned as a result of refactoring in this
file. It isn't providing any useful value. So delete it.
2016-06-25 07:32:02 -07:00
liscju
c7ec9d159e i18n: translate abort messages
I found a few places where message given to abort is
not translated, I don't find any reason to not translate
them.
2016-06-14 11:53:55 +02:00
Augie Fackler
ad67b99d20 cleanup: replace uses of util.(md5|sha1|sha256|sha512) with hashlib.\1
All versions of Python we support or hope to support make the hash
functions available in the same way under the same name, so we may as
well drop the util forwards.
2016-06-10 00:12:33 -04:00
Gregory Szorc
7a668208b8 sslutil: per-host config option to define certificates
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.
2016-06-07 20:29:54 -07:00
Gregory Szorc
0f55e28908 sslutil: print the fingerprint from the last hash used
Before, we would always print the unprefixed SHA-1 fingerprint when
fingerprint comparison failed. Now, we print the fingerprint of the
last hash used, including the prefix if necessary. This helps ensure
that the printed hash type matches what is in the user configuration.

There are still some cases where this can print a mismatched hash type.
e.g. if there are both SHA-1 and SHA-256 fingerprints in the config,
we could print a SHA-1 hash if it comes after the SHA-256 hash. But
I'm inclined to ignore this edge case.

While I was here, the "section" variable assignment has been moved to
just above where it is used because it is now only needed for this
error message and it makes the code easier to read.
2016-06-04 11:16:08 -07:00
Gregory Szorc
2337effc05 sslutil: make cert fingerprints messages more actionable
The previous warning and abort messages were difficult to understand.
This patch makes them slightly better.

I think there is still room to tweak the messaging. And as we adopt
new security defaults, these messages will certainly change again.
But at least this takes us a step in the right direction.

References to "section" have been removed because if no fingerprint
is defined, "section" can never be "hostfingerprints." So just print
"hostsecurity" every time.
2016-05-31 19:21:08 -07:00
Gregory Szorc
a59ed87b33 sslutil: refactor code for fingerprint matching
We didn't need to use a temporary variable to indicate success because
we just return anyway.

This refactor makes the code simpler. While we're here, we also call
into formatfingerprint() to ensure the fingerprint from the proper
hashing algorithm is logged.
2016-05-30 15:43:03 -07:00
Gregory Szorc
1a6d495880 sslutil: print SHA-256 fingerprint by default
The world is starting to move on from SHA-1. A few commits ago, we
gained the ability to define certificate fingerprints using SHA-256
and SHA-512.

Let's start printing the SHA-256 fingerprint instead of the SHA-1
fingerprint to encourage people to pin with a more secure hashing
algorithm.

There is still a bit of work to be done around the fingerprint
messaging. This will be addressed in subsequent commits.
2016-05-30 15:42:39 -07:00
Gregory Szorc
9ee23a401c sslutil: move and change warning when cert verification is disabled
A short time ago, validatesocket() didn't know the reasons why
cert verification was disabled. Multiple code paths could lead
to cert verification being disabled. e.g. --insecure and lack
of loaded CAs.

With the recent refactorings to sslutil.py, we now know the reasons
behind security settings. This means we can recognize when the user
requested security be disabled (as opposed to being unable to provide
certificate verification due to lack of CAs).

This patch moves the check for certificate verification being disabled
and changes the wording to distinguish it from other states. The
warning message is purposefully more dangerous sounding in order
to help discourage people from disabling security outright.

We may want to add a URL or hint to this message. I'm going to wait
until additional changes to security defaults before committing to
something.
2016-05-30 13:15:53 -07:00
Gregory Szorc
f84915da36 sslutil: add devel.disableloaddefaultcerts to disable CA loading
There are various tests for behavior when CA certs aren't loaded.
Previously, we would pass --insecure to disable loading of CA
certs. This has worked up to this point because the error message
for --insecure and no CAs loaded is the same. Upcoming commits will
change the error message for --insecure and will change behavior
when CAs aren't loaded.

This commit introduces the ability to disable loading of CA certs
by setting devel.disableloaddefaultcerts. This allows a testing
backdoor to disable loading of CA certs even if system/default
CA certs are available. The flag is purposefully not exposed to
end-users because there should not be a need for this in the wild:
certificate pinning and --insecure provide workarounds to disable
cert loading/validation.

Tests have been updated to use the new method. The variable used
to disable CA certs has been renamed because the method is not
OS X specific.
2016-06-01 19:57:20 -07:00
Gregory Szorc
46dd18b38d sslutil: store flag for whether cert verification is disabled
This patch effectively moves the ui.insecureconnections check to
_hostsettings(). After this patch, validatesocket() no longer uses the
ui instance for anything except writing messages.

This patch also enables us to introduce a per-host config option
for disabling certificate verification.
2016-05-30 11:20:31 -07:00
Gregory Szorc
f49cf73d42 sslutil: remove "strict" argument from validatesocket()
It was only used by mail.py as part of processing smtp.verifycert,
which was just removed.
2016-05-30 11:19:43 -07:00
Gregory Szorc
5ae8d037c8 sslutil: reference appropriate config section in messaging
Error messages reference the config section defining the host
fingerprint. Now that we have multiple sections where this config
setting could live, we need to point the user at the appropriate
one.

We default to the new "hostsecurity" section. But we will still
refer them to the "hostfingerprint" section if a value is defined
there.

There are some corner cases where the messaging might be off. e.g.
they could define a SHA-1 fingerprint in both sections. IMO the
messaging needs a massive overhaul. I plan to do this as part
of future refactoring to security settings.
2016-05-28 12:58:46 -07:00
Gregory Szorc
95fda4d981 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.
2016-05-28 12:37:36 -07:00
Gregory Szorc
c039b53ee9 sslutil: calculate host fingerprints from additional algorithms
Currently, we only support defining host fingerprints with SHA-1.
A future patch will introduce support for defining fingerprints
using other hashing algorithms. In preparation for that, we
rewrite the fingerprint verification code to support multiple
fingerprints, namely SHA-256 and SHA-512 fingerprints.

We still only display the SHA-1 fingerprint. We'll have to revisit
this code once we support defining fingerprints with other hash
functions.

As part of this, I snuck in a change to use range() instead of
xrange() because xrange() isn't necessary for such small values.
2016-05-28 11:58:28 -07:00
Gregory Szorc
e0771d90f9 sslutil: move CA file processing into _hostsettings()
The CA file processing code has been moved from _determinecertoptions
into _hostsettings(). As part of the move, the logic has been changed
slightly and the "cacerts" variable has been renamed to "cafile" to
match the argument used by SSLContext.load_verify_locations().

Since _determinecertoptions() no longer contains any meaningful
code, it has been removed.
2016-05-28 12:53:33 -07:00
Gregory Szorc
a2e2e628e9 sslutil: move SSLContext.verify_mode value into _hostsettings
_determinecertoptions() and _hostsettings() are redundant with each
other. _hostsettings() is used the flexible API we want.

We start the process of removing _determinecertoptions() by moving
some of the logic for the verify_mode value into _hostsettings().

As part of this, _determinecertoptions() now takes a settings dict
as its argument. This is technically API incompatible. But since
_determinecertoptions() came into existence a few days ago as part
of this release, I'm not flagging it as such.
2016-05-28 11:41:21 -07:00
Gregory Szorc
b5e0df781f 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.
2016-05-28 11:12:02 -07:00
Gregory Szorc
cafe3da00a sslutil: remove sslkwargs() (API)
It is now unused.
2016-05-25 19:57:31 -07:00
Gregory Szorc
32aa8e035d sslutil: move sslkwargs logic into internal function (API)
As the previous commit documented, sslkwargs() doesn't add any
value since its return is treated as a black box and proxied
to wrapsocket().

We formalize its uselessness by moving its logic into a
new, internal function and make sslkwargs() return an empty
dict.

The certificate arguments that sslkwargs specified have been
removed from wrapsocket() because they should no longer be
set.
2016-05-25 19:52:02 -07:00
Gregory Szorc
548b4e9e2c sslutil: remove ui from sslkwargs (API)
Arguments to sslutil.wrapsocket() are partially determined by
calling sslutil.sslkwargs(). This function receives a ui and
a hostname and determines what settings, if any, need to be
applied when the socket is wrapped.

Both the ui and hostname are passed into wrapsocket(). The
other arguments to wrapsocket() provided by sslkwargs() (ca_certs
and cert_reqs) are not looked at or modified anywhere outside
of sslutil.py. So, sslkwargs() doesn't need to exist as a
separate public API called before wrapsocket().

This commit starts the process of removing external consumers of
sslkwargs() by removing the "ui" key/argument from its return.
All callers now pass the ui argument explicitly.
2016-05-25 19:43:22 -07:00
Gregory Szorc
accb96048e sslutil: remove redundant check of sslsocket.cipher()
We are doing this check in both wrapsocket() and validatesocket().

The check was added to the validator in 8f98f4f9ff93 and the commit
message justifies the redundancy with a "might." The check in
wrapsocket() was added in 102733a3c3e1, which appears to be part of
the same series. I'm going to argue the redundancy isn't needed.

I choose to keep the check in wrapsocket() because it is working
around a bug in Python's wrap_socket() and I feel the check for
the bug should live next to the function call exhibiting the bug.
2016-05-15 11:50:49 -07:00
Gregory Szorc
6fd76860b1 sslutil: convert socket validation from a class to a function (API)
Now that the socket validator doesn't have any instance state,
we can make it a generic function.

The "validator" class has been converted into the "validatesocket"
function and all consumers have been updated.
2016-05-15 11:38:38 -07:00
Gregory Szorc
53550ebc63 sslutil: store and use hostname and ui in socket instance
Currently, we pass a hostname and ui to sslutil.wrap_socket()
then create a separate sslutil.validator instance also from
a hostname and ui. There is a 1:1 mapping between a wrapped
socket and a validator instance. This commit lays the groundwork
for making the validation function generic by storing the
hostname and ui instance in the state dict attached to the
socket instance and then using these variables in the
validator function.

Since the arguments to sslutil.validator.__init__ are no longer
used, we make them optional and make __init__ a no-op.
2016-05-15 11:32:11 -07:00
Gregory Szorc
286aa084ac sslutil: use a dict for hanging hg state off the wrapped socket
I plan on introducing more state on the socket instance. Instead
of using multiple variables, let's just use one to minimize risk
of name collision.
2016-05-15 11:25:07 -07:00
Gregory Szorc
c8b2685285 sslutil: require serverhostname argument (API)
All callers now specify it. So we can require it.

Requiring the argument means SNI will always work if supported
by Python.

The main reason for this change is to store state on the socket
instance to make the validation function generic. This will be
evident in subsequent commits.
2016-05-05 19:10:18 -07:00
Gregory Szorc
baf0fdcf97 sslutil: stop checking for web.cacerts=! (BC)
The previous patch stopped setting web.cacerts=! to indicate
--insecure.

That left user configs as the only source that could introduce
web.cacerts=!.

The practical impact of this patch is we no longer honor
web.cacerts=! in configs. Instead, we always treat web.cacerts
as a path. The patch is therefore technically BC. However,
since I don't believe web.cacerts=! is documented, it should be
safe to remove. 358b7bec186f (which introduced --insecure) has
no indication that web.cacerts=! is anything but an implementation
detail, reinforcing my belief it can be removed without major
debate.
2016-05-05 00:46:31 -07:00
Gregory Szorc
9295bebb69 sslutil: use CA loaded state to drive validation logic
Until now, sslkwargs may set web.cacerts=! to indicate
that system certs could not be found. This is really
obtuse because sslkwargs effectively sets state on a global
object which bypasses wrapsocket() and is later consulted
by validator.__call__. This is madness.

This patch introduces an attribute on the wrapped socket
instance indicating whether system CAs were loaded. We
can set this directly inside wrapsocket() because that
function knows everything that sslkwargs() does - and more.

With this attribute set on the socket, we refactor
validator.__call__ to use it.

Since we no longer have a need for setting web.cacerts=!
in sslkwargs, we remove that.

I think the new logic is much easier to understand and will
enable behavior to be changed more easily.
2016-05-05 00:38:18 -07:00
Gregory Szorc
7b654a4277 sslutil: handle ui.insecureconnections in validator
Right now, web.cacerts=! means one of two things:

1) Use of --insecure
2) No CAs could be found and were loaded (see sslkwargs)

This isn't very obvious and makes changing behavior of these
different scenarios independent of the other impossible.

This patch changes the validator code to explicit handle the
case of --insecure being used.

As the inline comment indicates, there is room to possibly change
messaging and logic here. For now, we are backwards compatible.
2016-05-05 00:37:28 -07:00
Gregory Szorc
efcc52cbeb sslutil: check for ui.insecureconnections in sslkwargs
The end result of this function is the same. We now have a more
explicit return branch.

We still keep the old code looking at web.cacerts=! a few lines
below because we're still setting web.cacerts=! and need to react
to the variable. This will be removed in an upcoming patch.
2016-05-05 00:35:45 -07:00
Gregory Szorc
b7af0f8d55 sslutil: make sslkwargs code even more explicit
The ways in which this code can interact with socket wrapping
and validation later are mind numbing. This patch helps make it
even more clear.

The end behavior should be identical.
2016-05-05 00:32:43 -07:00
Gregory Szorc
051832cb8b sslutil: move code examining _canloaddefaultcerts out of _defaultcacerts
Before, the return of _defaultcacerts() was 1 of 3 types. This was
difficult to read. Make it return a path or None.

We had to update hghave.py in the same patch because it was also
looking at this internal function. I wasted dozens of minutes
trying to figure out why tests were failing until I found the
code in hghave.py...
2016-05-04 23:38:34 -07:00
Gregory Szorc
8bc014a599 sslutil: further refactor sslkwargs
The logic here and what happens with web.cacerts is mind numbing.
Make the code even more explicit.
2016-05-04 23:01:49 -07:00
Gregory Szorc
4bc1239076 sslutil: document and slightly refactor sslkwargs
This will help me and any reviewers keep sane as this code
is refactored.
2016-05-05 00:31:11 -07:00
Gregory Szorc
ad77315d76 sslutil: restore old behavior not requiring a hostname argument (issue5210)
This effectively backs out changeset 60b56b3206cc.

The http library behind ui.http2=true isn't specifying the hostname.
It is the day before the expected 3.8 release and we don't want to ship
a regression.

I'll try to restore this requirement in the 3.9 release cycle as part
of planned improvements to Mercurial's SSL/TLS interactions.
2016-04-30 09:26:47 -07:00
Gregory Szorc
cec8c17960 sslutil: document and slightly refactor validation logic
This main purpose of this patch is to make it clearer that fingerprint
pinning takes precedence over CA verification. This will make
subsequent refactoring to the validation code easier to read.
2016-04-10 11:02:58 -07:00
Gregory Szorc
a8d6c2d5ca sslutil: require a server hostname when wrapping sockets (API)
All callers appear to be passing the hostname. So this shouldn't
break anything. By specifying the hostname, more validation options
from the ssl module are available to us. Although this patch stops
short of using them.
2016-04-10 11:00:41 -07:00
Gregory Szorc
fb3741de9b sslutil: move and document verify_mode assignment
Consolidating all the SSLContext options setting makes the code a
bit easier to read.
2016-04-10 10:59:45 -07:00
Gregory Szorc
b7edbc29ee sslutil: add docstring to wrapsocket()
Security should not be opaque.
2016-03-27 13:13:19 -07:00
Gregory Szorc
5ff877516b sslutil: remove indentation in wrapsocket declaration
It is no longer needed because we have a single code path.
2016-03-27 11:39:39 -07:00
Gregory Szorc
24502a149c sslutil: always use SSLContext
Now that we have a fake SSLContext instance, we can unify the code
paths for wrapping sockets to always use the SSLContext APIs.

Because this is security code, I've retained the try..except to
make the diff easier to read. It will be removed in the next patch.

I took the liberty of updating the inline docs about supported
protocols and how the constants work because this stuff is important
and needs to be explicitly documented.
2016-03-27 14:18:32 -07:00
Gregory Szorc
36e749aa82 sslutil: move _canloaddefaultcerts logic
We now have a newer block accessing SSLContext. Let's move this
code to make subsequent refactorings of the former block easier.
2016-03-27 14:08:52 -07:00
Gregory Szorc
93d8d2d711 sslutil: implement SSLContext class
Python <2.7.9 doesn't have a ssl.SSLContext class. In this patch,
we implement the interface to the class so we can have a unified
code path for all supported versions of Python.

This is similar to the approach that urllib3 takes.
2016-03-27 13:50:34 -07:00
Gregory Szorc
46d517af99 sslutil: store OP_NO_SSL* constants in module scope
An upcoming patch will introduce a global SSLContext type so we
have a single function used to wrap sockets. Prepare for that by
introducing module level constants for disabling SSLv2 and SSLv3.
2016-03-27 10:47:24 -07:00
Gregory Szorc
3f3e1d212c sslutil: better document state of security/ssl module
Pythons older than 2.7.9 are lacking the modern ssl module
and have horrible security. Let's document this explicitly.
2016-03-27 14:07:06 -07:00
Gregory Szorc
f57c8e82d3 sslutil: use preferred formatting for import syntax 2016-03-19 10:10:09 -07:00
Gregory Szorc
ab086daadb sslutil: allow multiple fingerprints per host
Certificate pinning via [hostfingerprints] is a useful security
feature. Currently, we only support one fingerprint per hostname.
This is simple but it fails in the real world:

* Switching certificates breaks clients until they change the
  pinned certificate fingerprint. This incurs client downtime
  and can require massive amounts of coordination to perform
  certificate changes.
* Some servers operate with multiple certificates on the same
  hostname.

This patch adds support for defining multiple certificate
fingerprints per host. This overcomes the deficiencies listed
above. I anticipate the primary use case of this feature will
be to define both the old and new certificate so a certificate
transition can occur with minimal interruption, so this scenario
has been called out in the help documentation.
2016-03-13 14:03:58 -07:00
Gábor Stefanik
75370c2faf sslutil: fix reversed logic (issue5034) 2016-01-08 16:27:25 +01:00
Gregory Szorc
6cc7d3daaa sslutil: expose attribute indicating whether SNI is supported
This will be used so clone bundles can advertise whether URLs require
SNI. This will be explained more in a subsequent patch.
2015-09-29 16:17:32 -07:00