diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 1b5b90727..7217c0f36 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -646,6 +646,7 @@ MessageReceiver.prototype.extend({ case textsecure.protobuf.Envelope.Type.CIPHERTEXT: window.log.info('message from', this.getEnvelopeId(envelope)); promise = Promise.resolve(ciphertext.toArrayBuffer())//;sessionCipher + // TODO: restore decryption & unpadding //.decryptWhisperMessage(ciphertext) //.then(this.unpad); break; diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 685ff6035..7393b7cc8 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -28,6 +28,7 @@ function OutgoingMessage( this.numbersCompleted = 0; this.errors = []; this.successfulNumbers = []; + this.fallBackEncryption = false; } OutgoingMessage.prototype = { @@ -63,11 +64,12 @@ OutgoingMessage.prototype = { return () => textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { if (deviceIds.length === 0) { - return this.registerError( - number, - 'Got empty device list when loading device keys', - null - ); + deviceIds = [1]; + // return this.registerError( + // number, + // 'Got empty device list when loading device keys', + // null + // ); } return this.doSendMessage(number, deviceIds, recurse); }); @@ -110,7 +112,7 @@ OutgoingMessage.prototype = { return null; }) ); - + // TODO: check if still applicable if (updateDevices === undefined) { return this.server.getKeysForNumber(number).then(handleResult); } @@ -121,15 +123,17 @@ OutgoingMessage.prototype = { textsecure.storage.protocol.loadContactPreKey(number), textsecure.storage.protocol.loadContactSignedPreKey(number) ]).then((keys) => { - const [preKey, signedPreKey] = keys; - if (preKey == undefined || signedPreKey == undefined) { - log.error("Will need to request keys!") - } - else { - const identityKey = StringView.hexToArrayBuffer(number); - return handleResult({ identityKey, devices: [{ deviceId: device, preKey, signedPreKey, registrationId: 0}] }) - } - }) + const [preKey, signedPreKey] = keys; + if (preKey == undefined || signedPreKey == undefined) { + log.error("Will need to request keys!") + this.fallBackEncryption = true; + return Promise.resolve(); + } + else { + const identityKey = StringView.hexToArrayBuffer(number); + return handleResult({ identityKey, devices: [{ deviceId: device, preKey, signedPreKey, registrationId: 0 }] }) + } + }) .catch(e => { if (e.name === 'HTTPError' && e.code === 404) { if (device !== 1) { @@ -153,7 +157,7 @@ OutgoingMessage.prototype = { const [response, status] = await this.lokiserver.sendMessage(pubKey, JSON.stringify(jsonData), ttl); return response; } - catch(e) { + catch (e) { if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { // 409 and 410 should bubble and be handled by doSendMessage // 404 should throw UnregisteredUserError @@ -186,11 +190,13 @@ OutgoingMessage.prototype = { getPlaintext() { if (!this.plaintext) { const messageBuffer = this.message.toArrayBuffer(); - this.plaintext = new Uint8Array( - this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 - ); + this.plaintext = new Uint8Array(messageBuffer.byteLength); + // TODO: figure out why we needed padding in the first place + // this.plaintext = new Uint8Array( + // this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 + // ); this.plaintext.set(new Uint8Array(messageBuffer)); - this.plaintext[messageBuffer.byteLength] = 0x80; + //this.plaintext[messageBuffer.byteLength] = 0x80; } return this.plaintext; }, @@ -211,11 +217,43 @@ OutgoingMessage.prototype = { options.messageKeysLimit = false; } - const sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address, - options - ); + let sessionCipher; + if (this.fallBackEncryption) { + // TODO: move to own file? + FallBackSessionCipher = function (address) { + this.recipientPubKey = StringView.hexToArrayBuffer(address.getName()); + this.encrypt = async (plaintext) => { + const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); + const myPrivateKey = myKeyPair.privKey; + const symmetricKey = libsignal.Curve.calculateAgreement(this.recipientPubKey, myPrivateKey); + const iv = libsignal.crypto.getRandomBytes(16); + const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv); + const ivAndCiphertext = new Uint8Array( + iv.byteLength + ciphertext.byteLength + ); + ivAndCiphertext.set(new Uint8Array(iv)); + ivAndCiphertext.set( + new Uint8Array(ciphertext), + iv.byteLength + ); + + return { + type : 6, //friend request + body : new dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString('binary'), + registrationId : null + }; + } + } + sessionCipher = new FallBackSessionCipher( + address + ); + } else { + sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address, + options + ); + } ciphers[address.getDeviceId()] = sessionCipher; return sessionCipher.encrypt(plaintext).then(ciphertext => ({ type: ciphertext.type, diff --git a/protos/SignalService.proto b/protos/SignalService.proto index e1beb304c..fa46ecaba 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -11,6 +11,7 @@ message Envelope { KEY_EXCHANGE = 2; PREKEY_BUNDLE = 3; RECEIPT = 5; + FRIEND_REQUEST = 6; } optional Type type = 1;