Opened 8 months ago

Closed 4 months ago

Last modified 3 months ago

#28168 closed project (implemented)

Use ESNI via Firefox HTTPS helper

Reported by: dcf Owned by: dcf
Priority: Medium Milestone:
Component: Circumvention/meek Version:
Severity: Normal Keywords:
Cc: jan@… Actual Points:
Parent ID: Points:
Reviewer: Sponsor:

Description (last modified by dcf)

As of 2018-10-18, Firefox Nightly supports encrypted SNI, and Cloudflare supports it on the server side. Because meek supports using Firefox as a channel for issuing HTTPS requests, it ought to be pretty easy to adapt the meek client software to use ESNI rather than domain fronting. The server software doesn't need any change.

These steps are untested:

  1. Download Tor Browser and Firefox Nightly.
  2. Go to about:config in Firefox Nightly and set
    • network.trr.mode=3
    • network.trr.uri=https://1.1.1.1/dns-query
    • network.security.esni.enabled=true
  3. Copy the meek-http-helper@bamsoftware.com.xpi from Tor Browser to Firefox Nightly.
  4. Hack meek-client-torbrowser/{mac,linux,windows}.go to point firefoxPath at the copy of Firefox Nightly and disable the custom profile. (Additional hacks to remove hardcoded Tor Browser assumptions may be required.)
  5. Set up a Cloudflare instance pointing to https://meek.bamsoftware.com/, call it https://meek.example.com/.
  6. Set up a custom bridge in Tor Browser, using url= without front= (because we're no longer domain fronting).
    bridge meek 0.0.2.0:3 url=https://meek.example.com/

Of course, once ESNI support makes it into the version of Firefox used by Tor Browser, this will be even easier, not requiring a separate Firefox Nightly.

Child Tickets

Attachments (2)

meek-esni.pcap (451.9 KB) - added by dcf 4 months ago.
meek-esni.2.pcap (90.0 KB) - added by dcf 3 months ago.
Another packet capture of ESNI bootstrap, this time with security.OCSP.enabled=0.

Download all attachments as: .zip

Change History (9)

comment:1 Changed 8 months ago by dcf

Description: modified (diff)

comment:2 Changed 6 months ago by darkspirit

Cc: jan@… added

comment:3 Changed 4 months ago by dcf

Keywords: easy removed

Removing the "easy" tag as it seems that the helper will have to be rewritten as a WebExtension first (#29347) in order to work with versions of Firefox that are recent enough to support ESNI.

comment:4 Changed 4 months ago by dcf

As of e551c05e47, the work in #29347 is far enough along to start experimenting with this. I didn't yet make a Cloudflare account to actually test ESNI, but I got Tor Browser running with meek-azure and an external Firefox 66 doing DNS-over-HTTPS and all the other necessary configuration for ESNI.

  1. Enter meek/webextension/native and run go build. This produces the native component of the extension.
  2. Enter meek/webextension and run make. This zips up the extension files into an installable extension, meek-http-helper@bamsoftware.com.xpi.
  3. Download Firefox Developer Edition. You need the developer edition in order to install an unsigned extension. I used firefox-66.0b9.
  4. Run firefox/firefox --ProfileManager and create a new "esni" profile. Go to about:config and set these prefs:
    browser.dom.window.dump.enabled
    network.trr.mode=3
    network.trr.uri=https://1.1.1.1/dns-query
    network.security.esni.enabled=true
    toolkit.startup.max_resumed_crashes=-1
    xpinstall.signatures.required=false
    
  5. Go to about:addons. Click Extensions. Click ⚙️ and select "Install Add-on From File...". Open meek/webextension/meek-http-helper@bamsoftware.com.xpi. Say yes to the permissions dialog.
  6. Close Firefox.
  7. Edit meek/webextension/meek.http.helper.json and change the "path" field to be the absolute path to meek/webextension/native/native.
  8. Copy meek/webextension/meek.http.helper.json into the Tor Browser tree. (The path is different on mac.)
    mkdir -p tor-browser_en-US/Browser/.mozilla/native-messaging-hosts
    cp meek/webextension/meek.http.helper.json tor-browser_en-US/Browser/.mozilla/native-messaging-hosts/
    
  9. Edit meek/meek-client-torbrowser/linux.go (or mac.go, windows probably doesn't work yet) and set the paths to the Firefox developer edition and the "esni" profile you created:
    firefoxPath        = "/path/to/firefox/firefox"
    firefoxProfilePath = "/home/user/.mozilla/firefox/<RANDCHARS>.esni"
    
  10. In meek/meek-client-torbrowser, run go build.
  11. Copy the resulting meek-client-torbrowser binary to tor-browser_en-US/Browser/TorBrowser/Tor/PluggableTransports/.
  12. Now you're good to go. Start up Tor Browser with meek-azure.

The only step of this that is cheating--the one I don't know how to automate yet--is step 7, where the WebExtension host manifest needs to know the absolute path to the native executable. I suppose meek-client-torbrowser could do a getcwd and write the entire file anew each time.

comment:5 Changed 4 months ago by dcf

I set up a Cloudflare account and got this all working: meek with ESNI in place of domain fronting, running in Tor Browser with an external Firefox helper. When Tor Browser starts using a Firefox newer than 60 ESR, it won't need an separate external Firefox.

Cloudflare setup

  • Register a new domain name. I'll use rinsed-tinsel.site for this example.
  • Go to https://dash.cloudflare.com/ and log in.
  • Click "+ Add site", enter the domain name, and choose the free plan.
  • At the DNS screen, add a new A/AAAA record pointing to the bridge that will host meek-server.
  • You'll also need a TLS certificate for meek-server. You cannot use meek-server's automatic Let's Encrypt support, because the DNS for meek.rinsed-tinsel.site points to a Cloudflare server (which has its own Cloudflare-issued certificate), not to the meek-server bridge. Go to the Crypto tab and click "Create Certificate" in the "Origin Certificates" panel. Click "I have my own private key and CSR". Generate and upload a CSR:
    $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out meek-cloudflare.key
    $ openssl req -new -key meek-cloudflare.key -out meek-cloudflare.csr
    
    Paste the resulting cert into meek-cloudflare.crt.
  • Go back to the name registrar and set the nameserver to the two *.ns.cloudflare.com servers that it tells you to set.
  • I then made the following Cloudflare configuration changes:
    • Crypto tab
      • SSL: Full (strict)
      • Always Use HTTPS: On
      • Minimum TLS Version: TLS 1.2
    • Firewall tab
      • Security Level: Essentially Off
      • Web Application Firewall
        • Browser Integrity Check: Off
    • Caching tab
      • Always Online™: Off
    • Scrape Shield tab
      • Email Address Obfuscation: Off
      • Server-side Excludes: Off
      • Hotlink Protection: Off

Bridge setup

  • Set up meek-server according to the instructions at doc/meek#Howtorunameek-serverbridge.
    • Use the command-line options --key meek-cloudflare.key --cert meek-cloudflare.crt using the key and certificate you generated while setting up Cloudflare.

WebExtension build

Start with commit e80c5b3217 or later in the webextension branch.

  1. Enter meek/webextension/native and run go build. This produces the native component of the extension.
  2. Enter meek/webextension/browser and run make. This zips up the extension files into an installable bundle, meek-http-helper@bamsoftware.com.xpi.

Firefox setup

  1. Download Firefox Developer Edition. You need the developer edition in order to install an unsigned extension.
  2. Run firefox/firefox --ProfileManager and create a new "esni" profile. Go to about:config and set these prefs:
    browser.dom.window.dump.enabled=true
    network.trr.mode=3
    network.trr.uri=https://1.1.1.1/dns-query
    network.security.esni.enabled=true
    security.OCSP.enabled=0
    toolkit.startup.max_resumed_crashes=-1
    xpinstall.signatures.required=false
    
    • Explanation: browser.dom.window.dump.enabled=true enables the WebExtension to write to stdout, which is needed as part of the protocol with meek-client-torbrowser. network.trr.mode means Trusted Recursive Resolver and controls DNS-over-HTTPS, which is a prerequisite for ESNI; mode 3 means always use DNS-over-HTTPS. network.trr.uri is the DNS-over-HTTPS server. We use an IP address to avoid a chicken-and-egg name resolution problem. An alternative to using an IP address is to set network.trr.bootstrapAddress. network.security.esni.enabled enables ESNI. security.OCSP.enabled=0 disables OCSP, because OCSP can leak the server name. toolkit.startup.max_resumed_crashes prevents Firefox from uninstalling add-ons when there's a startup error. xpinstall.signatures.required allows the installation of an unsigned add-on.
  3. Go to about:addons. Click Extensions. Click ⚙️ and select "Install Add-on From File...". Open meek/webextension/meek-http-helper@bamsoftware.com.xpi. Say yes to the permissions dialog.
  4. Close Firefox.

meek-client-torbrowser build

  1. Edit meek/meek-client-torbrowser/{linux,mac,windows}.go (whatever's needed for your platform) and adjust the paths:
    firefoxPath                = "/path/to/firefox/firefox"
    firefoxProfilePath         = "/home/user/.mozilla/firefox/<RANDCHARS>.esni"
    helperNativeManifestDir    = "/path/to/tor-browser_en-US/Browser/.mozilla/native-messaging-hosts"
    helperNativeExecutablePath = "/path/to/meek/webextension/native/native"
    
  2. In meek/meek-client-torbrowser, run go build.
  3. Copy the resulting meek-client-torbrowser binary to tor-browser_en-US/Browser/TorBrowser/Tor/PluggableTransports/.

Tor Browser setup

  1. Click the "Configure" button in Tor Launcher, or "Tor Network Settings..." in the onion toolbar icon.
  2. Click "Tor is censored in my country" and "Provide a bridge I know". Enter the bridge line:
    meek 0.0.2.0:3 1922840D0D66CB82EACE4327F5001430227C0127 url=https://meek.rinsed-tinsel.site/
    
  3. Because of #12774, it may not work right away and you'll have to restart.

This is a packet capture of bootstrapping and browsing to example.com: attachment:meek-esni.2.pcap. Here's a summary of all the Client Hellos it contains:

No.  Time                 Source         Destination    Protocol Info
 12  2019-03-28 18:04:44  192.168.111.2  1.1.1.1        TLSv1.2  Client Hello
 13  2019-03-28 18:04:44  192.168.111.2  1.1.1.1        TLSv1.3  Client Hello
 14  2019-03-28 18:04:44  192.168.111.2  1.1.1.1        TLSv1.2  Client Hello
 17  2019-03-28 18:04:45  192.168.111.2  1.1.1.1        TLSv1    Client Hello
135  2019-03-28 18:04:45  192.168.111.2  1.1.1.1        TLSv1.2  Client Hello
138  2019-03-28 18:04:45  192.168.111.2  1.1.1.1        TLSv1.2  Client Hello
139  2019-03-28 18:04:45  192.168.111.2  1.1.1.1        TLSv1.2  Client Hello
262  2019-03-28 18:04:47  192.168.111.2  104.27.168.47  TLSv1.3  Client Hello

All the handshakes with 1.1.1.1 are DNS-over-HTTPS name lookup—I'm guessing some of them are Firefox's internal lookups, unrelated to the meek tunnel. 104.27.168.47 is the Cloudflare edge server.

The TLS fingerprints are:

1.1.1.1 8300bf0e26f2a109 (archive) rank 3620 comparison (archive) with ESR 60 rank 31
104.27.168.47 2dcbeba533890640 (archive) rank 6272 comparison (archive) with ESR 60 rank 31

The differences against the currently ESR 60 fingerprint appear to be partly from the lack of plaintext SNI, and partly from unrelated TLS changes in Firefox.

Last edited 3 months ago by dcf (previous) (diff)

Changed 4 months ago by dcf

Attachment: meek-esni.pcap added

comment:6 Changed 4 months ago by dcf

Resolution: implemented
Status: newclosed

Calling this done because it works, and the rest is a matter of integration.

Changed 3 months ago by dcf

Attachment: meek-esni.2.pcap added

Another packet capture of ESNI bootstrap, this time with security.OCSP.enabled=0.

comment:7 Changed 3 months ago by dcf

I revised the instructions in comment:5 to add the pref security.OCSP.enabled=0, because OCSP can leak the server name even with DoH, ESNI, and TLS 1.3 are used. (This was never a problem with meek because any OCSP leaks would have been of the front domain, not the covert domain.)

Note: See TracTickets for help on using tickets.