From c4581462777ec3faf67e0075762c37648a5051b3 Mon Sep 17 00:00:00 2001 From: Gluzskiy Alexandr Date: Tue, 2 May 2017 13:24:46 +0300 Subject: jabber: omemo: basic implementation done omemo now works, done basic testing with miranda<>miranda but not ready for use, incompatible with gajim (due to upstream regression in libsignal ?, or some my own bugs), storage backend still missing few functions (which is crutical in some situations) fixed crash in libsignal-c crypto backend implementation few small changes in aes-gcm encrypt/decrypt implementation (auth tag verification still fails), but decryption looks ok more strict type check for device id to avoid overflow (it must be 4byte size unsigned integer) finished basic implementation of 4.7 4.5 return error and does not send anything if no sessions available --- protocols/JabberG/src/jabber_omemo.cpp | 179 +++++++++++++++++++++++--------- protocols/JabberG/src/jabber_proto.cpp | 8 +- protocols/JabberG/src/jabber_proto.h | 4 +- protocols/JabberG/src/jabber_thread.cpp | 2 +- 4 files changed, 141 insertions(+), 52 deletions(-) (limited to 'protocols/JabberG') diff --git a/protocols/JabberG/src/jabber_omemo.cpp b/protocols/JabberG/src/jabber_omemo.cpp index b4430cd6cf..9012488f6a 100755 --- a/protocols/JabberG/src/jabber_omemo.cpp +++ b/protocols/JabberG/src/jabber_omemo.cpp @@ -368,7 +368,7 @@ namespace omemo { complete: EVP_CIPHER_CTX_cleanup(&ctx); if (out_buf) { - free(out_buf); + mir_free(out_buf); } return result; } @@ -444,7 +444,7 @@ namespace omemo { { //TODO: more sanity checks //TODO: check and if necessary refresh prekeys - int id = proto->getDword("OmemoDeviceId", 0); + unsigned int id = proto->getDword("OmemoDeviceId", 0); if (id == 0) return true; ptrA buf(proto->getStringA("OmemoDevicePublicKey")); @@ -457,9 +457,9 @@ namespace omemo { return false; } - DWORD GetOwnDeviceId(CJabberProto *proto) + unsigned int GetOwnDeviceId(CJabberProto *proto) { - DWORD own_id = proto->getDword("OmemoDeviceId", 0); + unsigned int own_id = proto->getDword("OmemoDeviceId", 0); if (own_id == 0) { proto->OmemoInitDevice(); @@ -907,6 +907,7 @@ namespace omemo { mir_snprintf(setting_name, strlen("OmemoSignalPreKey_") + 31, "%s%u%d", "OmemoSignalPreKey_", GetOwnDeviceId(data->proto), pre_key_id); db_set_blob(0, data->proto->m_szModuleName, setting_name, record, (unsigned int)record_len); //TODO: check return value mir_free(setting_name); + //TODO: additionally store base64encoded public key for bundle return 0; } @@ -954,6 +955,7 @@ namespace omemo { mir_snprintf(setting_name, strlen("OmemoSignalPreKey_") + 31, "%s%u%d", "OmemoSignalPreKey_", GetOwnDeviceId(data->proto), pre_key_id); db_unset(0, data->proto->m_szModuleName, setting_name); mir_free(setting_name); + //TODO: additionally remove base64encoded public key for bundle return 0; } @@ -1009,6 +1011,7 @@ namespace omemo { mir_snprintf(setting_name, strlen("OmemoSignalSignedPreKey_") + 31, "%s%u%d", "OmemoSignalSignedPreKey_", GetOwnDeviceId(data->proto), signed_pre_key_id); db_set_blob(0, data->proto->m_szModuleName, setting_name, record, (unsigned int)record_len); //TODO: check return value mir_free(setting_name); + //TODO: additionally store base64encoded public key for bundle return 0; } @@ -1056,6 +1059,7 @@ namespace omemo { mir_snprintf(setting_name, strlen("OmemoSignalSignedPreKey_") + 31, "%s%u%d", "OmemoSignalSignedPreKey_", GetOwnDeviceId(data->proto), signed_pre_key_id); db_unset(0, data->proto->m_szModuleName, setting_name); mir_free(setting_name); + //TODO: additionally remove base64encoded public key for bundle return 0; } @@ -1149,7 +1153,7 @@ namespace omemo { return 0; } - int is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data) + int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/) { /** * Verify a remote client's identity key. @@ -1168,8 +1172,10 @@ namespace omemo { * @return 1 if trusted, 0 if untrusted, negative on failure */ + return 1; - signal_store_backend_user_data* data = (signal_store_backend_user_data*)user_data; + + /* signal_store_backend_user_data* data = (signal_store_backend_user_data*)user_data; char *id_buf = (char*)mir_alloc(address->name_len + sizeof(int32_t)); memcpy(id_buf, address->name, address->name_len); char *id_buf_ptr = id_buf; @@ -1204,7 +1210,7 @@ namespace omemo { db_free(&dbv); - return 1; + return 1; */ } //void(*destroy_func)(void *user_data); //use first one as we have nothing special to destroy @@ -1289,7 +1295,7 @@ namespace omemo { ec_public_key *prekey; curve_decode_point(&prekey, key_buf, key_buf_len, global_context); mir_free(pre_key_a); - mir_free(key_buf); //TODO: check this + mir_free(key_buf); int signed_pre_key_id_int = _wtoi(signed_pre_key_id); pre_key_a = mir_u2a(signed_pre_key_public); key_buf = (uint8_t*)mir_base64_decode(pre_key_a, &key_buf_len); @@ -1348,9 +1354,11 @@ void CJabberProto::OmemoInitDevice() omemo::RefreshDevice(this); } +DWORD JabberGetLastContactMessageTime(MCONTACT hContact); -void CJabberProto::OmemoHandleMessage(HXML node, MCONTACT hContact) +void CJabberProto::OmemoHandleMessage(HXML node, LPCTSTR jid, time_t msgTime) { + MCONTACT hContact = HContactFromJID(jid); if (!OmemoCheckSession(hContact)) //TODO: something better here return; HXML header_node = XmlGetChild(node, L"header"); @@ -1359,8 +1367,8 @@ void CJabberProto::OmemoHandleMessage(HXML node, MCONTACT hContact) HXML payload_node = XmlGetChild(node, L"payload"); if (!payload_node) return; //this is "KeyTransportElement" which is currently unused - LPCTSTR payload_base64 = XmlGetText(payload_node); - if (!payload_base64) + LPCTSTR payload_base64w = XmlGetText(payload_node); + if (!payload_base64w) return; LPCTSTR iv_base64 = XmlGetText(XmlGetChild(header_node, L"iv")); if (!iv_base64) @@ -1382,7 +1390,10 @@ void CJabberProto::OmemoHandleMessage(HXML node, MCONTACT hContact) LPCTSTR dev_id = xmlGetAttrValue(key_node, L"rid"); DWORD dev_id_int = _wtoi(dev_id); if (dev_id_int == own_id) + { encrypted_key_base64 = XmlGetText(key_node); + break; + } } if (!encrypted_key_base64) return; //node does not contain key for our device @@ -1432,17 +1443,58 @@ void CJabberProto::OmemoHandleMessage(HXML node, MCONTACT hContact) break; } } -/* int dec_success = 0; - const EVP_CIPHER *cipher = EVP_aes_128_gcm(); - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); */ + char *out = nullptr; + { + int dec_success = 0; + unsigned int payload_len = 0; + int outl = 0, round_len = 0; + char *payload_base64 = mir_u2a(payload_base64w); + unsigned char *payload = (unsigned char*)mir_base64_decode(payload_base64, &payload_len); + mir_free(payload_base64); + unsigned char tag[16]; + out = (char*)mir_alloc(payload_len + 1); //TODO: check this + const EVP_CIPHER *cipher = EVP_aes_128_gcm(); + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit(ctx, cipher, signal_buffer_data(decrypted_key), iv); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag); + //EVP_DecryptInit(ctx, NULL, signal_buffer_data(decrypted_key), iv); + //EVP_DecryptUpdate(ctx, NULL, &howmany, AAD, aad_len); + + for (;;) + { + EVP_DecryptUpdate(ctx, (unsigned char*)out + outl, &round_len, payload + outl, (payload_len >= 128) ? 128 : payload_len); + outl += round_len; + if (outl >= (int)payload_len - 128) + break; + } + EVP_DecryptUpdate(ctx, (unsigned char*)out + outl, &round_len, payload + outl, payload_len - outl); + outl += round_len; + out[outl] = 0; + mir_free(payload); + dec_success = EVP_DecryptFinal(ctx, tag, &round_len); + EVP_CIPHER_CTX_free(ctx); + if (!dec_success) + { + //return; + //TODO: handle decryption failure + } + } + + time_t now = time(NULL); + if (!msgTime) + msgTime = now; + + if (m_options.FixIncorrectTimestamps && (msgTime > now || (msgTime < (time_t)JabberGetLastContactMessageTime(hContact)))) + msgTime = now; -/* PROTORECVEVENT recv = { 0 }; + pResourceStatus pFromResource(ResourceInfoFromJID(jid)); + PROTORECVEVENT recv = { 0 }; recv.timestamp = (DWORD)msgTime; - recv.szMessage = buf; + recv.szMessage = mir_utf8encode(out); recv.lParam = (LPARAM)((pFromResource != NULL && m_options.EnableRemoteControl) ? pFromResource->m_tszResourceName : 0); - ProtoChainRecvMsg(hContact, &recv); */ - + ProtoChainRecvMsg(hContact, &recv); + mir_free(out); } void CJabberProto::OmemoHandleDeviceList(HXML node) @@ -1468,14 +1520,14 @@ void CJabberProto::OmemoHandleDeviceList(HXML node) { //check if our device exist bool own_device_listed = false; - DWORD own_id = omemo::GetOwnDeviceId(this); + unsigned int own_id = omemo::GetOwnDeviceId(this); char setting_name[64]; HXML list_item; int i = 0; for (int p = 1; (list_item = XmlGetNthChild(node, L"device", p)) != NULL; p++, i++) { current_id_str = xmlGetAttrValue(list_item, L"id"); - current_id = _wtoi(current_id_str); + current_id = _wtol(current_id_str); if (current_id == own_id) own_device_listed = true; mir_snprintf(setting_name, "OmemoDeviceId%d", i); @@ -1503,7 +1555,7 @@ void CJabberProto::OmemoHandleDeviceList(HXML node) for (int p = 1; (list_item = XmlGetNthChild(node, L"device", p)) != NULL; p++, i++) { current_id_str = xmlGetAttrValue(list_item, L"id"); - current_id = _wtoi(current_id_str); + current_id = _wtol(current_id_str); mir_snprintf(setting_name, "OmemoDeviceId%d", i); setDword(hContact, setting_name, current_id); } @@ -1526,7 +1578,7 @@ void CJabberProto::OmemoHandleDeviceList(HXML node) void CJabberProto::OmemoAnnounceDevice() { // check "OmemoDeviceId%d" for own id and send updated list if not exist - DWORD own_id = omemo::GetOwnDeviceId(this); + unsigned int own_id = omemo::GetOwnDeviceId(this); char setting_name[64]; for (int i = 0;; ++i) { @@ -1659,7 +1711,7 @@ bool CJabberProto::OmemoCheckSession(MCONTACT hContact) bool pending_check = false; char setting_name[64], setting_name2[64]; - DWORD id = 0; + unsigned int id = 0; bool checked = false; int i = 0; @@ -1704,11 +1756,40 @@ void CJabberProto::OmemoOnIqResultGetBundle(HXML iqNode, CJabberIqInfo *pInfo) if (iqNode == NULL) return; + LPCTSTR jid = XmlGetAttrValue(iqNode, L"from"); + MCONTACT hContact = HContactFromJID(jid); + const wchar_t *type = XmlGetAttrValue(iqNode, L"type"); if (mir_wstrcmp(type, L"result")) + { + //failed to get bundle, do not try to build session + unsigned int *dev_id = (unsigned int*)pInfo->GetUserData(); + char setting_name[64], setting_name2[64]; + DWORD id = 0; + int i = 0; + + mir_snprintf(setting_name, "OmemoDeviceId%d", i); + mir_snprintf(setting_name2, "%sChecked", setting_name); + db_set_resident(m_szModuleName, setting_name2); + id = getDword(hContact, setting_name, 0); + while (id) + { + if (id == *dev_id) + { + setByte(hContact, setting_name2, 1); + break; + } + i++; + mir_snprintf(setting_name, "OmemoDeviceId%d", i); + mir_snprintf(setting_name2, "%sChecked", setting_name); + db_set_resident(m_szModuleName, setting_name2); + id = getDword(hContact, setting_name, 0); + } + return; + } - LPCTSTR jid = XmlGetAttrValue(iqNode, L"from"); + HXML pubsub = XmlGetChildByTag(iqNode, L"pubsub", L"xmlns", L"http://jabber.org/protocol/pubsub"); if (!pubsub) @@ -1756,7 +1837,7 @@ void CJabberProto::OmemoOnIqResultGetBundle(HXML iqNode, CJabberIqInfo *pInfo) if (!preKeyId) return; - MCONTACT hContact = HContactFromJID(jid); + if (!omemo::create_session_store(hContact, device_id, this)) return; //failed to create session store @@ -1764,50 +1845,50 @@ void CJabberProto::OmemoOnIqResultGetBundle(HXML iqNode, CJabberIqInfo *pInfo) if (!omemo::build_session(hContact, jid, device_id, preKeyId, preKeyPublic, signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey)) return; //failed to build signal(omemo) session - unsigned int *dev_id = (unsigned int*)pInfo->GetUserData(); - char setting_name[64], setting_name2[64]; - DWORD id = 0; -// bool checked = false; - int i = 0; - - mir_snprintf(setting_name, "OmemoDeviceId%d", i); - mir_snprintf(setting_name2, "%sChecked", setting_name); - db_set_resident(m_szModuleName, setting_name2); - id = getDword(hContact, setting_name, 0); - while (id) { - if (id == *dev_id) - { - setByte(hContact, setting_name2, 1); - break; - } - i++; + unsigned int *dev_id = (unsigned int*)pInfo->GetUserData(); + char setting_name[64], setting_name2[64]; + DWORD id = 0; + int i = 0; + mir_snprintf(setting_name, "OmemoDeviceId%d", i); mir_snprintf(setting_name2, "%sChecked", setting_name); db_set_resident(m_szModuleName, setting_name2); id = getDword(hContact, setting_name, 0); + while (id) + { + if (id == *dev_id) + { + setByte(hContact, setting_name2, 1); + break; + } + i++; + mir_snprintf(setting_name, "OmemoDeviceId%d", i); + mir_snprintf(setting_name2, "%sChecked", setting_name); + db_set_resident(m_szModuleName, setting_name2); + id = getDword(hContact, setting_name, 0); + } } } -void CJabberProto::OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MCONTACT hContact) +unsigned int CJabberProto::OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MCONTACT hContact) { const EVP_CIPHER *cipher = EVP_aes_128_gcm(); - unsigned char key[16], iv[12], tag[16], aad[48]; + unsigned char key[16], iv[12], tag[16]/*, aad[48]*/; Utils_GetRandom(key, _countof_portable(key)); Utils_GetRandom(iv, _countof_portable(iv)); Utils_GetRandom(tag, _countof_portable(tag)); - Utils_GetRandom(aad, _countof_portable(aad)); + //Utils_GetRandom(aad, _countof_portable(aad)); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, _countof_portable(iv), NULL); EVP_EncryptInit(ctx, cipher, key, iv); char *in = mir_u2a(msg_text), *out; const size_t inl = strlen(in); int tmp_len = 0, outl; - EVP_EncryptUpdate(ctx, NULL, &outl, aad, _countof_portable(aad)); + //EVP_EncryptUpdate(ctx, NULL, &outl, aad, _countof_portable(aad)); out = (char*)mir_alloc(inl + _countof_portable(key) - 1); - //TODO: add gcm tag for (;;) { EVP_EncryptUpdate(ctx, (unsigned char*)(out + tmp_len), &outl, (unsigned char*)(in + tmp_len), (int)(inl - tmp_len)); @@ -1830,7 +1911,7 @@ void CJabberProto::OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MC mir_free(payload_base64w); HXML header = encrypted << XCHILD(L"header"); header << XATTRI64(L"sid", omemo::GetOwnDeviceId(this)); - + unsigned int session_count = 0; for (std::map::iterator i = omemo::sessions_internal[hContact].begin(), end = omemo::sessions_internal[hContact].end(); i != end; i++) { if (!i->second.cipher) @@ -1853,6 +1934,7 @@ void CJabberProto::OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MC xmlSetText(key_node, key_base64w); mir_free(key_base64w); SIGNAL_UNREF(encrypted_key); + session_count++; } } HXML iv_node = header << XCHILD(L"iv"); @@ -1862,6 +1944,7 @@ void CJabberProto::OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MC xmlSetText(iv_node, iv_base64w); mir_free(iv_base64w); msg << XCHILDNS(L"store", L"urn:xmpp:hints"); + return session_count; } bool CJabberProto::OmemoIsEnabled(MCONTACT /*hContact*/) { diff --git a/protocols/JabberG/src/jabber_proto.cpp b/protocols/JabberG/src/jabber_proto.cpp index 4635288ee8..f5df6dbc9a 100755 --- a/protocols/JabberG/src/jabber_proto.cpp +++ b/protocols/JabberG/src/jabber_proto.cpp @@ -964,7 +964,13 @@ int __cdecl CJabberProto::SendMsg(MCONTACT hContact, int, const char* pszSrc) if(m_options.UseOMEMO && OmemoIsEnabled(hContact) && !mir_wstrcmp(msgType, L"chat")) //omemo enabled in options, omemo enabled for contact { - OmemoEncryptMessage(m, msg, hContact); + //TODO: check if message encrypted for at least one session and return error if not + if (!OmemoEncryptMessage(m, msg, hContact)) + { + TFakeAckParams *param = new TFakeAckParams(hContact, Translate("OMEMO no valid sessions exists")); + ForkThread(&CJabberProto::SendMessageAckThread, param); + return 0; + } } else { diff --git a/protocols/JabberG/src/jabber_proto.h b/protocols/JabberG/src/jabber_proto.h index 3b164a37d1..9606544a22 100755 --- a/protocols/JabberG/src/jabber_proto.h +++ b/protocols/JabberG/src/jabber_proto.h @@ -337,14 +337,14 @@ struct CJabberProto : public PROTO, public IJabberInterface void DeleteMucListItem(JABBER_MUC_JIDLIST_INFO* jidListInfo, const wchar_t* jid); //---- jabber_omemo.cpp -------------------------------------------------------------- - void OmemoHandleMessage(HXML node, MCONTACT hContact); + void OmemoHandleMessage(HXML node, LPCTSTR jid, time_t msgTime); void OmemoHandleDeviceList(HXML node); void OmemoInitDevice(); void OmemoAnnounceDevice(); void OmemoSendBundle(); void OmemoPublishNodes(); bool OmemoCheckSession(MCONTACT hContact); - void OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MCONTACT hContact); + unsigned int OmemoEncryptMessage(XmlNode &msg, const wchar_t *msg_text, MCONTACT hContact); bool OmemoIsEnabled(MCONTACT hContact); void OmemoOnIqResultGetBundle(HXML iqNode, CJabberIqInfo *pInfo); diff --git a/protocols/JabberG/src/jabber_thread.cpp b/protocols/JabberG/src/jabber_thread.cpp index 4b8bca9b1e..9f6873e487 100755 --- a/protocols/JabberG/src/jabber_thread.cpp +++ b/protocols/JabberG/src/jabber_thread.cpp @@ -1206,7 +1206,7 @@ void CJabberProto::OnProcessMessage(HXML node, ThreadData *info) LPCTSTR jid = xmlGetAttrValue(node, L"from"); if (jid) { - OmemoHandleMessage(xNode, HContactFromJID(jid)); + OmemoHandleMessage(xNode, jid, msgTime); continue; } } -- cgit v1.2.3