Subject: Allowed frames in 0-RTT

From: Martin Thomson <[email-masked]>
Date: Wed, 06 Feb 2019 15:11:51 +1100

We had a rather unfruitful discussion about this subject in Tokyo, and it's one of the few things we didn't come to a conclusion on.  I'd like to share my conclusion.  Hopefully others agree.

# Background

Kazuho observed that prohibiting RESET_STREAM from 0-RTT made it awkward to cancel streams.  In the ensuing discussion, we identified that there might be use cases for other frame types.

https://github.com/quicwg/base-drafts/issues/2344 contains the full discussion.  There's some good stuff there if you want to spend some more time reading.  I apologize, but this is a difficult topic.  It's a long thread but this email might be worse.

# Risk Analysis

0-RTT comes with risks, primarily those arising from replay.  But those risks are not borne by QUIC they depend on triggering actions in the application that uses QUIC.  For instance, HTTP POST requests can have consequences that alter the state of resources and those consequences affect future connections.  That is why we have RFC 8470, which describes these risks and mitigation strategies for HTTP.

0-RTT affects the state of a QUIC connection, but the effects don't extend beyond a connection unless the application protocol permits it.  Thus, the consequences of replay on QUIC is limited to having more connections created.  Any replayed actions will accumulate to those replayed connections.  Ultimately however, only one of the connections will be completed.

Some people in the discussion were unhappy about this situation.  If they are not able to recognize replayed connection attempts using the techniques described in RFC 8446, then they would be obligated to process those connections.

I don't see this as changing the overall assessment.  When a server accepts a connection attempt, they have to expect to take on the state and processing costs of handling that connection.  If that connection turns out to be bogus, then any expended effort is fruitless, but no more than that.

Yes, the addition of 0-RTT changes the calculus slightly, because imperfect replay detection can be exploited to more cheaply induce server work related to frame processing.  Allow N replays and you can get N times the work without having to redo the heavy crypto (i.e., mostly key exchange, but also packet protection).  But that's a risk that servers need to integrate into their assessment about whether to accept 0-RTT.  0-RTT can be accepted only when servers have sufficient spare capacity to cover any costs from replay.  There should be a hard upper bound on the number of replays that might be accepted by a server, no matter the size of your deployment, so the costs are finite.

The specific concern raised was that the costs of processing overhead frames (flow control updates, PING, etc...) are not limited in any way.  However, I would contend that those are cheap and represent a lower cost-per-byte to process than STREAM, and we're not about to ban sending of STREAM frames in 0-RTT.  Any server should have defenses against clients that cause useless thrashing by sending pointless or wasteful frames (Section 10.5 of RFC 7540 goes into detail about this style of attack).  The combination of that style of defense and limits on replays is sufficient in my view.

# Choice of Principles

What principle can we use to motivate a decision?

It was suggested that we only enable frames for which we have an analysis that supports enabling it in 0-RTT.  However, my analysis suggests that this is every frame that does not carry information for an application protocol (i.e., everything except STREAM, and maybe RESET_STREAM and STOP_SENDING).  And we want to include frames for the application protocol or there is no point in doing 0-RTT at all.  So I don't think that there is a usable principle in that line of reasoning.

That leaves the that we find the set of things we know are needed and just enable those.  The principle there being YAGNI.  This seems like a fine principle.  It seems like people were generally OK with STREAM and all related frames.

The cost of doing that is that you have to treat 0-RTT specially.  This is already somewhat necessary as a receiver, because there are rules about packet processing that apply.  As a sender, this constrains behaviour in the same awkward ways that Kazuho originally identified.  If you can't update flow control limits, you have to set any increases aside for sending once the handshake completes.  It adds unnecessary complexity.

So I would instead advocate for a different principle: it would be a mistake to try to make decisions about 0-RTT safety in the transport, when replay risks are an application-layer concern.

If the application requests that the transport do something, then the application takes responsibility for any risk of replay.  The transport is not constrained in how it operates in 0-RTT.  The transport uses 0-RTT packets without regard to the risk of replay.  That said, as Roberto is fond of saying, servers will do what they need to do.  I have no doubt that all inputs will be subject to more scrutiny and caution before the handshake is complete.  But that's on individual implementations, it's not a principled position any more than having to use IP address reputation to manage DoS is.

This principle keeps the transport simple.  The only affordance needed then is to ensure that the application is aware of when it is at risk of replay.  The transport ensures that the application knows when data it sends might be replayed.  Once a request is made by the application, the transport sends it.  If it wants to send flow control updates, it can.

So I think that the two candidates are:

* YAGNI, with constraints
* 0-RTT is an application concern

# Affected Frames

If you look at the list of frame types, the interesting thing is that the difference between these positions is in a very small subset of frames.  Here's the complete list (including the upcoming, to-be-named KEYS_READY frame):

0x00      PADDING - already allowed
0x01 	PING - in question, but harmless; sending a redundant STREAM frame would have the same effect
0x02 - 0x03 	ACK - impossible
0x04 	RESET_STREAM - the trigger for this mess; but we seem to want it
0x05 	STOP_SENDING - more valuable than RESET_STREAM in many ways
0x06 	CRYPTO - impossible
0x07 	NEW_TOKEN - impossible
0x08 - 0x0f 	STREAM - very important, the most dangerous, and already allowed
0x10 	MAX_DATA - seems useful
0x11 	MAX_STREAM_DATA - seems useful
0x12 - 0x13 	MAX_STREAMS - seems useful
0x14 	DATA_BLOCKED - either redundant - because connection flow control is used to limit 0-RTT - or useful - because knowing when 0-RTT flow control limits are hit is useful information; this might depend on whether you think that the blocked signals are useful in general
0x15 	STREAM_DATA_BLOCKED - marginally useful
0x16 - 0x17 	STREAMS_BLOCKED - marginally useful
0x18 	NEW_CONNECTION_ID - in question
0x19 	RETIRE_CONNECTION_ID - impossible
0x1a 	PATH_CHALLENGE - in question
0x1b 	PATH_RESPONSE - impossible
0x1c - 0x1d 	CONNECTION_CLOSE - potentially useful
0x??       KEYS_READY - impossible

