public void Test_OmemoKeyExchangeMessage() { Random rand = new Random(); byte[] hmac = new byte[16]; rand.NextBytes(hmac); byte[] sessionKey = new byte[32]; rand.NextBytes(sessionKey); IdentityKeyPairModel identityKey = KeyHelper.GenerateIdentityKeyPair(); SignedPreKeyModel signedPreKey = KeyHelper.GenerateSignedPreKey(0, identityKey.privKey); OmemoSessionModel refOmemoSession = new OmemoSessionModel(new Bundle() { identityKey = identityKey.pubKey, preKeys = KeyHelper.GeneratePreKeys(0, 10), preKeySignature = signedPreKey.signature, signedPreKey = signedPreKey.preKey.pubKey, signedPreKeyId = signedPreKey.preKey.keyId }, 0, KeyHelper.GenerateIdentityKeyPair()) { ek = KeyHelper.GenerateKeyPair().pubKey }; OmemoMessage refOmemoMessage = new OmemoMessage(refOmemoSession); OmemoAuthenticatedMessage refOmemoAuthenticatedMessage = new OmemoAuthenticatedMessage(new byte[16], refOmemoMessage.ToByteArray()); OmemoKeyExchangeMessage refOmemoKeyExchangeMessage = new OmemoKeyExchangeMessage((uint)rand.Next(), (uint)rand.Next(), KeyHelper.GenerateKeyPair().pubKey, KeyHelper.GenerateKeyPair().pubKey, refOmemoAuthenticatedMessage); byte[] data = refOmemoKeyExchangeMessage.ToByteArray(); Assert.IsTrue(data.Length > 0); OmemoKeyExchangeMessage omemoKeyExchangeMessage = new OmemoKeyExchangeMessage(data); Assert.IsTrue(omemoKeyExchangeMessage.Equals(refOmemoKeyExchangeMessage)); }
public OmemoMessage(OmemoSessionModel session) { N = session.nS; PN = session.pn; DH = session.dhS.pubKey; cipherText = null; }
public void StoreSession(OmemoProtocolAddress address, OmemoSessionModel session) { OmemoDeviceModel device; if (string.Equals(address.BARE_JID, dbAccount.bareJid)) { device = dbAccount.omemoInfo.devices.Where(d => address.DEVICE_ID == d.deviceId).FirstOrDefault(); } else { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(dbAccount.bareJid, address.BARE_JID, semaLock); } if (chat is null) { throw new InvalidOperationException("Failed to store session. Chat '" + address.BARE_JID + "' does not exist."); } device = chat.omemoInfo.devices.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault(); } if (device is null) { throw new InvalidOperationException("Failed to store session. Device '" + address.ToString() + "' does not exist."); } if (device.session is null) { device.session = session; } bool newSession = device.session.id != session.id; OmemoSessionModel oldSession = null; if (newSession) { oldSession = device.session; device.session = session; } device.Update(); // Remove the old session: if (newSession) { using (MainDbContext ctx = new MainDbContext()) { ctx.Remove(oldSession); } } }
private async Task buildSessionForDevicesAsync(OmemoDeviceGroup deviceGroup, IList <OmemoProtocolAddress> devices) { if (devices.Count <= 0) { return; } OmemoProtocolAddress device = devices[0]; devices.RemoveAt(0); OmemoFingerprint fingerprint = OMEMO_HELPER.OMEMO_STORAGE.LoadFingerprint(device); // Check if there exists already a session for this device: OmemoSessionModel session = OMEMO_HELPER.OMEMO_STORAGE.LoadSession(device); if (session is null) { // Try to build a new session by requesting the devices bundle information: OmemoBundleInformationResultMessage bundleMsg = await requestBundleInformationAsync(device); if (!(bundleMsg is null) && !(bundleMsg.BUNDLE_INFO.bundle is null)) { int preKeyIndex = bundleMsg.BUNDLE_INFO.bundle.GetRandomPreKeyIndex(); session = new OmemoSessionModel(bundleMsg.BUNDLE_INFO.bundle, preKeyIndex, CONNECTION.account.omemoIdentityKey); // Validate fingerprints: if (fingerprint is null) { fingerprint = new OmemoFingerprint(bundleMsg.BUNDLE_INFO.bundle.identityKey, device); OMEMO_HELPER.OMEMO_STORAGE.StoreFingerprint(fingerprint); } else { OmemoFingerprint receivedFingerprint = new OmemoFingerprint(bundleMsg.BUNDLE_INFO.bundle.identityKey, device); // Make sure the fingerprint did not change or somebody is performing an attack: if (!fingerprint.checkIdentityKey(receivedFingerprint.IDENTITY_KEY)) { Logger.Warn("[OmemoSessionBuildHelper] Unable to establish session with " + device.ToString() + " - other fingerprint received than stored locally."); await buildSessionForDevicesAsync(deviceGroup, devices); return; } } } else { Logger.Warn("[OmemoSessionBuildHelper] Unable to establish session with: " + device.ToString()); } }
/// <summary> /// Tries to decrypt the message and returns true on success. /// </summary> /// <param name="decryptCtx">The <see cref="OmemoDecryptionContext"/> containing all necessary information for decrypting the message.</param> /// <param name="storage">An instance of the <see cref="IOmemoStorage"/> interface.</param> public void decrypt(OmemoDecryptionContext decryptCtx) { try { // Check if the message has been encrypted for us: OmemoKeys omemoKeys = keys.Where(k => string.Equals(k.BARE_JID, decryptCtx.RECEIVER_ADDRESS.BARE_JID)).FirstOrDefault(); if (keys is null) { throw new NotForDeviceException("Failed to decrypt message. Not encrypted for JID."); } decryptCtx.key = omemoKeys.KEYS?.Where(k => k.DEVICE_ID == decryptCtx.RECEIVER_ADDRESS.DEVICE_ID).FirstOrDefault(); if (decryptCtx.key is null) { throw new NotForDeviceException("Failed to decrypt message. Not encrypted for device."); } decryptCtx.senderAddress = new OmemoProtocolAddress(Utils.getBareJidFromFullJid(FROM), SID); // Check if the PreKey and SignedPreKey are still available in case the massage is a key exchange message: byte[] data = Convert.FromBase64String(decryptCtx.key.BASE64_PAYLOAD); if (decryptCtx.key.KEY_EXCHANGE) { decryptCtx.keyExchangeMsg = new OmemoKeyExchangeMessage(data); // Check if there already exists a session. In case yes, compare the ephemeral public key stored in the Double Ratchet: OmemoSessionModel oldSession = decryptCtx.STORAGE.LoadSession(decryptCtx.senderAddress); if (!(oldSession is null) && oldSession.ek.Equals(decryptCtx.keyExchangeMsg.EK)) { // Process only the auth message: decryptCtx.authMsg = decryptCtx.keyExchangeMsg.MESSAGE; decryptCtx.keyExchange = false; Logger.Info($"Received an {nameof(OmemoKeyExchangeMessage)} for a session from '{decryptCtx.senderAddress.ToString()}' that already exists. This could be because we have not yet responded with an own message to the sender."); } else { decryptCtx.keyExchange = true; decryptCtx.authMsg = decryptCtx.keyExchangeMsg.MESSAGE; if (decryptCtx.keyExchangeMsg.SPK_ID != decryptCtx.RECEIVER_SIGNED_PRE_KEY.preKey.keyId) { throw new NotForDeviceException("Failed to decrypt message. Signed PreKey with id " + decryptCtx.keyExchangeMsg.SPK_ID + " not available any more."); } decryptCtx.usedPreKey = decryptCtx.RECEIVER_PRE_KEYS.Where(k => k.keyId == decryptCtx.keyExchangeMsg.PK_ID).FirstOrDefault(); if (decryptCtx.usedPreKey is null) { throw new NotForDeviceException("Failed to decrypt message. PreKey with id " + decryptCtx.keyExchangeMsg.PK_ID + " not available any more."); } } } else { decryptCtx.authMsg = new OmemoAuthenticatedMessage(data); } // Content can be null in case we have a pure keys exchange message: byte[] contentEnc = null; if (!string.IsNullOrEmpty(BASE_64_PAYLOAD)) { contentEnc = Convert.FromBase64String(BASE_64_PAYLOAD); } else { IS_PURE_KEY_EXCHANGE_MESSAGE = true; } // Check the senders fingerprint and handle new devices: ValidateSender(decryptCtx); byte[] contentDec = decryptContent(contentEnc, decryptCtx); if (!IS_PURE_KEY_EXCHANGE_MESSAGE) { string contentStr = Encoding.UTF8.GetString(contentDec); XmlNode contentNode = getContentNode(contentStr); parseContentNode(contentNode); } // In case nothing went wrong, store the session: decryptCtx.STORAGE.StoreSession(decryptCtx.senderAddress, decryptCtx.session); ENCRYPTED = false; }
public void StoreSession(OmemoProtocolAddress address, OmemoSessionModel session) { SESSIONS[address] = session; }
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- public OmemoDevice(uint deviceId, OmemoSessionModel session) { DEVICE_ID = deviceId; SESSION = session; }
/// <summary> /// Tries to decrypt the message and returns true on success. /// </summary> /// <param name="decryptCtx">The <see cref="OmemoDecryptionContext"/> containing all necessary information for decrypting the message.</param> /// <param name="storage">An instance of the <see cref="IOmemoStorage"/> interface.</param> public void decrypt(OmemoDecryptionContext decryptCtx) { try { // Check if the message has been encrypted for us: OmemoKeys omemoKeys = keys.Where(k => string.Equals(k.BARE_JID, decryptCtx.RECEIVER_ADDRESS.BARE_JID)).FirstOrDefault(); if (omemoKeys is null) { throw new NotForDeviceException("Failed to decrypt message. Not encrypted for JID."); } decryptCtx.key = omemoKeys.KEYS?.Where(k => k.DEVICE_ID == decryptCtx.RECEIVER_ADDRESS.DEVICE_ID).FirstOrDefault(); if (decryptCtx.key is null) { throw new NotForDeviceException("Failed to decrypt message. Not encrypted for device."); } decryptCtx.senderAddress = new OmemoProtocolAddress(Utils.getBareJidFromFullJid(FROM), SID); // Check if the PreKey and SignedPreKey are still available in case the massage is a key exchange message: byte[] data = Convert.FromBase64String(decryptCtx.key.BASE64_PAYLOAD); if (decryptCtx.key.KEY_EXCHANGE) { decryptCtx.keyExchangeMsg = new OmemoKeyExchangeMessage(data); // Check if there already exists a session. In case yes, compare the ephemeral public key stored in the Double Ratchet: OmemoSessionModel oldSession = decryptCtx.STORAGE.LoadSession(decryptCtx.senderAddress); if (!(oldSession is null) && oldSession.ek.Equals(decryptCtx.keyExchangeMsg.EK)) { // Process only the auth message: decryptCtx.authMsg = decryptCtx.keyExchangeMsg.MESSAGE; decryptCtx.keyExchange = false; Logger.Info($"Received an {nameof(OmemoKeyExchangeMessage)} for a session from '{decryptCtx.senderAddress}' that already exists. This could be because we have not yet responded with an own message to the sender."); } else { decryptCtx.keyExchange = true; decryptCtx.authMsg = decryptCtx.keyExchangeMsg.MESSAGE; if (decryptCtx.keyExchangeMsg.SPK_ID != decryptCtx.RECEIVER_SIGNED_PRE_KEY.preKey.keyId) { throw new NotForDeviceException($"Failed to decrypt message. Signed PreKey with id {decryptCtx.keyExchangeMsg.SPK_ID} not available any more."); } decryptCtx.usedPreKey = decryptCtx.RECEIVER_PRE_KEYS.Where(k => k.keyId == decryptCtx.keyExchangeMsg.PK_ID).FirstOrDefault(); if (decryptCtx.usedPreKey is null) { throw new NotForDeviceException($"Failed to decrypt message. PreKey with id {decryptCtx.keyExchangeMsg.PK_ID} not available any more."); } } } else { decryptCtx.authMsg = new OmemoAuthenticatedMessage(data); } // Check the senders fingerprint and handle new devices: validateSender(decryptCtx); // Load the existing OMEMO session and create a new one if the message is a key exchange message: loadSession(decryptCtx); if (!IS_PURE_KEY_EXCHANGE_MESSAGE) { // Content can be null in case we have a pure keys exchange message: byte[] contentEnc = Convert.FromBase64String(BASE_64_PAYLOAD); byte[] contentDec = decryptContent(contentEnc, decryptCtx); string contentStr = Encoding.UTF8.GetString(contentDec); XmlNode envelopeNode = getEnvelopeNode(contentStr); parseEnvelopeNode(envelopeNode); } else { // For pure key exchange messages we decrypt 32 zero bytes instead the (key || HMAC) combination: byte[] dummyKeyHmac = decryptKeyHmac(decryptCtx); if (!dummyKeyHmac.SequenceEqual(new byte[32])) { throw new OmemoException($"Expected to decrypt 32 zero bytes from pure key exchange messages, but received: {CryptoUtils.ToHexString(dummyKeyHmac)}"); } } // In case nothing went wrong, store the session: if (decryptCtx.session.state == SessionState.SEND) { decryptCtx.session.state = SessionState.READY; } decryptCtx.STORAGE.StoreSession(decryptCtx.senderAddress, decryptCtx.session); ENCRYPTED = false; }