Following the discussion at https://lists.torproject.org/pipermail/tor-dev/2014-February/006266.html and the summary at [[doc/meek#HowtolooklikebrowserHTTPS]], I think our best option for having TLS that looks like a browser is to make a browser extension that meek-client uses as a tool to make HTTPS requests.
To summarize: meek-client needs to make HTTPS requests, but needs to do so with a TLS signature that isn't trivially blockable. A browser doesn't have a blockable TLS signature, so we can have meek-client drive a browser to make requests on its behalf. Rather than ship an entire separate browser to users, we can use an extension in Tor Browser itself, one whose only purpose is to make HTTPS requests using the browser's networking code, bypassing the browser's proxy settings that would otherwise send all requests through Tor.
The communication goes:
browser tor meek-client extension www.google.com
There will need to be some kind of IPC between meek-client and the extension. I haven't figured out how that will work. Maybe the extension can be an HTTP proxy--that would be super easy to integrate with meek-client. But maybe you don't want an HTTP proxy running in your browser bundle, even if it's only intended for a specific purpose. Maybe the IPC needs to be authenticated somehow, and the extension needs to somehow inform the other process of how to contact it.
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Child items
0
Show closed items
No child items are currently assigned. Use child items to break down this issue into smaller parts.
Linked items
0
Link issues together to show that they're related.
Learn more.
We'll need to make sure that user cookies for www.google.com are not sent over the non-Tor helper. Are there other leaks to consider besides cookies? I found these articles:
In this post I reported that I had a prototype browser extension that worked in Iceweasel but not in Tor Browser. Mark discovered that the connection was throwing NS_ERROR_UNKNOWN_PROXY_HOST (0x804B002A). Mike traced the cause to this patch that is specific to Tor Browser:
The 5069a3ee Tor Browser patch has a reason for existing, though, so we shouldn't simply undo it. It's meant to guard against unexpected DNS leaks in Firefox and extensions. I've thought of two potential ways to deal with the situation:
Make a special API or key that allows DNS lookups by a "direct" type proxy, which still prohibiting it from all other callers. Maybe the key is mere use of the "direct" type; maybe it's a magic string in the host field, or something like that.
Run a second copy of Firefox solely for making meek HTTP requests. The second browser would have network.proxy.socks_remote_dns=false, which setting is enough to disable the Tor Browser patch that breaks name lookups.
My current rough design for the extension is this. Open an nsIServerSocket on a well-known port. Connections to the port will send a JSON blob describing an HTTP request to make, looking like
The extension parses the JSON and makes the request using nsIHttpChannel. The extension reads the response and sends it out as JSON on the original connection it received on the well-known port, like
Why a listening socket for communication between meek-client and the extension, rather than some other IPC? I don't know enough IPC to know of another way that will work.
Why a well-known port instead of an ephemeral port? I don't know how the extension can listen on an ephemeral port and then tell meek-client what port to connect to. Maybe it would be easier to do if we ran a second browser and the second browser was a child process of the meek-client process.
Why JSON instead of an HTTP proxy (meek-client is already able to talk to an HTTP proxy)? The little reason is that being an HTTP proxy would require us to parse HTTP in the extension. I don't know if there's a standard library for that and I don't want another implementation of HTTP. JSON.parse and JSON.stringify work fine for data serialization (json.Marshal and json.Unmarshal in golang). The big reason is that HTTPS through an HTTP proxy uses the CONNECT method which merely tunnels the client's own TLS handshake.
Why nsIHttpChannel and not nsISocketTransportService.createTransport? I tested nsISocketTransportService, and its TLS handshake doesn't have the next_protocol_negotation=http/1.1 extension that nsIHttpChannel and normal browser requests have.
Why use a separate meek-client program to talk to the extension, rather than tor talking to the extension directly (with the extension implementing the PT protocol)? meek-client already exists and works fine apart from its TLS signature. I don't know extension programming so well, and would prefer to have less code in the extension than more. It seems more difficult to run a browser as a managed transport. I would like to treat the browser-like HTTP service as a mostly independent layer. For example we may want to try plugging in a different browser than Firefox. (Firefox is the most compelling option only because it's already included in the browser bundle.)
The 5069a3ee Tor Browser patch has a reason for existing, though, so we shouldn't simply undo it. It's meant to guard against unexpected DNS leaks in Firefox and extensions. I've thought of two potential ways to deal with the situation:
Make a special API or key that allows DNS lookups by a "direct" type proxy, which still prohibiting it from all other callers. Maybe the key is mere use of the "direct" type; maybe it's a magic string in the host field, or something like that.
Run a second copy of Firefox solely for making meek HTTP requests. The second browser would have network.proxy.socks_remote_dns=false, which setting is enough to disable the Tor Browser patch that breaks name lookups.
There is a third option on the horizon for bundles shipping a Tor Browser based on ESR 31: Mozilla fixed the WebSocket DNS leak (https://bugzilla.mozilla.org/show_bug.cgi?id=751465) which caused the defense-in-depth AND there will probably be a way to write tests that detect DNS leaks (https://bugzilla.mozilla.org/show_bug.cgi?id=971153). Thus, we could think about dropping the current patch that prevents your original idea from working while not throwing the defense-in-depth we currently have away for nothing.
A potential problem: Tor Browser's TLS ClientHello differs slightly from Firefox's. Tor Browser doesn't send the "SessionTicket TLS" extension. I think it's on account of #4099 (closed).
This is what Iceweasel 24.3.0 sends that Tor Browser 3.5.2.1 doesn't:
I have some code for you to try out. The whole pipeline is working, more or less. (I'm typing this comment through browser-camouflaged meek.) At this point, you have to run the extension in a separate Firefox because of comment:6.
In your separate Firefox's extensions directory, create a file called meek-http-helper@bamsoftware.com whose contents are the directory containing the extension (plus a trailing slash). For me, it is \
/home/david/meek/firefox/
Start the separate Firefox. You might have to active the extension in the Addons menu.
Create a torrc file with the contents (you can edit the torrc that's in the meek-client directory) \
// This is an extension that allows external programs to make HTTP requests// using the browser's networking libraries.//// The extension opens a TCP socket listening on localhost (port 7000). When it// receives a connection, it reads a 4-byte big-endian length field, then tries// to read that many bytes of data. The data is UTF-8–encoded JSON, having the// format// {// "method": "POST",// "url": "https://www.google.com/",// "header": {// "Host": "meek-reflect.appspot.com",// "X-Session-Id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}// }// }// The extension makes the request as commanded. It returns the response to the// client as a JSON blob, preceded by a 4-byte length as before. If successful,// the response looks like// {// "status": 200,// "body": "...base64..."// }// If there is a network error, the "error" key will be defined. A 404 response// or similar from the target web server is not considered such an error.// {// "error": "NS_ERROR_UNKNOWN_HOST"// }// The extension closes the connection after each transaction, and the client// must reconnect to do another request.
Comments on the code and design continue to be welcome. One thing to do is to make the listening port configurable as a pref instead of being hardcoded.
A potential problem: Tor Browser's TLS ClientHello differs slightly from Firefox's. Tor Browser doesn't send the "SessionTicket TLS" extension. I think it's on account of #4099 (closed).
Setting the pref security.enable_tls_session_tickets=true is enough to make the "SessionTicket TLS" extension appear. I'm not sure what other side effects it may have. (See comment:6:ticket:4099 ff.)
Still holding out hope that we can avoid shipping a second browser binary, my thinking now is that we should run the meek HTTP helper in a separate instance of Tor Browser, under a separate profile. The separate profile will have at least the following configuration changes:
network.proxy.socks_remote_dns=false
security.enable_tls_session_tickets=true
The second instance should be headless so there's no chance of a user interacting with it directly. (Perhaps the HTTP helper itself could enforce headlessness.)
A separate process and profile means that we can be freer in changing settings that might compromise the security of Tor Browser. The separate profile will have its own history and cookies. The second profile won't be used for browsing: it is strictly an HTTPS driver under the control of a pluggable transport.
Here are bundles that use an extension within Tor Browser to make HTTP requests. The extension listens on localhost port 7000. meek-client connects to the extension on that port and commands it what HTTP requests to make.
These bundles have a TLS signature that is almost like Firefox's, only missing the "SessionTicket TLS" extension as noted in comment:9. As explained in comment:11, my next planned iteration is to do bundles where the extension runs in a separate browser instance with a separate profile. That will take care of the "SessionTicket TLS" and socks_remote_dns issues, and will make it possible not to run the extension when you're not using meek.
I had some trouble with these bundles on OS X initially. Bootstrapping would die after about 5 minutes at 50% (so it's not #9229 (moved)) with an "EOF" error in the meek-client log. But after investigating it for a while, I couldn't reproduce it.
Here are bundles that use an extension in a separate instance of Firefox. The second instance sets network.proxy.socks_remote_dns=false so that no patch for DNS lookups is needed in Tor Browser, and sets security.enable_tls_session_tickets=true in order to send the session ticket TLS extension. This version has the extension listen on an ephemeral port, which is written to the browser's stdout and read by the transport plugin.
When you start the browser, it's immediately going to open a dialog box. The dialog is actually the sub-instance of Firefox running the meek-http-helper extension.
Don't close the dialog or it will shut down the extension. The modal dialog prevents a browser window from being shown, and the extension kills the whole program when the dialog is closed. We need to find a way to accomplish the same thing without showing a visible dialog. For now it's kind of nice in that it makes it easy to see if the sub-instance of Firefox is being killed properly, etc.
There's a known bug, which is that subprocesses don't get cleaned up on Windows. In particular, meek-client and the second Firefox keep running when you close the main browser. I think it's because of #9330 (moved)--the program that starts meek-client and Firefox gets killed by ProcessTerminate without being able to notify its children. I have an idea for dealing with that that I'll try in the next round of bundles.
I merged the extension branch to master. Now we need to find out how to run the second browser instance without a dialog (and [ticket:11429 without opening a second dock icon] if possible).
Now we need to find out how to run the second browser instance without a dialog.
This idea of pumping the event loop seems to work.
The MDN page says:
Warning: Spinning the event loop breaks run to completion semantics of JavaScript (it creates a nested event loop). This can cause code to reenter itself, which can result in broken functionality. This approach should be avoided whenever possible.
I don't think it applies to us. We're already breaking extension best practices by hijacking control and never returning, because that's the point.
Trac: Resolution: N/Ato fixed Status: assigned to closed
4 // The extension opens a TCP socket listening on localhost (port 7000).
See previous comment: you seem to advocate hard-coding a listener port, no?
50 Components.interfaces.nsIServerSocketListener,
Nit: there is no "," needed here as this is the last interfaces listed.
56 return
Nit: a ";" is missing at the end.
124 this.requestreader = null;
Nit: should be "this.requestReader = null;" see line 131 as well.
130 readRequest: function(callback) {
Seems you can omit the |callback|, no? You are not using it in the readRequest method. Or did you plan to pass it to the RequestReader constructor?
I have to think a bit about your usage of nsISocketTransport and whether you are affected by the things we ran into in #9531 (moved). See the comments there (e.g. regarding OPEN_BLOCKING|OPEN_UNBUFFERED)
We talked about this on IRC today. The ephemeral port is to avoid local port conflicts. The dump call alerts the controller program, meek-client-torbrowser, what port to connect on, and also signals when the port is open and listening and safe to connect to.
The dump call that writes the port number to stdout seems like an unusual use of the function, at least, so it's a candidate for replacement if we think of something better.
The way the extension reports its listening port is similar to how the pluggable transports protocol works. tor tells its transports to listen on 127.0.0.1:0, and the transports report what their actual listening port is to stdout.
{{{
4 // The extension opens a TCP socket listening on localhost (port 7000).
}}}
See previous comment: you seem to advocate hard-coding a listener port, no?
{{{
50 Components.interfaces.nsIServerSocketListener,
}}}
Nit: there is no "," needed here as this is the last interfaces listed.
{{{
56 return
}}}
Nit: a ";" is missing at the end.
{{{
124 this.requestreader = null;
}}}
Nit: should be "this.requestReader = null;" see line 131 as well.
{{{
130 readRequest: function(callback) {
}}}
Seems you can omit the |callback|, no? You are not using it in the readRequest method. Or did you plan to pass it to the RequestReader constructor?
I have to think a bit about your usage of nsISocketTransport and whether you are affected by the things we ran into in #9531 (moved). See the comments there (e.g. regarding OPEN_BLOCKING|OPEN_UNBUFFERED)
We talked about this on IRC today. The ephemeral port is to avoid local port conflicts. The dump call alerts the controller program, meek-client-torbrowser, what port to connect on, and also signals when the port is open and listening and safe to connect to.
The dump call that writes the port number to stdout seems like an unusual use of the function, at least, so it's a candidate for replacement if we think of something better.
I think a more natural way would be using nsIEnvironment for passing stuff around. At least it is much more common. Does something speak against this approach?
We talked about this on IRC today. The ephemeral port is to avoid local port conflicts. The dump call alerts the controller program, meek-client-torbrowser, what port to connect on, and also signals when the port is open and listening and safe to connect to.
The dump call that writes the port number to stdout seems like an unusual use of the function, at least, so it's a candidate for replacement if we think of something better.
I think a more natural way would be using nsIEnvironment for passing stuff around. At least it is much more common. Does something speak against this approach?
Would that then require the pluggable transport to be a child process of the sub-Firefox, instead of a sibling as it is now?
We talked about this on IRC today. The ephemeral port is to avoid local port conflicts. The dump call alerts the controller program, meek-client-torbrowser, what port to connect on, and also signals when the port is open and listening and safe to connect to.
The dump call that writes the port number to stdout seems like an unusual use of the function, at least, so it's a candidate for replacement if we think of something better.
I think a more natural way would be using nsIEnvironment for passing stuff around. At least it is much more common. Does something speak against this approach?
Would that then require the pluggable transport to be a child process of the sub-Firefox, instead of a sibling as it is now?
Uhm... I hadn't thought about that. Probably, yes. But that seems to be a lot of work (if it works at all) for little gain.
I have to think a bit about your usage of nsISocketTransport and whether you are affected by the things we ran into in #9531 (moved). See the comments there (e.g. regarding OPEN_BLOCKING|OPEN_UNBUFFERED)
but that may be deliberate again (what is the rationale for this, btw?).
Then I was wondering about the length limit especially that of the response. What is the reasoning behind it? Doesn't that prohibit certain use cases like retrieving PDF files larger than 1000000 Bytes?
I have to think a bit about your usage of nsISocketTransport and whether you are affected by the things we ran into in #9531 (moved). See the comments there (e.g. regarding OPEN_BLOCKING|OPEN_UNBUFFERED)
The trailing comma is so that there's only a single line in the diff when you add or remove something from the literal. The trailing comma is part of the ArrayLiteral and ObjectLiteral syntax. It's a part of most other languages too: the K&R C book says "A list may end with a comma, a nicety for neat formatting." As I recall, there's a problem with trailing commas in Internet Explorer, but this file is for Firefox only so I don't worry about it.
Then I was wondering about the length limit especially that of the response. What is the reasoning behind it? Doesn't that prohibit certain use cases like retrieving PDF files larger than 1000000 Bytes?
That's a good question. The limit is per-request, and it's only a failsafe in case limits are not enforced in other parts of the code. The meek-client and meek-server programs actually use a smaller limit of 65536 bytes per request, but I tried to design the programs so they don't trust one another. If a malicious meek-client or meek-server sends an endless stream of data, the browser extension should fail fast with an error rather than buffer forever. Similarly meek-client tries to protect itself from a malicious browser extension (see maxHelperResponseLength in helper.go).
A Tor stream is multiplexed across multiple requests and responses. The meek-client and meek-server programs reconstruct the HTTP into continuous TCP sessions at both ends. So it doesn't matter too much how big each individual payload is (though there is a tradeoff between bandwidth and latency). I've downloaded files of hundreds of megabytes.