Dismantling BrowserPlus

June 9th 03:58
by why

So, Yahoo!’s BrowserPlus came out of nowhere. And, well, it does other things, but I want to look at how it embeds Ruby in the browser. I know, I know. Enough with Ruby in the browser.

But I had to peek a little. And to pry it open like the upturned crab it was. Don’t ask me if they are doing the right thing or if they are going to win or anything. (I look at Facebook and all I see is a guy in an over-starched shirt with a blue collar who leans on the wall with his legs crossed and has to analyze everything I say to death. Go peg your pants, guy.)

I wanted to find out if the interpreter was sandboxed, first of all. And, next, I wanted to set up an eval method so you can run arbitrary Ruby code.

And to what end? Come on, everybody quit asking me that.


Actually, BrowserPlus doesn’t come with a Ruby interpreter. Ruby is an addition you can download and a couple of the samples use it for talking to Flickr and for storing marshalled data. (Their equivalent to Google Gears’ SQLite is just a PStore wrapper?) The Photo Uploader and IRC Client demos both use the Ruby interp.

It also doesn’t really embed Ruby directly in the browser. Plus runs as a separate process that you pass calls out to. The process hosts these additions called services or corelets (but I like to call them roo-dads!) And they are sort of like a ruby gem. A versioned library that’s downloaded only when you need it.

Okay, so that’s the nutshell. And the hacks hereforward will be:

  • A web app which serves up the roo-dads.
  • Some OpenSSL commandline scripting to sign them.
  • Some Ruby scripts in a zip file to act as our sample.
  • Injecting a library into their RubyInterpreter corelet.
  • And a few hacks on the user-side to get things opened up.

After you’ve got a handle on these hacks, you’ll be able to screw around with Plus yourself, rather than being confined to the restricted set of demos on the Yahoo! dev site.


Let’s free up Plus first. Since this is just a beta release, the Plus team have it all locked up so you can only use it on the browserplus.yahoo.com subdomain.

Step one. Edit BrowserPlus.config.

  • On Windows, look for C:\Program Files\Yahoo! BrowserPlus\2.0.4\BrowserPlus.config.
  • On OS X, it’s at $HOME/Library/Application Support/Yahoo!/BrowserPlus/2.0.4/BrowserPlus.config.
/* THIS FILE IS GENERATED BY THE BUILD SYSTEM.  EDIT THE CORRESPONDING .cmakeIn FILE.
 */

/*
 * This is the configuration file for BrowserPlus
 */

