go
go copied to clipboard
How to properly deploy ECH in transparent proxy mode
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
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 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 wherego-vhost
helps me). - After getting the SNI, for example
cloudflare.com
, my proxy will open aHTTP CONNECT
request tocloudflare.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)?
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):
- Client sends ClientHello to proxy
- Proxy forwards ClientHello (without changing it) to server
- Server sends ServerHello to Proxy
- Proxy forwards ServerHello to Client
Or do you mean this (active proxy):
- Client sends ClientHello to proxy
- 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 yes, it's a passive proxy (transparent proxy).
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.
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?
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 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.
I'd be happy to review a PR! A good place to start might be to propose the changes to the API.