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

Supporting QUIC #392

Closed
2 tasks done
kazu-yamamoto opened this issue Aug 8, 2019 · 11 comments
Closed
2 tasks done

Supporting QUIC #392

kazu-yamamoto opened this issue Aug 8, 2019 · 11 comments

Comments

@kazu-yamamoto
Copy link
Collaborator

kazu-yamamoto commented Aug 8, 2019

QUIC requires TLS 1.3 without the transport feature.
To support QUIC, a lot of refactorings and new APIs are necessary.
I'm thinking 4 steps to merge my QUIC implemenatation:

Edited: the approach was changed. The new approach is found in #419.

@ocheron
Copy link
Contributor

ocheron commented Oct 6, 2019

From a look at draft-ietf-quic-tls-23 it seems this is all about using a different record layer.

I'm wondering if it is possible to push more into infrastructure code instead of duplicating handshake code. Especially:

  • Making the interface between QUIC and TLS more general. i.e. it would exchange any sequence of messages and the key updates, and remain driven by tls for the entire handshake sequence.
  • Using a callback design, similar to the Backend API, but one level above (before record protection).

Having a more general API is desirable because it could be used or tested independently of QUIC. We need to be able to maintain and refactor for TLS without impacting QUIC.

I'm also wondering what is the story for HelloRetryRequest.

@kazu-yamamoto
Copy link
Collaborator Author

From a look at draft-ietf-quic-tls-23 it seems this is all about using a different record layer.

Yes. Historically speaking, old drafts designed to use backend with the TLS record layer. Since it is inefficient, the current design was taken.

I'm wondering if it is possible to push more into infrastructure code instead of duplicating handshake code. Especially:

  • Making the interface between QUIC and TLS more general. i.e. it would exchange any sequence of messages and the key updates, and remain driven by tls for the entire handshake sequence.
  • Using a callback design, similar to the Backend API, but one level above (before record protection).

Having a more general API is desirable because it could be used or tested independently of QUIC. We need to be able to maintain and refactor for TLS without impacting QUIC.

Did you suggest the following?

  • Introducing a new backend for record layer.
    • An encoder encrypts payload and put it into a specific record.
    • An decoder extracts payload from the record and decrypts it.
  • For QUIC
    • Implementing IO backend using packet queues or someting (to not send to/receive from the outside)
    • Implementing record layer backend carrying out only encryption and decryption. The record layer is empty.

I'm also wondering what is the story for HelloRetryRequest.

Good point. Since quicly sends HRR in the handshake phase, I should support it quickly. :-)

@kazu-yamamoto
Copy link
Collaborator Author

If a QUIC client calls handshake, it should be return both CH and CF separately. An example implementation is:

  • A caller spawns a new Haskell thread to execute handshake.
  • The caller receives CH which handshake sends via backendSend.
  • The caller gives SH..SF so that the thread can receive them via backendRecv
  • The caller receives CF which handshake sends via backendSend.

Is this what you want?

@ocheron
Copy link
Contributor

ocheron commented Oct 9, 2019

I cited Backend as example of pluggable module API we already have. If interface between QUIC and TLS needs to exchange packet plaintext, it should not be backendSend/backendRecv but a higher-level API dealing with something like Record Plaintext or even Packet(13). QUIC transport replaces the record layer and performs encryption/decryption with cipher and keys provided by TLS.

I don't know which side is supposed to take care of packet fragmentation.

What I don't understand too is the call model that is actually required. Does QUIC need to pull a Client Hello out of TLS in its calling thread, or is it possible to push the Client Hello to QUIC from an external thread executing handshake.

@kazu-yamamoto
Copy link
Collaborator Author

I'm now exploring your idea:

https://github.com/kazu-yamamoto/hs-tls/tree/record-layer

I will bring my experience here soon.

@kazu-yamamoto
Copy link
Collaborator Author

I don't know which side is supposed to take care of packet fragmentation.

It is a job of QUIC CRYPTO frame which provides the offset field.

@kazu-yamamoto
Copy link
Collaborator Author

kazu-yamamoto commented Oct 24, 2019

Good news: the new approach works elegantly.

  1. Approach one: dividing handshake functions to make them transport-free
    • Pros: APIs are synchronous, so easy-to-use. No additional Haskell thread is necessary.
    • Cons: we need to provide many APIs for cases such as HRR and NST
  2. Approach two: introducing a record layer
    • Pros: we can reuse the handshake code without drastic modifications. So, HRR and NST can be implemented free
    • Cons: we need to spawn a Haskell thread for negotiation. So, APIs are asynchronous and hard-to-use
  3. Appoarch three: introducing handshake controller for synchronization in addition to the record layer
    • Pros: no drastic modification, HRR and NST are free, easy-to-use APIs and Haskell thread can be terminated safely
    • Cons: we need to spawn a Haskell thread for negotiation.

I think the approach 3) is promising. In this sample code, a client and a server negotiates with HRR and NST. I also confirmed that this APIs works well for my QUIC client.

  • https://github.com/kazu-yamamoto/hs-tls/tree/send-recv contains refactoring for Sending and Receiving. They were not symmetric. For instance, Sending uses engage stuff but disengage is used in IO. This branch refactors them and IO symmetrically.
  • The commit of introducing record layer introduces the record layer. My conclusion is:
data RecordLayer = RecordLayer {
    -- Sending.hs and Sending13.hs
    encodeRecord :: Record Plaintext -> IO (Either TLSError ByteString)
    -- IO.hs
  , sendBytes    :: ByteString -> IO ()
    -- IO.hs
  , recvRecord   :: IO (Either TLSError (Record Plaintext))
  }

I don't have to hurry for merging since these branches are not drastic changes. But I would like to merge send-recv as soon as possible to make my work easy.

Any comments?

@kazu-yamamoto
Copy link
Collaborator Author

record-layer and handshake-controller were rebased onto master.

@ocheron
Copy link
Contributor

ocheron commented Dec 1, 2019

Cons: we need to spawn a Haskell thread for negotiation.

How much of an issue is this in practice?

With some effort, I think the encoding I gave in #380 could be extended and then provide
synchronous execution while keeping the existing code structure.

@kazu-yamamoto
Copy link
Collaborator Author

How much of an issue is this in practice?

I'm not sure. Probably, its cost is cheap.

With some effort, I think the encoding I gave in #380 could be extended and then provide
synchronous execution while keeping the existing code structure.

I don't understand this idea completely. Anyway, I'm satisfied with the current implementation: https://github.com/kazu-yamamoto/hs-tls/tree/handshake-controller

This code was tested with other QUIC implementations.

I have spent much time to implement this and would like to spend time to explore other stuffs of QUIC than the handshake. Would you compare my implementation and your idea by yourself, if necessary?

@ocheron
Copy link
Contributor

ocheron commented Apr 26, 2020

Todo items identified after reviewing #419, will work on this over time:

  • Add documentation to the QUIC module, possibly taking content from my notes → Document the QUIC module #428
  • Allow arbitrary exceptions to go across layers, i.e. QUIC record layer should be able to throw a QUICError and get it back as the other end wrapped in an TLSException, not converted to String.
  • See if it is possible to avoid repeating the TLS cipher in the SecretInfo data types. Similarly, handshake mode and negotiated protocol could be available from the TLS context through API.
  • Verify if the new handshake ACK logic gives expected result. Unclear if the frame should be sent before or after new receive. → Better generation of handshake ACKs kazu-yamamoto/quic#4
  • Verify if quic IORefs modified by the TLS to QUIC callbacks need atomic modify or not. → Use compare-and-swap when modifying secret info kazu-yamamoto/quic#5

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

2 participants