{
   // the base url for the "primary" distribution server.  This server will
   // be the single source of truth for Permissions, and will used to
   // attain services
   "DistServer": "http://browserplus.yahoo.com",

   // An array of "secondary" distribution servers, which will be checked
   // in order for services if the primary server has no components
   // available which match an issued require statement.
   "SecondaryDistServers": [ "" ],

Alter the DistServer line. Change "http://browserplus.yahoo.com" to "http://plus.hackety.org".

Second step, we’ve got to add some S/MIME keys. I can’t sign stuff as Yahoo! considering that I lack their private keys.

Download BrowserPlus.crt and copy to the Permissions folder.

  • On Windows, this is at C:\Program Files\Yahoo! BrowserPlus\Permissions\BrowserPlus.crt.
  • On OS X, go to $HOME/Library/Application Support/Yahoo!/BrowserPlus/Permissions/BrowserPlus.crt.

Don’t worry about overwriting the file. Yahoo!’s cert is still in there as well.

Third and last step, alter the Permissions file.

  • On Windows, you’ll want %LOCALAPPDATA%/Yahoo!/BrowserPlus/Permissions/Permissions.
  • On OS X, edit $HOME/Library/Application Support/Yahoo!/BrowserPlus/Permissions/Permissions.
{
    // What domains are approved?  Entries are PCRE regular expressions
    // which must match the scheme://host:port of the requesting URI.
    // Regular expressions must be anchored with $.
    // Note that the PCRE expression may need to be protected in order
    // to be valid JSON (e.g. a '\' needs to be '\\')
    //
    "whitelist" : [
      "^http(s?)://(.*)\\.yahoo\\.com$",
      "^http(s?)://(.*)\\.yahoo\\.com:[0-9]+$"
    ],

Add to the "whitelist" array an entry for hackety.org: "^http(s?)://(.*)\\.hackety\\.org$".

If you want to write your own web pages using BrowserPlus, you can follow that last step for your site as well. However, to actually write Ruby extensions, you’ll need a cert and a dist server of your own, until Yahoo! opens up their dist server.


The certificate is an S/MIME cert. Which is used for signing e-mail. In fact, all the meat cutlet core-dads get served as smime.p7m. They look like e-mail attachments. (Here’s one for your inspection, if you like.)

So, let’s say you get a cert and end up with a PEM file that has a list of certs and your private key. You’ll want to split that file up into separate files. From a really great HOWTO that explains it all:

You should get out a bunch of certificates. You’ll need to look at the text above each one to find the one that is your certificate. The rest are part of Thawte’s Certifying Authority. It turns out that if you want your messages to verify correctly, you must also include Thawte’s intermediate CA key.

There should be 3 certificates. The one whose identity is your e-mail address is your certificate. The one whose subject and issue are identical is the Thawte CA root. You won’t need that one, since we’ll include it in the trusted root file later. The 3rd one will have the CA root as the issuer and something else as the subject (which will be the same as the issuer of your certificate). You need to save that vertificate as an additional certificate for signing. We’ll refer to the file containing this cert as othercert.

To BrowserPlus.crt, you add the 3 certificates. If your CA is a common one, you can probably omit the CA root certificate.

As for signing the payload, you take a zip with Ruby scripts inside and use openssl from the commandline.

$ openssl smime -sign -inkey HacketyPlus.key -signer HacketyPlus.crt \
    -certfile HacketyPlus.other -in Payload-1.0.0.zip -out Payload-1.0.0.p7m \
    -nodetach -binary

You can test unpacking the payload with your master BrowserPlus.crt.

$ openssl smime -verify -CAfile BrowserPlus.crt \
    -in Payload-1.0.0.p7m -out Payload-1.0.0.zip

So, yeah, cert created. Signing and unpacking is good.

The zip itself should have Ruby scripts and a manifest.json in the root.

  Eval-1.0.0.zip:
    manifest.json
    eval.rb

Now, what goes in the Ruby scripts? Here’s a trivial example:


class Eval
  def initialize(args)
  end

  def eval(bp, args)
    begin
      code = args['code']
      obj = Kernel.eval(code, TOPLEVEL_BINDING)
      bp.complete(obj.inspect)
    rescue Exception => err
      bp.error('FAIL', "Error: #{err}")
    end
  end
end

RubyCoreletDefinition = {
  'class' => "Eval",
  'name' => "Eval",
  'major_version' => 1,
  'minor_version' => 0,
  'micro_version' => 0,
  'documentation' => 'Run any Ruby whatsoever.',
  'functions' =>
  [
    { 
      'name' => 'eval',
      'documentation' => "Executes a string of Ruby code.
         Returns a string of the inspected data.",
      'arguments' =>
      [ 
        { 
          'name'          => 'code',
          'type'          => 'string',
          'required'      => true,
          'documentation' => 'The code to execute.'
        }
      ]
    }
  ]
}

Gah. That’s a pretty arduous Hello World. The definition at the bottom declares the signature of every method you expose to JavaScript.

Take particular note of the eval method. For every method you offer up, you get a pair of arguments: the first being a bp object for communicating with BrowserPlus and an args hash containing the incoming JSON-marshalled object.

We need not go through that mess, though. Since I was able to unpack the RubyInterpreter corelet, I’ve injected a bit of code into my version (3.1.2) to make this more comfortable for you.


class Eval
  bp_version "1.0.0"
  bp_doc "Run any Ruby whatsoever."

  def eval(code)
    obj = Kernel.eval(code, TOPLEVEL_BINDING)
    obj.inspect
  end
  bp_doc :eval, 
    "Executes a string of Ruby code.
     (code: string) The code to execute."
end

RubyCoreletDefinition = Eval.to_corelet

The code that makes this possible is found in stdlib/bp_hacks.rb in the RubyInterpreter 3.1.2 download.


As for serving up all these libs, I give you FakePlus. This is a very small Camping app, accompanied by a YAML index. The index lists all the available additions. The Camping app mimicks Yahoo!’s web service at browserplus.yahoo.com/api.

Finally, you can mess with the Eval addition by visiting here once you’ve got the hacks at the beginning all nicely in place. And I think if you horse around on that page for a little while, it will become very evident whether things are sandboxed or not.

Now begin the comments …

11 comments

Dr Nic

said on June 9th 04:42

Inspirational hacking. Now I need to muster the energy to setup the pre-hacking steps :)

Charles

said on June 9th 07:07

This looks cool. I admit I’m curious to know if they did sandbox it properly (they’d better…), but not curious enough to go to all that effort yet.

bryanl

said on June 9th 07:47

Cool hack. I’m with Dr Nic on this one. I need to find the energy and the time to set it up.

CyndyA

said on June 9th 09:06

Well, that sure didn’t take long to see. ;)

_why

said on June 9th 09:34

The only steps you really need to follow (in order to use the Eval module) are the three steps in the first section. Edit BrowserPlus.config. Replace BrowserPlus.crt. And alter Permissions.

The other steps are for serving your own code. Which is much harder and difficult to troubleshoot.

elliottcable

said on June 9th 11:07

Dontcha gotta love ‘much harder and difficult to troubleshoot’? I have to say, that half of most things is where I spend 90% of my time and 90% of my interest – so it balances out.

stepheneb

said on June 9th 12:55

Did the first three steps, restarted Safari, and am getting:

Error Loading Eval: core.serverError

when I connect to http://plus.hackety.org

kamran

said on June 9th 16:43

wish it supported linux

zapnap

said on June 9th 20:47

Hrmm very cool. Going to have to toy around with this later in the week. Thanks!

Dan M

said on June 10th 18:27

Awesome, that is interesting. I actually just heard a talk from one of the browser plus developers. It was kind of interesting I didn’t realize how much it was competing with the Silverlight / Google Gears space. It is odd that all three companies have basically decided browser development isn’t going towards these solutions fast enough and that plugging in an providing additional functionally is the way to improve the system.

I guess I could see why I might want to run Ruby in the browser because I like Ruby, but most of the time I would just expect to make server calls and ajax back the results. Is there some real benefits to running Ruby locally or is it just a great way to offload some of the more complex tasks onto the client and into languages that can handle the tasks.

lloyd

said on June 11th 20:00

I dig stdlib/bp_hacks.rb very deeply. started out with return value being what gets passed back, but then folks wanted to do sync stuff and not block up other calls from getting executed:


Thread.new(foo) { |myfoo|
  ... blocking stuff ... 
  bp.complete(returnValue) 
}

Any idea how to get the simplicity of the return value approach and the flexibility of the bp.complete() route?

_why, may I roll some of these ideas into the official distro?

lloyd

Comments are closed for this entry.