Skip to content
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

does TLSAcceptor read early data? #70

Open
tahmid-23 opened this issue Apr 27, 2024 · 8 comments
Open

does TLSAcceptor read early data? #70

tahmid-23 opened this issue Apr 27, 2024 · 8 comments

Comments

@tahmid-23
Copy link

hi, I was wondering if TLSAcceptor is able to read TLS1.3 early data? it looks like the early data test does not use TLSAcceptor
if so, is there a reason why?

@cpu
Copy link
Member

cpu commented Apr 29, 2024

I think you could use TlsAcceptor::accept_with() to read early data from the ServerConnection provided to your callback FnOnce.

If you experiment and find that works a PR adding an example would be great 👍

@tahmid-23
Copy link
Author

I think I could try that.
but does it not make more sense to have it automatically read early data when you read from the stream? for parity with when the tls connector writes to the stream with early data enabled

@cpu
Copy link
Member

cpu commented Apr 29, 2024

does it not make more sense to have it automatically read early data when you read from the stream?

Early data reads must be handled separately from regular application data reads after the handshake completes. There are security implications for early data reads that require extra care. See RFC 8446 Appendix E.5 and RFC 8470 Section 3 for more information.

@tahmid-23
Copy link
Author

I was writing a CTF challenge on this so that's why I'm interested 🙂 but after thinking about it I realize why it's better to always separate them

  1. Here's an initial attempt
#[tokio::main]
async fn main() {
    let certs = rustls_pemfile::certs(&mut std::fs::read("localhost.direct.crt").unwrap().as_slice())
        .map(Result::unwrap)
        .collect();
    let key = rustls_pemfile::private_key(&mut std::fs::read("localhost.direct.key").unwrap().as_slice())
        .unwrap()
        .unwrap();

    let tcp_listener = TcpListener::bind("0.0.0.0:8000").await.unwrap();
    let server_config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap();
    let tls_acceptor = TlsAcceptor::from(Arc::new(server_config));

    let server_task = tokio::spawn(async move {
        let (tcp_stream, _) = tcp_listener.accept().await.unwrap();
        tls_acceptor.accept_with(tcp_stream, |connection| {
            println!("{:?}", connection.early_data().is_some());
        }).await.unwrap();
    });

    server_task.await.unwrap();
}

If I run nc localhost 8000, this will immediately print False. Since early data clearly hasn't been sent at this point I imagine it's inappropriate to read early data here. Is there some other way to know that the stream has reached early data without waiting for the handshake?

  1. Early data is io::Read. Can you confirm whether that means that not all early data will become available at the same time? If so, wouldn't it make sense to have some async way to read the early data, rather than relying on the sync api?

@cpu
Copy link
Member

cpu commented May 1, 2024

I was writing a CTF challenge on this so that's why I'm interested 🙂

Cool :-)

If I run nc localhost 8000, this will immediately print False. Since early data clearly hasn't been sent at this point I imagine it's inappropriate to read early data here. Is there some other way to know that the stream has reached early data without waiting for the handshake?

It should be appropriate to read early data here. You don't need to wait for the full handshake to complete, just for the peer to have sent a full client hello.

I think the reason you always get None from early_data() is that you need to customize your server config to set the max_early_data_size to a non-zero value:

 let mut server_config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap();
 server_config.max_early_data_size = xxxx;        

Early data is io::Read. Can you confirm whether that means that not all early data will become available at the same time? If so, wouldn't it make sense to have some async way to read the early data, rather than relying on the sync api?

I'm not sure off-hand. Someone more familiar with Tokio Rustls might have a better suggestion here.

@leptonyu
Copy link

leptonyu commented Oct 2, 2024

I use following code successfully, accept_with() does not help. This means we need to read early data after server side handshake.

        let mut stream = self.acceptor.accept(stream).await?;
        let buf = if let Some(mut ed) = stream.get_mut().1.early_data() {
            let mut buf = Vec::new();
            ed.read_to_end(&mut buf)?;
            Bytes::from(buf)
        } else {
            Bytes::new()
        };
        Ok(PeekableStream::new(stream, buf))

@valpackett
Copy link

I have played around with this before.. Basically we have to start with something like this:

diff --git i/src/common/handshake.rs w/src/common/handshake.rs
index d1272fd..dd75860 100644
--- i/src/common/handshake.rs
+++ w/src/common/handshake.rs
@@ -88,6 +88,12 @@ where
             }
 
             try_poll!(Pin::new(&mut tls_stream).poll_flush(cx));
+
+            if let Some(mut data) = session.early_data() {
+                let buf = Vec::new();
+                data.read_to_end(&mut buf).unwrap();
+                *state = TlsState::EarlyData(0, buf);
+            }
         }
 
         Poll::Ready(Ok(stream))

and then expose a way to read that early data to the application

@quininer
Copy link
Member

quininer commented Nov 7, 2024

I think we can expose a fn get_session() -> Option<&mut Session> in Accept

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants