go icon indicating copy to clipboard operation
go copied to clipboard

How to properly deploy ECH in transparent proxy mode

Open cuonglm opened this issue 2 years ago • 9 comments

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

cuonglm avatar Jun 03 '22 15:06 cuonglm

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.

cjpatton avatar Jun 09 '22 14:06 cjpatton

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

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

  • For each TLS request, sniff the SNI from net.Conn (this is where go-vhost helps me).
  • After getting the SNI, for example cloudflare.com, my proxy will open a HTTP CONNECT request to cloudflare.com.
  • Then the proxy will forward the TLS request to cloudflare.com, and let the TLS handshake happens normally.
  • Client can now access cloudflare.com via my proxy.

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)?

cuonglm avatar Jun 09 '22 16:06 cuonglm

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.

cjpatton avatar Jun 10 '22 02:06 cjpatton

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

cuonglm avatar Jun 10 '22 02:06 cuonglm

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.

cjpatton avatar Jun 10 '22 03:06 cjpatton

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?

cuonglm avatar Jun 10 '22 04:06 cuonglm

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 ([email protected]).

cjpatton avatar Jun 10 '22 04:06 cjpatton

@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.

cuonglm avatar Jun 10 '22 06:06 cuonglm

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

cjpatton avatar Jun 10 '22 14:06 cjpatton