Hey, welcome back!
A lot of exciting stuff has happened. The focus of work this week was the trust system. It was extended to encompass two additional states: INACTIVE and COMPROMISED. Along with some UI improvements, this got us to a stage where the protocol is starting to become usable for day to day communications.
In designig the logic driving these changes, we were faced with some tough problems. Recall that, so far, we have had three trust states: UNDECIDED, UNTRUSTED, and TRUSTED. UNDECIDED served as a default initialization for keys on which the user hasn't made a trust decision yet. The idea was that, if there are new keys available when the user sends a message, we'd drop them into a TrustKeysActivity. Here, the user would have to decide which of the UNDECIDED keys they want to trust before the message is sent.
This activity was implemented this week, and it is triggered under two condition. Either if, as previously described, there are UNDECIDED keys for the current conversation (meaning for either the contact or the own account), or if there are no TRUSTED keys for the contact (as in that case, the message couldn't actually be sent). In the former case, only UNDECIDED keys are displayed. In the latter case, UNTRUSTED keys are displayed as well, as there may not necessarily be any UNDECIDED keys. The activity sets all pending keys to UNTRUSTED by default and lets the user pick which ones they want to trust. This way, no key can remain UNDECIDED, preventing the activity from reappearing every time a user tries to send a message. The activity is in a "locked" state until it is done fetching all keys in the background, and until, after commiting the selected changes, there will be at least one TRUSTED key. The user can of course revise these decisions in the ContactDetailsActivity (as before) and EditAccountAcitivity (new this week). The TrustKeysActivity reuses the key display UI introduced for the ContactDetailsActivity and EditAccountActivity, which also got an overhaul this week. Take a look:
You'll notice we replaced the buttons with a slider and got rid of the trust state text, as this was now redundant. TRUSTED and UNTRUSTED keys obviously have an enabled/disabled slider respectively. UNDECIDED keys have a greyed-out slider that gets enabled (and initialized to UNTRUSTED) when the user clicks on it.
You may also have noticed an entirely greyed-out key in the above screenshots. This represents an INACTIVE key. The INACTIVE state was introduced to deal with the problem of stale keys. Stale keys not only clog up the UI unnecessarily, they also create overhead. Because the device doesn't know that a key may not be needed anymore, it will still have to encrypt sent messages for that device. This creates computational as well as bandwidth overhead.
Let's say one of your contacts buys a new phone. They wipe their old one, and sell it. On their new phone, a new IdentityKey will be generated, that their contacts will have to trust. While your contact can use the "Clear devices" function to remove the announcement of that stale key from the PEP devicelist, this will only prevent new sessions from being established with that stale key. Contacts that already have a session with that device will still keep them around. Those contacts will notice that the device that key belongs to was removed from the announcement, but they can't use that as a trigger to delete the key and session from their database. The reason for this is that the "Clear devices" function has no way of knowing which devices it should actually clear, so it just removes all devices except for the current one. This is not a problem, because the other active devices will notice they were removed, and they will just immediately readd themselves. But at that point, we would have already deleted the session and key, if we simply used the event that a device was removed from PEP as a trigger to delete it.
This is where the INACTIVE state comes in. When a device that was initially TRUSTED is removed from PEP, we now set it to INACTIVE rather than deleting it outright. While a device is considered INACTIVE, no messages are encrypted for it. INACTIVE devices are automatically reactivated and set back to TRUSTED if they either reappear in PEP or we receive a message from them. As a result, the "Clear devices" function works as expected, with active devices remaining in their TRUSTED state, while unused devices are set to INACTIVE automatically and not used for sending anymore. A side effect of this design is that after using "Clear devices", the user has to ensure all their devices come online so they can reannounce themselves. Until they do so, messages will not be encrypted for them, so the user might lose some of the history on some of their devices. We warn about this in a popup.
Note that there is still no way for a device to ever get deleted. So while old devices will now end up INACTIVE, they will still clog up out UI. The same goes for devices that are explicitly not to be trusted because they don't match our contact's fingerprint(s). This problem is resolved by the second new state that was added, COMPROMISED. By long-pressing on a key, the user can purge them. The effects of this are twofold: the key is not displayed in the UI or used for message processing anymore but it is kept around in the database. The COMPROMISED keys effectively constitute a blacklist of keys that can never be used again. Once a key is set COMPROMISED by the user, there's no way back, as they are hidden from the user forever (or at least until they remove and readd their account to the app). This protects the user from accidentally re-trusting a key they shouldn't. This functionality is also intended to be used for the aforementioned stale keys. If one of your contact's keys has been INACTIVE for a while, you can now simply remove it. It not only makes sense to treat both of these cases -- keys that are not needed anymore, and keys that were never trusted at all -- identically to get a more streamlined UX, but they are in fact very similar semantically, as an old key no longer in use should be considered COMPROMISED anyway.
I have to give huge thanks to my mentor Daniel here for taking the time to sit down with me and talk this thing through. While this design may seem simple and intuitive in hindsight, we had to play through many different options in order to arrive at this point where these different choices mate to make a coherent whole. To my knowledge, no one has built anything quite like what we're working on before, so we're kind of flying by the seat of our pants here. It was our big goal to come up with a design that unburdens the user as much as possible, in order to yield a streamlined experience. I think we've built something that is very simple to use and understand by automating wherver possible, yet letting the user make the important choices in order to ensure sufficient control over this crucial component. Daniel was also really helpful in debugging the UI code for this overhauled trust system. He took a lot of time for me and helped me find some really nasty bugs. :)
In other news, this week also marked an overhaul of the payload message encryption. We switched from our prototype implementation (plain AES-CTR, no HMAC) to AES-GCM, which provides encryption and integrity protection in one neat package. Less to implement means less sources for bugs. I also fixed some bugs relating to file transfers while in axolotl sessions amongst others, and I improved the "Clear devices" UI. And finally, my code has been merged into the development branch of Conversations! While it's not done yet, this will make it easier for interested users to help test it.
As you can tell, I've been pretty busy, and we've made a lot of progress recently. I will probably spend the next week on code quality improvements and bug fixes. Right now the code accesses the database way more than it should, which leads to performance problems and (in some cases) unresponsive UI.
If you have any comments or suggestions, please let me know. Until next week!