Пример #1
0
        public Maybe<UInt32> Process(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            var messageVersion   = message.MessageVersion;
            var theirIdentityKey = message.IdentityKey;

            Maybe<UInt32> unsignedPreKeyId;

            if (!_identityKeyStore.IsTrustedIdentity(_remoteAddress.Name, theirIdentityKey)) {
              throw new UntrustedIdentityException();
            }

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

            _identityKeyStore.SaveIdentity(_remoteAddress.Name, theirIdentityKey);
            return unsignedPreKeyId;
        }
Пример #2
0
        public void TestBadMessageBundle()
        {
            var aliceStore          = new TestInMemoryAxolotlStore();
            var aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            var bobStore = new TestInMemoryAxolotlStore();

            var bobPreKeyPair            = Curve.GenerateKeyPair();
            var bobSignedPreKeyPair      = Curve.GenerateKeyPair();
            var bobSignedPreKeySignature = Curve.CalculateSignature(bobStore.GetIdentityKeyPair().PrivateKey,
                                                                          bobSignedPreKeyPair.PublicKey.Serialize());

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

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, currentMillis, bobSignedPreKeyPair, bobSignedPreKeySignature));

            aliceSessionBuilder.Process(bobPreKey);

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

            Assert.True(outgoingMessageOne.GetKeyType() == CiphertextMessage.PREKEY_TYPE);

            var goodMessage = outgoingMessageOne.Serialize();
            var badMessage  = new byte[goodMessage.Length];

            Array.Copy(goodMessage, 0, badMessage, 0, badMessage.Length);

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

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

            var plaintext = new byte[0];

            try {
                plaintext = bobSessionCipher.Decrypt(incomingMessage);
                throw new InvalidOperationException("Decrypt should have failed!");
            } catch (InvalidMessageException e) {
                Assert.Pass ();
            }

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

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

            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(plaintext)));
            Assert.True(!bobStore.ContainsPreKey(31337));
        }
Пример #3
0
        public byte[] Decrypt(PreKeyWhisperMessage ciphertext, IDecryptionCallback callback)
        {
            lock(SESSION_LOCK)
            {
                SessionRecord sessionRecord = _sessionStore.LoadSession(_remoteAddress);
                Maybe<UInt32> unsignedPreKeyId = _sessionBuilder.Process(sessionRecord, ciphertext);
                byte[] plaintext = Decrypt(sessionRecord, ciphertext.Message);

                callback.HandlePlaintext(plaintext);

                _sessionStore.StoreSession(_remoteAddress, sessionRecord);

                if(unsignedPreKeyId.HasValue)
                {
                    _preKeyStore.RemovePreKey(unsignedPreKeyId.Value);
                }

                return plaintext;
            }
        }
Пример #4
0
        private Maybe<UInt32> ProcessV3(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            if (sessionRecord.HasSessionState(message.MessageVersion, message.BaseKey.Serialize()))
            {
                Logger.w("SessionBuilder : ", "We've already setup a session for this V3 message, letting bundled message fall through...");
                return Maybe<UInt32>.Nothing;
            }

            var ourSignedPreKey = _signedPreKeyStore.LoadSignedPreKey(message.SignedPreKeyId).keyPair;

            var bobParams = BobAxolotlParameters.NewBuilder ();
            bobParams.SetTheirBaseKey(message.BaseKey)
                        .SetTheirIdentityKey(message.IdentityKey)
                        .SetOurIdentityKey(_identityKeyStore.GetIdentityKeyPair())
                        .SetOurSignedPreKey(ourSignedPreKey)
                        .SetOurRatchetKey(ourSignedPreKey);

            if (message.PreKeyId.IsSomething()) {
                message.PreKeyId.Do (pKid => {
                    bobParams.SetOurOneTimePreKey(_preKeyStore.LoadPreKey(pKid).KeyPair.ToMaybe());
                });
            } else {
                bobParams.SetOurOneTimePreKey(Maybe<ECKeyPair>.Nothing);
            }

            if (!sessionRecord.IsFresh) sessionRecord.ArchiveCurrentState();

            RatchetingSession.InitializeSession(sessionRecord.SessionState, message.MessageVersion, bobParams.Create());

            sessionRecord.SessionState.LocalRegistrationId  = _identityKeyStore.GetLocalRegistrationId();
            sessionRecord.SessionState.RemoteRegistrationId = message.RegistrationId;
            sessionRecord.SessionState.AliceBaseKey 		= message.BaseKey.Serialize();

            if (message.PreKeyId.IsSomething() &&
                message.PreKeyId.Value != Medium.MAX_VALUE) {
                return message.PreKeyId;
            } else {
                return Maybe<UInt32>.Nothing;
            }
        }
Пример #5
0
        private Maybe<UInt32> ProcessV2(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            if (message.PreKeyId.IsNothing()) {
                throw new InvalidKeyIdException("V2 message requires one time prekey id!");
            }

            if (!_preKeyStore.ContainsPreKey(message.PreKeyId.Value) &&
                _sessionStore.ContainsSession(_remoteAddress))
            {
                Console.WriteLine("TAG" + "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
                return Maybe<UInt32>.Nothing;
            }

            var ourPreKey = _preKeyStore.LoadPreKey(message.PreKeyId.Value).KeyPair;

            var bobParams = BobAxolotlParameters.NewBuilder();

            bobParams.SetOurIdentityKey(_identityKeyStore.GetIdentityKeyPair())
                    .SetOurSignedPreKey(ourPreKey)
                    .SetOurRatchetKey(ourPreKey)
                    .SetOurOneTimePreKey(Maybe<ECKeyPair>.Nothing)
                    .SetTheirIdentityKey(message.IdentityKey)
                    .SetTheirBaseKey(message.BaseKey);

            if (!sessionRecord.IsFresh) sessionRecord.ArchiveCurrentState();

            RatchetingSession.InitializeSession(sessionRecord.SessionState, message.MessageVersion, bobParams.Create());

            sessionRecord.SessionState.LocalRegistrationId 	= _identityKeyStore.GetLocalRegistrationId();
            sessionRecord.SessionState.RemoteRegistrationId = message.RegistrationId;
            sessionRecord.SessionState.AliceBaseKey 		= message.BaseKey.Serialize();

            if (message.PreKeyId.Value != Medium.MAX_VALUE) {
                return message.PreKeyId;
            } else {
                return Maybe<UInt32>.Nothing;
            }
        }
Пример #6
0
        public void TestRepeatBundleMessageV3()
        {
            var aliceStore          = new TestInMemoryAxolotlStore();
            var aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            var bobStore = new TestInMemoryAxolotlStore();

            var bobPreKeyPair            = Curve.GenerateKeyPair();
            var bobSignedPreKeyPair      = Curve.GenerateKeyPair();
            var bobSignedPreKeySignature = Curve.CalculateSignature(bobStore.GetIdentityKeyPair().PrivateKey,
                                                                          bobSignedPreKeyPair.PublicKey.Serialize());

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

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, currentMillis, bobSignedPreKeyPair, bobSignedPreKeySignature));

            aliceSessionBuilder.Process(bobPreKey);

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

            Assert.True(outgoingMessageOne.GetKeyType() == CiphertextMessage.PREKEY_TYPE);
            Assert.True(outgoingMessageTwo.GetKeyType() == CiphertextMessage.PREKEY_TYPE);

            var incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.Serialize());

            var bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

            var plaintext = bobSessionCipher.Decrypt(incomingMessage);
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(plaintext)));

            var bobOutgoingMessage = bobSessionCipher.Encrypt (Encoding.UTF8.GetBytes (originalMessage));

            var alicePlaintext = aliceSessionCipher.Decrypt(new WhisperMessage(bobOutgoingMessage.Serialize()));
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(alicePlaintext)));

            // The test

            var incomingMessageTwo = new PreKeyWhisperMessage(outgoingMessageTwo.Serialize());

            plaintext = bobSessionCipher.Decrypt(new PreKeyWhisperMessage(incomingMessageTwo.Serialize()));
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(plaintext)));

            bobOutgoingMessage = bobSessionCipher.Encrypt (Encoding.UTF8.GetBytes (originalMessage));
            alicePlaintext = aliceSessionCipher.Decrypt(new WhisperMessage(bobOutgoingMessage.Serialize()));
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString (alicePlaintext)));
        }
Пример #7
0
        public void TestOptionalOneTimePreKey()
        {
            var aliceStore          = new TestInMemoryAxolotlStore();
            var aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            var bobStore = new TestInMemoryAxolotlStore();

            var bobPreKeyPair            = Curve.GenerateKeyPair();
            var bobSignedPreKeyPair      = Curve.GenerateKeyPair();
            var bobSignedPreKeySignature = Curve.CalculateSignature(bobStore.GetIdentityKeyPair().PrivateKey,
                                                                          bobSignedPreKeyPair.PublicKey.Serialize());

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

            aliceSessionBuilder.Process(bobPreKey);

            Assert.True(aliceStore.ContainsSession(BOB_ADDRESS));
            Assert.True(aliceStore.LoadSession(BOB_ADDRESS).SessionState.GetSessionVersion() == 3);

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

            Assert.True(outgoingMessage.GetKeyType() == CiphertextMessage.PREKEY_TYPE);

            var incomingMessage = new PreKeyWhisperMessage(outgoingMessage.Serialize());
            Assert.True(!incomingMessage.PreKeyId.HasValue);

            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, currentMillis, bobSignedPreKeyPair, bobSignedPreKeySignature));

            var bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);
            var plaintext        = bobSessionCipher.Decrypt(incomingMessage);

            Assert.True(bobStore.ContainsSession(ALICE_ADDRESS));
            Assert.True(bobStore.LoadSession(ALICE_ADDRESS).SessionState.GetSessionVersion() == 3);
            Assert.True(bobStore.LoadSession(ALICE_ADDRESS).SessionState.AliceBaseKey != null);
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(plaintext)));
        }
