This page describes the structure of the various messages passed around by Murmeli. This won't be of any interest to the users of Murmeli, because they won't see any of these details, but it may be of interest to developers, and to any crypto specialists who want to help out reviewing the design of the system.
To recap, Murmeli sends its messages from peer to peer through the Tor network. It is therefore already unlikely that the messages can be intercepted on their way, unless the Tor entry / exit nodes are compromised, or unless there is something funny going on at either ISP.
Murmeli's messages travel via socket connections, and no attempt is made to disguise the fact that they are Murmeli messages. To send a message, the sender opens a socket connection, transmits the message, and then closes the connection again. No acknowledgement is given (apart from some message types which are automatically answered by returning other messages in the same way). If the recipient is not online, then the socket connection cannot be established and the sender can decide whether to keep trying to send it or not.
All Murmeli messages have the same overall structure, as follows:
So all messages identify themselves as Murmeli messages, and have a version number (currently always 1). The "encryption type" identifies how to interpret the contents of the grey box, the body of the message. The possible values are unencrypted, asymmetrically encrypted, symmetrically encrypted, or relay.
If someone intercepts a message, they can identify what kind of message is being sent, but almost all messages will be encrypted and the exact message type is then concealed within the encrypted section. For most types, they cannot tell who it's from or who it's being sent to.
Note that not all of the following message types have been implemented yet, and all details are subject to change based on feedback.
Most of Murmeli's messages are encrypted, but there are two message types which are not. These are the simplest to describe, so we'll start with them first, even though they are different from the majority of messages. Unencrypted messages are always sent directly, never via a relay.
As already discussed, a contact request message has to be sent unencrypted, because the sender hasn't got the recipient's public key yet. The fields are then sent in the clear, inside the grey box as follows:
This does unfortunately leak some information, but the name and message can be reduced if the recipient already knows to expect a request from a particular id. The recipient has to have enough information to be able to decide whether to accept or reject the request though.
If the contact request is rejected, the response also cannot be encrypted because the request originator is clearly not trusted and their key has been rejected. So just the minimum of information is returned:
Of course the request could just be ignored and deleted without sending a response, but an explicit rejection would allow a mistaken rejection to be corrected.
Apart from those two exceptional message types described above, all the other message types have an encrypted payload, making them unintelligible to those who don't have the correct decryption key. In the case of direct, asymmetrically-encrypted messages, the grey box becomes a blue container, containing a block of encrypted and signed data. Everything inside the blue box is only accessible if it's decrypted with the correct (private) key.
It's not possible to fake one of these packages because it has to be signed by a recognised key, and only one's trusted friends have a suitable key. The timestamp is included inside the packet so that replays of previous messages can be detected.
The contents of the grey box depends again on the message type, as described below.
Accepting a contact request leads to the public key being sent back, together with the accepter's name and a message, as follows:
Most encrypted messages don't need to send the sender id, because this can be deduced from the signature. However, in this case the recipient of the contact accept message can't check the signature, so the sender id is included in the message.
This general message will form the bulk of the messages being exchanged. It is the one used for emails, tweets, SMS messages, blog posts, muffin recipes and everything else, whether it's a message to one person or to everybody on your contact list. As before, it's all inside the blue container which means it's encrypted and signed.
It's encrypted for each recipient individually, but the "recipients" field includes the ids of all the people to whom the message was sent. This allows the recipient to reply to everybody in a group mail. If it's a reply to an earlier such message, the hash of the parent message can be included to allow the client to connect the messages together into threads.
This message type will be sent automatically and invisibly when connecting to someone new and when coming online. It could also be sent at some regular interval when online too, and perhaps also when going offline.
It contains the status (online or about to go offline), and a hash of the profile information so that recipients can tell whether anything in the profile has changed (such as descriptions or the avatar picture). It also has a mode, either "ping" or "pong". A "pong" message is sent as a reply to a "ping", and the "pong" message doesn't need to be replied to.
The recipient of this message learns that the sender is currently online (or about to go offline) and can update the availability status accordingly. If the profile hash doesn't match the one stored in the database for that contact, then the recipient can (automatically) send an information request message (see below) to get the updated information.
If a status notification message can't immediately be sent, it will be deleted rather than re-queued for later transmission.
This message type will be sent automatically and invisibly when one Murmeli client wants to request a set of information from another trusted client. The most obvious use for this is to request the profile information.
The receiver of such a message will check the signature and the timestamp to decide whether to respond or not. If it's a valid request, the answer will be sent in an information response message (see below) containing the requested information.
This message could potentially be intercepted by an attacker and replayed to the recipient later, in an attempt to trigger an automatic response. But if the timestamp is too old then the receiver will just ignore the request.
This message type will be sent automatically and invisibly in response to an information request message (see above).
This message type allows user A to refer a contact C to one of A's friends B. We assume that A has both B and C as trusted friends, but B and C haven't exchanged keys yet. This allows A to vouch for C, recommending that B also becomes friends with C, and allows A to send C's public key in encrypted and signed form, so it's much safer than having C send an unencrypted contact request to B.
This message type covers the situation where user B knows that their friend A is friends with user C. Instead of user A initiating the introduction using a Contact referral (as above), this request allows user B (or C) to send a request to their friend A asking for an introduction. If user A approves, then contact referral messages will be sent to both B and C.
If a "general message" (see above) can't be sent directly to the intended recipient, then perhaps it can be sent to one of their trusted friends instead. However, if it were just sent as it is, it would be rejected by the relay because the message would not be able to be decrypted or the signature verified. In order to prevent the possibility that relays are given nonsense messages to forward, the messages to be relayed must be verifiable.
Therefore, the encrypted and signed message is packed inside a signature container, shown as a green outline in the structure diagram below. This allows the relay to verify the signature, extract the blue blob, and then try to forward that to the intended recipient.
Obviously, the relay cannot see what's inside the blue blob because that is encrypted for the recipient. He cannot even tell who it's for, only who it is from (from the signature on the green container). But the relay can still store the blue blob as it is, and try to send it later.
Unfortunately, an interceptor can also tell who the message is from, because they also (potentially) have access to the sender's public key and so can check the signature and verify which key it was signed by. The only way to avoid this would be to have the green container also being encrypted, but then the same message would have to be encrypted several times, once for each relay.