private-octopus / picoquic

Minimal implementation of the QUIC protocol
MIT License
551 stars 162 forks source link

Implement robustness extensions #590

Open huitema opened 5 years ago

huitema commented 5 years ago

The robustness extensions defined in Kazuho's draft rely on a new protocol type, and would prevent all interference by man-on-the-side attacker.

huitema commented 4 years ago

The draft has expired, so maybe it is time to go back to base. There are a series of threats open against Quic:

1) Intermediate routers dropping packets. This is can be mitigated by multipath and repetitions.

2) Intermediaries munging packets for various goals. Encryption protects against that, except for Initial packets. That mean intermediaries can for example send fake "connection close" messages to either peer and drop the connection during the handshake.

3) Adversaries changing the addresses or replaying packets with wrong addresses to ensure that the traffic goes through some place, including a place where (1) applies. No mitigation today. Would require somehow adding proofs of addresses.

4) The change of addresses, done with the Initial packet, is a simple way to force a connection through an MITM.

The draft proposes mitigating (2) by adding a cryptographic authentication to the packets, using a key derived from the ESNI secret.

For the first Initial packet, there is a protection against munging by making the ICID a cryptographic hash of the initial packet, or at least of its content. That protection could allow sealing the public key offered in the Initial packet, which could be a first step for something like "Quic-ESNI" if the goal is simply to hide some of the connection parameters -- maybe "TP in TP".

It could be possible to prevent address games by bring copies of the addresses "inside the encryption". Something like "I use CID number X to talk to you with this pair of addresses". This is not realistic in IPv4 due to the prevalence of NAT, but it would work for IPv6, or for IPv4 inside private networks.

There are features of Quic that are hard to secure: version negotiation, and retry. EKR has proposed bring version negotiation inside the encryption, which is plausible. Retry is harder, but could be solved by letting the client try multiple connections in parallel.

huitema commented 4 years ago

The draft on quic version aliasing provides an interesting approach: negotiate a version number during a prior exchange, and associate that version number with a different secret than the default for Initial packets in Quic V1. Middleboxes that do not have access to the secret cannot inject packets -- in fact they cannot read them. The design uses an "Initial Token Extension" as a key identifier, ensuring that different clients using the same 32 bit version will have different keys.

This gets most of the benefits of the robustness extensions for the "second" connections. There may be some latent issues, such as replaying packets or switching source or destination addresses, but they also exist in the initial definition of the robustness extensions. But the main issue is that the design does not protect the "first" connection.

huitema commented 2 years ago

Work appears to have almost stopped on this topic in the WG. ESNI as morphed into ECH, and is almost ready. We also have progress on secure version negotiation. The main issue that I have with either ECH or version negotiation is the lack of stealth: the number of the TLS extension for ECH is visible in clear text, and the negotiated version is also visible in clear text. There are already noises of networks dropping QUIC initial packets if they carry ECH, although the really bad folks are just as likely to simply drop QUIC altogether. We have work in progress in the protected-initial draft, which relies on ECH and adds a new version number -- not really stealthy.

The first scenario involves:

The client initial packet has to carry a token of some kind, so that:

There are multiple locations were tokens can be hidden:

The protection requires negotiating a key. Three approaches:

The private key approach protects only the "second" connection, unless some Kerberos-like exchange is used to provision the key on both server and client. The public key approaches require that the public key is present in the initial packet -- this will stop working when ECH will have to use Post Quantum. (Still, that's what the protected-initial draft uses.)

huitema commented 1 year ago

Another approach would be specialized for "stealth", with the following assumption:

1) For now, the client hello and the server hello typically fits in a single packet 2) Negative consequences only happen if the server or the client accept "bad" packets before the connection is complete 3) Thus it might be sufficient to simply drop or store packets that look suspect.

The stealth requirement is that the "robust" packets should not be easily distinguishable from "standard" packets.

On the server side, this is rather simple: once the client hello is received, do not accept more packets unless they are repeat of the the first initial packet, in which case the server hello has to be repeated. The only reasonable exception is if the client wants to close the connection before it is fully established, so maybe we need a way to protect that. (It helps that servers typically create a handshake context per 5 tuple + Initial CID.)

On the client side, it is a bit more complicated, because the server hello might be spoofed, and also because servers might have to send something else than a server hello, notably:

Of course, the man on the side can inject such packets to disrupt the connection, which leaves the client with the choice to either ignore the incoming packets, or find a way to differentiate between legit and spoofed packets. The latter requires some kind of shared secret between client and server, and means to convey that secret that do not raise suspicion. For example:

For Server Hello, we need some creativity. It is hard to hide value in the TLS message itself, because few TLS extensions are allowed in the Server Hello: key_share, pre_shared_key, and supported_versions. We could create new extensions, but the server hello can only carry extensions that were already set by the client, which would fail the "stealth" requirement. The message itself is defined as:

    struct {
        ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
        Random random;
        opaque legacy_session_id_echo<0..32>;
        CipherSuite cipher_suite;
        uint8 legacy_compression_method = 0;
        Extension extensions<6..2^16-1>;
    } ServerHello;

The only plausible variable here is the "random" field, 32 bytes long. It might be possible to hide something there. The Initial message is only carrying a CRYPTO frame. In the QUIC header, only two fields are really usable: the source connection ID, which is chosen by the server; and, the initial sequence number. The format of the source connection ID may be dictated by load balancers, so this could be hard to use. If we can use it, then we get 64 bits of randomness, which is good. The sequence number is hard to use properly, because the server may have to repeat the ServerHello. But the plausible number of such repetitions is limited, so maybe reserving 3 bits for them is enough, leaving 28 bits to encode some kind of signal. That's not very robust, but combined with stealth that may be OK.

We can think of the Server Hello protection as a first stage filter. Let's suppose that the client creates a tentative connection context upon receiving a Server Hello -- and creates another context if it receives a second one, etc. The client will receive handshake packets, and can use trial decryption to direct them to the proper context. The client will eventually be able to verify the server credentials on one of these contexts, and discard the spoofed ones. We want to limit the number of trial decryptions that will be necessary. A simple filter may achieve that.

The server hello sequence number or SCID solutions may also work for HRR.

huitema commented 1 year ago

The first step is to implement ECH, in order to obtain broad confidence that the connection is to the right server in 1-RTT. Then, write own specification of robustness behavior. What are the attacks?