You can see that many of these frames simply can't be sent.  Mostly because they answer something that the server would send in 1-RTT, and the client can't respond in 0-RTT to 1-RTT packets (it has to use 1-RTT for that).  We could explicitly prohibit those frames, but it is sufficient in my view to rely on the existing rules.  We don't need to go through and piecemeal prohibit frames, though we could point out for each one the impossibility of it being in 0-RTT, it would be redundant to do so.

For those that are genuinely in question: PING, NEW_CONNECTION_ID, and PATH_CHALLENGE, this might seem like a waste of time and effort to talk about, but we are talking about establishing guidelines on what can and cannot be sent in 0-RTT.  We are deciding what factors need to be considered when new frames are defined.

# Conclusion

Assigning responsibility for replay defense to application protocols is the clearest principle we can apply.  That is, the transport does not have any inherent opinion about replay risk.  All frames are for the transport and none of them are specially privileged for use in 0-RTT.  What an application protocol endpoint decides to do with the transport in 0-RTT is an application responsibility.  An analysis of replay risks is always performed in the context of the application protocol.

Based on this, my view is that it would be a mistake to go with YAGNI.  Doing that signs us up for a whole bunch of difficult analysis for every frame, having to anticipate the many ways in which it might be abused by applications.  Instead, I think that we should articulate the considerations that broadly apply and require that application protocols do the analysis.

This is not just because I have a PR mostly ready (absent this last bit about considerations and application responsibility, which I plan to add soon).  Though I have no desire to write up the piecemeal allow/deny stuff, I will if I have to, but it will only be because I'm in the rough.

https://github.com/quicwg/base-drafts/pull/2355

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 6 Feb 2019 14:24:44 +0900

Martin,

Thank you for the detailed analysis, and for starting the thread.

My response inline.

> * 0-RTT is an application concern

I am not sure if I agree with the framing.

I think we can agree that 0-RTT is a concern for both the application
and the transport.

For application, the precedence we have in TLS 1.3 is that each
application protocol is required to create a profile specifying how
0-RTT data should be handled. My assumption is that we would be
following that. With that in mind, I think we agree that all frames
related to transmission of data (and streams) should be permitted.

The question is what to do with the frame types that control the
properties of the transport. We could either go with YAGNI, or allow
them assuming that they do not pose a security concerns.

> 0x01    PING - in question, but harmless; sending a redundant STREAM frame would have the same effect

Regarding PING, I think there's a broader discussion about if it the
use should be limited to 1-RTT. If we are to continue forbidding use
of PING in Initial and Handshake packets, I do not think that there
would be a reason for an endpoint to send PING in a 0-RTT packets.
OTOH, if we are to allow PING to be sent in Initial and Handshake
packets, I think there's no strong reason to forbid that to be sent in
a 0-RTT packet.

> 0x1c - 0x1d     CONNECTION_CLOSE - potentially useful

I think allowing CONNECTION_CLOSE in 0-RTT just causes confusion,
because the client cannot be sure if the peer would be able to process
the frame contained in a 0-RTT packet. What a client should do is
always send CONNECTION_CLOSE using an Initial packet or a Handshake
packet.

> 0x??       KEYS_READY - impossible

The table tells us that there are 6 frame types that are impossible to
be used in a 0-RTT packet. I'd assume that we'd want receivers to
detect misuse and call it a protocol violation (for example, I do not
think we'd want to accept ACK or CRYPTO frame sent in 0-RTT packet).
That means that frame types permitted in 0-RTT is going to be very
different from what is permitted in 1-RTT packets, regardless of the
outcome of the debate.

And there are only two frames that are debatable: NEW_CONNECTION_ID,
PATH_CHALLENGE.

Do we need to allow them? Does allowing those simplify the
specification or the implementations?

I am not sure if the answer to either of the two questions are yes.
Besides, I have a mild concern about allowing more frames to be sent
in 0-RTT, because it requires a server implementation to consider what
happens in case of a replay attack (note that because HTTP is capable
of mitigating 0-RTT replay attacks thanks to 425 Too Early, it has so
far been possible to have a very lax defense against 0-RTT replay in
TLS 1.3 stacks).

Hence my preference goes to applying YAGNI.

I would certainly _not_ lie down on the road on this debate, I prefer
making progress rather than being blocked by this issue. But anyways,
above is how I think about the issue.

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Wed, 06 Feb 2019 17:01:07 +1100

> I think we can agree that 0-RTT is a concern for both the application

My entire thesis here is that replay risks are primarily an application concern, so I can't agree.  If you are willing to accept that the risk to transport is the far less severe DoS one, then I'd concede that.  My latest update to the PR should address that point.  But transient costs, like the amount of CPU you spend on connections is entirely in server control, so I don't think that's the important point here.  I'm concerned about things like double-spend, which exist only at the application layer.
 
> following that. 

Yes, this is exactly the point.  TLS 1.3 can't say what the risk of replay is generically, only HTTP can say what the risk is for HTTP.

> With that in mind, I think we agree that all frames

Sure, but the principle you are applying is YAGNI.  I'm suggesting that there is a better and simpler principle: applications own the responsibility for replay risk.

> them assuming that they do not pose a security concerns.

My point is that the frames do not inherently carry a risk - it's the application of the information they carry that carries the real risk.

> packets, I think there's no strong reason to forbid that to be sent in

PING is easy.  It is trivially possible to show that a STREAM frame with duplicated data produces the same effect.  So the only question is whether to allow people to save a few bytes to use PING instead.

Whether it is advisable to send PING is not the question, and nor should it be.

> always send CONNECTION_CLOSE using an Initial packet or a Handshake

This is a fine argument.  And I would be OK with forbidding CONNECTION_CLOSE.  CONNECTION_CLOSE is special enough, and the rules for generating it during the handshake are already fraught; I think that forbidding CONNECTION_CLOSE in 0-RTT is a natural consequence of applying the changes we agreed here in Tokyo.

> specification or the implementations?

I think that we do, but only because it would simplify clients.  You could say that we know that it's 0-RTT, so we can do anything we like, but that's not principled.

Marten had an argument in favour of NEW_CONNECTION_ID.  Sending it in 0-RTT could speed up moving to a privately negotiated connection ID (which enables stateless reset).  But to my earlier point, if a client wanted to send that frame, having to withhold sending is awkward for the same reason that holding a RESET_STREAM would be.  So I'd say yes to NEW_CONNECTION_ID.

Having to decide to hold a frame at the client is an unnecessary imposition.  Why should the client have to treat 0-RTT specially?  Sure, there are things that it shouldn't be sending, but those all come down to stuff that is enforced by the handshake state machine (e.g., you can't send ACK because you can't read anything that could be acknowledged).  For that matter, as we discussed for the flow control matter, we don't want to require a server to check that the client didn't comply in all cases like this (though they SHOULD do so, as we agreed in https://github.com/quicwg/base-drafts/issues/2360).  Applying that logic, allowing PATH_CHALLENGE is fine, if a little pointless.
 
> far been possible to have a very lax defense against 0-RTT replay in

I don't understand this point.  That's an HTTP-layer concern, not a QUIC one.

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 6 Feb 2019 16:06:14 +0900

> My entire thesis here is that replay risks are primarily an application concern, so I can't agree.  If you are willing to accept that the risk to transport is the far less severe DoS one, then I'd…

I agree that the view that the risk to transport is far less severe
than the risk to the application, even though there is a risk.

I have just read the updated text on the PR. Looks perfect to me.
Thank you for writing that down.

> Sure, but the principle you are applying is YAGNI.  I'm suggesting that there is a better and simpler principle: applications own the responsibility for replay risk.

Yeah, what I am trying to state here is that we share the view that
application should be responsible for handling replay of data.

> Marten had an argument in favour of NEW_CONNECTION_ID.  Sending it in 0-RTT could speed up moving to a privately negotiated connection ID (which enables stateless reset).

I am not sure if it helps. A client needs to migrate to a different
address in order to enforce the peer to switch to a new CID. That
cannot happen before the handshake completes (which is going to be
signalled using the new frame). That means that there would be at
least 1 RTT between when the client obtains the 1-RTT write key and
when it can initiate a migration to use a different connection ID.
That's why I wonder if sending NEW_CONNECTION_ID in 0-RTT makes things
better, even ignoring the potential negative impact on path validation
and congestion control when the client migrates the connection.

> But to my earlier point, if a client wanted to send that frame, having to withhold sending is awkward for the same reason that holding a RESET_STREAM would be.  So I'd say yes to NEW_CONNECTION_ID.
>
> Having to decide to hold a frame at the client is an unnecessary imposition.  Why should the client have to treat 0-RTT specially?

I think that's a fair argument, though I might point out that
regardless of the conclusion of this discussion, we'd need to hold
things until various events.

* Stream data needs to be held until either 0-RTT key or 1-RTT key
becomes available.
* Transport needs to notify the application when 1-RTT key becomes
available, otherwise it cannot support HTTP/3. This is because the
client is required to withhold reissuing a HTTP request in response to
425 until it obtains the 1-RTT key.
* Migration needs to be held until the handshake is complete.

The first two point shows that we need to do things at the moment when
1-RTT write key is obtained.

> Sure, there are things that it shouldn't be sending, but those all come down to stuff that is enforced by the handshake state machine (e.g., you can't send ACK because you can't read anything that could be acknowledged).  For that matter, as we discussed for the flow control matter, we don't want to require a server to check that the client didn't comply in all cases like this (though they SHOULD do so, as we agreed in https://github.com/quicwg/base-drafts/issues/2360).  Applying that logic, allowing PATH_CHALLENGE is fine, if a little pointless.
>

What I am trying to clarify is how you _tune_ your TLS 1.3 stack can
depend on how sensitive the application protocol is against replay
attacks. It is beneficial to be lax in when you accept 0-RTT, because
it improves the chance of reducing an RTT (e.g., should a server
accept ticket reuse when the client is doing happy eyeballs?).

My view is that minimizing the concerns unique to the transport layer
enables us to utilize 0-RTT as much as possible for a replay-resistant
protocol like HTTP.

-- 
Kazuho Oku

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Tue, 5 Feb 2019 23:52:59 -0800

Thanks for making this analysis.

As to future frames: it is comforting to know the current token / crypto
things are impossible, and that should apply forward.

I agree that the application should take control of replay
responsibilities, but I have concerns here:

1. the application is lazy and greedy. For example, yes detect 0-RTT, only
allow GET, but don’t bother with special flow credits that no-one
understands, don’t think too hard, don’t save state, don’t analyze if GET
actually PUT's. Greedy: yes Fast connection is better. We cannot hold
hands, but this is the reality. We are not talking about years of web
server development, but of http enabled application endpoints with a
development cycle of two days.

2. the stream is an attack vector on resources spent.

This makes me think that we need some hard defaults on flow credits and
perhaps also stream max. Both to protect the transport (and potentially OS
kernel), and to simplify life for application developers who do not
generally have a clue about these concerns. I can see this being set with
configurable defaults, so I’, not sure what the drafts can enforce or help
with here.

Mikkel

On 6 February 2019 at 08.06.48, Kazuho Oku ([email-masked]) wrote:

2019年2月6日(水) 15:01 Martin Thomson <[email-masked]>:
> My entire thesis here is that replay risks are primarily an application
concern, so I can't agree. If you are willing to accept that the risk to
transport is the far less severe DoS one, then I'd concede that. My latest
update to the PR should address that point. But transient costs, like the
amount of CPU you spend on connections is entirely in server control, so I
don't think that's the important point here. I'm concerned about things
like double-spend, which exist only at the application layer.

> Yes, this is exactly the point. TLS 1.3 can't say what the risk of replay
is generically, only HTTP can say what the risk is for HTTP.
> Sure, but the principle you are applying is YAGNI. I'm suggesting that
there is a better and simpler principle: applications own the
responsibility for replay risk.

> My point is that the frames do not inherently carry a risk - it's the
application of the information they carry that carries the real risk.
> PING is easy. It is trivially possible to show that a STREAM frame with
duplicated data produces the same effect. So the only question is whether
to allow people to save a few bytes to use PING instead.
> This is a fine argument. And I would be OK with forbidding
CONNECTION_CLOSE. CONNECTION_CLOSE is special enough, and the rules for
generating it during the handshake are already fraught; I think that
forbidding CONNECTION_CLOSE in 0-RTT is a natural consequence of applying
the changes we agreed here in Tokyo.
> I think that we do, but only because it would simplify clients. You could
say that we know that it's 0-RTT, so we can do anything we like, but that's
not principled.
>
> Marten had an argument in favour of NEW_CONNECTION_ID. Sending it in
0-RTT could speed up moving to a privately negotiated connection ID (which
enables stateless reset).

> But to my earlier point, if a client wanted to send that frame, having to
withhold sending is awkward for the same reason that holding a RESET_STREAM
would be. So I'd say yes to NEW_CONNECTION_ID.
>
> Having to decide to hold a frame at the client is an unnecessary
imposition. Why should the client have to treat 0-RTT specially?

> Sure, there are things that it shouldn't be sending, but those all come
down to stuff that is enforced by the handshake state machine (e.g., you
can't send ACK because you can't read anything that could be acknowledged).
For that matter, as we discussed for the flow control matter, we don't want
to require a server to check that the client didn't comply in all cases
like this (though they SHOULD do so, as we agreed in
https://github.com/quicwg/base-drafts/issues/2360). Applying that logic,
allowing PATH_CHALLENGE is fine, if a little pointless.

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 6 Feb 2019 17:25:40 +0900

2019/02/06 16:52、Mikkel Fahnøe Jørgensen <[email-masked]>のメール:

> As to future frames: it is comforting to know the current token / crypto things are impossible, and that should apply forward.
> 
> I agree that the application should take control of replay responsibilities, but I have concerns here:
> 
> 1. the application is lazy and greedy. For example, yes detect 0-RTT, only allow GET, but don’t bother with special flow credits that no-one understands, don’t think too hard, don’t save state, don’t analyze if GET actually PUT's. Greedy: yes Fast connection is better. We cannot hold hands, but this is the reality. We are not talking about years of web server development, but of http enabled application endpoints with a development cycle of two days.
> 
> This makes me think that we need some hard defaults on flow credits and perhaps also stream max. Both to protect the transport (and potentially OS kernel), and to simplify life for application developers who do not generally have a clue about these concerns. I can see this being set with configurable defaults, so I’, not sure what the drafts can enforce or help with here.

We’ve discussed this in https://github.com/quicwg/base-drafts/issues/2360 and also in Tokyo. I think the conclusion is essentially what you are suggesting.

The point we are discussing now is the remainder which is far less concerning than the replay concerns at the application level :-)

---

From: Stephen Farrell <[email-masked]>
Date: Wed, 6 Feb 2019 08:40:44 +0000

piping in from the sidelines so I don't expect this to be found so
convincing, however...

On 06/02/2019 04:11, Martin Thomson wrote:
> it would be a mistake to try to make decisions about 0-RTT safety in
> the transport, when replay risks are an application-layer concern.
I believe that is not true for some decisions.

If the WG decide to disallow something that could cause an application
layer concern then that will remove the need to do application layer
analyses of replay/0-rtt for those cases. Given application devs will
in general not do those analyses, disallowing as much as you can (of
the risks arising from 0-rtt) in the transport seems safer to me.

If you want to name a principle ISTM that 0-rtt is a quite like a
dangerous implement, which seems better avoided as far as possible.

One question though - do we have any information about real world
0-rtt issues arising in http/tls1.3 or is it still too early? If we
did have such information that may better inform this discussion.

Cheers,
S.

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Wed, 6 Feb 2019 00:56:20 -0800

I came to think about the recent discussion about interception of the
replay token by a man on the side.
Here it was pointed out that it would be practical to enforce single token
use because

1. the retry to goes to server designated CID
2. the token can have a timestamp

We could do the same with 0-RTT (I think)
There has previously been discussions on the CID used in 0-RTT - whether it
is the original random CID or a server designated.
But if 0-RTT uses the same approach as single use retry tokens, then it
should be practical to enforce no replay.
Of course data centers can work around this if they care enough with the
risks involved and moved to the application layer - so the draft could have
an escape hatch for that.

Such an enforcement would make the 0-RTT more short lived and at higher
risk of being rejected, but I don’t think that is a major concern.

Also, 0-RTT tokens can allow servers to track anonymous end users, so
limiting their life time is a good thing. Maybe more important than the
discussion on migration linkability.

Mikkel

On 6 February 2019 at 09.41.16, Stephen Farrell ([email-masked])
wrote:

Cheers,
S.

---

From: Ian Swett <[email-masked]>
Date: Wed, 6 Feb 2019 04:01:32 -0500

I agree with Martin's analysis and principles, and I think the PR is good,
though I'd prefer to allow CONNECTION_CLOSE in 0-RTT(see below)

Frames that are disallowed are already largely disallowed due to other
requirements in the transport draft:
 *CRYPTO* is disallowed because 0-RTT is not used for the handshake
 *ACK* frames are disallowed because there are no 1-RTT packets to ACK
 *RETIRE_CONNECTION_ID* is disallowed because the client has received no
connection IDs from the peer and they can't retire the CID they're using.
 *PATH_RESPONSE* - You haven't received a PATH_CHALLENGE yet if you haven't
received anything.
 *NEW_TOKEN* - Only a server can send this and 0-RTT is client-only
 *CONNECTION_CLOSE*- I don't see a reason to disallow this one, but the
text requires sending it at an encryption level the peer can process, so
it's also not that useful.  In practice, if you disallow it, there's not a
large behavior change.  Either you close the connection because the peer
asked or you close the connection because the frame is invalid in 0-RTT.

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 6 Feb 2019 20:03:02 +0900

2019年2月6日(水) 18:02 Ian Swett <ianswett=[email-masked]>:
>
> I agree with Martin's analysis and principles, and I think the PR is good, though I'd prefer to allow CONNECTION_CLOSE in 0-RTT(see below)
>
> Frames that are disallowed are already largely disallowed due to other requirements in the transport draft:
>  CRYPTO is disallowed because 0-RTT is not used for the handshake
>  ACK frames are disallowed because there are no 1-RTT packets to ACK
>  RETIRE_CONNECTION_ID is disallowed because the client has received no connection IDs from the peer and they can't retire the CID they're using.
>  PATH_RESPONSE - You haven't received a PATH_CHALLENGE yet if you haven't received anything.

I forgot to point out this, but if we are to generally allow
transmission of any frame in 0-RTT, I think we should allow
RETIRE_CONNECTION_ID and PATH_RESPONSE as well. This is because we do
not (cannot) enforce a client to switch to 1-RTT send key when it
obtains the 1-RTT write key, and because it makes sense for a client
to stick to continue using 0-RTT key until it receives an ACK for all
the Handshake packets it has sent, assuming that the client does not
get blocked by the maximums advertised by TP. Disposal of the 0-RTT
key will be signaled using the new RETIRE_KEY frame.

>  NEW_TOKEN - Only a server can send this and 0-RTT is client-only
>  CONNECTION_CLOSE- I don't see a reason to disallow this one, but the text requires sending it at an encryption level the peer can process, so it's also not that useful.  In practice, if you disallow it, there's not a large behavior change.  Either you close the connection because the peer asked or you close the connection because the frame is invalid in 0-RTT.
>
> On Wed, Feb 6, 2019 at 3:41 AM Stephen Farrell <[email-masked]> wrote:
>>
>>

-- 
Kazuho Oku

---

From: Mike Bishop <[email-masked]>
Date: Wed, 6 Feb 2019 18:41:32 +0000

I'm not aware of any, mostly because the people who are deploying 0-RTT are also deploying the RFC on what's permitted in HTTP (or home-grown rules that are equivalent).  I know in our discussions, we've focused on situations where GET has side-effects, because GET is permitted by default.  Ideally, we'd be able to delay those side-effects until the handshake completes, but a fallback is to either disable 0-RTT for such resources or (worst case) tell customers there are no guarantees if they enable both options.

I'm on board with Martin's initial analysis.  Nothing in the transport layer has a lifetime outside the scope of the connection, and the connection will be torn down if the handshake doesn't complete, so I'm comfortable with having a permissive 0-RTT policy for transport features.  The application layer is responsible for defining when 0-RTT can be used safely; if we don't already have it (I don't think we do), we should reiterate TLS 1.3's requirement that the application layer make such definitions, and I should add a reference to RFC 8470 in the HTTP draft.

-----Original Message-----
From: QUIC <[email-masked]> On Behalf Of Stephen Farrell
Sent: Wednesday, February 6, 2019 12:41 AM
To: Martin Thomson <[email-masked]>; [email-masked]
Subject: Re: Allowed frames in 0-RTT

piping in from the sidelines so I don't expect this to be found so convincing, however...

If the WG decide to disallow something that could cause an application layer concern then that will remove the need to do application layer analyses of replay/0-rtt for those cases. Given application devs will in general not do those analyses, disallowing as much as you can (of the risks arising from 0-rtt) in the transport seems safer to me.

If you want to name a principle ISTM that 0-rtt is a quite like a dangerous implement, which seems better avoided as far as possible.

One question though - do we have any information about real world 0-rtt issues arising in http/tls1.3 or is it still too early? If we did have such information that may better inform this discussion.

Cheers,
S.

---

From: Roberto Peon <[email-masked]>
Date: Wed, 6 Feb 2019 19:07:15 +0000

Makes sense to me.
Replay is semantically dangerous to applications (charge your bank account twice? Bad).

Replay is merely computationally dangerous to transport (i.e. DoS), thus entirely different levels of risk which are fixed in different ways-- indeed, the only provably safe way to fix this is to have single-use tokens supplied from the application and (re)transmitted at the application-layer. Any other fix is heuristic/probabilistic.

