Change:
Otherwise information about the token might leak. As NAME says, there is no strict need to compare across the entire set of values. That could leaks two things: that the inbound packet was dropped and the total number of stateless reset tokens. Both are things that we might care about, but will probably find prohibitive to protect.
On consideration, I'm going to make this a design issue so that it gets under noses.
Maybe I'm missing something obvious here, but how is this easy to implement? In my implementation, I have a map of reset tokens -> connections. Unless I switch to a constant time map implementation (the standard library doesn't provide one, you'd have to write that yourself), the comparisons are (probably?) not constant time. Given that this would cause some considerable complexity, I don't think it's worth defending against this rather uninteresting attack, and I'm not sure if a MUST is the right way to go here.
To support NAME point, a SHOULD and a comment about why you should do it and the impact of not doing so seems fine to me.
One thing that I'm trying to protect is the integrity of the specification and the analysis that (I hope) will come. If we allow weaseling out of this sort of requirement, then the analysis has to allow for that too. If you are using a hash map, then you can do as NAME suggests: use a PRP (a PRF would also work) to produce an intermediate value from the incoming value and compare that in non-constant time. As long as the operation cannot be reversed by an attacker and the secret input to the PRP (i.e., the token) are handled in constant time, the secret doesn't leak. We can't assume that resetting a connection is without cost. QUIC has the nice property that connections can survive network flutters, which means that a lot of investment could be made in a connection. (That's probably still inadvisable.) A well-positioned attacker doesn't have to perform a huge amount of work to recover a stateless reset token, relative to the value they might gain in disrupting a "high-value" connection of that sort. If we use a "MUST", then we're not stopping someone from deciding that this requirement doesn't apply to them. But they would take responsibility for that, hopefully with some awareness of what is being risked. Yes, we could use "MUST avoid leaking through timing side channels unless ", but determining what X needs to be is tricky as it depends so much on application protocol and other contextual stuff.
NAME As I stated on the issue, using a "standard hash map implementation" for storing stateless reset tokens as-is is not a good approach, as it might be vulnerable to . I think this is a unique property for a hash map containing stateless reset tokens; hash maps containing CIDs is not susceptible to the issue as CIDs are chosen by the endpoint (rather than the peer), nor are the hash maps containing streams, as stream IDs are generated in sequential order (assuming that an endpoint would not have a very high stream concurrency per each connection). Considering that, we might want to state something like the following in Security Considerations: As the stateless reset tokens are controlled by the peers, QUIC endpoints SHOULD be resistant to hash flooding DoS attacks when using a hash map for retaining those tokens. One way of achieving such property is to retain and compare the transformed values of the stateless tokens where the transformation is defined as a cryptographically-secure pseudo-random permutation (e.g., block cipher) or a pseudo-random function (e.g., secure hash function), instead of using the raw token values as the hash keys. This approach also satisfies the constant time comparison requirements in section x.y.
A PR that said that might be worth talking about. Nit: You need the "secure hash function" to be keyed in some way.
NAME Fair enough. Created .
NAME I was wrong on this, and you're absolutely right. Implementation difficulties shouldn't make us lower our security guaranties, or the language we use to describe them. NAME Thanks for the pointer to the hash flooding attack. By the way, here's the corresponding to the slides you linked.
Rust has inbuilt defenses against hash flooding, and some languages have that built in. Note that this is something that doesn't guarantee constant time over inputs, as that is generally not a design goal, though many schemes have that naturally. Even then, the functions used might be trivially reversible.
This will make the process more robust, as the process of splitting a datagram into packets could cause the token to be split. We recommend sending a packet (and one that can't be coalesced to boot). This was always going to be a single packet datagram anyway, so this shouldn't affect implementations much.
Not sure if this is editorial or not. We already require sending to be a whole datagram: So this could be seen to match things up. But it could require code changes in implementations, so I'm leaning toward .
Discussed in Cupertino. to be merged following consensus call.
Why does the text mandate this order? You need a constant time comparison in any case.
I'm not sure the order is (intended to be) normative. An SR looks like a junk packet that you would otherwise drop, either because it doesn't match a known connection by CID, or because it happens to match a known CID (zero-length helps) but gives a whacko packet number compared to the connection state, or because the packet number looks reasonable but the MAC is bad. In those situations, you then have to decide whether it's just junk or actually a SR. So the order is what we expect a logical implementation would do, but not necessarily required. Can you propose better explanatory text for that?
Sure, but I have to get my other issues from my read-through in first.
NAME I know that this is now old, but I think that there's something here. Not in the ordering, which I think is not important, but in the constant time piece. I think that this implies that constant-time comparison with every viable stateless reset is something we should do on every packet. There's been an assumption throughout that packet processing time for dropped packets isn't observable. I think that's a bad assumption, frankly. It's not like you can't put a packet with a known processing time after each probe. So I think that we've got to at least say that this is what happens. But we've just raised the packet processing load considerably, so I doubt that this will be well received.
Depending on implementation it can be very hard to do constant time. Even if you compare keys in constant time, the cache latency can be much higher. For stateless resets I'd assume a lookup only happens once and then it is removed from, say, a hash table. That is, there is no cache latency difference between a valid and an invalid lookup. Compare this to connectionsID's that are looked up frequently. Here it is possible to guess to valid CID's based on lookup times. But even if you do not have cache latency issues, you have hash collision issues. This makes a hash table unsuitable. The alternernative is binary tree, or a trie. The binary tree will be have to modified to do log N comparisons for all lookups even if the key is found higher up. And regardless a binary tree is much much slower than a hash table. Tries are very complex to implement correctly and must still be implemented for maximum search path comparison. Another approach is to timestamp the lookup and busyloop until you reach, say, 100ns of cpu cycles after performaning the lookup in a hash table. This is still faster than a binary tree, but slower than the trie. I seriously doubt most implementations would go out of their way to make resets effectively constants time. So the question is - does that make most implementations trivial targets for DoS resets? And what about guessing CID's? How can practically protect against this, because trusting the implementation to be constant time is not realistic.
A related problem is the use of API access tokens. Many people argue against JWT token validation because you cannot retire a JWT token. But a JWT token is easy to verify in constant time. An opaque access token must be looked up, and this is very hard to do constant time. This could be major security concern on the internet at large.
Thus, a solution to the reset problem could be to define an algorithm and share keys with the client. The problem is that the clients key must be session specific while that server cannot share its server keys, so something creative must be invented. If this is done, another latency will be added to the per packet overhead, which is also not desirable, similar to header protection.
I imagine this to be much simpler than you suggest. An endpoint that sends packets using a given set of connection IDs puts all the associated stateless reset tokens into a structure. Then along with decrypting packets, it iterates through that structure and performs a constant time comparison with each. That's something that won't have weird cache usage patterns, other than the usual cost of getting these values into the cache if they fall out. The number of active values shouldn't be a secret, which is most of what that tells you. The result of this will be two Boolean values: whether the AEAD was OK and whether the stateless reset was triggered. An authentic packet will be the logical OR of those two values. Anything else is dropped. After that point, you have crossed into packets being "good", so the time you take to process the packet is less of a concern.
If you can guarantee that there are only a short list to search, then yes. But it is only per connection ID that list is short, so you need to make the connection ID lookup constant time. You can potentially have much more than 100K connection ids to map. For normal operation you first validate the packet, so here the CID lookup is less critical, but for reset that is not the case. Clearly performing a linear search over all CID's is not viable.
I think that the list could be narrowed by path. If you have tons of connections on the same path, yeah, that's not great.
You don't need constant time because you are not replying to the reset. Worst case is that you ignore / reject the reset and continue sending observable packets. The timing of those packets is largely (but not entirely) independent of the reset lookup timing. Further, if a reset is successfully guessed, you are dead, so the only thing to protect is how you test for and react to unmatched resets. If packets are paced you should be good.
That assumes that you don't have observable behaviour that follows the processing of that packet. What I'm concerned about is being able to measure the time to compare, which gives an attacker a read on the value of the stateless reset token. I think that it is sufficient for the comparison of each stateless reset token to be constant time, but the process might be cut short if a match is found. And it might be OK to not check stateless reset tokens if the packet decrypts successfully. That would all be on the basis that we're protecting multiple discrete items and not a single secret.
A practical solution to the problem is to sample dropped packets and process these during idle time and eventually tear down a connection if necessary, rather than immediately. It can also be done in a separate thread although that on its own will not fully hide timing.
While I agree that endpoints should not leak information that let's the attacker guess a valid token value, I am not sure if we need to require constant time comparison, as suggested by . I think that the practical defense would be mix a secret seed securely into the hash key (e.g., by using HMAC), much like we do for hash tables to prevent DoS collision attacks. Assuming that we mix a secure seed, the attacker would not be possible to deduce information from the response time of the hash table.
Assume that you do have a way of measuring how long it takes to process a garbage packet. Also assume that there is just one token. If you don't compare in constant time, the attacker can measure how long it takes to compare the possible token with the real token. For every byte that matches, you take a tiny bit longer. A complete search takes just 2^12 guesses, multiplied by any oversampling factor you might need to extract the side channel information. That makes it not very hard.
But if you need that many guesses to kill a connection, it is not viable. If it is to guess a master secret, the solution is to use a one way hash. Guessing a specific token will not reveal anything of importance beyond being able to kill the connection.
NAME That's a fair point. Let me adjust my approach: use internally comparing the stateless reset tokens. For example, we could use . Then, we would not leak anything even if both the hash lookup or the comparison of the token values (now done by comparing the values permuted by AES) are not constant-time.
OK for comparing each reset token, I think using PRP can be considered as a way of implementing constant-time comparison. Then, the discussion would be if we should state something like: "lookup of stateless reset tokens MUST NOT leak timing information. Such lookup table can for example be implemented by using PRP(statelessresettoken) instead of the stateless reset token itself, for lookup and comparing the values." Though I'm inclined to think that the text in might be fine after all.
Using a PRP like that requires touching all the values in constant time anyway, so that's just a more expensive method of doing a constant-time comparison.
I do not think that it would be expensive. Rather, I think using PRP might be the simplest and cheapest way. The hash table needs to use be resistant to hash collision DoS attacks, because the stateless reset tokens are supplied by the peers. Therefore, to calculate the hash key, I'd assume that endpoints would be using some function that takes a master secret and a stateless reset token. That function can be a PRP.
If you are using a hash table, sure. But if you can narrow the values down to those tokens that were associated with packets sent on that path, a list would be a better storage method and then a straight constant-time comparison is much simpler.
Yeah I am not sure if you would want to maintain a hash table using paths as keys just for that purpose. IIUC, at the moment, there is no need for such a hash table on the server side.
I don't think that the text here resolves the issue. I agree that you need to have a constant time comparison, but I don't believe it needs to post-date the MAC check, because the attacker knows that there is an invalid MAC. I don't object to landing the PR, but it doesn't resolve this issue.
NAME is right that the PR doesn't resolve the original issue about the ordering. NAME : is there a reason for the ordering?
Oh, I completely missed the question about ordering in there. Reading comprehension fail on my part. I've tweaked the proposed fix to account for this. Specifically: Note that I've switched this to datagram rather than packet, so match the sending and to allow for variations in how packets are assembled into datagrams. I think that fixes it, but I'll wait for input on the new changes before reattaching .
Can someone explain to a dumb guy like me why this is important? We have three paths: 1) Good packet: decrypt, validate, and then process the data in the packet. 2) Stateless reset: attempt to decrypt, fail, find a match for a registered reject token, kill connection. 3) Garbage: attempt to decrypt, fail, do not find a match, do nothing. I content that (1) and (2) are observable no matter what. If the connection goes on as in (1), further packets can be observed. If the connection stops, absence of further packets can be observed. The behavior after (3) depends on the state -- if the connection is up, further data will come out as in (1). If the connection is down, nothing will happen. What exactly is the concern?
Christian, the original text required that we only evaluate 2 after failing to match on 1 (i.e., fail to decrypt). The new text allows any order of operations. The secondary thing is that 2 needs to be constant time for each stateless reset token.
Thanks for the updated PR, NAME I think this resolves NAME concern.
Discussed in Cupertino. to be merged following consensus call.
Some small suggestions, but LG

