Original text:
4.1.2. <del> In this document, the TLS handshake is considered confirmed at an endpoint when the following two conditions are met: the handshake is complete, and the endpoint has received an acknowledgment for a packet sent with 1-RTT keys.  This second condition can be implemented by recording the lowest packet number sent with 1-RTT keys, and the highest value of the Largest Acknowledged field in any received 1-RTT ACK frame: once the latter is higher than or equal to the former, the handshake is confirmed. </del>  4.1.3. 4.10.2. <del> An endpoint MUST NOT discard its handshake keys until the TLS handshake is confirmed (handshake-confirmed).  An endpoint SHOULD discard its handshake keys as soon as it has confirmed the handshake. Most application protocols will send data after the handshake, resulting in acknowledgements that allow both endpoints to discard their handshake keys promptly.  Endpoints that do not have reason to send immediately after completing the handshake MAY send ack- eliciting frames, such as PING, which will cause the handshake to be confirmed when they are acknowledged. </del>  4.10.3.

Reviewer comments:
I actually like this better than . It is simpler. NAME and I discussed the point that this solution might cause endpoints to lose some RTT and congestion signals since not all ACKs need to be received, and we believe that is ok. It might be useful to recommend that endpoints bundle ACKs when sending initial and handshake packets to allow the peer to get RTT signals as early and frequently as possible at the start of the connection, but that can be done separately.
Do we need a concept of handshake fully confirmed server side once HSD has been ACK'ed? For example to say that path migration cannot start before handshake is really complete and likewise key updates.
I like the simplicity of this proposal. The main downside I see is that it's possible for a server to never send HANDSHAKEDONE. We could solve that however, by saying: the server MUST send HANDSHAKEDONE in all its 1-RTT packets once its handshake is complete and until one of them is ACKed if the client receives an ACK for a 1-RTT packet it sent and has not seen HANDSHAKEDONE, it MUST close the connection with a PROTOCOLVIOLATION
I don't think that we need clients to be policing server behaviour. Especially if it requires creating abnormal sending policies for frames.
I tend to agree with NAME here. I do not think we should make the protocol complex just for enforcing the correct behavior. Regarding clients using the receipt of an ACK for a 1-RTT packet as the alternative signal, I agree that that'd work, but I am not sure if we need to write it down. Writing it down (or mandating it) gives less incentive for the server to send HANDSHAKE_DONE.
PS. The approach proposed in this PR has the following two properties: sending one-way signal from to the client, when the server reaches handshake complete use a frame as the conveyor of the signal It's good to see emerging consensus (on the first point), but it seems to me that some are concerned about the second point. Specifically, I see suggestions to use a continuous signal (URL), or an ACK as a signal too (see URL). If we are in fact unhappy with using a frame, then I might suggest using Key Update as the conveyor of the one-way signal. We can require the server to initiate a Key Update when the handshake is complete and to repeatedly send PINGs until it receives an ACK for one of them. That'd give us an continuous signal, and also removes the need for an alternative signal. And also the need to have a different frame.
NAME I think that 1-RTT packets could be sent from the server before the handshake is confirmed in which case the HSD frame cannot be sent. Or if that is not the case, then any 1-RTT packet could be used as a HSD signal and we wouldn't need the frame. But , I think it is a good idea to keep sending HSD frames in every packet until done because if the HSD frame is lost, there could be unnecessary delays. As to client policing, that wouldn't work due to the above because of loss and reordering.
While I am not too keen on NAME key update because it conflates issues, it has the one benefit that it uses a header bit for a continous signal.
I also had this thought, but flipping the key update bit doesn't force something to be sent until it's acknowledged, which this frame does. We could do both if we thought it was very important for every packet to have the signal, but I'm not convinced it's worthwhile.
The part I care about here is that we disallow some protocol features (such as migration) until the handshake is confirmed. So I want to make sure the server cannot prevent the client from confirming the handshake. If we explicitly write that the client receiving an ACK for a 1-RTT packet can treat that like receipt of HANDSHAKE_DONE, then I'm fine with this.
Looks great!
NAME see URL
Catching up on old PR's: The text says that the client can consider the handshake done when receiving a handshake done frame. Such a frame can only be sent in 1-RTT and the server can only send it when it completes the handshake. However, a MITM could inject a handshake done packet earlier in the flow. Even if the client is required to reject early handshake done frames, it might be a good idea to make it explicit that a handshake done packet MUST NOT be trusted before 1-RTT.
MITM or not, the client will terminate the connection with a Protocol Violation error if it receives a Handshake Done frame before sending the last handshake frame. We spent time debugging that during the Zurich interop.
At the moment, each endpoint confirms the handshake by it's own. That means that, a client might confirm the handshake and initiate a migration and that migration might complete while the server is transmitting Handshake packets, in case the 1-RTT acks sent by the client is repeatedly lost. This is a possibility, as we do not require 1-RTT acks to be bundled with packets carrying PATHCHALLENGE or PATHRESPONSE frames. However, we do not specify if the path used for sending Handshake packets migrate or continue to use the old path. In case it is the former, we also need to specify how the server fills in the SCID field of the Handshake packets it sends. Credit: NAME
I think there is a key principle we've previously agreed upon. The 5-tuple doesn't change during the handshake. That means we should not send Handshake packets down the new path.
NAME I agree. Assuming that is the case, we need to define a mechanism that allows the server to drop Handshake keys and the path before migration completes, or even better before the sends the first packet in response to a migration attempt. Otherwise, the server would be required to have a special-case for handle the original path. Proposed solution is .
I think this comes back to a lot of what NAME was bringing up during the interim -- there are other things that rely on having a explicit signal for the handshake being entirely complete, and this is one of them. Having a well-defined moment where we're entirely done and can move on from Handshake packets would make this a lot nicer.
NAME this appears to be addressed by , like the others in this cluster. Is that right?
NAME Yes. That's correct.
There's a loss pattern that QUIC cannot recover from: Assume client and server perform a handshake up to the point just after the server received the ClientFinished message. No packet loss happens so far. Both peers have now completed (but not yet confirmed) the handshake. Right after sending the ClientFinished, the client sends a 1-RTT packet (e.g. containing application data). This packet is lost. The server sends an acknowledgement for the ClientFinished (in a Handshake packet), and this packet is lost as well. Furthermore, the server also sends a 1-RTT packet (e.g. containing a session ticket). This packet is successfully received and acknowledged by the client. At this point the endpoint are in the following state: Since it received an acknowledgement for a packet sent with 1-RTT keys, the server now confirmed the handshake, and drops the Handshake keys. On the other hand, the client is still waiting for an acknowledgement for the ClientFinished, and retransmits it. It will however never receive an acknowledgement, since the server dropped the keys already.
Sounds like receiving a 1-RTT packet on the client side might need to be another way to confirm the handshake.
A server can send a 1-RTT packet before even completing the handshake, so a client receiving one doesn't mean anything. In this scenario, the client will continue retransmitting the last cryptographic handshake flight. That sounds terrible. But if it ever has cause to send 1-RTT data (as it does in Marten's example, which I'm not sure is relevant), then it will retransmit that data on PTO. When that is eventually acknowledged, that will confirm the handshake. The more troublesome scenario is where the client sends nothing, only acknowledgments. In that case the client will have a complete handshake that cannot be confirmed. Would it be enough to say that "unless an endpoint sends some ack-eliciting data in 1-RTT packets, the handshake might remain unconfirmed indefinitely" and leave it at that?
NAME Agreed. IIRC, the assumption in the design team was that the client would have something to send. +1. We may hint that "a client MAY send a PING packet to confirm the handshake."
NAME Unfortunately, that wouldn't work. As long as the client has outstanding crypto data, it will only retransmit the crypto packets, see URL NAME Agreed, although I'd prefer a SHOULD here.
FWIW, what I had in mind was something like "once the handshake is complete, endpoints SHOULD send ack-eliciting packets to confirm the handshake. If there is no application data to be sent, such packets can contain PING frames."
No I think the state needs to progress. Resources might be tied up and timeouts might be in place to weed out bad handshakes before a longer or indefinite idle timeout. I would say the client MUST send at least one ack-eliciting 1-RTT packet once it is able to do so, and within, say, 1 PTO.
If I understand the issue correctly, the problem is that server drops handshake key too early before client gets ack for its last handshake flight. Server can ack any client Short packet without client last Handshake flight. That means that sending any ack-eliciting 1RTT packet from client does not fix this issue. Coalescing missing Handshake and Short packet would suffer MTU overflow as disscused in PR. I think server has to wait for client's confirmation of handshake before confirming handshake. It might be good idea to reconsider explicit signal again?
From the : We could change this MAY to a SHOULD and it'll resolve this issue.
I'm sorry, but I'm having trouble following the initial example. You said that the client sent a 1-RTT packet and it got lost. In this case, it will continue to retransmit it and will eventually get an ACK, at which point the handshake will be confirmed and it will drop the keys and stop retransmitting ClientFinished. And as David indicates above, if the client is not sending at all, then it can just send ack-eliciting frames.
Unfortunately, it doesn't. As I described in the initial example, and in URL, the client won't retransmit 1-RTT data until the handshake is completed.
NAME well in that case, the source of the problem is the retransmission algorithm, not this. Why does it make sense to not retransmit 1-RTT data when there is an outstanding CFIN? Just consider the simple case where the client's second flight (CFIN + some data) is lost. When the PTO expires, you should resend all of that.
NAME The client could have sent an arbitrary amount of 1-RTT data, so you wouldn't want to retransmit all of this. As discussed on the PR (), this can have some weird interaction with the congestion controller as well.
Sure, you would, however, want to retransmit some of it
Technically, 1RTT packets can be sent on the PTO when there is unacked Handshake data, but you're correct that if there are two MSS or more of Handshake data, the client would repeatedly retransmit that data instead of sending any 1RTT packets as written today. I think MT's PR is heading in the right direction. NAME do you have an alternate suggestion of how to deal with this situation?
Discussed in Cupertino. With the change in and , we believe this deadlock issue is addressed. (also, NAME has a pending edit).
NAME My point is URL
Has anyone thought about revisiting implicit ACK's in handshake? It appears to me that many problems occur because of waiting for an ACK that doesn't happen?
IIUC, we agreed today in Cupertino that forbidding handshake key discarding () is the solution (at least for the time being), based on the assumption that it works. I am starting to wonder if it works as expected. Consider the case where the client migrates to a new path once the handshake is confirmed. That is in fact the recommended behavior when server-preferred address is used. But IIUC, the existing transport logic is designed based on the assumption that path migration happens only after the exchange of Handshake packets have ceased. I think I have found at least one issue: when the client intentionally migrates to a new path while still having unacknowledged handshake data, how does it choose the new client CID when sending the Handshake packet? Current design assumes that the server to pick the client CID from the pool of the CIDs it has received with the NEWCONNECTIONID frames. That works for 1-RTT packets, because 1-RTT packet does not have the Source CID field. But when using the long header packets, the sender choose the Source CID (the source being the client in this particular case). What is the solution here?
Regarding the way on discarding the Handshake keys, I think the failures the WG have made until now are that we have been looking for a clever method in trying to infer when a Handshake key can be dropped, and/or using 1-RTT packet to tell the peer that the peer can drop the handshake keys (the problem here is that the peer might continue retransmitting Handshake packets if the endpoint ceases to send 1-RTT packets). Based on the discussion in Cupertino, I think that if we are to drop the Handshake keys, we should adopt a safer approach. I'd argue that there exists a class of approaches that can be considered safe. That is to require an endpoint to retain the Handshake keys until the peer sends an explicit signal instructing the endpoint to drop the Handshake keys. Assuming that my observation in is correct, we'd also need to adjust the transition to the handshake confirmed state to when an endpoint sends and receives that message. That signal can be a new frame, a header bit, or a KeyUpdate.
What if each peer sends a required PING every 1/2 RTT until it sees a 1-RTT packet from peer or it times out using an aggressive version of PTO? Or something? And preferably replace handshake ACK's with implicit ACK's if possible.
Or better a each peer sends a handshake-PING (HSPING) such the a received HSPING indicates that the peer is still not happy. This differs from ACK's in that it is unsolicited and therefore cannot hang. EDIT: the details needs to be worked out - when to send HSPING on HS PN space and when to send on 1-RTT PN space, and when to stop. But I think this should stop deadlocks and eventually confirm the handshake. Ordinary 1-RTT can start before HSPINGS stop, but HS is not complete until HSPING's are no longer required to be sent. After that point the peer can go into a mode where HS keys can be dropped, keys updated, and paths migrated. Also the connection handler can use an optimized versions for post handshake logic.
NAME it sounds like you are recommending something like the previous RETIRE_KEYS proposal?
I would definitely prefer something like that over never getting rid of the handshake keys.
I still prefer an implicit signal, and can think we can get it to work. But I would much prefer an explicit frame to keeping it forever.
Why not simply prohibit migration until all handshake packets have been ACKed. There is no ambiguity here, as the server is forbidden from initiating migration.
When the handshake is confirmed, all the Handshake data is known to be delivered and Handshake recovery state can be discarded. Previously, this was the moment Handshake keys were discarded, so the recovery doc relied on text about key discard to discard recovery state. But now that the Handshake keys are staying around longer than the Handshake confirmed point, I'd suggest we add a paragraph to section 5.5(URL) like: "When the handshake has been confirmed, all data sent in Handshake packet is known to be delivered and all recovery state should be discarded." To be clear, this suggestion is not a hack. We should do this if we're going to keep Handshake keys past Handshake Confirmed(which every solution I believe requires?), regardless of how long or why. I'll note that the server can do this upon receipt of any 1-RTT packet, but waiting for an ACK isn't likely to delay dropping Handshake state significantly and is consistent between client and server.
NAME The problem here is that even in the case where an endpoint (e.g., server) has received all the handshake transcript and also the ACKs for all the ack-eliciting Handshake packets it has sent, the other side (e.g., client) might have still some data outstanding (due to ACKs being lost). NAME (Assuming that your presumption here is that the path for the Handshake packets do not change when the client migrates) It's not only the the receive-side state of the Handshake packets that you need to retain forever, but also the state related to that path (e.g., socket). I am not sure we'd prefer that. This might include CIDs that are retired.
NAME That might be the case (honestly I do not remember the details of the RETIRES_KEYS proposal), though I think I'm inclined to suggesting using a continuous signal (i.e., a Key Update or burning an unused header bit). The reasons being i) using a continuous signal allows us to abandon the Handshake keys earlier ii) we do not need to define a new signal that needs to be retransmitted in the transport draft, or rely on a particular behavior defined in the recovery draft that is not generally considered as necessary for a recovery logic (i.e. PTO "MUST" trigger a PING when there is no data to send).
I remember thinking RETIRE_KEYS was extraneous given all the implicit signals. But it seems that screwy-but-legal behavior seems to make those implicit signals risky. So let's stop being cute and just use the signal.
In response to NAME "when the client intentionally migrates to a new path while still having unacknowledged handshake data, how does it choose the new client CID when sending the Handshake packet?" I'm arguing the client should never have unacknowledged Handshake data when it migrates, because it waits for handshake confirmation to migrate and at that point, all Handshake data should be discarded. There is a possibility the server does not believe the handshake is confirmed but the client does. In that case, if the client didn't keep listening on the old path when it migrated AND the server didn't send anything in 1-RTT, there would be a problem. But don't we require both client and server to validate the path in this case, which would mean both would achieve handshake confirmation on the new path, if they had not done so previously?
NAME Yes. This is the case I am pointing out. I am not sure if there is a guarantee that handshake confirmation would be obtained before the path is validated, because the path might become validated while all the packets carrying 1-RTT ACKs get dropped. And at that point, the endpoints might cease sending 1-RTT packets to each other.
What NAME points out is a problem. Keeping HS keys forever prevents the deadlock, but it does so by admitting that HS packets can be sent forever. This means after migration as well. That is something we don't consider in the spec. NAME I don't think you're right about this: Handshake data is discarded when the handshake keys are dropped, not when confirmation is reached (see . Now with handshake keys forever, the client can keep retransmitting forever. And as NAME notes, the server can surely send keep retransmitting as well since if it hasn't reached confirmation yet. There are a few ways to curb this... but I'm strongly inclined towards an explicit signal from both sides indicating handshake confirmation, after which migration can be initiated.
There is a much larger issue at stake here. The currently published proofs for the TLS 1.3 handshake do not allow the server to process 1-RTT packets until the client finished has been verified. It does not mean there is an attack, because of the defensive design of TLS 1.3 which implicitly authenticates the transcript in the transport secret derivation. However, this is still a weaker argument than checking the finish, particularly with respect to downgrade attacks. If a server supports a transport ciphersuite with broken integrity (e.g. someone discovers a way to forge CCM8 tags), then clients of this server that support strong ciphersuites may still be attacked. There is also a clear risk with client authentication. If a server is configured to require a client certificate, but QUIC gives to the application 1-RTT packets before the client finished flight has been received, the application may incorrectly believe that this data has passed the client authentication check. A reliable way to prevent such issues is to mandate that every 1-RTT packet sent by the client before the client finished frame is acknowledged by the server must include in the same datagram a copy of the finished frame (in the form of a separate handshake packet, before the short 1-RTT packet). The overhead is limited because finished messages are short, so even with the overhead of the long packet headers and AEAD tag, less than 100 bytes are used in the datagram (leaving the rest for the 1-RTT packet). If this is done, it is possible to enforce that a server must have checked the client finished before processing 1-RTT packets from the client. Adding some separate signal for handshake confirmation is a terrible idea - in TLS, receiving the peer's finished message IS the the explicit handshake confirmation signal (which is why CCS is removed in 1.3). Restoring a transport-level signal is a clear step backwards and must be avoided.
Hi NAME since you're describing a "much larger issue" that I agree should be discussed, could you please open a separate issue? This issue is about a specific deadlock that can happen during the handshake, not about the security properties of the handshake.
This was closed erroneously... apparently we're not quite fully zen with github's AI yet.
Discussed in Singapore; CfC .
I like this. The HANDSHAKE_DONE has a clear point in time where it must be transmitted and retransmitted, and the client explicitly has something to send (ACKs) driving the handshake forward in a timely manner. Using the alternative key update proposal might work, but it couples somewhat separate problems tightly - e.g. if one parts will change in future versions. A done signal is a clean way to communicate this.Re-reviewing, since there have been a lot of commits since I reviewed before. Still looks good.Thanks for making changes, I support this.Ship it!LGTM

Revised text:
4.1.2.  <ins> In this document, the TLS handshake is considered confirmed at the server when the handshake completes.  At the client, the handshake is considered confirmed when a HANDSHAKE_DONE frame is received. A client MAY consider the handshake to be confirmed when it receives an acknowledgement for a 1-RTT packet.  This can be implemented by recording the lowest packet number sent with 1-RTT keys, and comparing it to the Largest Acknowledged field in any received 1-RTT ACK frame: once the latter is greater than or equal to the former, the handshake is confirmed. </ins> 4.1.3. 4.10.2.  <ins> An endpoint MUST discard its handshake keys when the TLS handshake is confirmed (handshake-confirmed).  The server MUST send a HANDSHAKE_DONE frame as soon as it completes the handshake. </ins> 4.10.3.