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

Set TLS SNI hostname to be the same as Host field in header #428

Closed
wants to merge 1 commit into from

Conversation

wen-long
Copy link

In http, Host field is correctly set from struct requestHeader, while https also need this to verify server cert, SNI hostname also be used in TLS handshake client hello to tell server which host to connect

SNI hostname don't contains port, so it should be either hostNoPort or Host field from requestHeader

@ghost
Copy link

ghost commented Sep 17, 2018

See https://play.golang.org/p/1E8omJr8N5_U. The net/http package uses the host from the request URL to verify the server cert, not the host from the request header. This package should do the same thing.

If someone should decide that this change is ok, then the PR should include a test that fails without change. Also, I think the code will be clearer if the name is set from r.Host:

if cfg.ServerName == "" {
                     h, _, err := net.SplitHostPort(.rHost)
                     if err != nil {
                           return nil, nil, err
                     }
		cfg.ServerName = h
	}

@wen-long
Copy link
Author

wen-long commented Sep 17, 2018

I did a test before make this PR, just Dial 1.1.1.1(any cloudflare IP) with a http.Header contains correct domain,it fails with below

dial:x509: certificate is valid for *.cloudflare-dns.com, cloudflare-dns.com, not 1.1.1.1:443

@stevenscott89 your code approved my PR. Pls check https://play.golang.org/p/iZy0uY-McX4 I added some Println,the result is below

r.Host r.URL.Host
127.0.0.1:2 127.0.0.1:2 fails
badhost.com 127.0.0.1:2 works

we see that only if r.Host is domain it goes right.

some code like


	u := url.URL{Scheme: "https", Host: "1.1.1.1", Path: "/echo"}
	var head http.Header
	if obfsHost != nil {
		head = http.Header{
			"Host": []string{"example.com"},
		}
	}
	log.Println(u.String(), head)
	ws, _, err := websocket.DefaultDialer.Dial(u.String(), head)

will failed baseuse old code doesn't use example.com in TLS connect.

Setting Host field in header just like

  1. self DNS resolve. change hosts file or build own dnsmasq and add any record
  2. like --resolve option in curl curl -I -k https://www.cloudflare.com/ --resolve "www.cloudflare.com:443:1.1.1.1" -v and this will work(cause cloudflare uses cloudflare)

In conclusion, hostname in URL is used to build TCP connection,But Host in Header settings is to be used by http headers and https SNI hostname. if Host is setted to some string, we should use it both in http and https.

BYW hostname used to build TCP connection and Host field in http head, https sni hostname is definitely needs to be stored as TWO variable

@elithrar
Copy link
Contributor

elithrar commented Sep 17, 2018 via email

@ghost
Copy link

ghost commented Sep 17, 2018

Here's an improved example: https://play.golang.org/p/YI8QJUEP1Nv

r.Host r.URL.Host Dialed address Host header on server TLS verify
example.com example.com example.com:443 example.com ok
127.0.0.1:2 127.0.0.1:2 127.0.0.1:2 127.0.0.1:2 ok
badhost.com example.com example.com:443 badhost.com ok
bahdhost.com 127.0.0.1:2 127.0.0.1:2 badhost.com ok
example.com badhost.com badhost.com:443 fails

The net/http client does not use r.Host (which is copied to the host header) to verify the server name or to dial the connection.

A dial function is the equivalent of curl --resolve, not setting the host header.

@wen-long
Copy link
Author

wen-long commented Sep 17, 2018 via email

@ghost
Copy link

ghost commented Sep 17, 2018

if url host is ip, net/http uses r.Host

I cannot observe this using a blackbox test, nor can I find code in net/http that does this. Perhaps I missed the code. I find the net/http transport code to be somewhat complex.

In any case, this package should remain consistent with net/http.

@ghost
Copy link

ghost commented Sep 17, 2018

See the proposed new test for host names in PR #429. The test in #429 locks in current host name handling and confirms that the websocket client matches the net/http client.

The current PR fails the new test added #429.

Use a dial function to dial the cloudfare IP address.

@wen-long
Copy link
Author

Maybe the right way is to set TLSClientConfig.ServerName in dialer, before call Dial, instead of letting inner mechanism infer the right servername.

Learn a lot from this discuss, thank you
gonna close this

@wen-long wen-long closed this Sep 18, 2018
@gorilla gorilla locked and limited conversation to collaborators Apr 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants