Instant Messaging Encryption Engine

Last modified by Sylvain Berfini on 2022/01/06 10:49

Instant Messaging Encryption Engine

Liblinphone allows developers to make their own encryption for exchanging messages through the Instant Messaging Encryption Engine (IMEE in the following).

How does it work

LinphoneCore can contain an object of type LinphoneImEncryptionEngine that has a few callbacks that can be implemented and set at runtime by an application.

Here are the list of the supported callbacks (you can find it along with each callback prototype in the im_encryption_engine header in the include folder of the library):

  • Process incoming message: called when a message is received by Linphone ;
  • Process outgoing message: called when a message is about to be sent by Linphone ;
  • Is encryption enabled for file transfer: whether or not files sent by chat will be encrypted (see File Transfer);
  • Generate file transfer key: if encryption for file transfer is enabled, this let you generate the encryption key;
  • Process file uploading: called (one time or more) when Linphone is uploading a file to the file sharing server ;
  • Process file downloading: called (one time or more) when Linphone is downloading a file from the file sharing server.

How to use it

In Liblinphone, we use it for our custom chat encryption LIME (Linphone Instant Messaging Encryption) which is based on ZRTP.

Here's the code to enable the IMEE:

LinphoneImEncryptionEngine *imee = linphone_im_encryption_engine_new(lc);
LinphoneImEncryptionEngineCbs *cbs = linphone_im_encryption_engine_get_callbacks(imee);

linphone_im_encryption_engine_cbs_set_process_incoming_message(cbs, lime_im_encryption_engine_process_incoming_message_cb);
linphone_im_encryption_engine_cbs_set_process_downloading_file(cbs, lime_im_encryption_engine_process_downloading_file_cb);
linphone_im_encryption_engine_cbs_set_process_outgoing_message(cbs, lime_im_encryption_engine_process_outgoing_message_cb);
linphone_im_encryption_engine_cbs_set_process_uploading_file(cbs, lime_im_encryption_engine_process_uploading_file_cb);
linphone_im_encryption_engine_cbs_set_is_encryption_enabled_for_file_transfer(cbs, lime_im_encryption_engine_is_file_encryption_enabled_cb);
linphone_im_encryption_engine_cbs_set_generate_file_transfer_key(cbs, lime_im_encryption_engine_generate_file_transfer_key_cb);

linphone_core_set_im_encryption_engine(lc, imee);
linphone_im_encryption_engine_unref(imee);

Each process callback must return an integer which must be either -1 if encryption is skipped, 0 if it was successful or an error code > 0. When an error code is returned, a SIP message matching the error code will be sent to the remote party (for example 500 internal error).

Each callback will also be given at least the LinphoneImEncryptionEngine object and the LinphoneChatMessage. From the LinphoneChatMessage, you can access multiple relevant information such as content type, the message itself, the LinphoneChatRoom, the LinphoneCore etc... and in LinphoneImEncryptionEngine you can store whatever you want in the user_data field.

Keep in mind that if you change the content type when encrypting the message, you must change it back to text/plain when you decrypt it!

File transfer callbacks give directly in parameters the buffer to encrypt/decrypt, the offset of the buffer in the file, the size of the buffer and an empty buffer of the same size for you to store the result of the encryption/decryption.

In the process_uploading_file_cb callback, the size parameter is in/out so you can return a buffer smaller than the original one, but it can't be bigger!

When encrypting a file, you can generate the key in the generate_file_transfer_key callback and store it the LinphoneContent structure inside the LinphoneChatMessage, so you can access it in the process_downloading_file callback on the other side.

linphone_content_set_key(msg->file_transfer_information, keyBuffer, keySize); /* key is duplicated in the content private structure */

Sample code

The message_tester.c file in tester/ contains a simple test that does a XOR encryption using the IMEE:

char* xor(char* message, char* key) {
size_t messagelen = strlen(message);
size_t keylen = strlen(key);
char* encrypted = (char *)ms_malloc(messagelen+1);

int i;
for(i = 0; i < (int)messagelen; i++) {
  encrypted[i] = message[i] ^ key[i % keylen];
 }
 encrypted[messagelen] = '\0';

return encrypted;
}

int xor_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
char *new_content_type = "cipher/xor";
if (msg->content_type) {
 if (strcmp(msg->content_type, new_content_type) == 0) {
   msg->message = xor(msg->message, "SuperSecretXorKey");
   msg->content_type = ms_strdup("text/plain");
  return 0;
  } else if (strcmp(msg->content_type, "text/plain") == 0) {
  return -1; // Not encrypted, nothing to do
 } else {
  return 488; // Not acceptable
 }
 }
return 500;
}

int xor_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
char *new_content_type = "cipher/xor";
 msg->message = xor(msg->message, "SuperSecretXorKey");
 msg->content_type = ms_strdup(new_content_type);
return 0;
}

void im_encryption_engine_xor(void) {
 LinphoneChatMessage *chat_msg = NULL;
 LinphoneChatRoom* chat_room = NULL;
 LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc");
 LinphoneImEncryptionEngine *marie_imee = linphone_im_encryption_engine_new(marie->lc);
 LinphoneImEncryptionEngineCbs *marie_cbs = linphone_im_encryption_engine_get_callbacks(marie_imee);
 LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc");
 LinphoneImEncryptionEngine *pauline_imee = linphone_im_encryption_engine_new(pauline->lc);
 LinphoneImEncryptionEngineCbs *pauline_cbs = linphone_im_encryption_engine_get_callbacks(pauline_imee);

 linphone_im_encryption_engine_cbs_set_process_incoming_message(marie_cbs, xor_im_encryption_engine_process_incoming_message_cb);
 linphone_im_encryption_engine_cbs_set_process_outgoing_message(marie_cbs, xor_im_encryption_engine_process_outgoing_message_cb);
 linphone_im_encryption_engine_cbs_set_process_incoming_message(pauline_cbs, xor_im_encryption_engine_process_incoming_message_cb);
 linphone_im_encryption_engine_cbs_set_process_outgoing_message(pauline_cbs, xor_im_encryption_engine_process_outgoing_message_cb);

 linphone_core_set_im_encryption_engine(marie->lc, marie_imee);
 linphone_core_set_im_encryption_engine(pauline->lc, pauline_imee);

 chat_room = linphone_core_get_chat_room(pauline->lc, marie->identity);
 chat_msg = linphone_chat_room_create_message(chat_room, "Bla bla bla bla");
 linphone_chat_room_send_chat_message(chat_room, chat_msg);
 BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1));
 BC_ASSERT_PTR_NOT_NULL(marie->stat.last_received_chat_message);
if (marie->stat.last_received_chat_message) {
  BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), "Bla bla bla bla");
 }
 BC_ASSERT_PTR_NOT_NULL(linphone_core_get_chat_room(marie->lc,pauline->identity));

 linphone_im_encryption_engine_unref(marie_imee);
 linphone_im_encryption_engine_unref(pauline_imee);
 linphone_core_manager_destroy(marie);
 linphone_core_manager_destroy(pauline);
}