[tor-bugs] #12208 [Obfuscation/meek]: Make it possible to use an IP address as a front (no DNS request and no SNI)

Tor Bug Tracker & Wiki blackhole at torproject.org
Tue May 1 07:16:27 UTC 2018


#12208: Make it possible to use an IP address as a front (no DNS request and no
SNI)
------------------------------+------------------------------
 Reporter:  dcf               |          Owner:  dcf
     Type:  enhancement       |         Status:  needs_review
 Priority:  Medium            |      Milestone:
Component:  Obfuscation/meek  |        Version:
 Severity:  Normal            |     Resolution:
 Keywords:                    |  Actual Points:
Parent ID:                    |         Points:
 Reviewer:                    |        Sponsor:
------------------------------+------------------------------
Changes (by dcf):

 * status:  new => needs_review


Comment:

 The past couple of days I've been experimenting with ways to set or omit
 the SNI. There's a demo program in attachment:names.go and I'd appreciate
 any review or questions about design decisions. I took inspiration from
 Ox's [https://gist.github.com/oxtoacart/5e78d25a7f9a9cda10cd
 domainfront.go demo] from 2014, but changes in the standard library
 (especially since Go 1.8) provide better ways to do some things now. If
 this looks good, I'm going to use it as the basis of new code in meek-
 client.

 The basic situation is that we are managing four domain names:
  * `urlName`: this is the name we resolve and actually establish a TCP
 connection with.
  * `hostName`: this is the name that goes in the HTTP Host header.
  * `sniName`: this is the name that goes in the SNI extension.
  * `verifyName`: this is the name that the client verifies the server
 certificate against.
 In normal everyday HTTPS, all four of these names are the same. Domain
 fronting allows `hostName` to differ, but the other three names are the
 same. attachment:names.go shows how to make all four names independent, so
 you can, for example, send no SNI but still verify the server certificate
 against a hostname, while still fronting a different domain in the Host
 header.

 An explanation of some decisions:
  1. The way to set the SNI is to modify `TLSClientConfig.ServerName`; the
 way to set the verification name is to modify the
 `TLSClientConfig.VerifyPeerCertificate` callback. Both of these are
 properties of [https://golang.org/pkg/net/http/#Transport http.Transport],
 which is a long-lived data structure that manages multiple HTTP
 roundtrips. We therefore need a separate `http.Transport` for each unique
 (`sniName`, `verifyName`) pair. The only exception is when `urlName` =
 `sniName` = `verifyName`: that's the built-in behavior of the net/http
 package, so we can share an `http.Transport` in that case. The function
 `getHTTPTransportForNames` creates and caches new `http.Transport`s as
 needed. (And note that `hostName` does not enter this logic at all:
 whether or not you are domain fronting is orthogonal to certificate
 verification.)
  2. The way to omit the SNI extension is to set
 `TLSClientConfig.ServerName` to an IP address. The IP address is not
 actually used for anything else, as long as
 `TLSClientConfig.InsecureSkipVerify` is set. The trick of setting
 `TLSClientConfig.TLSDial`, and then pulling the certificates from the
 `tls.Conn` using `ConnectionState`,
 [https://github.com/golang/go/issues/9126 doesn't work] when a proxy is
 set, because the proxy bypasses the dialer (which is the reason why
 [https://github.com/golang/go/issues/16363 VerifyPeerCertificate was
 introduced]).
  3. We need to use multiple simultaneous `http.Transport`s, so we can't
 just modify the global [https://golang.org/pkg/net/http/#RoundTripper
 http.DefaultTransport] like we do currently. But `http.DefaultTransport`
 has some nice default settings for things like timeouts. Go doesn't
 provide any method for creating a new `http.Transport` that has the
 default settings, and you can't just copy the struct literal because it
 contains mutexes. So I used a reflection trick I found on a mailing list
 to copy just the public struct members. Also if we naively set
 `TLSClientConfig`, it [https://github.com/golang/go/issues/17051 disables
 HTTP/2 support]; because we plan to modify `TLSClientConfig`, we have to
 first call `http2.ConfigureTransport`.

 Here are examples use cases.
  all names equal (ordinary HTTPS)::
  {{{
  $ ./names -host example.com -sni example.com -verify example.com
 https://example.com/
  urlName:    "example.com"
  hostName:   "example.com"
  sniName:    "example.com"
  verifyName: "example.com"
  HTTP/2.0 200 OK
  }}}
  omit SNI, but still verify::
  {{{
  $ ./names -host example.com -sni "" -verify example.com
 https://example.com/
  urlName:    "example.com"
  hostName:   "example.com"
  sniName:    ""
  verifyName: "example.com"
  HTTP/2.0 200 OK
  }}}
  omit DNS request and SNI, but still verify::
  {{{
  $ dig +short example.com
  93.184.216.34
  $ ./names -host example.com -sni "" -verify example.com
 https://93.184.216.34/
  urlName:    "93.184.216.34"
  hostName:   "example.com"
  sniName:    ""
  verifyName: "example.com"
  HTTP/2.0 200 OK
  }}}
  ask for one name in the SNI but verify against another (still valid)
 name::
  {{{
  $ ./names -host example.com -sni example.com -verify www.example.net
 https://example.com/
  urlName:    "example.com"
  hostName:   "example.com"
  sniName:    "example.com"
  verifyName: "www.example.net"
  HTTP/2.0 200 OK
  }}}
  try verifying against a name that's not valid for the certificate::
  {{{
  $ ./names -host example.com -sni example.com -verify microsoft.com
 https://example.com/
  urlName:    "example.com"
  hostName:   "example.com"
  sniName:    "example.com"
  verifyName: "microsoft.com"
  error: x509: certificate is valid for www.example.org, example.com,
 example.edu, example.net, example.org, www.example.com, www.example.edu,
 www.example.net, not microsoft.com
  }}}
  domain fronting::
  {{{
  $ ./names -host maps.google.com -sni www.google.com -verify
 www.google.com https://www.google.com/
  urlName:    "www.google.com"
  hostName:   "maps.google.com"
  sniName:    "www.google.com"
  verifyName: "www.google.com"
  HTTP/2.0 302 Found
  }}}
  domain fronting [ticket:25804 thwarted]::
  {{{
  $ ./names -host html5-demos.appspot.com -sni www.google.com -verify
 www.google.com https://www.google.com/
  urlName:    "www.google.com"
  hostName:   "html5-demos.appspot.com"
  sniName:    "www.google.com"
  verifyName: "www.google.com"
  HTTP/2.0 502 Bad Gateway
  }}}
  but works if you leave off the SNI::
  {{{
  $ ./names -host html5-demos.appspot.com -sni "" -verify www.google.com
 https://www.google.com/
  urlName:    "www.google.com"
  hostName:   "html5-demos.appspot.com"
  sniName:    ""
  verifyName: "www.google.com"
  HTTP/2.0 200 OK
  }}}

--
Ticket URL: <https://trac.torproject.org/projects/tor/ticket/12208#comment:11>
Tor Bug Tracker & Wiki <https://trac.torproject.org/>
The Tor Project: anonymity online


More information about the tor-bugs mailing list