Пример #8
0
        public void TestBasicPreKeyV3()
        {
            var aliceStore         		 = new TestInMemoryAxolotlStore();
            var aliceSessionBuilder		 = new SessionBuilder(aliceStore, BOB_ADDRESS);

            var bobStore           		 = new TestInMemoryAxolotlStore();
            var bobPreKeyPair      		 = Curve.GenerateKeyPair();
            var bobSignedPreKeyPair		 = Curve.GenerateKeyPair();
            var bobSignedPreKeySignature = Curve.CalculateSignature(bobStore.GetIdentityKeyPair().PrivateKey,
                                                                             bobSignedPreKeyPair.PublicKey.Serialize());

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

            aliceSessionBuilder.Process(bobPreKey);

            Assert.True(aliceStore.ContainsSession(BOB_ADDRESS));
            Assert.True(aliceStore.LoadSession(BOB_ADDRESS).SessionState.GetSessionVersion() == 3);

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

            Assert.True(outgoingMessage.GetKeyType() == CiphertextMessage.PREKEY_TYPE);

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.Serialize());
            bobStore.StorePreKey(31337, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            bobStore.StoreSignedPreKey(22, new SignedPreKeyRecord(22, currentMillis, bobSignedPreKeyPair, bobSignedPreKeySignature));

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

            Assert.True(bobStore.ContainsSession(ALICE_ADDRESS));
            Assert.True(bobStore.LoadSession(ALICE_ADDRESS).SessionState.GetSessionVersion() == 3);
            Assert.True(bobStore.LoadSession(ALICE_ADDRESS).SessionState.AliceBaseKey != null);
            Assert.True(originalMessage.Equals(Encoding.UTF8.GetString(plaintext)));

            CiphertextMessage bobOutgoingMessage = bobSessionCipher.Encrypt (Encoding.UTF8.GetBytes (originalMessage));
            Assert.True(bobOutgoingMessage.GetKeyType() == CiphertextMessage.WHISPER_TYPE);

            var alicePlaintext = aliceSessionCipher.Decrypt(new WhisperMessage(bobOutgoingMessage.Serialize()));
            Assert.True (Encoding.UTF8.GetString(alicePlaintext).Equals (originalMessage));

            RunInteraction(aliceStore, bobStore);

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

            bobPreKeyPair            = Curve.GenerateKeyPair();
            bobSignedPreKeyPair      = Curve.GenerateKeyPair();
            bobSignedPreKeySignature = Curve.CalculateSignature(bobStore.GetIdentityKeyPair().PrivateKey, bobSignedPreKeyPair.PublicKey.Serialize());
            bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(),
                                         1, 31338, bobPreKeyPair.PublicKey,
                                         23, bobSignedPreKeyPair.PublicKey, bobSignedPreKeySignature,
                                         bobStore.GetIdentityKeyPair().PublicKey);

            bobStore.StorePreKey(31338, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            bobStore.StoreSignedPreKey(23, new SignedPreKeyRecord(23, currentMillis, bobSignedPreKeyPair, bobSignedPreKeySignature));
            aliceSessionBuilder.Process(bobPreKey);

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

            try {
                plaintext = bobSessionCipher.Decrypt(new PreKeyWhisperMessage(outgoingMessage.Serialize()));
                throw new InvalidOperationException("shouldn't be trusted!");
            } catch (UntrustedIdentityException uie) {
                bobStore.SaveIdentity(ALICE_ADDRESS.Name, new PreKeyWhisperMessage(outgoingMessage.Serialize()).IdentityKey);
            }

            plaintext = bobSessionCipher.Decrypt(new PreKeyWhisperMessage(outgoingMessage.Serialize()));
            Assert.True(Encoding.UTF8.GetString(plaintext).Equals(originalMessage));

            bobPreKey = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                         31337, Curve.GenerateKeyPair().PublicKey,
                                         23, bobSignedPreKeyPair.PublicKey, bobSignedPreKeySignature,
                                         aliceStore.GetIdentityKeyPair().PublicKey);

            try {
                aliceSessionBuilder.Process(bobPreKey);
                throw new InvalidOperationException("shoulnd't be trusted!");
            } catch (UntrustedIdentityException uie) {
                Assert.Pass();
            }
        }
