Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating a ECH client connection #138

Open
mbinns opened this issue Jan 10, 2023 · 6 comments
Open

Creating a ECH client connection #138

mbinns opened this issue Jan 10, 2023 · 6 comments

Comments

@mbinns
Copy link

mbinns commented Jan 10, 2023

I am trying to create an ECH connection as a client and I was hoping to use this fork in a proof of concept example.

Looking through the source it looks like if I specify ECHEnabled: true in my TLS config without specifying an ECHConfig it will fail over to GREASE.
I am missing on what value/how to create my own ECHConfig struct to pass that in so that I can get a legitimate ECH connection

Performing a dig -t TYPE65 crypto.cloudflare.com request provides me the following and I am assuming ech=<value> is the config I need?

crypto.cloudflare.com.  198     IN      HTTPS   1 . alpn="http/1.1,h2" ipv4hint=162.159.137.85,162.159.138.85 ech=AEb+DQBClAAgACCA88XUXJSY8yxlp6HWzE6uW3fyWQRcLW1e8o48eVAIfQAEAAEAAQATY2xvdWRmbGFyZS1lc25pLmNvbQAA ipv6hint=2606:4700:7::a29f:8955,2606:4700:7::a29f:8a55

I see you guys have an ECHConfig struct in tls/ech_config.go and clearly the tls config options take an array for that is there a marshaling function that I should be using with the raw value that comes back from the DNS lookup?

Also how then would I go about modifying the ClientHelloInner to point to the remote resource I am trying to access.

Sorry for the potentially naive questions, I've been reading over the source and trying to piece things together but I figured I might have better luck asking.

As an example of what I have so far in a toy example. I am fairly certain this is a successful GREASE request?

func main() {
    req, _ := http.NewRequest("GET", "https://crypto.cloudflare.com/cdn-cgi/trace", nil)

    req.Host =  "crypto.cloudflare.com"
    req.Header.Set("User-Agent", "Mozilla/5.0")



    client := http.Client{
        Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            ServerName:         "crypto.cloudflare.com",
            ECHEnabled:         true,
            },
        },
    }

    o, err:= client.Do(req)

    /* Print response */
    if nil != err {
        log.Fatalf("Dump: %v", err)
    }
    bodyBytes, _ := io.ReadAll(o.Body)
    fmt.Printf("%s\n", string(bodyBytes))
}

Thanks again for your time and work on this!

@cjpatton
Copy link
Contributor

Hi @mbinns, to enable ECH in our test client, you need to

  1. Set config.ECHEnabled = true (you've alredy got this part)
  2. Set config.ClientECHConfigs to the []ECHConfig advertised by the DNS server.

Looks like you've already done the hard part of fetching the ECH configs. To parse them:

    echConfigListBase64 := "AEb+DQBClAAgACCA88XUXJSY8yxlp6HWzE6uW3fyWQRcLW1e8o48eVAIfQAEAAEAAQATY2xvdWRmbGFyZS1lc25pLmNvbQAA"
    echConfigListBtyes, err := base64.StdEncoding.DecodeString(echConfigListBase64)
    if err != nil {
        panic(err)
    }

    echConfigList, err := tls.UnmarshalECHConfigs(echConfigListBtyes)
    if err != nil {
        panic(err)
    }

Also, just FYI that we rotate our ECH config fairly frequently ... you should only expect a given config to be accepted for a couple of hours.

@cjpatton
Copy link
Contributor

As an example of what I have so far in a toy example. I am fairly certain this is a successful GREASE request?

Yes, seting ECHEnabled without providing ECH configs will result in a GREASE handshake.

@mbinns
Copy link
Author

mbinns commented Jan 11, 2023

Yup I have supporting code to grab the ECH config on an hourly basis since you guys mentioned you rotate frequently in one of your blog posts. I think you mentioned you guys leave some wiggle room in there because DNS cache issues on client systems/networks?

looks like I was able to successfully hit crypto.cloudflare.com/cdn-cgi/trace
I received this:

fl=541f47
h=crypto.cloudflare.com
ip=<removed>
ts=1673402819.281
visit_scheme=https
uag=Mozilla/5.0
colo=SEA
sliver=none
http=http/1.1
loc=US
tls=TLSv1.3
sni=encrypted
warp=off
gateway=off
kex=X25519

The SNI shows encrypted and the connection in wireshark looks good, so looks like things worked out great there!

Probably an obvious question, but how do I go about specifying the values like SNI inside the InnerHello.
from reading the source it looks like the inner hello is derived from the outer hello. and there doesn't seem to be a way to modify the values separately?

My ignorant attempt would be to just change the tls.ServerName and the req.Host values to what I want the inner to represent.

@cjpatton
Copy link
Contributor

When ECH is enabled, config.ServerName is used as the inner SNI. The outer SNI is specified by the ECHConfig.

@cjpatton
Copy link
Contributor

I think you mentioned you guys leave some wiggle room in there because DNS cache issues on client systems/networks?

Yeah, there is wiggle room to allow for the DNS server and TLS server to get out of sync (very unlikely, at least in our deployment) or the TLS client and TLS server to get out of sync.

@mbinns
Copy link
Author

mbinns commented Jan 11, 2023

It looks like I have things working on my end.

Some other questions, if you have time (if not feel free to close)

  • The hello retry request process:
    • Its my understanding that if I were to send an invalid ECH config, like lets say one that has expired. The client facing server should send a HelloRR and at which point if things check out on the client I use the newly provided ECHConfig to retry the request. Looking through the function echAcceptOrReject it looks like that is supposed to be the case. But when I send an expired config it just returns the error tls: ech: rejected
      According to this comment it looks like that functionality isn't supported/implemented?

    // BUG(cjpatton): Upon ECH rejection, if retry configurations are provided, then
    // the client is expected to retry the connection. Otherwise, it may regard ECH
    // as being securely disabled by the client-facing server. The client in this
    // package does not attempt to retry the handshake.

If the server at crypto.cloudflare.com supports a HelloRR I would be willing to put some time into trying to make the client side work if you guys are interested in an outside PR.

Also I wanted to say thanks for helping me out with all the questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants