示例#1
0
		/**
         * Decrypt a message.
         *
         * @param  ciphertext The {@link PreKeyWhisperMessage} to decrypt.
         * @param  callback   A callback that is triggered after decryption is complete,
         *                    but before the updated session state has been committed to the session
         *                    DB.  This allows some implementations to store the committed plaintext
         *                    to a DB first, in case they are concerned with a crash happening between
         *                    the time the session state is updated but before they're able to store
         *                    the plaintext to disk.
         *
         * @return The plaintext.
         * @throws InvalidMessageException if the input is not valid ciphertext.
         * @throws DuplicateMessageException if the input is a message that has already been received.
         * @throws LegacyMessageException if the input is a message formatted by a protocol version that
         *                                is no longer supported.
         * @throws InvalidKeyIdException when there is no local {@link org.whispersystems.libaxolotl.state.PreKeyRecord}
         *                               that corresponds to the PreKey ID in the message.
         * @throws InvalidKeyException when the message is formatted incorrectly.
         * @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
         */
		public byte[] decrypt(PreKeyWhisperMessage ciphertext, DecryptionCallback callback)

		{
			lock (SESSION_LOCK)
			{
				SessionRecord sessionRecord = sessionStore.LoadSession(remoteAddress);
				May<uint> unsignedPreKeyId = sessionBuilder.process(sessionRecord, ciphertext);
				byte[] plaintext = decrypt(sessionRecord, ciphertext.getWhisperMessage());

				callback.handlePlaintext(plaintext);

				sessionStore.StoreSession(remoteAddress, sessionRecord);

				if (unsignedPreKeyId.HasValue)
				{
					preKeyStore.RemovePreKey(unsignedPreKeyId.ForceGetValue());
				}

				return plaintext;
			}
		}
示例#2
0
		/**
         * Encrypt a message.
         *
         * @param  paddedMessage The plaintext message bytes, optionally padded to a constant multiple.
         * @return A ciphertext message encrypted to the recipient+device tuple.
         */
		public CiphertextMessage encrypt(byte[] paddedMessage)
		{
			lock (SESSION_LOCK)
			{
				SessionRecord sessionRecord = sessionStore.LoadSession(remoteAddress);
				SessionState sessionState = sessionRecord.getSessionState();
				ChainKey chainKey = sessionState.getSenderChainKey();
				MessageKeys messageKeys = chainKey.getMessageKeys();
				ECPublicKey senderEphemeral = sessionState.getSenderRatchetKey();
				uint previousCounter = sessionState.getPreviousCounter();
				uint sessionVersion = sessionState.getSessionVersion();

				byte[] ciphertextBody = getCiphertext(sessionVersion, messageKeys, paddedMessage);
				CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.getMacKey(),
																		 senderEphemeral, chainKey.getIndex(),
																		 previousCounter, ciphertextBody,
																		 sessionState.getLocalIdentityKey(),
																		 sessionState.getRemoteIdentityKey());

				if (sessionState.hasUnacknowledgedPreKeyMessage())
				{
					SessionState.UnacknowledgedPreKeyMessageItems items = sessionState.getUnacknowledgedPreKeyMessageItems();
					uint localRegistrationId = sessionState.GetLocalRegistrationId();

					ciphertextMessage = new PreKeyWhisperMessage(sessionVersion, localRegistrationId, items.getPreKeyId(),
																 items.getSignedPreKeyId(), items.getBaseKey(),
																 sessionState.getLocalIdentityKey(),
																 (WhisperMessage)ciphertextMessage);
				}

				sessionState.setSenderChainKey(chainKey.getNextChainKey());
				sessionStore.StoreSession(remoteAddress, sessionRecord);
				return ciphertextMessage;
			}
		}
        private May<uint> processV2(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            if (!message.getPreKeyId().HasValue)
            {
                throw new InvalidKeyIdException("V2 message requires one time prekey id!");
            }

            if (!preKeyStore.ContainsPreKey(message.getPreKeyId().ForceGetValue()) &&
                sessionStore.ContainsSession(remoteAddress))
            {
                //Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
                return May<uint>.NoValue; //May.absent();
            }

            ECKeyPair ourPreKey = preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair();

            BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();

            parameters.setOurIdentityKey(identityKeyStore.GetIdentityKeyPair())
                          .setOurSignedPreKey(ourPreKey)
                          .setOurRatchetKey(ourPreKey)
                          .setOurOneTimePreKey(May<ECKeyPair>.NoValue) //absent
                          .setTheirIdentityKey(message.getIdentityKey())
                          .setTheirBaseKey(message.getBaseKey());

            if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();

            RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());

            sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId());
            sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
            sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());

            if (message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE)
            {
                return message.getPreKeyId();
            }
            else
            {
                return May<uint>.NoValue; // May.absent();
            }
        }
示例#4
0
		/**
         * Decrypt a message.
         *
         * @param  ciphertext The {@link PreKeyWhisperMessage} to decrypt.
         *
         * @return The plaintext.
         * @throws InvalidMessageException if the input is not valid ciphertext.
         * @throws DuplicateMessageException if the input is a message that has already been received.
         * @throws LegacyMessageException if the input is a message formatted by a protocol version that
         *                                is no longer supported.
         * @throws InvalidKeyIdException when there is no local {@link org.whispersystems.libaxolotl.state.PreKeyRecord}
         *                               that corresponds to the PreKey ID in the message.
         * @throws InvalidKeyException when the message is formatted incorrectly.
         * @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
         */
		public byte[] decrypt(PreKeyWhisperMessage ciphertext)

		{
			return decrypt(ciphertext, new NullDecryptionCallback());
		}
        /**
         * Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
         *
         * After a session is constructed in this way, the embedded {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
         * can be decrypted.
         *
         * @param message The received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
         * @throws org.whispersystems.libaxolotl.InvalidKeyIdException when there is no local
         *                                                             {@link org.whispersystems.libaxolotl.state.PreKeyRecord}
         *                                                             that corresponds to the PreKey ID in
         *                                                             the message.
         * @throws org.whispersystems.libaxolotl.InvalidKeyException when the message is formatted incorrectly.
         * @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
         */
        /*package*/
        internal May<uint>  process(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            uint messageVersion = message.getMessageVersion();
            IdentityKey theirIdentityKey = message.getIdentityKey();

            May<uint> unsignedPreKeyId;

            if (!identityKeyStore.IsTrustedIdentity(remoteAddress.getName(), theirIdentityKey))
            {
                throw new UntrustedIdentityException(remoteAddress.getName(), theirIdentityKey);
            }

            switch (messageVersion)
            {
                case 2: unsignedPreKeyId = processV2(sessionRecord, message); break;
                case 3: unsignedPreKeyId = processV3(sessionRecord, message); break;
                default: throw new Exception("Unknown version: " + messageVersion);
            }

            identityKeyStore.SaveIdentity(remoteAddress.getName(), theirIdentityKey);
            return unsignedPreKeyId;
        }
        private May<uint> processV3(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {

            if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize()))
            {
                //Log.w(TAG, "We've already setup a session for this V3 message, letting bundled message fall through...");
                return May<uint>.NoValue;
            }

            ECKeyPair ourSignedPreKey = signedPreKeyStore.LoadSignedPreKey(message.getSignedPreKeyId()).getKeyPair();

            BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();

            parameters.setTheirBaseKey(message.getBaseKey())
                      .setTheirIdentityKey(message.getIdentityKey())
                      .setOurIdentityKey(identityKeyStore.GetIdentityKeyPair())
                      .setOurSignedPreKey(ourSignedPreKey)
                      .setOurRatchetKey(ourSignedPreKey);

            if (message.getPreKeyId().HasValue)
            {
                parameters.setOurOneTimePreKey(new May<ECKeyPair>(preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair()));
            }
            else
            {
                parameters.setOurOneTimePreKey(May<ECKeyPair>.NoValue);
            }

            if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();

            RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());

            sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId());
            sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
            sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());

            if (message.getPreKeyId().HasValue && message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE)
            {
                return message.getPreKeyId();
            }
            else
            {
                return May<uint>.NoValue;
            }
        }
        public void testOptionalOneTimePreKey()
        {
            AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
            SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            AxolotlStore bobStore = new TestInMemoryAxolotlStore();

            ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
            ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
            byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.GetIdentityKeyPair().getPrivateKey(),
                                                                          bobSignedPreKeyPair.getPublicKey().serialize());

            PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                                      0, null,
                                                      22, bobSignedPreKeyPair.getPublicKey(),
                                                      bobSignedPreKeySignature,
                                                      bobStore.GetIdentityKeyPair().getPublicKey());

            aliceSessionBuilder.process(bobPreKey);

            Assert.IsTrue(aliceStore.ContainsSession(BOB_ADDRESS));
            Assert.AreEqual((uint)3, aliceStore.LoadSession(BOB_ADDRESS).getSessionState().getSessionVersion());

            String originalMessage = "L'homme est condamné à être libre";
            SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
            CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            Assert.AreEqual(outgoingMessage.getType(), CiphertextMessage.PREKEY_TYPE);

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
            Assert.IsFalse(incomingMessage.getPreKeyId().HasValue);

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, DateUtil.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));

            SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);
            byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);

            Assert.IsTrue(bobStore.ContainsSession(ALICE_ADDRESS));
            Assert.AreEqual((uint)3, bobStore.LoadSession(ALICE_ADDRESS).getSessionState().getSessionVersion());
            Assert.IsNotNull(bobStore.LoadSession(ALICE_ADDRESS).getSessionState().getAliceBaseKey());
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));
        }
        public void testBasicPreKeyV2()
        {
            AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
            SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            AxolotlStore bobStore = new TestInMemoryAxolotlStore();
            ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
            PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                                          31337, bobPreKeyPair.getPublicKey(),
                                                          0, null, null,
                                                          bobStore.GetIdentityKeyPair().getPublicKey());

            aliceSessionBuilder.process(bobPreKey);

            Assert.IsTrue(aliceStore.ContainsSession(BOB_ADDRESS));
            Assert.AreEqual((uint)2, aliceStore.LoadSession(BOB_ADDRESS).getSessionState().getSessionVersion());

            String originalMessage = "L'homme est condamné à être libre";
            SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
            CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            Assert.AreEqual(CiphertextMessage.PREKEY_TYPE, outgoingMessage.getType());

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));

            SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);
            byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);

            Assert.IsTrue(bobStore.ContainsSession(ALICE_ADDRESS));
            Assert.AreEqual((uint)2, bobStore.LoadSession(ALICE_ADDRESS).getSessionState().getSessionVersion());
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));

            CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));
            Assert.AreEqual(CiphertextMessage.WHISPER_TYPE, bobOutgoingMessage.getType());

            byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage);
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(alicePlaintext));

            runInteraction(aliceStore, bobStore);

            aliceStore = new TestInMemoryAxolotlStore();
            aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
            aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);

            bobPreKeyPair = Curve.generateKeyPair();
            bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(),
                                         1, 31338, bobPreKeyPair.getPublicKey(),
                                         0, null, null, bobStore.GetIdentityKeyPair().getPublicKey());

            bobStore.StorePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
            aliceSessionBuilder.process(bobPreKey);

            outgoingMessage = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            try
            {
                bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
                throw new Exception("shouldn't be trusted!");
            }
            catch (UntrustedIdentityException uie)
            {
                bobStore.SaveIdentity(ALICE_ADDRESS.getName(), new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey());
            }

            plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));

            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));

            bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                         31337, Curve.generateKeyPair().getPublicKey(),
                                         0, null, null,
                                         aliceStore.GetIdentityKeyPair().getPublicKey());

            try
            {
                aliceSessionBuilder.process(bobPreKey);
                throw new Exception("shoulnd't be trusted!");
            }
            catch (UntrustedIdentityException uie)
            {
                // good
            }
        }
        public void testBadMessageBundle()
        {
            AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
            SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            AxolotlStore bobStore = new TestInMemoryAxolotlStore();

            ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
            ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
            byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.GetIdentityKeyPair().getPrivateKey(),
                                                                          bobSignedPreKeyPair.getPublicKey().serialize());

            PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                                      31337, bobPreKeyPair.getPublicKey(),
                                                      22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
                                                      bobStore.GetIdentityKeyPair().getPublicKey());

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, DateUtil.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));

            aliceSessionBuilder.process(bobPreKey);

            String originalMessage = "L'homme est condamné à être libre";
            SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
            CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            Assert.AreEqual(CiphertextMessage.PREKEY_TYPE, outgoingMessageOne.getType());

            byte[] goodMessage = outgoingMessageOne.serialize();
            byte[] badMessage = new byte[goodMessage.Length];
            Array.Copy(goodMessage, 0, badMessage, 0, badMessage.Length);

            badMessage[badMessage.Length - 10] ^= 0x01;

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(badMessage);
            SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

            byte[] plaintext = new byte[0];

            try
            {
                plaintext = bobSessionCipher.decrypt(incomingMessage);
                throw new Exception("Decrypt should have failed!");
            }
            catch (InvalidMessageException e)
            {
                // good.
            }

            Assert.IsTrue(bobStore.ContainsPreKey(31337));

            plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage));

            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));
            Assert.IsFalse(bobStore.ContainsPreKey(31337));
        }
        public void testRepeatBundleMessageV3()
        {
            AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
            SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            AxolotlStore bobStore = new TestInMemoryAxolotlStore();

            ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
            ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
            byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.GetIdentityKeyPair().getPrivateKey(),
                                                                          bobSignedPreKeyPair.getPublicKey().serialize());

            PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                                      31337, bobPreKeyPair.getPublicKey(),
                                                      22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
                                                      bobStore.GetIdentityKeyPair().getPublicKey());

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, DateUtil.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));

            aliceSessionBuilder.process(bobPreKey);

            String originalMessage = "L'homme est condamné à être libre";
            SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
            CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));
            CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            Assert.AreEqual(CiphertextMessage.PREKEY_TYPE, outgoingMessageOne.getType());
            Assert.AreEqual(CiphertextMessage.PREKEY_TYPE, outgoingMessageTwo.getType());

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize());

            SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

            byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));

            CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));

            byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize()));
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(alicePlaintext));

            // The test

            PreKeyWhisperMessage incomingMessageTwo = new PreKeyWhisperMessage(outgoingMessageTwo.serialize());

            plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(incomingMessageTwo.serialize()));
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(plaintext));

            bobOutgoingMessage = bobSessionCipher.encrypt(Encoding.UTF8.GetBytes(originalMessage));
            alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize()));
            Assert.AreEqual(originalMessage, Encoding.UTF8.GetString(alicePlaintext));

        }
