From 184b1ec89cbccb54f0762caea1796dcc16ce0bfd Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 15 Jan 2015 10:42:32 -1000 Subject: [PATCH] Move protocol protobufs to libaxolotl/, handling DeviceControl --- Gruntfile.js | 1 + js/background.js | 2 +- libaxolotl/protobufs.js | 17 ++++++ libaxolotl/protocol.js | 77 +++++++++----------------- libtextsecure/axolotl_wrapper.js | 45 +++++++++++++++ libtextsecure/protobufs.js | 3 - libtextsecure/test/protocol_test.js | 8 +-- protos/IncomingPushMessageSignal.proto | 16 +++--- 8 files changed, 102 insertions(+), 67 deletions(-) create mode 100644 libaxolotl/protobufs.js diff --git a/Gruntfile.js b/Gruntfile.js index ce859ba248c8..434e2043bc25 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -81,6 +81,7 @@ module.exports = function(grunt) { 'libaxolotl/groups_storage.js', 'libaxolotl/crypto.js', 'libaxolotl/protocol.js', + 'libaxolotl/protobufs.js', ], dest: 'libtextsecure/libaxolotl_concat.js', }, diff --git a/js/background.js b/js/background.js index f36d29fc5ae8..a9534c016082 100644 --- a/js/background.js +++ b/js/background.js @@ -87,7 +87,7 @@ conversation.save().then(function() { message.save().then(function() { return new Promise(function(resolve) { - resolve(textsecure.protocol.handleIncomingPushMessageProto(pushMessage).then( + resolve(textsecure.protocol_wrapper.handleIncomingPushMessageProto(pushMessage).then( function(pushMessageContent) { handlePushMessageContent(pushMessageContent, message); } diff --git a/libaxolotl/protobufs.js b/libaxolotl/protobufs.js new file mode 100644 index 000000000000..408a367790fb --- /dev/null +++ b/libaxolotl/protobufs.js @@ -0,0 +1,17 @@ +;(function() { + function loadProtoBufs(filename) { + return dcodeIO.ProtoBuf.loadProtoFile({root: 'protos', file: filename}).build('textsecure'); + }; + + var protocolMessages = loadProtoBufs('WhisperTextProtocol.proto'); + var deviceMessages = loadProtoBufs('DeviceMessages.proto'); + + window.axolotl = window.axolotl || {}; + window.axolotl.protobuf = { + WhisperMessage : protocolMessages.WhisperMessage, + PreKeyWhisperMessage : protocolMessages.PreKeyWhisperMessage, + DeviceInit : deviceMessages.DeviceInit, + IdentityKey : deviceMessages.IdentityKey, + DeviceControl : deviceMessages.DeviceControl, + }; +})(); diff --git a/libaxolotl/protocol.js b/libaxolotl/protocol.js index ddb52813c12f..f6eacc0d7320 100644 --- a/libaxolotl/protocol.js +++ b/libaxolotl/protocol.js @@ -349,20 +349,8 @@ window.textsecure.protocol = function() { crypto_storage.saveSession(encodedNumber, session); } - var initSessionFromPreKeyWhisperMessage; - var decryptWhisperMessage; - var handlePreKeyWhisperMessage = function(from, encodedMessage) { - var preKeyProto = textsecure.protobuf.PreKeyWhisperMessage.decode(encodedMessage, 'binary'); - return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) { - return decryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) { - if (sessions[1] !== undefined) - sessions[1](); - return result; - }); - }); - } - - var wipeIdentityAndTryMessageAgain = function(from, encodedMessage, message_id) { + //TODO: Rewrite this! + /*var wipeIdentityAndTryMessageAgain = function(from, encodedMessage, message_id) { // Wipe identity key! textsecure.storage.removeEncrypted("devices" + from.split('.')[0]); return handlePreKeyWhisperMessage(from, encodedMessage).then( @@ -374,9 +362,9 @@ window.textsecure.protocol = function() { } ); } - textsecure.replay.registerFunction(wipeIdentityAndTryMessageAgain, textsecure.replay.Type.INIT_SESSION); + textsecure.replay.registerFunction(wipeIdentityAndTryMessageAgain, textsecure.replay.Type.INIT_SESSION);*/ - initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { + var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { var preKeyPair = crypto_storage.getStoredKeyPair("preKey" + message.preKeyId); var signedPreKeyPair = crypto_storage.getStoredKeyPair("signedKey" + message.signedPreKeyId); @@ -478,15 +466,18 @@ window.textsecure.protocol = function() { return finish(); } - // returns decrypted protobuf - decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) { + /************************* + *** Public crypto API *** + *************************/ + // returns decrypted plaintext + self.decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) { if (messageBytes[0] != String.fromCharCode((3 << 4) | 3)) throw new Error("Bad version number on WhisperMessage"); var messageProto = messageBytes.substring(1, messageBytes.length - 8); var mac = messageBytes.substring(messageBytes.length - 8, messageBytes.length); - var message = textsecure.protobuf.WhisperMessage.decode(messageProto, 'binary'); + var message = axolotl.protobuf.WhisperMessage.decode(messageProto, 'binary'); var remoteEphemeralKey = toArrayBuffer(message.ephemeralKey); if (session === undefined) { @@ -526,17 +517,9 @@ window.textsecure.protocol = function() { } delete session['pendingPreKey']; - - var finalMessage = textsecure.protobuf.PushMessageContent.decode(plaintext); - - if ((finalMessage.flags & textsecure.protobuf.PushMessageContent.Flags.END_SESSION) - == textsecure.protobuf.PushMessageContent.Flags.END_SESSION) - closeSession(session, true); - removeOldChains(session); - crypto_storage.saveSession(encodedNumber, session, registrationId); - return finalMessage; + return [plaintext, session]; }); }); }); @@ -544,9 +527,18 @@ window.textsecure.protocol = function() { }); } - /************************* - *** Public crypto API *** - *************************/ + // Inits a session (maybe) and then decrypts the message + self.handlePreKeyWhisperMessage = function(from, encodedMessage) { + var preKeyProto = axolotl.protobuf.PreKeyWhisperMessage.decode(encodedMessage, 'binary'); + return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) { + return self.decryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) { + if (sessions[1] !== undefined) + sessions[1](); + return result; + }); + }); + } + // Decrypts message into a raw string self.decryptWebsocketMessage = function(message) { var signaling_key = textsecure.storage.getEncrypted("signaling_key"); //TODO: in crypto_storage @@ -599,31 +591,12 @@ window.textsecure.protocol = function() { }); }; - self.handleIncomingPushMessageProto = function(proto) { - switch(proto.type) { - case textsecure.protobuf.IncomingPushMessageSignal.Type.PLAINTEXT: - return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message)); - case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT: - var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); - return decryptWhisperMessage(from, getString(proto.message)); - case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE: - if (proto.message.readUint8() != ((3 << 4) | 3)) - throw new Error("Bad version byte"); - var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); - return handlePreKeyWhisperMessage(from, getString(proto.message)); - case textsecure.protobuf.IncomingPushMessageSignal.Type.RECEIPT: - return Promise.resolve(null); - default: - return new Promise(function(resolve, reject) { reject(new Error("Unknown message type")); }); - } - } - // return Promise(encoded [PreKey]WhisperMessage) self.encryptMessageFor = function(deviceObject, pushMessageContent) { var session = crypto_storage.getOpenSession(deviceObject.encodedNumber); var doEncryptPushMessageContent = function() { - var msg = new textsecure.protobuf.WhisperMessage(); + var msg = new axolotl.protobuf.WhisperMessage(); var plaintext = toArrayBuffer(pushMessageContent.encode()); var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1); @@ -672,7 +645,7 @@ window.textsecure.protocol = function() { }); } - var preKeyMsg = new textsecure.protobuf.PreKeyWhisperMessage(); + var preKeyMsg = new axolotl.protobuf.PreKeyWhisperMessage(); preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getIdentityKey().pubKey); preKeyMsg.registrationId = textsecure.storage.getUnencrypted("registrationId"); diff --git a/libtextsecure/axolotl_wrapper.js b/libtextsecure/axolotl_wrapper.js index 2c9948ef348c..6ad76c3c91c5 100644 --- a/libtextsecure/axolotl_wrapper.js +++ b/libtextsecure/axolotl_wrapper.js @@ -23,4 +23,49 @@ }, }, }; + + window.textsecure = window.textsecure || {}; + window.textsecure.protocol_wrapper = { + handleIncomingPushMessageProto: function(proto) { + var decodeContents = function(res) { + var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]); + + //TODO + /*if ((finalMessage.flags & textsecure.protobuf.PushMessageContent.Flags.END_SESSION) + == textsecure.protobuf.PushMessageContent.Flags.END_SESSION) + textsecure.protocol.closeSession(res[1], true);*/ + + return finalMessage; + } + + switch(proto.type) { + case textsecure.protobuf.IncomingPushMessageSignal.Type.PLAINTEXT: + return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message)); + case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT: + var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); + return textsecure.protocol.decryptWhisperMessage(from, getString(proto.message)).then(decodeContents); + case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE: + if (proto.message.readUint8() != ((3 << 4) | 3)) + throw new Error("Bad version byte"); + var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); + return textsecure.protocol.handlePreKeyWhisperMessage(from, getString(proto.message)).then(decodeContents); + case textsecure.protobuf.IncomingPushMessageSignal.Type.RECEIPT: + return Promise.resolve(null); + case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE_DEVICE_CONTROL: + if (proto.message.readUint8() != ((3 << 4) | 3)) + throw new Error("Bad version byte"); + var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); + return textsecure.protocol.handlePreKeyWhisperMessage(from, getString(proto.message)).then(function(res) { + return textsecure.protobuf.DeviceControl.decode(res[0]); + }); + case textsecure.protobuf.IncomingPushMessageSignal.Type.DEVICE_CONTROL: + var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); + return textsecure.protocol.decryptWhisperMessage(from, getString(proto.message)).then(function(res) { + return textsecure.protobuf.DeviceControl.decode(res[0]); + }); + default: + return new Promise(function(resolve, reject) { reject(new Error("Unknown message type")); }); + } + } + }; })(); diff --git a/libtextsecure/protobufs.js b/libtextsecure/protobufs.js index 84c5adef2c08..364b14d92635 100644 --- a/libtextsecure/protobufs.js +++ b/libtextsecure/protobufs.js @@ -5,7 +5,6 @@ }; var pushMessages = loadProtoBufs('IncomingPushMessageSignal.proto'); - var protocolMessages = loadProtoBufs('WhisperTextProtocol.proto'); var subProtocolMessages = loadProtoBufs('SubProtocol.proto'); var deviceMessages = loadProtoBufs('DeviceMessages.proto'); @@ -13,8 +12,6 @@ window.textsecure.protobuf = { IncomingPushMessageSignal : pushMessages.IncomingPushMessageSignal, PushMessageContent : pushMessages.PushMessageContent, - WhisperMessage : protocolMessages.WhisperMessage, - PreKeyWhisperMessage : protocolMessages.PreKeyWhisperMessage, ProvisioningUuid : deviceMessages.ProvisioningUuid, ProvisionEnvelope : deviceMessages.ProvisionEnvelope, ProvisionMessage : deviceMessages.ProvisionMessage, diff --git a/libtextsecure/test/protocol_test.js b/libtextsecure/test/protocol_test.js index 189f2155eeb8..2371b4220bfe 100644 --- a/libtextsecure/test/protocol_test.js +++ b/libtextsecure/test/protocol_test.js @@ -31,7 +31,7 @@ describe('Protocol', function() { message: text_message.encode() }; - return textsecure.protocol.handleIncomingPushMessageProto(server_message). + return textsecure.protocol_wrapper.handleIncomingPushMessageProto(server_message). then(function(message) { assert.equal(message.body, text_message.body); assert.equal(message.attachments.length, text_message.attachments.length); @@ -134,7 +134,7 @@ describe('Protocol', function() { message.sourceDevice = 1; try { var proto = textsecure.protobuf.IncomingPushMessageSignal.decode(message.encode()); - return textsecure.protocol.handleIncomingPushMessageProto(proto).then(function(res) { + return textsecure.protocol_wrapper.handleIncomingPushMessageProto(proto).then(function(res) { if (data.expectTerminateSession) return res.flags == textsecure.protobuf.PushMessageContent.Flags.END_SESSION; return res.body == data.expectedSmsText; @@ -182,11 +182,11 @@ describe('Protocol', function() { //XXX: This should be all we do: isEqual(data.expectedCiphertext, msg.body, false); if (msg.type == 1) { var expected = getString(data.expectedCiphertext); - var decoded = textsecure.protobuf.WhisperMessage.decode(expected.substring(1, expected.length - 8), 'binary'); + var decoded = axolotl.protobuf.WhisperMessage.decode(expected.substring(1, expected.length - 8), 'binary'); var result = getString(msg.body); return getString(decoded.encode()) == result.substring(1, result.length - 8); } else { - var decoded = textsecure.protobuf.PreKeyWhisperMessage.decode(getString(data.expectedCiphertext).substr(1), 'binary'); + var decoded = axolotl.protobuf.PreKeyWhisperMessage.decode(getString(data.expectedCiphertext).substr(1), 'binary'); var result = getString(msg.body).substring(1); return getString(decoded.encode()) == result; } diff --git a/protos/IncomingPushMessageSignal.proto b/protos/IncomingPushMessageSignal.proto index 88a4c5ed4d87..837963efc5b3 100644 --- a/protos/IncomingPushMessageSignal.proto +++ b/protos/IncomingPushMessageSignal.proto @@ -5,12 +5,14 @@ option java_outer_classname = "PushMessageProtos"; message IncomingPushMessageSignal { enum Type { - UNKNOWN = 0; - CIPHERTEXT = 1; - KEY_EXCHANGE = 2; - PREKEY_BUNDLE = 3; - PLAINTEXT = 4; - RECEIPT = 5; + UNKNOWN = 0; + CIPHERTEXT = 1; + KEY_EXCHANGE = 2; + PREKEY_BUNDLE = 3; + PLAINTEXT = 4; + RECEIPT = 5; + PREKEY_BUNDLE_DEVICE_CONTROL = 6; + DEVICE_CONTROL = 7; } optional Type type = 1; optional string source = 2; @@ -50,4 +52,4 @@ message PushMessageContent { repeated AttachmentPointer attachments = 2; optional GroupContext group = 3; optional uint32 flags = 4; -} \ No newline at end of file +}