-=R

On 2/5/19, 8:12 PM, "QUIC on behalf of Martin Thomson" <[email-masked] on behalf of [email-masked]> wrote:

    # Background

    
    # Risk Analysis

    

    
    
    # Choice of Principles

    

    

    
    
    # Affected Frames

    

    # Conclusion

---

From: Martin Thomson <[email-masked]>
Date: Thu, 07 Feb 2019 12:59:47 +1100

> dangerous implement, which seems better avoided as far as possible.

Yes.  In fact, my pull request prominently advises disabling 0-RTT as a defense.  However, when the chairs asked in Tokyo, the group was near unanimous in support of 0-RTT for HTTP.  Sorry.
 
> did have such information that may better inform this discussion.

It's still too early to really tell.  All we really know is that 0-RTT usage rates are still very low.  (1% of TLS 1.3 connections for us, with TLS 1.3 sitting around 8% of the whole.)

---

From: Martin Thomson <[email-masked]>
Date: Thu, 07 Feb 2019 13:06:36 +1100

Hi Mikkel, are you talking about Retry?  The token that is used for 0-RTT (that is, the session ticket) is encrypted.  The (very) short-lived token that appears in a Retry packet is the only token-like thing that is exposed to the network.

The point about servers tracking clients is already well-treated in the literature.  (I can't remember if we have text in QUIC specifically, but it should be in TLS.)

As for time-limiting, TLS 1.3 already provides measures that strictly limit the time that a 0-RTT can be replayed.  Firstly, 0-RTT can only be retried within a week that the token is issued.  More importantly, there is a field in the TLS handshake that limits the time over which the token can be replayed.  There are some limitations because it relies on clients and servers having clocks that run at approximately the same speed and we have to make allowances for retransmissions, but the general guidance we have is that a 10 second window is what you get for replay.  Of course, that is only one of the defenses.

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Wed, 6 Feb 2019 22:44:39 -0800

Yes, I forgot the 0-RTT token is called a ticket. The fact that it is
encrypted does not change the general similarity to Retry if we assume
retry tokens are created in safe manner. Both can be replayed and both can
have non-forgeable timestamps.

What is different is that TLS 1.3 over TCP does not have a concept of
Connection ID that it can use for routing, but Retry does.

(Restating that currently Retry is not required, only recommended, to
enforce single use, but analysis shows that it is feasible to require).

The time limit is, in this context, needed to ensure you do not have to
keep infinite state whereas for TLS in general it (I presume) has more to
do with tracking, reducing the attack surface, but to an extend also to
clean up state.

By requiring that the 0-RTT NewSessionTicket is used with a CID that routes
to a specific endpoint, or cluster of endpoints, that can synchronously
verify non-reuse within the time limit, you can lift a complex burden from
the application layer - at the cost of complexity in transport and cluster
configuration.

One reason not to do this is complexity - but it is still simpler that
dumping this on the average application, and you are not forced to to do
0-RTT.
Another reason to not do this is that if you can’t control your routing
tightly you need an internal lookup which adds latency.

The application layer has a few advantages: One is that it might not care
about replay (like GET hello/world), or it might have a convenient sync
mechanism like a database query. But these are notoriously difficult to get
right and fast at the same time.

If you place the burden on transport, you loose the ability of cluster wide
fast GET, but that might not be a big issue, since routing to a dedicated
host will also work and on average it will level out.

If the servers QUIC implementation is able and willing to share
synchronized state (e.g. via Redis or Zookeeper, etcd, or custom built-in),
you can take the complexity hit once and let applications piggyback on
that, and if its not, you still have single server guarantees.

I believe this is the only sane way to go about it. I really can’t see
0-RTT being safe left to the average application developer (however smart)
when under time pressure to deliver.

Regardless of much work the transport does, an application should of course
maintain its own integrity, and avoid double bookings etc. which can happy
by other means as well, unless, of course, that would be a business
strategy to do so.

On 7 February 2019 at 03.06.37, Martin Thomson ([email-masked]) wrote:

Hi Mikkel, are you talking about Retry? The token that is used for 0-RTT
(that is, the session ticket) is encrypted. The (very) short-lived token
that appears in a Retry packet is the only token-like thing that is exposed
to the network.

The point about servers tracking clients is already well-treated in the
literature. (I can't remember if we have text in QUIC specifically, but it
should be in TLS.)

As for time-limiting, TLS 1.3 already provides measures that strictly limit
the time that a 0-RTT can be replayed. Firstly, 0-RTT can only be retried
within a week that the token is issued. More importantly, there is a field
in the TLS handshake that limits the time over which the token can be
replayed. There are some limitations because it relies on clients and
servers having clocks that run at approximately the same speed and we have
to make allowances for retransmissions, but the general guidance we have is
that a 10 second window is what you get for replay. Of course, that is only
one of the defenses.

---

From: Ian Swett <[email-masked]>
Date: Thu, 7 Feb 2019 08:49:41 -0500

Mikkel, are you suggesting a connection ID be paired with a
NewSessionTicket or Token for the purposes of resumption to make it easier
for a server operator to prevent using the same NST or Token multiple times?

If so, this thread has gone extraordinarily off the original topic of what
frames to allow in 0RTT, so if you want to discuss that further, please
start a new thread.

On Thu, Feb 7, 2019 at 1:45 AM Mikkel Fahnøe Jørgensen <[email-masked]>
wrote:

> have non-forgeable timestamps..
>
> By requiring that the 0-RTT NewSessionTicket is used with a CID that
> routes to a specific endpoint, or cluster of endpoints, that can
> synchronously verify non-reuse within the time limit, you can lift a
> complex burden from the application layer - at the cost of complexity in
> transport and cluster configuration.
>
> If you place the burden on transport, you loose the ability of cluster
> wide fast GET, but that might not be a big issue, since routing to a
> dedicated host will also work and on average it will level out.
>
> Regardless of much work the transport does, an application should of
> course maintain its own integrity, and avoid double bookings etc. which can
> happy by other means as well, unless, of course, that would be a business
> strategy to do so.
>
> As for time-limiting, TLS 1.3 already provides measures that strictly
> limit the time that a 0-RTT can be replayed. Firstly, 0-RTT can only be
> retried within a week that the token is issued. More importantly, there is
> a field in the TLS handshake that limits the time over which the token can
> be replayed. There are some limitations because it relies on clients and
> > dangerous implement, which seems better avoided as far as possible..
> >

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Thu, 7 Feb 2019 07:00:08 -0800

Well, yes, but I already made my point.
New thread here in case anyone wants to contest or support this proposal.

I’m actually not suggestion this just make it easier for an operator to
achieve single use, I am suggesting to require operators to honor it,
otherwise application developers are still left guessing, although this
belongs to the class of things that cannot easily be enforced.

On 7 February 2019 at 14.49.56, Ian Swett ([email-masked]) wrote:

---

From: Kazuho Oku <[email-masked]>
Date: Fri, 8 Feb 2019 01:56:44 +0900

Hi,

I think that the proposed approach works, and I think it's an
interesting idea. However, I am not sure if I would prefer having
this, at least in v1. The reasons are as follows:

1. The design requires CID-based routing to be stable for the lifetime
of the session ticket (7 days). I am not sure if all of us can achieve
that. Note that the CID encryption key (and therefore the stateless
reset token key) needs to be stable for that amount of time.

2. The design requires the resumption CID to be bundled to the session
ticket. That goes against our previous consensus to not introduce
extensions in the session ticket (IIRC we discussed it during the
handshake refactor).

3. HTTP (which is the target application of QUICv1) already has a way
to handle replay attacks (425 Too Early). Most if not all server
operators will deploy that regardless, because we need it to support
0-RTT in TLS 1.3. I do not think we need a different mechanism for
HTTP/3. My preference goes to using the same mechanism in all HTTP
versions to avoid confusion among the users. I might expand the
argument that this would be the case not only for HTTP but for any
protocol that might run on TLS-over-TCP and/or QUIC.

4. The design introduces burden to certain HTTP-QUIC stacks.
Currently, it is possible to mitigate replay attacks by
pattern-matching the HTTP method / URI to see if a request needs to be
delayed until receiving the proof of non-replay. The proposed approach
mandates the stacks to deploy a hash-table-based replay detection,
which IMO is rather costly, considering the fact that most GET
requests are idempotent and therefore replay-resistant.

To summarize, the proposal seems like a gratuitous change considering
the state of HTTP and QUIC v1.

2019年2月8日(金) 0:00 Mikkel Fahnøe Jørgensen <[email-masked]>:
> I’m actually not suggestion this just make it easier for an operator to achieve single use, I am suggesting to require operators to honor it, otherwise application developers are still left guessing, although this belongs to the class of things that cannot easily be enforced.
>
> Mikkel, are you suggesting a connection ID be paired with a NewSessionTicket or Token for the purposes of resumption to make it easier for a server operator to prevent using the same NST or Token multiple times?
>
> If so, this thread has gone extraordinarily off the original topic of what frames to allow in 0RTT, so if you want to discuss that further, please start a new thread.
>
> On Thu, Feb 7, 2019 at 1:45 AM Mikkel Fahnøe Jørgensen <[email-masked]> wrote:
>>
>> Yes, I forgot the 0-RTT token is called a ticket. The fact that it is encrypted does not change the general similarity to Retry if we assume retry tokens are created in safe manner. Both can be replayed and both can have non-forgeable timestamps..
>>
>> What is different is that TLS 1.3 over TCP does not have a concept of Connection ID that it can use for routing, but Retry does.
>>
>> (Restating that currently Retry is not required, only recommended, to enforce single use, but analysis shows that it is feasible to require).
>>
>> The time limit is, in this context, needed to ensure you do not have to keep infinite state whereas for TLS in general it (I presume) has more to do with tracking, reducing the attack surface, but to an extend also to clean up state.
>>
>> By requiring that the 0-RTT NewSessionTicket is used with a CID that routes to a specific endpoint, or cluster of endpoints, that can synchronously verify non-reuse within the time limit, you can lift a complex burden from the application layer - at the cost of complexity in transport and cluster configuration.
>>
>> One reason not to do this is complexity - but it is still simpler that dumping this on the average application, and you are not forced to to do 0-RTT.
>> Another reason to not do this is that if you can’t control your routing tightly you need an internal lookup which adds latency.
>>
>> The application layer has a few advantages: One is that it might not care about replay (like GET hello/world), or it might have a convenient sync mechanism like a database query. But these are notoriously difficult to get right and fast at the same time.
>>
>> If you place the burden on transport, you loose the ability of cluster wide fast GET, but that might not be a big issue, since routing to a dedicated host will also work and on average it will level out.
>>
>> If the servers QUIC implementation is able and willing to share synchronized state (e.g. via Redis or Zookeeper, etcd, or custom built-in), you can take the complexity hit once and let applications piggyback on that, and if its not, you still have single server guarantees.
>>
>> I believe this is the only sane way to go about it. I really can’t see 0-RTT being safe left to the average application developer (however smart) when under time pressure to deliver.
>>
>> Regardless of much work the transport does, an application should of course maintain its own integrity, and avoid double bookings etc. which can happy by other means as well, unless, of course, that would be a business strategy to do so.
>>

-- 
Kazuho Oku

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Thu, 7 Feb 2019 09:37:47 -0800

On 7 February 2019 at 17.56.55, Kazuho Oku ([email-masked]) wrote:

This is not necessarily the case. If the ticket is keyed for a specific
host, the CID only needs to make sure it hits the proper host, it does not
need to verify the CID. If someone forges the CID they would either reach
the same host as intended, or a different host, which does not have the key
to verify the session ticket.

If verification is based on CID rather than master keys for session
tickets, it can become a guessing game that is easier to master.

Another concern I have, is that the CID’s need to live for a long time, and
your recent issue on rate limiting CID’s highlights that they are a limited
resource, if short. Therefore the CID would need to be long, preferably so
long that it need not be remembered. It should be thrown away like the
original random CID of a new connection. This could also alleviate some
routing consistency concerns, provided the actual host is online because
the CID can hold more data. But, it complicates middle boxes that must now
understand different CID lengths.

On v1: If people are comfortable with the HTTP/3 as it is, the issue is not
pressing. From what I hear it is still all rather unproven, so this would
be an opportunity catch potential problems early. OTOH: there are probably
many hidden problems that could use more time that v1 can afford.

---

From: Martin Thomson <[email-masked]>
Date: Fri, 08 Feb 2019 10:00:20 +1100

We had a similar debate in TLS.  By my recollection, the conclusion was that while there are distinct advantages in binding 0-RTT resumption to a single instance, the practicalities (such as those that Kazhuo lists here) are such that it was impractical for too many deployments.  As such, there is advice in TLS 1.3 about this (see Appendix E.5 and Section 8), but none of that advice rises to the level of being mandatory.  In the end, if your server infrastructure can support this, or the rate of successful 0-RTT acceptance is not reduced significantly by enabling this, then all power to you.

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Fri, 8 Feb 2019 00:32:31 -0800

Thanks for these insights.

I think it would, as a minimum, be good to ensure the design is friendly
towards single use tickets.

There is an open ticket on which DCID a 0-RTT uses.
https://github.com/quicwg/base-drafts/issues/2398

If 0-RTT could (optionally) use a longer server selected DCID than normal,
that would make the design possible even if not enforced - noting that it
isn’t necessary for the session ticket to know the CID.

As to the TLS discussion - TLS would have to make allowances for load
balancers that cannot be assumed to understand 0-RTT flow. I think this is
different for QUIC where CID routing must be assumed. That does not mean
that QUIC single instance 0-RTT doesn’t cause more problems than is
worthwhile, but dismissing it based on TLS observations alone is probably
premature.

On 8 February 2019 at 00.00.38, Martin Thomson ([email-masked]) wrote:

We had a similar debate in TLS. By my recollection, the conclusion was that
while there are distinct advantages in binding 0-RTT resumption to a single
instance, the practicalities (such as those that Kazhuo lists here) are
such that it was impractical for too many deployments. As such, there is
advice in TLS 1.3 about this (see Appendix E.5 and Section 8), but none of
that advice rises to the level of being mandatory. In the end, if your
server infrastructure can support this, or the rate of successful 0-RTT
acceptance is not reduced significantly by enabling this, then all power to
you.

NewSessionTicket or Token for the purposes of resumption to make it easier
for a server operator to prevent using the same NST or Token multiple
times?
> >
> > If so, this thread has gone extraordinarily off the original topic of
what frames to allow in 0RTT, so if you want to discuss that further,
please start a new thread.
> >>
> >> The time limit is, in this context, needed to ensure you do not have
to keep infinite state whereas for TLS in general it (I presume) has more
to do with tracking, reducing the attack surface, but to an extend also to
clean up state.
> >> Another reason to not do this is that if you can’t control your
routing tightly you need an internal lookup which adds latency.
> >>
> >> The application layer has a few advantages: One is that it might not
care about replay (like GET hello/world), or it might have a convenient
sync mechanism like a database query. But these are notoriously difficult
to get right and fast at the same time.
> >> Hi Mikkel, are you talking about Retry? The token that is used for
0-RTT (that is, the session ticket) is encrypted. The (very) short-lived
token that appears in a Retry packet is the only token-like thing that is
exposed to the network.
> >>
> >> The point about servers tracking clients is already well-treated in
the literature. (I can't remember if we have text in QUIC specifically, but
it should be in TLS.)