Discussion:
10.4.1. <del> An endpoint detects a potential stateless reset when an incoming packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset Tokens corresponding to active connection IDs that the endpoint has used for sending packets to the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated </del> <ins> An endpoint detects a potential stateless reset using the trailing 16 bytes of the UDP datagram.  An endpoint remembers all Stateless Reset Tokens associated with the connection IDs and remote addresses for datagrams it has recently sent.  This includes Stateless Reset Tokens from NEW_CONNECTION_ID frames and the server's transport parameters but excludes Stateless Reset Tokens associated with connection IDs that are either unused or retired.  The endpoint identifies a received datagram as a stateless reset by comparing the last 16 bytes of the datagram with all Stateless Reset Tokens associated with the remote address on which the datagram was received. This comparison can be performed for every inbound datagram. Endpoints MAY skip this check if any packet from a datagram is successfully processed.  However, the comparison MUST be performed when the first packet in an incoming datagram either cannot be associated with a connection, or cannot be decrypted. An endpoint MUST NOT check for any Stateless Reset Tokens associated </ins> with connection IDs it has not used or for connection IDs that have been retired. <del> If the last 16 bytes of the packet values are identical to a </del> <ins> When comparing a datagram to Stateless Reset Token values, endpoints MUST perform the comparison without leaking information about the value of the token.  For example, performing this comparison in constant time protects the value of individual Stateless Reset Tokens from information leakage through timing side channels.  Another approach would be to store and compare the transformed values of Stateless Reset Tokens instead of the raw token values, where the transformation is defined as a cryptographically-secure pseudo-random function using a secret key (e.g., block cipher, HMAC RFC2104).  An endpoint is not expected to protect information about whether a packet was successfully decrypted, or the number of valid Stateless Reset Tokens. If the last 16 bytes of the datagram are identical in value to a </ins> Stateless Reset Token, the endpoint MUST enter the draining period <del> and not send any further packets on this connection.  If the comparison fails, the packet can be discarded. </del> <ins> and not send any further packets on this connection. </ins> 10.4.2.