cloudflare / go

Go with Cloudflare experimental patches
BSD 3-Clause "New" or "Revised" License
319 stars 43 forks source link

How to properly deploy ECH in transparent proxy mode #110

Open cuonglm opened 2 years ago

cuonglm commented 2 years ago

I have a TLS transparent proxy which works like this:

Client ==|TLS request|==> Proxy ==|HTTP Connect|==> Target 

I use https://github.com/inconshreveable/go-vhost to sniff SNI and construct the HTTP Connect request to the target site.

Now I want to add ECH support to my transparent proxy. But I stuck after getting the inner SNI:

tlsConn, _ := vhost.TLS(conn)
sconn := tls.Server(tlsConn, &tls.Config{
    ECHEnabled: true,
    ServerECHProvider: echProvider,
    Certificates: []tls.Certificate{cert},
})
sconn.Handshake()
sconn.ConnectionState().Servername // This is the inner SNI

IIUC, the proxy must tell the client to construct the new inner ClientHello, how can I archive that with the current crypto/tls API?

cc @cjpatton

cjpatton commented 2 years ago

Hi @cuonglm, I'm not sure I'm familiar with your deployment scenario. Can you provide a bit more detail? For context, I'm not familiar with the go-vhost.

To configure a client to use ECH, you need to set crypto/tls.Config.ECHEnabled to true and you need to set crypto/tls.Config.ClientECHConfigs to a set of ECH configs advertised by the server.

cuonglm commented 2 years ago

@cjpatton Sorry for my bad description, let me re-phrase the deployment.

I have a TLS proxy written in Go, what it does:

Now, I want to add ECH support to my proxy, what should I do on my proxy?

Currently, I am able to get the inner SNI:

tlsConn, _ := vhost.TLS(conn)  // After this, the outer SNI is extracted, for example, ech.my-domain.com
sconn := tls.Server(tlsConn, &tls.Config{
    ECHEnabled: true,
    ServerECHProvider: echProvider,
    Certificates: []tls.Certificate{cert},
})
sconn.Handshake()
sconn.ConnectionState().Servername  // This is the inner SNI, for example, cloudflare.com

Now, I'm stuck here. What should I do next? AFAIU, after the call to sconn.Handshake(), I'm able to get the inner hello, so how can I forward it to the target site (cloudflare.com in this case)?

cjpatton commented 2 years ago

Setting aside ECH for a second: What do you mean by "TLS request"? Do you mean the ClientHello? Is this the sequence of events (passive proxy):

  1. Client sends ClientHello to proxy
  2. Proxy forwards ClientHello (without changing it) to server
  3. Server sends ServerHello to Proxy
  4. Proxy forwards ServerHello to Client

Or do you mean this (active proxy):

  1. Client sends ClientHello to proxy
  2. Proxy terminates TLS, sending its own ServerHello in response to the client. Meanwhile, it establishes a TLS connection to the server with which it can forward HTTP requests.
cuonglm commented 2 years ago

@cjpatton yes, it's a passive proxy (transparent proxy).

cjpatton commented 2 years ago

If ECH is used in the handshake, then a passive proxy won't have access to the inner SNI. This is because the inner ClientHello is encrypted the server's HPKE public key. Only the outer SNI is sent in the clear.

If you need to passively inspect the inner SNI, then you'll have to arrange for the client to use an HPKE public key for which the proxy knows the corresponding secret key. However, this won't be possible in a typical deployment.

cuonglm commented 2 years ago

If ECH is used in the handshake, then a passive proxy won't have access to the inner SNI. This is because the inner ClientHello is encrypted the server's HPKE public key. Only the outer SNI is sent in the clear.

If you need to passively inspect the inner SNI, then you'll have to arrange for the client to use an HPKE public key for which the proxy knows the corresponding secret key. However, this won't be possible in a typical deployment.

Yes, you can see I was able to decrypt the inner SNI. I mean after that, what must I do to forward the inner hello to target?

cjpatton commented 2 years ago

Oh, perhaps I misunderstood. You mean that you're forwarding the inner ClientHello to the server, not the outer ClientHello?

You might be asking about "Split Mode". The transparent proxy is known as the "client-facing server" in ECH-lingo; and the target server is known as the "backend server". The client-facing server has the HPKE secret key. Split Mode is designed so that the backend server does not need the secret key.

Split Mode is not currently supported by our implementation. We might be willing to consider a patch to add Split Mode, however the design would need to be carefully thought out. When we first looked at this, we concluded that Split Mode would complicate the state machine significantly.

There may be other implementations of ECH that have support for Split Mode. You should consider reaching out to the mailing list (tls@ietf.org).

cuonglm commented 2 years ago

@cjpatton Yes, split-mode is what I'm trying to do. Do you have plan and accept contribution outside Cloudflare? I'm happy to join and implement split-mode.

cjpatton commented 2 years ago

I'd be happy to review a PR! A good place to start might be to propose the changes to the API.