Skip to content

Cryptography

Gregor Herdmann edited this page Nov 2, 2016 · 14 revisions

Decryption errors

Code generation

(z.util.murmurhash3('Field too long',42)+'').substr(0,4); // 1738
Code Type/Error Reason
1300 DecryptError.TooDistantFuture
Message is from too distant in the future
More than 1000 messages on the receive chain were skipped
1701 DecryptError.DuplicateMessage
Duplicate message
Happens if an encrypted message is decrypted twice
1976 DecryptError.InvalidMessage
No matching session state.
Usually happens when we receive a message intended for another client.
2237 DecryptError.InvalidMessage
Can't initialise a session from a CipherMessage.
Relates to not persisting sessions correctly. Will happen when the remote party thinks we have an initialised session, but we can't load it from our local store. However, this will only happen when we have already confirmed the session with the remote party by sending them a message. If they just send us messages without any reply, they will continue to send us PreKeyMessages instead of raw CipherMessages. This bug is caused if we already had established a session and lost (null or undefined) it.
2521 DecryptError.OutdatedMessage Outdated message Opposite of "Too distant future" error
3690 DecryptError.RemoteIdentityChanged Remote identity changed Client of the user has changed without informing us (Man in the middle attack? or database conflicts on the remote side: sessions get mixed with new client)
8550 DecryptError.InvalidSignature
Invalid signature
Session broken or out of sync. Reset the session and decryption is likely to work again!

Cause decryption errors

Destroy session states

// This will enforce 'No matching session state.'
var client_id = "494C60375CC490E4".toLowerCase();
var sessions = wire.app.repository.cryptography.cryptobox.store.sessions;
var cryptobox_session = sessions[Object.keys(sessions).filter((session_key) => session_key.endsWith(client_id))[0]];
cryptobox_session.session_states = {};

Detecting if conversation has degraded

  1. conv is verified
  2. backend rejects message because of missing client id
  3. we check for that device.... if it is unknown to us and not verified we show "conv degraded"

Retrieve the identity key pair

wire.app.repository.encryption.cryptobox

Payload after successful registration

Permanent device:

{
	"cookie": "Wire - 569cff83-a338-487f-8f06-7a7c250664eb",
	"time": "2015-12-17T16:06:09.097Z",
	"location": {
		"lat": 51.5,
		"lon": -0.13
	},
	"address": "62.96.148.44",
	"model": "Chrome 47.0.2526.80",
	"id": "5791adc5672bf043",
	"type": "permanent",
	"class": "desktop",
	"label": "Windows NT 10.0"
}

Temporary device:

{
	"cookie": "Wire - 2507b1a5-1cf0-4144-b284-c349e6e8029e",
	"time": "2015-12-17T16:08:56.293Z",
	"location": {
		"lat": 51.5,
		"lon": -0.13
	},
	"address": "62.96.148.44",
	"model": "Chrome 47.0.2526.80",
	"id": "585d8f6b2a118d6f",
	"type": "temporary",
	"class": "desktop",
	"label": "Windows NT 10.0"
}

iOS behaviour

Sending a message

  1. req to /conversation/xxxxxxx/otr
  2. BE returns { missed: deleted: redundant: }
  3. take the missed json object, and put in a request to: /users/prekeys
  4. pass missed keys to: public func sessionWithId(sessionId: String, fromStringPreKey base64StringKey: String) throws -> CBSession to create a session
  5. resent to same /conversation/xxxx/otr
  6. repeat until there are no more "missed"

Note: When I want to send, I need to get the session of each client recipient, with: CBSession *session = [box sessionById:client.remoteIdentifier error:&error] and encrypt for that client with: NSData *encryptedData = [session encrypt:dataToEncrypt error:&error];

Receiving a message

When receiving an event, the event has the "sender" JSON field. That's the client ID of the sender.

CBSession *session = [self sessionById:sessionId error:&error];
    if (session != nil) { // I already have a session
        decryptedMessageData = [session decrypt:data error:&error];
        if (createdNewSession) {
            *createdNewSession = NO;
        }
    }
	else {
		CBSessionMessage *sessionMessage = [self sessionMessageWithId:sessionId fromMessage:data error:&error];
        decryptedMessageData = sessionMessage.data;
	}

Useful:

  • When fetching /notifications, pass your client ID (not mandatory)
  • When opening web socket, same

Objects

Composition

cryptobox.CryptoboxSession = client_id, Store, Proteus.session.Session
Proteus.session.Session(local Proteus.keys.IdentityKeyPair, remote Proteus.keys.PreKeyBundle)
Proteus.keys.PreKeyBundle = Proteus.keys.IdentityKey & Proteus.keys.PreKey
Proteus.keys.PreKey = key_id & Proteus.keys.KeyPair
Proteus.keys.KeyPair = secret_key & public_key

Creation

pre_key = Proteus.keys.PreKey.new prekey_id
pre_key_bundle = Proteus.keys.PreKeyBundle.new(@identity.public_key, pk).serialise()

Proteus.keys.PreKey

Serialized format

serialized_format =
  id: 456
  payload: 'base64encodedstring'
  type: 'Proteus.keys.PreKey'
  version: 1
Clone this wiki locally