Пример #9
0
        public void TestBasicPreKeyV2()
        {
            IAxolotlStore   aliceStore         = new TestInMemoryAxolotlStore();
            SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);

            IAxolotlStore bobStore      = new TestInMemoryAxolotlStore();
            ECKeyPair    bobPreKeyPair = Curve.GenerateKeyPair();
            PreKeyBundle bobPreKey     = new PreKeyBundle(bobStore.GetLocalRegistrationId(), 1,
                                                          31337, bobPreKeyPair.PublicKey,
                                                          0, null, null,
                                                          bobStore.GetIdentityKeyPair().PublicKey);

            aliceSessionBuilder.Process(bobPreKey);

            Assert.True(aliceStore.ContainsSession(BOB_ADDRESS));
            Assert.True(aliceStore.LoadSession(BOB_ADDRESS).SessionState.GetSessionVersion() == 2);

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

            Assert.True(outgoingMessage.GetKeyType() == CiphertextMessage.PREKEY_TYPE);

            PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.Serialize());
            bobStore.StorePreKey((UInt32)31337, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));

            var bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);
            var plaintext        = bobSessionCipher.Decrypt(incomingMessage);

            Assert.True(bobStore.ContainsSession(ALICE_ADDRESS));
            Assert.True(bobStore.LoadSession(ALICE_ADDRESS).SessionState.GetSessionVersion() == 2);
            Assert.True(originalMessage.Equals(new string(plaintext.Select(x => (char)x).ToArray())));

            var bobOutgoingMessage = bobSessionCipher.Encrypt (Encoding.UTF8.GetBytes (originalMessage));
            Assert.True(bobOutgoingMessage.GetKeyType() == CiphertextMessage.WHISPER_TYPE);

            var alicePlaintext = aliceSessionCipher.Decrypt((WhisperMessage)bobOutgoingMessage);
            Assert.True(new string(alicePlaintext.Select(x => (char)x).ToArray()).Equals(originalMessage));

            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.PublicKey,
                                         0, null, null, bobStore.GetIdentityKeyPair().PublicKey	);

            bobStore.StorePreKey((UInt32)31338, new PreKeyRecord(bobPreKey.PreKeyId, bobPreKeyPair));
            aliceSessionBuilder.Process(bobPreKey);

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

            try {
                bobSessionCipher.Decrypt(new PreKeyWhisperMessage(outgoingMessage.Serialize()));
                throw new InvalidOperationException("shouldn't be trusted!");
            } catch (UntrustedIdentityException uie) {
                bobStore.SaveIdentity(ALICE_ADDRESS.Name, new PreKeyWhisperMessage(outgoingMessage.Serialize()).IdentityKey);
            }

            plaintext = bobSessionCipher.Decrypt(new PreKeyWhisperMessage(outgoingMessage.Serialize()));

            Assert.True(new string(plaintext.Select(x => (char)x).ToArray()).Equals(originalMessage));

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

            try {
                aliceSessionBuilder.Process(bobPreKey);
                throw new InvalidOperationException("shoulnd't be trusted!");
            } catch (UntrustedIdentityException uie) {
                Assert.Pass();
            }
        }
Пример #10
0
 public byte[] Decrypt(PreKeyWhisperMessage ciphertext)
 {
     return Decrypt(ciphertext, new NullDecryptionCallback());
 }
Пример #11
0
        /// <summary>
        /// Encrypt a message.
        /// </summary>
        /// <param name="paddedMessage">The plaintext message bytes, optionally padded to a constant multiple.</param>
        public CiphertextMessage Encrypt(byte[] paddedMessage)
        {
            lock(SESSION_LOCK)
            {
                SessionRecord sessionRecord = _sessionStore.LoadSession(_remoteAddress);
                SessionState sessionState = sessionRecord.SessionState;
                ChainKey chainKey = sessionState.GetSenderChainKey();
                MessageKeys messageKeys = chainKey.GetMessageKeys();
                ECPublicKey senderEphemeral = sessionState.SenderRatchetKey;
                UInt32 previousCounter = sessionState.PreviousCounter;
                UInt32 sessionVersion = sessionState.GetSessionVersion();

                byte[] ciphertextBody = GetCiphertext(sessionVersion, messageKeys, paddedMessage);
                CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.MacKey,
                                                          senderEphemeral, chainKey.Index,
                                                          previousCounter, ciphertextBody,
                                                          sessionState.GetLocalIdentityKey(),
                                                          sessionState.GetRemoteIdentityKey());

                if(sessionState.HasUnacknowledgedPreKeyMessage())
                {
                    SessionState.UnacknowledgedPreKeyMessageItems items = sessionState.GetUnacknowledgedPreKeyMessageItems();
                    UInt32 localRegistrationId = sessionState.LocalRegistrationId;

                    ciphertextMessage = new PreKeyWhisperMessage(sessionVersion, localRegistrationId, items.PreKeyId,
                        items.SignedPreKeyId, items.BaseKey,
                        sessionState.GetLocalIdentityKey(),
                        (WhisperMessage)ciphertextMessage);
                }

                sessionState.SetSenderChainKey (chainKey.GetNextChainKey ());
                _sessionStore.StoreSession(_remoteAddress, sessionRecord);
                return ciphertextMessage;
            }
        }