New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for TLS session resumption in HTTPSConnection #1886
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1886 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 25 25
Lines 2290 2305 +15
=========================================
+ Hits 2290 2305 +15
Continue to review full report at Codecov.
|
Looks like the Tornado app server used in tests does not handle TLS session tickets correctly, not sure how to fix that. It is not accepting the session ticket in connnection2 that it sends in connection1. It's not only the unittests failing, but also Firefox and Chrome behave the same with this app server. The session resumption fails because the server does not accept the ticket, but the code seems to be working as intended (the ticket is sent in connection2) |
The codecov error is confusing.
|
@PleasantMachine9 The issue with coverage is that Travis CI did not run at all. Closing/reopening, hopefully that should fix the coverage issue. |
Travis tells me they fixed the issue. That was quick! Retrying. |
Is there someone in particular I should ask for review or should I just wait? |
Now that the tests pass, I'll try to take a first look at this, hopefully tomorrow. The overall approach sounds good, and indeed it's unlikely that we'll be able to test this automatically since this feature isn't supported by Tornado. Thanks for this pull request, by the way! |
One possibility could be using some external HTTPS server that does support & correctly handle session tickets, but that's also very fragile for a test, and unit tests needing external https access is probably a no-go. |
Thanks for starting this discussion, TLS resumption would be a fantastic addition to urllib3. FYI there are multiple instances where sessions should not be resumed for security reasons (eg: broken or indefinite response framing, differing auth) that should be investigated for this review. Should read relevant RFCs re HTTP+TLS and sessions and dump findings here. |
At least with the python/SSL library versions I've tried, if the session resumption fails, it's transparent (except for If the server does not support or accept the session ticket (due to expiry or other internal reasons it may have), it will continue with the full TLS handshake as if it never saw the session ticket being sent. Incidentally the Tornado test server is a good case study for this in the unit tests, as it completely ignores the sent TLS session ticket on the 2nd connection. The documentation in python3's ssl library is quite lacking on this |
Looks like there is also a way in the SSLContext to specify that we do not want a session ticket to be issued. By default (unless the users provide their own SSLContext) it's on, which means we will ask for a session ticket:
I will add that on as an improvement that we avoid asking for tickets if we don't have session resumption enabled and are using the default SSLContext. |
Other than what I mentioned I don't have a deep security background, and a lot of concerns you brought up are actually more for openssl than for the python code here. If such a thurough security check is needed on this then each openssl version would need to be inspected for its behavior. Aside from the RFC related to session tickets: https://tools.ietf.org/html/rfc5077 This has some more details on possible drawbacks https://blog.filippo.io/we-need-to-talk-about-session-tickets/ The following seem to be the major concerns: If the server has a bad impl. of session ticketing, and does not encrypt properly or does not rotate its keys properly, then Forward Secrecy is compromisedNot sure if the client code should implement workarounds for badly implemented servers here. I imagine this would need to be done on a case-by-case basis, or making the feature opt-in instead of opt-out. We fundamentally don't know if a session ticket is "good" because it's encrypted so it's opaque to us. If TLS 1.2 or earlier is used, the session ticket is sent in the clear, before Change Cipher Spec, so passive eavesdropping is easyMinor risk to have it in the clear in case the server implements key rotation properly. According to the RFC the session ticket should use strong encryption and the server should rotate its ticketing keys frequently.
Being able to tell that the same session is being resumed (ie. easy fingerprinting of TLS channels).I think this is not that big of a concern, as anyway the SNI and Firefox, Edge & Chrome all have session ticketing enabled by default on TLSv1.2, so as additional reasoning, others also think that the benefit of fast resumption is worth the risks. If security is the highest priority here then a compromise would be making it so it's only enabled by default if resuming a TLSv1.3 connection, however the vast majority of servers are not on 1.3 so that would disqualify a lot of use cases. |
Thanks for looking into this! https://blog.filippo.io/we-need-to-talk-about-session-tickets/ was an interesting read. I believe we should follow the lead of curl: curl/curl#3202. That is, allow session resumption only via session ids with TLS <= 1.2, and allow session resumption only via session tickets with TLS 1.3. @sethmlarson Thoughts? |
I'm okay with that suggestion as long as it will be something that can at least be explicitly enabled for TLSv1.2 (as that was my original use case). A few notes about such an approach though:
If this approach with 3 options is preferred, then some more thinking is needed on Option nr. 2. There are 2 possibilities I can think of right now is (if opt2 is selected policy, and
Do you like this enum-based solution to give options to library users? |
I remembered another interesting note: it seemed from the tests that If this is working as intended in TLSv1.3 (meaning the client cannot hint it does not want a ticket), then actually that simplifies the logic I detailed above a lot, because |
I couldn't figure out where in openssl this flag is used in the openssl codebase, I'm not great a C. At least from the TLSv1.3 RFC, it seems that the extension which was used on v1.2 to indicate whether the client wanted tickets is not mentioned, so I suppose that OP_NO_TICKET is pointless on v1.3. Actually from the RFC, it seems that session tickets and IDs are no longer distinguished in v1.3, and it is up to the server completely whether the ticket contains crypto data, or is just a database-id. In that case, the suggested logic for Opt2 is:
|
I uploaded a new commit with the skeleton of the suggestions I made above. Tests are not done for it yet, please let me know if you think this is a good approach overall, then I will continue with it in more depth. |
Maybe the I realized also there is one limitation of my current approach to note: this only works on a per- At least for the use-case I had in mind when making this, only supporting it on the |
@pquentin could you please check the travis hook again? I hope I'm not DoS'ing them. |
Don't worry, you're not DDoSing them! I'm not seeing failed requests here like before. Closing/reopening to see if that helps... (edit: it did, here is your run: https://travis-ci.org/github/urllib3/urllib3/builds/699571876) Anyway, we need to figure out what the best tradeoff here is between security, usability and maintainability. (I'm sorry that you wrote so much code already, but those questions need to be answered first.) It looks like adding policies for this feature is a lot of added complexity. I think I'm leaning towards allowing this on TLS 1.2 instead, if that's important for your use case. @sethmlarson @sigmavirus24 Thoughts? |
I can add another draft without this "policy" behavior (where the policy is basically the "ALWAYS" option). |
@sethmlarson Thoughts? |
@PleasantMachine9 Before we start writing code can we discuss within an issue what TLS session resumption by default in urllib3 would look like (where do tickets live, under which conditions do we attempt to resume sessions, what exact performance gains are we achieving by adding this support) and once we nail that down we can start a PR that accomplishes that. |
@sethmlarson I've opened one a little while ago: #1898 |
4cc2877
to
8f02599
Compare
Indicate with a simple flag kwarg whether this should be enabled. To test it in the wild, CPython 3.6+ and an SSL ticket/id issuing server is needed. This cannot be tested with current libraries in the repo, as tornado (which is the mock https server) does not seem to work properly with SSL tickets/ids. Below is a way to test it in the real world: import logging from urllib3.connectionpool import connection_from_url logging.basicConfig(level=logging.DEBUG) # example sites that support SSL resumption, at time of writing: urls = [ 'https://www.reddit.com/', 'https://www.twitch.tv/', 'https://twitter.com/', ] for url in urls: conn = connection_from_url(url) # by default, connection is kept alive, # for testing this, it's easier if it isn't: conn.request('GET', '/', headers={'Connection':'close'}) conn.request('GET', '/', headers={'Connection':'close'}) # should see: # DEBUG:urllib3.connection:For <...>: session_reused=True
8f02599
to
0461c49
Compare
Also since it was previously asked what my rationale is for this change, I can write a short summary about that. I'm using the https://github.com/streamlink/streamlink library. This library supports HLS streams, which are basically just a loop of HTTP What I have observed is that the edge servers for some video streaming sites do not always respect This causes When I compared this with a browser client (Firefox) I observed a similar behavior from the edge server (sending TCP FIN), but, as expected, browsers do resend the TLS 1.2 session ticket, unlike the pre-pull request revision of |
I haven't followed your discussions closely, but is this obsolete now that #1970 was merged? |
I mean, the two are separate things. If urllib is not using the tickets it makes no sense to request them. This commit would enable their use, so it's still viable. #1898 has the rest of the discussion. Overall I don't see why the PR should be rejected. There are just some unclear parts about what should or should not be done in scope of the change. From my perspective, trying to track/check if SSL sockets are shut down gracefully seems unnecessary, as I don't see any practical vulnerabilities that not doing so could introduce. The commit itself is actually working well for me, I have been using its locally applied version for a few months at this point. |
Explicitly passing the kwarg and it being None is not the same as passing a kwarg dict where it's not added when it is None.
@@ -116,6 +118,7 @@ def _const_compare_digest_backport(a, b): | |||
try: | |||
from ssl import SSLContext # Modern SSL? | |||
except ImportError: | |||
IS_SSL_SESSION_SUPPORTED = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should delete the other commit which disables session tickets.
The two don't conflict, this commit overwrites it in practice but it's not elegant.
I'll close this PR for now to stop clutering the board. For now, applying it as a local patch will do fine for me at least. |
Related to issue #590
If using the
ssl
module from the reference Python3 binding, theSSLSocket.session
attribute references a TLS session which may have a TLS session ticket/ID.See: https://docs.python.org/3.8/library/ssl.html#ssl-session (New in version 3.6.)
If the python version is below 3.6, then
getattr(sock, 'session', None)
should return None (inHTTPSConnection
), in which case we will not invokecontext.wrap_socket
with the extra new kwargssession
.Unfortunately the tests do not seem to work locally in my WSL env, and IPv6 does not work either.
Verified locally like this (for manual testing purposes):
Compared with Wireshark outputs:
I am unsure how to add test cases for session resumption, as the test libraries are very daunting (not to mention, an SSL server that sends TLS session tickets is needed). Any help/tips would be appreciated on that front.