2004/11/27

Annoyingly complicated conundrums.

So, the next version of Gradient is going to loosen up a bit and allow for XMPP traffic between loaded documents, as well as between documents and the server they were loaded from.

Each document is identified to the XMPP network by three things:
  • The JID contained in the namespaced XML element that specifies the "controller" for this document
  • The full URL that the document was loaded from.
  • The thread ID assigned to the document by the server.
The thread ID is basically a unique identifier for the document, and a shared secret between the client and the server. When messages or IQs meant for one document alone are sent to the client, they use the thread ID to specify which document.

So how do third parties, such as other clients, interact with documents via XMPP? Well, they have to identify the target document in their stanzas. How do they do this? Using the thread ID. Points to note:

  • Either the client or the server has to share the thread ID with the third party in order for this to happen
  • Stanzas that arrive from non-controller JIDs are treated differently than controller-originating stanzas:
    • They cannot modify the document using directives.
    • They trigger different events in the DOM environment.
If the server sends an IQ to a document, the function processIQ(elements, type) is called (if declared). If anyone else sends the document an IQ, the function processForeignIQ(from, elements, type) is called. This ensures that malicious JIDs can't mess around inside of code points/decision trees/logic meant to be used exclusively by the server, unless you explicitly allow it by doing something like this, which is brain-dead from a security point of view:

function processForeignIQ(from, elements, type) {
processIQ(elements, type);
}

function processIQ(elements, type) {
//now we're not just dealing with IQs from the server here!
}

But I digress. The point is that the thread ID is guaranteed to be unique between two JIDs (client and server) as per the RFC - any other behaviour is breaking the spec. However, the thread ID cannot be guaranteed unique across all threads with all servers - i.e. it's possible to have different conversations with two different JIDs, each with thread ID "123".

This means we cannot simply specify the target thread ID on a third-party stanza meant for a document loaded from a different JID. Thread collisions become possible.

At least three options immediately came to mind:
  1. Ignore this design flaw. A bad idea on principle.

  2. Break the spec and put the JID in the thread ID. Also a bad idea on principle, and just plain braindead. NO.

  3. Require inter-document XMPP stanzas to specify the document controller JID. This is annoyingly complicated, and precludes third-party JIDs from broadcasting stanzas to documents who ask for it, regardless of their controller.
Another problem with all the above solutions is that sharing the client-server thread ID could possibly open the model to side-channel attacks, and have other unforeseen consequences on the security model.

  1. Require the document to initiate communication to third-party JIDs, by sending a message or IQ with the new thread specified. The thread could be the same as the server-client thread, but that's up to the script running on the client.
The problem with this is that it then becomes impossible for a document to initiate a conversation with another document with identical behaviour. That limits third-party nodes to a server-like role, and prevents inter-document (i.e. p2p) communication.

Grrrr.