Opened 3 years ago

Last modified 10 months ago

#12208 new enhancement

Make it possible to use an IP address as a front (no DNS request and no SNI)

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

Description

meek puts one domain name on the "outside" of your connection (the DNS request and SNI), and a different name on the "inside" (the HTTP Host header). It would be good for some uses if the outside could be just to an IP address rather than a domain name, so that there were no DNS request, and no server_name extension in the CLientHello. Kind of like if you were to browse to https://38.229.72.16/ instead of https://www.torproject.org/.

The motivating use case is using a CDN as a front instead of www.google.com. A CDN has many domains behind it, but if we choose just one of them as the front, that domain might get blocked (because the collateral damage would be limited to just one domain). Such blocking would break the transport and also incidentally get the innocent third-party domain, who has nothing to do with any of this, censored even for non-circumventors. What we want is to use one of the CDN's frontend IP addresses as a front, so that the censor has to block the whole IP and the thousands of domains behind it, not just a single domain.

Child Tickets

Change History (10)

comment:1 Changed 3 years ago by dcf

I didn't find an obvious way to reach this goal.

In the naked golang HTTPS code (without the browser extension), the first thing I tried was putting an IP address in place of www.google.com for the --front option.

-ClientTransportPlugin meek exec ./meek-client --url=https://meek-reflect.appspot.com/ --front=www.google.com
+ClientTransportPlugin meek exec ./meek-client --url=https://meek-reflect.appspot.com/ --front=74.125.224.116

It works fine up until certificate verification, then exits with this error:

error in handling request: x509: cannot validate certificate for 74.125.224.116 because it doesn't contain any IP SANs

What we would want is to connect to an IP without a domain name, but still let the TLS code know that it should use "www.google.com" for verification purposes. It doesn't seem possible to do it with the crypto/tls library. The clientHandshake function builds a ClientHello using the value c.config.ServerName, and then later does verification using the same value c.config.ServerName. In other words, it doesn't look possible to use one value for the SNI and another during verification.

Everything works fine if you set InsecureSkipVerify on the TLSClientConfig of the Transport, but then of course you're not doing any verification.

The golang HTTPS library, when you connect to an IP address, sends a server_name extension whose value is the IP address as a string. When I tried the same thing in Firefox 29 just now, it simply leaves off the server_name extension.

How GoAgent does it is it cooks up its own TLS connection using Python's ssl library, Python's ssl library doesn't do any verification, and then GoAgent itself does some nonstandard verification, not checking any certs but either looking for an organizationName starting with "Google " or looking for ".google" or ".appspot.com" in the commonName. It's essentially the same as InsecureSkipVerify.

I don't know yet if there's a way to do what we want in the Firefox and Chrome helpers.

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

comment:2 Changed 3 years ago by dcf

A way to do this in Firefox may be nsICertOverrideService, specifically hasMatchingOverride. nsiCertOverrideService is the service that manages the overrides that get added when you click through a certificate warning (which are visible through Preferences→Advanced→Certificates→View Certificates, Lifetime=Temporary).

It looks like we can define hasMatchingOverride and request HTTPS to a CloudFront IP (for example). hasMatchingOverride will get called back. We check that the error is only ERROR_MISMATCH (and not ERROR_UNTRUSTED or ERROR_TIME). We check that the commonName in the cert is "*.cloudfront.net", and return true. In effect, we defer to the normal verification mechanisms for everything but hostname verification, and for that we check that the wildcard is what we expect.

skipCertError is a third-party Firefox extension to automatically ignore certificate errors.

Selenium's docs on hasMatchingOverride.

comment:3 Changed 3 years ago by dcf

A case where this ticket would be useful: In China, it apparently works to route through CloudFront, except that *.cloudfront.net domain names are DNS-poisoned. If you edit /etc/hosts, or use an IP in place of a domain name, then it works.

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

comment:4 Changed 3 years ago by dcf

Psiphon and Lantern both independently discovered this issue. They found that connecting with no SNI is necessary when using the Fastly CDN. They both worked around it by patching the Go crypto/tls library.

Psiphon:

Lantern:

comment:5 Changed 3 years ago by dcf

I contacted the ICSI Certificate Notary (notary.icsi.berkeley.edu), which monitors SSL traffic at some big Internet sites, asking what fraction of their monitored connections use SNI. The answer:

In the last month:

  • 83.5% of connections had SNI.
  • 80.1% of connections where certificates were exchanged (excluding e.g. resumed sessions) had SNI.

comment:6 Changed 3 years ago by dcf

I wrote a post to the golang-nuts mailing list asking about this use case:

Brad Fitzpatrick replied to say that what we want should be possible once this in-progress issue is resolved:

comment:7 Changed 3 years ago by dcf

Following discussion in the thread at https://groups.google.com/forum/#!topic/golang-nuts/bt9vBbZLYgU, Ox found a workaround that works with an unmodified crypto/tls library. It uses manual dialing and verification.

comment:9 follow-up: Changed 10 months ago by cypherpunks

  • Severity set to Normal

When I tried the same thing in Firefox 29 just now, it simply leaves off the server_name extension.

Amazon CDN doesn't support for such requests now. Seems like it terminates TLS session if ClientHello lack of SNI?

comment:10 in reply to: ↑ 9 Changed 10 months ago by dcf

Replying to cypherpunks:

When I tried the same thing in Firefox 29 just now, it simply leaves off the server_name extension.

Amazon CDN doesn't support for such requests now. Seems like it terminates TLS session if ClientHello lack of SNI?

Thanks for checking this. Could you add a note to doc/meek#AmazonCloudFront?

Note: See TracTickets for help on using tickets.