python-trio / python-trio/trio

Work around openssl's weird SSL_shutdown semantics by calling SSL_read during shutdown

Open
#536 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

design discussion polish TLS
Dominant language
Python
Stars
7.3k
Forks
431
Avg merge
2d 17h
Merged PRs (30d)
6

Description

So apparently openssl's SSL_shutdown semantics are even more convoluted than I realized:

https://bugs.python.org/issue30437#msg317028
https://github.com/openssl/openssl/issues/6262#issuecomment-389987860

There are two issues, which interact:

When SSL_shutdown is called a second time to read from the socket looking for a close_notify from the peer, it actually errors out if it sees something that isn't close_notify. Which of course can happen, because the other side could be doing anything, we don't know. And when this happens, the error reported is "syscall error", which makes no sense but at least explains this (also) Apparently the recommendation in this case is to call SSL_read to drain the socket of actual data / other control messages, until it reports EOF (and then you know you got a close_notify).

In TLS 1.3, session tickets are sent from the server to the client as control mesages right after the handshake completes. This means that if a client connects, sends some data, and then disconnects without calling SSL_read, they never get the session tickets. If you then try to do a full clean shutdown, this means you will trigger the SSL_shutdown issue, and need to call SSL_read to clear it, so it's relevant to the above.

But, it also means that you kind of want to call SSL_read always, because reading session tickets is important – it lets you save a round-trip on your next handshake. ...maybe this should be a separate issue? I'm still confused and don't anticipate dealing with any of this immediately so I'm going to leave it here for now. Anyway, what this means is that maybe before shutting down your TLS connection you always want to call SSL_read a few times, just while there's data to process, so you don't miss reading session tickets. And fixing the full-SSL_shutdown case as described above won't actually help with this, because in normal usage we only ever call SSL_shutdown once.

But annoyingly, we can't do this automatically, or at all really, because of the requirement to only read the data that's already arrived, without blocking. The Stream interface doesn't have a receive_some_nowait method, and that's what SSLStream would need to call.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with trio/_ssl.py lines 768-776 and the linked OpenSSL and Python discussions. Then inspect the Stream and SSLStream interfaces, especially the lack of a nonblocking receive_some_nowait operation. The issue is resolved only after the shutdown and TLS session-ticket behavior has a clear, agreed scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
networking
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.