Security implications for TLS validation post handshake

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Security implications for TLS validation post handshake

Gregory Szorc-3
Context:

Python has a long and sad history with regards to getting connection
security right. Modern versions of Python (>=2.7.9 and >=3.6) have a vastly
better story. But software often needs to handle what happens when running
on older versions of Python in the wild or else connection security could
be compromised. I'm trying to understand the security implications of the
interaction between Python <2.7.9 and TLS so I don't inadvertently roll out
insecure software.

The way you specify the desired TLS protocol version (which is heavily
inspired by OpenSSL's API) is to pass a protocol constant along with some
more options to control ciphers, protocol options (like compression), etc.
If you want to require TLS 1.2+, you use SSLv23 and then mask out older
protocols. e.g. ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 |
ssl.OP_NO_TLSv1_1.

Python versions before 2.7.9 lacked controls necessary to ensure optimal
security. For example, Python didn't expose constants to force TLS versions
>1.0. Instead, you had to use PROTOCOL_TLSv1 (the latest available
constant) and force TLS 1.0. Or, you used SSLv23 (masking out SSL v2 and v3
of course) and hoped the underlying crypto library can negotiate TLS >1.0.

The Problem:

I'm very naive about how TLS libraries are implemented and how the TLS
handshake works. But it seems to me that software establishing secure
connections can generally perform pre or post filtering. In
"pre-filtering," the ClientHello message only advertises ciphers/protocols
that we want to use. In "post-filtering," you advertise a more liberal list
of ciphers and depending on the negotiation results/security, you continue
or drop the connection.

Again, I'm naive, but it feels like pre-filtering is better because it
eliminates surface area for e.g. downgrade attacks. However - and this is
where the problem resides - Python <2.7.9 doesn't exactly give you the
requisite tools for adequate pre-filtering. Since the constants aren't
there, you have to use PROTOCOL_SSLv23 and "hope" that a TLS >1.0
connection is established.

Question:

Python exposes the negotiated TLS protocol version and cipher info post TLS
handshake (results of OpenSSL's SSL_get_version() and
SSL_get_current_cipher() functions). So it is possible to examine these
values to determine whether to proceed with the connection. My question is:
what are the dangers or concerns in doing so? I'm assuming there's a
surface area of downgrade-type attacks in play. But I'm not sure the
specifics.

e.g. on Python <2.7.9, the best we can do is use PROTOCOL_SSLv23 and "hope"
the underlying crypto library is able to negotiate TLS >1.0. But this will
advertise protocols and ciphers for TLS 1.0+ in ClientHello. I don't think
this is ideal: I think I'd prefer to not advertise client support for TLS
1.0 (and even 1.1) support at all if there is no intent on speaking these
older (and known vulnerable) protocols.

If you aren't able to limit the advertisement of TLS 1.0 and 1.1 protocols
from the client, is it safe to validate the TLS-level security from
negotiated protocol and cipher info? Is the TLS protocol version itself
sufficient or does it need to be supplemented with e.g. a "safe" list of
ciphers?
--
dev-tech-crypto mailing list
[hidden email]
https://lists.mozilla.org/listinfo/dev-tech-crypto
Reply | Threaded
Open this post in threaded view
|

Re: Security implications for TLS validation post handshake

Alex Gaynor-2
Hi Gregory,

Using PROTOCOL_SSLv23 with OP_NO_SSLv2 | OP_NO_SSL3 | OP_NO_TLSv1 |
OP_NO_TLSv1_1 is the correct way to do things in Python (of all versions)
-- as you note the OP_NO_TLSv1_1/1_2 constants aren't available in older
Pythons though.

Luckily (unluckily?) these constants are really just integers we directly
expose from OpenSSL, so it's safe to just hardcode the integer values, I
don't believe OpenSSL has ever changed them. (If you're feeling
particularly paranoid you can check that the OpenSSL version matches
particular ABIs you've checked and only use the hardcoded values there). A
bit ugly, yes, but should work fine.

Ciphersuites are a slightly more interesting case. The biggest issue I see
with sending a very liberal ClientHello and then erroring out based on
what's negotiated is that it fails more often than necessary. Specifically,
it fails whenever the server prioritizes accepting something nonsensical
from the client. Unfortunately tons of servers have silly configurations;
where they support both good and bad ciphers, and prioritize the bad ones!

Alex

On Wed, Oct 18, 2017 at 5:27 AM, Gregory Szorc <[hidden email]> wrote:

> Context:
>
> Python has a long and sad history with regards to getting connection
> security right. Modern versions of Python (>=2.7.9 and >=3.6) have a vastly
> better story. But software often needs to handle what happens when running
> on older versions of Python in the wild or else connection security could
> be compromised. I'm trying to understand the security implications of the
> interaction between Python <2.7.9 and TLS so I don't inadvertently roll out
> insecure software.
>
> The way you specify the desired TLS protocol version (which is heavily
> inspired by OpenSSL's API) is to pass a protocol constant along with some
> more options to control ciphers, protocol options (like compression), etc.
> If you want to require TLS 1.2+, you use SSLv23 and then mask out older
> protocols. e.g. ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 |
> ssl.OP_NO_TLSv1_1.
>
> Python versions before 2.7.9 lacked controls necessary to ensure optimal
> security. For example, Python didn't expose constants to force TLS versions
> >1.0. Instead, you had to use PROTOCOL_TLSv1 (the latest available
> constant) and force TLS 1.0. Or, you used SSLv23 (masking out SSL v2 and v3
> of course) and hoped the underlying crypto library can negotiate TLS >1.0.
>
> The Problem:
>
> I'm very naive about how TLS libraries are implemented and how the TLS
> handshake works. But it seems to me that software establishing secure
> connections can generally perform pre or post filtering. In
> "pre-filtering," the ClientHello message only advertises ciphers/protocols
> that we want to use. In "post-filtering," you advertise a more liberal list
> of ciphers and depending on the negotiation results/security, you continue
> or drop the connection.
>
> Again, I'm naive, but it feels like pre-filtering is better because it
> eliminates surface area for e.g. downgrade attacks. However - and this is
> where the problem resides - Python <2.7.9 doesn't exactly give you the
> requisite tools for adequate pre-filtering. Since the constants aren't
> there, you have to use PROTOCOL_SSLv23 and "hope" that a TLS >1.0
> connection is established.
>
> Question:
>
> Python exposes the negotiated TLS protocol version and cipher info post TLS
> handshake (results of OpenSSL's SSL_get_version() and
> SSL_get_current_cipher() functions). So it is possible to examine these
> values to determine whether to proceed with the connection. My question is:
> what are the dangers or concerns in doing so? I'm assuming there's a
> surface area of downgrade-type attacks in play. But I'm not sure the
> specifics.
>
> e.g. on Python <2.7.9, the best we can do is use PROTOCOL_SSLv23 and "hope"
> the underlying crypto library is able to negotiate TLS >1.0. But this will
> advertise protocols and ciphers for TLS 1.0+ in ClientHello. I don't think
> this is ideal: I think I'd prefer to not advertise client support for TLS
> 1.0 (and even 1.1) support at all if there is no intent on speaking these
> older (and known vulnerable) protocols.
>
> If you aren't able to limit the advertisement of TLS 1.0 and 1.1 protocols
> from the client, is it safe to validate the TLS-level security from
> negotiated protocol and cipher info? Is the TLS protocol version itself
> sufficient or does it need to be supplemented with e.g. a "safe" list of
> ciphers?
> --
> dev-tech-crypto mailing list
> [hidden email]
> https://lists.mozilla.org/listinfo/dev-tech-crypto
>
--
dev-tech-crypto mailing list
[hidden email]
https://lists.mozilla.org/listinfo/dev-tech-crypto
Reply | Threaded
Open this post in threaded view
|

Re: Security implications for TLS validation post handshake

Kurt Roeckx
In reply to this post by Gregory Szorc-3
On Wed, Oct 18, 2017 at 11:27:45AM +0200, Gregory Szorc wrote:
> The way you specify the desired TLS protocol version (which is heavily
> inspired by OpenSSL's API) is to pass a protocol constant along with some
> more options to control ciphers, protocol options (like compression), etc.
> If you want to require TLS 1.2+, you use SSLv23 and then mask out older
> protocols. e.g. ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 |
> ssl.OP_NO_TLSv1_1.

OpenSSL 1.1.0 added a new way to do this, setting a minimum and
maximum supported version, and at the same time deprecated the use
of the flags. I suggest you do something like that in python too.

It should be easy enough to turn the mimimum the version in a list
of flags for version that don't support the minimum version.

> Python versions before 2.7.9 lacked controls necessary to ensure optimal
> security. For example, Python didn't expose constants to force TLS versions
> >1.0. Instead, you had to use PROTOCOL_TLSv1 (the latest available
> constant) and force TLS 1.0. Or, you used SSLv23 (masking out SSL v2 and v3
> of course) and hoped the underlying crypto library can negotiate TLS >1.0.

Python deprecated PROTOCOL_TLSv1 since version 2.7.13. (It only
allowed TLS 1.0, people have been confused by the SSLv23 name
which is why OpenSSL 1.1.0 deprecated that and other names.)

> Again, I'm naive, but it feels like pre-filtering is better because it
> eliminates surface area for e.g. downgrade attacks. However - and this is
> where the problem resides - Python <2.7.9 doesn't exactly give you the
> requisite tools for adequate pre-filtering. Since the constants aren't
> there, you have to use PROTOCOL_SSLv23 and "hope" that a TLS >1.0
> connection is established.

The flags mentioned above did move the check for the version from
python to at least OpenSSL.

> Question:
>
> Python exposes the negotiated TLS protocol version and cipher info post TLS
> handshake (results of OpenSSL's SSL_get_version() and
> SSL_get_current_cipher() functions). So it is possible to examine these
> values to determine whether to proceed with the connection. My question is:
> what are the dangers or concerns in doing so? I'm assuming there's a
> surface area of downgrade-type attacks in play. But I'm not sure the
> specifics.
>
> e.g. on Python <2.7.9, the best we can do is use PROTOCOL_SSLv23 and "hope"
> the underlying crypto library is able to negotiate TLS >1.0. But this will
> advertise protocols and ciphers for TLS 1.0+ in ClientHello. I don't think
> this is ideal: I think I'd prefer to not advertise client support for TLS
> 1.0 (and even 1.1) support at all if there is no intent on speaking these
> older (and known vulnerable) protocols.

With TLS up to 1.2 you just indicate the highest supported verion,
you don't have a list of supported version. With TLS 1.3 you
actually send a list of versions you support, and using the
minimum version (and flags) will limit the versions that are send
as supported.


Kurt

--
dev-tech-crypto mailing list
[hidden email]
https://lists.mozilla.org/listinfo/dev-tech-crypto
Reply | Threaded
Open this post in threaded view
|

Re: Security implications for TLS validation post handshake

Martin Thomson
In reply to this post by Gregory Szorc-3
I think that Alex and Kurt partially answered your questions.

On Wed, Oct 18, 2017 at 8:27 PM, Gregory Szorc <[hidden email]> wrote:
> I'm very naive about how TLS libraries are implemented and how the TLS
> handshake works.

The basic design is that the client decides what to offer and then the
server picks.  You can decide to offer fewer options, which risks the
server having no good option to choose from.   As you suggested, you
could let the server pick from a larger set and then decide
afterwards, but that's wasteful and probably unnecessary except for
version negotiation.

As Kurt points out, version negotiation works differently (except in
TLS 1.3, but if we could rely on that being available, we wouldn't be
having this discussion).  For versions you offer version X, which
implies support for all versions up to that value.  If the server
picks a value that is too low, you have to drop the connection
afterwards.  An API for that is now commonplace in TLS stacks, but it
might not be available to you.  Patching Python to use that API isn't
an insane path to choose, but I wouldn't depend on that.

The two things you need to think about are versions and ciphersuites.
You need to think about both unfortunately, because willingness to use
particular ciphersuites can lead to version downgrades.

> Question:
>
> Python exposes the negotiated TLS protocol version and cipher info post TLS
> handshake (results of OpenSSL's SSL_get_version() and
> SSL_get_current_cipher() functions). So it is possible to examine these
> values to determine whether to proceed with the connection. My question is:
> what are the dangers or concerns in doing so? I'm assuming there's a
> surface area of downgrade-type attacks in play. But I'm not sure the
> specifics.

If you have the option to use the OpenSSL APIs that Kurt was talking
about, that is ideal.  Tell the stack what you want and let it handle
things.

If you want to know what a "good" configuration looks like, starting
with something like what Firefox uses is fine, but we do a few
inadvisable things in the name of compatibility, so get someone who
knows the space to review the specifics of your plan.  (The only thing
I'm aware of is that we offer a 3DES suite, which you really don't
want.)

As for downgrade, if you have a policy and stick to it, downgrade
attacks are contained.  Rule of thumb: don't change your position
about anything, especially based on stuff that you get from the
network.  Again, ask for help here to be sure.

> If you aren't able to limit the advertisement of TLS 1.0 and 1.1 protocols
> from the client, is it safe to validate the TLS-level security from
> negotiated protocol and cipher info? Is the TLS protocol version itself
> sufficient or does it need to be supplemented with e.g. a "safe" list of
> ciphers?

You definitely need a policy for both versions and ciphers.  There are
also a bunch of other recommendations here:
https://tools.ietf.org/html/rfc7525, but you will want a translator
for that.

If the only API surface you have appears after the connection is
established, as long as you don't *use* the connection (or anything
you get from it) before checking that the result is OK, then you are
safe.  We do this for HTTP/2 in Firefox; you just have to be careful
not to accidentally use the connection.
--
dev-tech-crypto mailing list
[hidden email]
https://lists.mozilla.org/listinfo/dev-tech-crypto