OpenF2 / F2

Redefining web integration for the financial services community
Apache License 2.0
129 stars 62 forks source link

Secure messaging between apps #150

Open montlebalm opened 10 years ago

montlebalm commented 10 years ago

As an F2 app, I'd like to specify who can receive the events I broadcast. This is required to prevent nefarious apps from spying on the data I'm sending.

My proposed API is below. It's targeted at F2 version 2, which is AMD compliant.

// F2 library definition
// ----------------------------------------------------------------------------
define('F2', [], function() {
  var F2 = function(options) {
    if (options) {
      this._trustedAppIds = options.trustedAppIds;
    }
  };

  F2.prototype = {
    spawn: function(options) {
      return new F2(options);
    },
    emit: function(name, data) {
      // Normal messaging
    },
    secureEmit: function(name, data) {
      // Emit to the trusted AppIds only
    }
  };

  return new F2();
});

// AppClass from "Red" client
// ----------------------------------------------------------------------------
define('com_red_appId', ['F2'], function(F2) {
  F2 = F2.spawn({
    trustedAppIds: [
      'com_red_*',
      'com_blue_*'
    ]
  });

  // Emit an event to everyone
  F2.emit('global broadcast');

  // Send an event to all appIds that begin with "com_red_" or "com_blue_"
  F2.secureEmit('secret password', 'hopscotch');
});

// AppClass from "Blue" client
// ----------------------------------------------------------------------------
define('com_blue_appId', ['F2'], function(F2) {
  F2 = F2.spawn({
    trustedAppIds: [
      'com_blue_*'
    ]
  });

  // Send an event to all appIds that begin with "com_blue_"
  F2.secureEmit('secret password', 'butterscotch');
});

The results of the example are that com_red_appid will broadcast its secure events to any "red" or "blue" apps. The other app, com_blue_appid, will only send its secure messages to "blue" apps.

The secureEmit could be replaced with an additional parameter to the regular emit event. The community can decide which is the better API.

I believe this example fulfills the following goals of secure messaging:

ilinkuo commented 10 years ago

I think that F2's prototype can still be hijacked -- there's really no difference between hijacking F2.Events.emit or F2.__proto__.emit. As long as friendly and unfriendly apps share the same F2 singleton, there's no way to prevent hijacking. On the other hand, if the Apps don't share something, then they can't communicate.

For me, I would have the Apps trust the Container (maybe because I'm developing the TDA F2 Container :) ). The Container is responsible for instantiating instances of F2 or EventEmitter etc. to pass to the App's constructor function. That's how I'd do it. I don't see how to avoid having the App trust the Container, especially in the eventing because most events are between Container and App. It is possible to avoid trusting the Container in the event of App-to-App communication, but then you'd need to implement your own messaging and bypass the F2 singleton -- greatly reducing the usefulness of F2. That messaging would have to go through a shared channel -- either the DOM itself or through the shared backend. So I'm not sure that degree of paranoia is feasible in the majority of cases.

montlebalm commented 10 years ago

@ilinkuo:

As long as friendly and unfriendly apps share the same F2 singleton, there's no way to prevent hijacking.

In the example above, F2.spawn() creates a new instance of F2. Overriding the emit event of your instance wouldn't affect any F2 but your own.

ilinkuo commented 10 years ago

@montlebalm

Please look at this fiddle http://jsfiddle.net/ilinkuo/LFzYW/ to see how to override the prototype's emit() method (not the instance) and also read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

montlebalm commented 10 years ago

@ilinkuo Is your contention with the idea of my example or simply the implementation of it? If it's the latter, trust that there will be plenty of time to take issue with the actual library code.

In the snippet below, do you think there's a way to modify "F2.emit" such that it would affect future instances?

var F2Class = function() {
    return {
        emit: function(name, data) {
            console.log("Safe and sound");
        }
    };
};

var F2 = new F2Class();
ilinkuo commented 10 years ago

@montlebalm, there's two points of contention which I have:

  1. The major point of contention is the role of the Container. I do like the idea in your proposal of how the App is able to protect itself against everyone else including the Container by spawning its own copy. I suspect that this level of paranoia is going to be really really hard to respect without having a high cost in browser memory usage, and I would recommend trusting the Container a bit more. I am voicing my reservations, holding my breath, and waiting to see how things work out on this issue. Unlike the plugin issue, I don't feel that the Container has to have a role here -- I just think it's going to be costly and hard if it doesn't.
  2. I wanted to point out that making things unhijackable is really damned tricky. Your second example above works, and the emit() function is no longer hijackable.
brianbaker commented 10 years ago

Alright, so the original point of contention (emit being hijacked) appears to have been solved. The second point of contention (apps trusting containers?) also seems like it is solved by the fact that there is a normal emit for regular messages like there is today in addition to a secureEmit for secure messages (app-to-app or who knows, maybe even secure app-to-container). The third point of contention (browser memory usage) could go away if we just trusted apps and containers (seems like in some of other threads we're not willing to trust apps, though...).

All-in-all @montlebalm, my main point of contention with what you wrote is that I didn't write secure messaging into the original v1 :smile:

montlebalm commented 10 years ago

I just wanted to post an update that we're working with a potential solution to this issue. We've created a new event method called emitTo that takes an array of AppIds or InstanceIds. F2 will look through its collection of instantiated apps and restrict the recipients to apps that match the provided filters. Here's an example:

F2.Events.emitTo(['com_example_foo', '7975565d-f516-48c6-82d3-1ca715c5009a'], 'TESTING');

F2 will emit the TESTING event to any app whose AppId is com_example_foo or whose InstanceId is 7975565d-f516-48c6-82d3-1ca715c5009a. You will be able to prevent your emitTo being tampered with by creating a new instance of F2.

This is certainly more low-tech than we were discussing, but so far it seems to do the job easily and elegantly. There is still some discussion about allowing pattern matching through regex or wildcards.

markhealey commented 10 years ago

How does an app know the instanceId of a given app? Are you thinking app-to-app would just be using AppId? And Container-to-App would be instanceId?

montlebalm commented 10 years ago

I've seen situations on projects where you might have two instances of the same app, but you only want to broadcast to one of them. In that case you could get a handle on the InstanceId (potentially via events as well) and target your messages to that specific app. Containers certainly have an easier time getting InstanceId, but anyone could do it.

ilinkuo commented 10 years ago

How does an app know the instanceId of a given app?

See #163 for a way this could happen, using a registry.

ilinkuo commented 10 years ago

alternate implementation #164

montlebalm commented 10 years ago

A private event instance was bandied about early on, but we removed it when we tabled the idea of implementing F2 as several small modules. To make a bad analogy, multiple event instances feels like having a different email client for each person in your contact list. In practice, I think the two approaches would be used similarly. You'd reference a saved "vendorEvents" object and I would save a "vendorAppIds" array.

I agree that someone who communicates in a single channel could have cleaner syntax using the approach in #164. However, one alternative would be to allow a param to new F2() that would allow someone to specify an array of ids that would be treated as default filters. Ultimately, we're going to encourage anyone with security concerns to create their own instance of F2 anyway. This kills two birds with one stone.

Thanks for the proposal. We'll give it some thought.

ilinkuo commented 10 years ago

Sounds good to me. I'm looking forward to seeing what comes out. Please let me know when it is available on a public branch.