示例#11
0
        private void handleUntrustedIdentityMessage(TextSecureEnvelope envelope, May<long> smsMessageId)
        {
            try
            {
                var database = DatabaseFactory.getTextMessageDatabase(); //getEncryptingSmsDatabase(context);
                Recipients recipients = RecipientFactory.getRecipientsFromString(envelope.getSource(), false);
                long recipientId = recipients.getPrimaryRecipient().getRecipientId();
                PreKeyWhisperMessage whisperMessage = new PreKeyWhisperMessage(envelope.getLegacyMessage());
                IdentityKey identityKey = whisperMessage.getIdentityKey();
                String encoded = Base64.encodeBytes(envelope.getLegacyMessage());
                IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
                                                                               envelope.getTimestamp(), encoded,
                                                                               May<TextSecureGroup>.NoValue);

                if (!smsMessageId.HasValue)
                {
                    IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
                    Pair<long, long> messageAndThreadId = database.InsertMessageInbox(bundleMessage);

                    database.SetMismatchedIdentity(messageAndThreadId.first(), recipientId, identityKey);
                    //MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
                }
                else
                {
                    var messageId = smsMessageId.ForceGetValue();
                    database.UpdateMessageBody(messageId, encoded);
                    database.MarkAsPreKeyBundle(messageId);
                    database.SetMismatchedIdentity(messageId, recipientId, identityKey);
                }
            }
            catch (InvalidMessageException e)
            {
                throw new InvalidOperationException(e.Message);
            }
            catch (InvalidVersionException e)
            {
                throw new InvalidOperationException(e.Message);
            }
        }