diff --git a/build/android/Android.mk b/build/android/Android.mk index cabeccd8f..6a55fc92b 100755 --- a/build/android/Android.mk +++ b/build/android/Android.mk @@ -58,6 +58,7 @@ LOCAL_SRC_FILES := \ linphone_tunnel_config.c \ localplayer.c \ lpc2xml.c \ + lime.c \ lpconfig.c \ message_storage.c \ misc.c \ @@ -118,7 +119,8 @@ LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/../../belle-sip/include \ $(LOCAL_PATH)/../../../gen \ $(LOCAL_PATH)/../../externals/libxml2/include \ - $(LOCAL_PATH)/../../externals/build/libxml2 + $(LOCAL_PATH)/../../externals/build/libxml2 \ + $(LOCAL_PATH)/../../externals/polarssl/include LOCAL_LDLIBS += -llog -ldl -lz diff --git a/configure.ac b/configure.ac index 706f502ae..21a3d9ff4 100644 --- a/configure.ac +++ b/configure.ac @@ -76,7 +76,7 @@ case $target in LIBS="$LIBS -framework CoreFoundation -framework AudioToolbox -framework CoreAudio -framework Foundation -framework QuartzCore -framework OpenGLES -framework UIKit -framework AVFoundation" ios_found=yes ;; - x86_64-apple-darwin|i686-apple-darwin*) + x86_64-apple-darwin*|i686-apple-darwin*) MSPLUGINS_CFLAGS="" dnl use macport installation AS_IF([test -d "/opt/local/share/aclocal"], [ACLOCAL_MACOS_FLAGS="-I /opt/local/share/aclocal"]) diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 5a347f3d2..969d39794 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -65,6 +65,7 @@ liblinphone_la_SOURCES=\ linphonecore_utils.h \ localplayer.c \ lpc2xml.c \ + lime.c \ lpconfig.c lpconfig.h \ lsd.c \ message_storage.c \ diff --git a/coreapi/bellesip_sal/sal_op_message.c b/coreapi/bellesip_sal/sal_op_message.c index 017845833..e44e15823 100644 --- a/coreapi/bellesip_sal/sal_op_message.c +++ b/coreapi/bellesip_sal/sal_op_message.c @@ -18,6 +18,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sal_impl.h" +#include "linphonecore.h" +#include "private.h" +#include "lime.h" +#include + static void process_error( SalOp* op) { if (op->dir == SalOpDirOutgoing) { op->base.root->callbacks.text_delivery_update(op, SalTextDeliveryFailed); @@ -56,14 +61,22 @@ static void process_response_event(void *op_base, const belle_sip_response_event } static bool_t is_rcs_filetransfer(belle_sip_header_content_type_t* content_type) { - return strcmp("application",belle_sip_header_content_type_get_type(content_type))==0 - && strcmp("vnd.gsma.rcs-ft-http+xml",belle_sip_header_content_type_get_subtype(content_type))==0; + return (strcmp("application",belle_sip_header_content_type_get_type(content_type))==0) + && ((strcmp("vnd.gsma.rcs-ft-http+xml",belle_sip_header_content_type_get_subtype(content_type))==0) || (strcmp("cipher.vnd.gsma.rcs-ft-http+xml",belle_sip_header_content_type_get_subtype(content_type))==0)); } static bool_t is_plain_text(belle_sip_header_content_type_t* content_type) { return strcmp("text",belle_sip_header_content_type_get_type(content_type))==0 && strcmp("plain",belle_sip_header_content_type_get_subtype(content_type))==0; } + +static bool_t is_cipher_xml(belle_sip_header_content_type_t* content_type) { + return (strcmp("xml",belle_sip_header_content_type_get_type(content_type))==0 + && strcmp("cipher",belle_sip_header_content_type_get_subtype(content_type))==0) + + || (strcmp("application",belle_sip_header_content_type_get_type(content_type))==0 + && strcmp("cipher.vnd.gsma.rcs-ft-http+xml",belle_sip_header_content_type_get_subtype(content_type))==0); +} static bool_t is_external_body(belle_sip_header_content_type_t* content_type) { return strcmp("message",belle_sip_header_content_type_get_type(content_type))==0 && strcmp("external-body",belle_sip_header_content_type_get_subtype(content_type))==0; @@ -74,7 +87,7 @@ static bool_t is_im_iscomposing(belle_sip_header_content_type_t* content_type) { } static void add_message_accept(belle_sip_message_t *msg){ - belle_sip_message_add_header(msg,belle_sip_header_create("Accept","text/plain, message/external-body, application/im-iscomposing+xml, application/vnd.gsma.rcs-ft-http+xml")); + belle_sip_message_add_header(msg,belle_sip_header_create("Accept","text/plain, message/external-body, application/im-iscomposing+xml, xml/cipher, application/vnd.gsma.rcs-ft-http+xml, application/cipher.vnd.gsma.rcs-ft-http+xml")); } void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *event){ @@ -90,13 +103,65 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve char* from; bool_t plain_text=FALSE; bool_t external_body=FALSE; + bool_t cipher_xml=FALSE; bool_t rcs_filetransfer=FALSE; + uint8_t *decryptedMessage = NULL; from_header=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_from_t); content_type=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_content_type_t); + /* check if we have a xml/cipher message to be decrypted */ + if (content_type && (cipher_xml=is_cipher_xml(content_type))) { + /* access the zrtp cache to get keys needed to decipher the message */ + LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); + FILE *CACHEFD = fopen(lc->zrtp_secrets_cache, "r+"); + if (CACHEFD == NULL) { + ms_warning("Unable to access ZRTP ZID cache to decrypt message"); + } else { + int cacheSize; + uint8_t *cacheString; + int retval; + xmlDocPtr cacheXml; + + fseek(CACHEFD, 0L, SEEK_END); /* Position to end of file */ + cacheSize = ftell(CACHEFD); /* Get file length */ + rewind(CACHEFD); /* Back to start of file */ + cacheString = (uint8_t *)malloc(cacheSize*sizeof(uint8_t)+1); /* string must be null terminated */ + fread(cacheString, 1, cacheSize, CACHEFD); + cacheString[cacheSize] = '\0'; + cacheSize += 1; + fclose(CACHEFD); + cacheXml = xmlParseDoc(cacheString); + free(cacheString); + retval = lime_decryptMultipartMessage(cacheXml, (uint8_t *)belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)), &decryptedMessage); + if (retval != 0) { + ms_warning("Unable to decrypt message, reason : %s - op [%p]", lime_error_code_to_string(retval), op); + free(decryptedMessage); + xmlFreeDoc(cacheXml); + resp = belle_sip_response_create_from_request(req,488); + belle_sip_server_transaction_send_response(server_transaction,resp); + return; + } else { + /* dump updated cache to a string */ + xmlChar *xmlStringOutput; + int xmlStringLength; + xmlDocDumpFormatMemoryEnc(cacheXml, &xmlStringOutput, &xmlStringLength, "UTF-8", 0); + /* write it to the cache file */ + CACHEFD = fopen(lc->zrtp_secrets_cache, "w+"); + fwrite(xmlStringOutput, 1, xmlStringLength, CACHEFD); + xmlFree(xmlStringOutput); + fclose(CACHEFD); + } + + xmlFreeDoc(cacheXml); + } + + } + + rcs_filetransfer=is_rcs_filetransfer(content_type); if (content_type && ((plain_text=is_plain_text(content_type)) || (external_body=is_external_body(content_type)) - || (rcs_filetransfer=is_rcs_filetransfer(content_type)))) { + || (decryptedMessage!=NULL) + || rcs_filetransfer)) { SalMessage salmsg; char message_id[256]={0}; @@ -111,7 +176,12 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve ,belle_sip_header_call_id_get_call_id(call_id) ,belle_sip_header_cseq_get_seq_number(cseq)); salmsg.from=from; - salmsg.text=(plain_text||rcs_filetransfer)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; + /* if we just deciphered a message, use the deciphered part(which can be a rcs xml body pointing to the file to retreive from server)*/ + if (cipher_xml) { + salmsg.text = (char *)decryptedMessage; + } else { /* message body wasn't ciphered */ + salmsg.text=(plain_text||rcs_filetransfer)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; + } salmsg.url=NULL; salmsg.content_type = NULL; if (rcs_filetransfer) { /* if we have a rcs file transfer, set the type, message body (stored in salmsg.text) contains all needed information to retrieve the file */ @@ -125,6 +195,8 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve salmsg.message_id=message_id; salmsg.time=date ? belle_sip_header_date_get_time(date) : time(NULL); op->base.root->callbacks.text_received(op,&salmsg); + + free(decryptedMessage); belle_sip_object_unref(address); belle_sip_free(from); if (salmsg.url) ms_free((char*)salmsg.url); @@ -154,11 +226,13 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t sal_process_incoming_message(op,event); } -int sal_message_send(SalOp *op, const char *from, const char *to, const char* content_type, const char *msg){ +int sal_message_send(SalOp *op, const char *from, const char *to, const char* content_type, const char *msg, const char *peer_uri){ belle_sip_request_t* req; char content_type_raw[256]; size_t content_length = msg?strlen(msg):0; time_t curtime=time(NULL); + uint8_t *multipartEncryptedMessage = NULL; + int retval; if (op->dialog){ /*for SIP MESSAGE that are sent in call's dialog*/ @@ -179,13 +253,66 @@ int sal_message_send(SalOp *op, const char *from, const char *to, const char* co belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(sal_op_create_contact(op))); } } + + /* shall we try to encrypt the message?*/ + if ((strcmp(content_type, "xml/cipher") == 0) || ((strcmp(content_type, "application/cipher.vnd.gsma.rcs-ft-http+xml") == 0))) { + /* access the zrtp cache to get keys needed to cipher the message */ + LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); + FILE *CACHEFD = fopen(lc->zrtp_secrets_cache, "r+"); + if (CACHEFD == NULL) { + ms_warning("Unable to access ZRTP ZID cache to encrypt message"); + sal_error_info_set(&op->error_info, SalReasonNotAcceptable, 488, "Unable to encrypt IM", NULL); + op->base.root->callbacks.text_delivery_update(op,SalTextDeliveryFailed); + return 0; + } else { + int cacheSize; + uint8_t *cacheString; + xmlDocPtr cacheXml; + int retval; + + fseek(CACHEFD, 0L, SEEK_END); /* Position to end of file */ + cacheSize = ftell(CACHEFD); /* Get file length */ + rewind(CACHEFD); /* Back to start of file */ + cacheString = (uint8_t *)malloc(cacheSize*sizeof(uint8_t)+1); /* string must be null terminated */ + fread(cacheString, 1, cacheSize, CACHEFD); + cacheString[cacheSize] = '\0'; + cacheSize += 1; + fclose(CACHEFD); + cacheXml = xmlParseDoc(cacheString); + free(cacheString); + retval = lime_createMultipartMessage(cacheXml, (uint8_t *)msg, (uint8_t *)peer_uri, &multipartEncryptedMessage); + if (retval != 0) { + ms_warning("Unable to encrypt message for %s : %s - op [%p]", peer_uri, lime_error_code_to_string(retval), op); + xmlFreeDoc(cacheXml); + free(multipartEncryptedMessage); + sal_error_info_set(&op->error_info, SalReasonNotAcceptable, 488, "Unable to encrypt IM", NULL); + op->base.root->callbacks.text_delivery_update(op,SalTextDeliveryFailed); + return 0; + } else { + /* dump updated cache to a string */ + xmlChar *xmlStringOutput; + int xmlStringLength; + xmlDocDumpFormatMemoryEnc(cacheXml, &xmlStringOutput, &xmlStringLength, "UTF-8", 0); + /* write it to the cache file */ + CACHEFD = fopen(lc->zrtp_secrets_cache, "w+"); + fwrite(xmlStringOutput, 1, xmlStringLength, CACHEFD); + xmlFree(xmlStringOutput); + fclose(CACHEFD); + content_length = strlen((const char *)multipartEncryptedMessage); + } + xmlFreeDoc(cacheXml); + } + } + snprintf(content_type_raw,sizeof(content_type_raw),BELLE_SIP_CONTENT_TYPE ": %s",content_type); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_content_type_parse(content_type_raw))); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_content_length_create(content_length))); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(belle_sip_header_date_create_from_time(&curtime))); - belle_sip_message_set_body(BELLE_SIP_MESSAGE(req),msg,content_length); - return sal_op_send_request(op,req); + belle_sip_message_set_body(BELLE_SIP_MESSAGE(req),(multipartEncryptedMessage==NULL)?msg:(const char *)multipartEncryptedMessage,content_length); + retval = sal_op_send_request(op,req); + free(multipartEncryptedMessage); + return retval; } int sal_message_reply(SalOp *op, SalReason reason){ @@ -200,7 +327,7 @@ int sal_message_reply(SalOp *op, SalReason reason){ } int sal_text_send(SalOp *op, const char *from, const char *to, const char *msg) { - return sal_message_send(op,from,to,"text/plain",msg); + return sal_message_send(op,from,to,"text/plain",msg, NULL); } static belle_sip_listener_callbacks_t op_message_callbacks={0}; diff --git a/coreapi/chat.c b/coreapi/chat.c index 269af03e6..e97073368 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -26,6 +26,8 @@ #include "private.h" #include "lpconfig.h" #include "belle-sip/belle-sip.h" +#include "lime.h" +#include "ortp/b64.h" #include #include @@ -34,6 +36,7 @@ #define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60 #define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120 +#define FILE_TRANSFER_KEY_SIZE 32 static LinphoneChatMessageCbs * linphone_chat_message_cbs_new(void) { return belle_sip_object_new(LinphoneChatMessageCbs); @@ -176,6 +179,7 @@ static void process_io_error_upload(void *data, const belle_sip_io_error_event_t if (msg->cb) { msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->cb_ud); } + if (linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)) { linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)(msg, LinphoneChatMessageStateNotDelivered); } @@ -244,18 +248,49 @@ static int linphone_chat_message_file_transfer_on_send_body(belle_sip_user_body_ /* if we've not reach the end of file yet, ask for more data*/ if (offsetfile_transfer_information)){ - /* get data from call back */ - if (linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)) { - LinphoneBuffer *lb = linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, offset, *size); - if (lb == NULL) *size = 0; - else { - *size = linphone_buffer_get_size(lb); - memcpy(buffer, linphone_buffer_get_content(lb), *size); - linphone_buffer_unref(lb); + + if (linphone_content_get_key(chatMsg->file_transfer_information) != NULL) { /* if we have a key to cipher the message, use it! */ + char *plainBuffer; + /* get data from callback to a plainBuffer */ + /* if this chunk is not the last one, the lenght must be a multiple of block cipher size(16 bytes)*/ + if (offset+*size < linphone_content_get_size(chatMsg->file_transfer_information)) { + *size -=(*size%16); + } + plainBuffer = (char *)malloc(*size); + if (linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)) { + LinphoneBuffer *lb = linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, offset, *size); + if (lb == NULL) *size = 0; + else { + *size = linphone_buffer_get_size(lb); + memcpy(plainBuffer, linphone_buffer_get_content(lb), *size); + linphone_buffer_unref(lb); + } + } else { + /* Legacy */ + linphone_core_notify_file_transfer_send(lc, chatMsg, chatMsg->file_transfer_information, plainBuffer, size); + } + + lime_encryptFile(linphone_content_get_cryptoContext_address(chatMsg->file_transfer_information), (unsigned char *)linphone_content_get_key(chatMsg->file_transfer_information), *size, plainBuffer, (char*)buffer); + free(plainBuffer); + /* check if we reach the end of file */ + if (offset+*size >= linphone_content_get_size(chatMsg->file_transfer_information)) { + /* conclude file ciphering by calling it context with a zero size */ + lime_encryptFile(linphone_content_get_cryptoContext_address(chatMsg->file_transfer_information), NULL, 0, NULL, NULL); } } else { - /* Legacy */ - linphone_core_notify_file_transfer_send(lc, chatMsg, chatMsg->file_transfer_information, buf, size); + /* get data from call back */ + if (linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)) { + LinphoneBuffer *lb = linphone_chat_message_cbs_get_file_transfer_send(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, offset, *size); + if (lb == NULL) *size = 0; + else { + *size = linphone_buffer_get_size(lb); + memcpy(buffer, linphone_buffer_get_content(lb), *size); + linphone_buffer_unref(lb); + } + } else { + /* Legacy */ + linphone_core_notify_file_transfer_send(lc, chatMsg, chatMsg->file_transfer_information, buf, size); + } } } @@ -286,8 +321,19 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co char *first_part_header; belle_sip_body_handler_t *first_part_bh; - /* temporary storage for the Content-disposition header value */ - first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"%s\"", linphone_content_get_name(msg->file_transfer_information)); + /* shall we encrypt the file */ + if (msg->chat_room->lc->lime == 1) { + char keyBuffer[FILE_TRANSFER_KEY_SIZE]; /* temporary storage of generated key: 192 bits of key + 64 bits of initial vector */ + /* generate a random 192 bits key + 64 bits of initial vector and store it into the file_transfer_information->key field of the message */ + sal_get_random_bytes((unsigned char *)keyBuffer, FILE_TRANSFER_KEY_SIZE); + linphone_content_set_key(msg->file_transfer_information, keyBuffer, FILE_TRANSFER_KEY_SIZE); /* key is duplicated in the content private structure */ + /* temporary storage for the Content-disposition header value : use a generic filename to not leak it + * Actual filename stored in msg->file_transfer_information->name will be set in encrypted message sended to the */ + first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"filename.txt\""); + } else { + /* temporary storage for the Content-disposition header value */ + first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"%s\"", linphone_content_get_name(msg->file_transfer_information)); + } /* create a user body handler to take care of the file and add the content disposition and content-type headers */ if (msg->file_transfer_filepath != NULL) { @@ -324,11 +370,65 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co l=belle_http_request_listener_create_from_callbacks(&cbs,msg); belle_http_provider_send_request(msg->chat_room->lc->http_provider,msg->http_request,l); } - if (code == 200 ) { /* file has been uploaded correctly, get server reply and send it */ + + if (code == 200 ) { /* file has been uplaoded correctly, get server reply and send it */ const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response); belle_sip_object_unref(msg->http_request); msg->http_request = NULL; - msg->message = ms_strdup(body); + + /* if we have an encryption key for the file, we must insert it into the message and restore the correct filename */ + if (linphone_content_get_key(msg->file_transfer_information) != NULL) { + /* parse the message body */ + xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body); + + xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody); + if (cur != NULL) { + cur = cur->xmlChildrenNode; + while (cur!=NULL) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check it has a type="file" attribute */ + xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type"); + if(!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) { /* this is the node we are looking for : add a file-key children node */ + xmlNodePtr fileInfoNodeChildren = cur->xmlChildrenNode; /* need to parse the children node to update the file-name one */ + /* convert key to base64 */ + int b64Size = b64_encode(NULL, FILE_TRANSFER_KEY_SIZE, NULL, 0); + char *keyb64 = (char *)malloc(b64Size+1); + int xmlStringLength; + + b64Size = b64_encode(linphone_content_get_key(msg->file_transfer_information), FILE_TRANSFER_KEY_SIZE, keyb64, b64Size); + keyb64[b64Size] = '\0'; /* libxml need a null terminated string */ + + /* add the node containing the key to the file-info node */ + xmlNewTextChild(cur, NULL, (const xmlChar *)"file-key", (const xmlChar *)keyb64); + xmlFree(typeAttribute); + free(keyb64); + + /* look for the file-name node and update its content */ + while (fileInfoNodeChildren!=NULL) { + if (!xmlStrcmp(fileInfoNodeChildren->name, (const xmlChar *)"file-name")) { /* we found a the file-name node, update its content with the real filename */ + /* update node content */ + xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(linphone_content_get_name(msg->file_transfer_information))); + break; + } + fileInfoNodeChildren = fileInfoNodeChildren->next; + } + + + /* dump the xml into msg->message */ + xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&msg->message, &xmlStringLength, "UTF-8", 0); + + break; + } + xmlFree(typeAttribute); + } + cur = cur->next; + } + } + xmlFreeDoc(xmlMessageBody); + + } else { /* no encryption key, transfer in plain, just copy the message sent by server */ + msg->message = ms_strdup(body); + } + msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); if (msg->cb) { msg->cb(msg, LinphoneChatMessageStateFileTransferDone, msg->cb_ud); @@ -339,7 +439,6 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co _linphone_chat_room_send_message(msg->chat_room, msg); } } - } @@ -632,20 +731,29 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM linphone_configure_op(cr->lc,op,cr->peer_url,msg->custom_headers,lp_config_get_int(cr->lc->config,"sip","chat_msg_with_contact",0)); sal_op_set_user_pointer(op, msg); /*if out of call, directly store msg*/ } + + if (msg->external_body_url) { content_type=ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"",msg->external_body_url); - sal_message_send(op,identity,cr->peer,content_type, NULL); + sal_message_send(op,identity,cr->peer,content_type, NULL, NULL); ms_free(content_type); - } else { /* the message is either text or have a file transfer using RCS recommendation */ - if (msg->content_type == NULL) { /* if no content type is specified, it is a text message */ - sal_text_send(op, identity, cr->peer,msg->message); + } else { + if (cr->lc->lime == 1) { /* shall we try to encrypt messages? */ + linphone_chat_message_ref(msg); /* ref the message or it may be destroyed by callback if the encryption failed */ + if ((msg->content_type != NULL) && (strcmp(msg->content_type, "application/vnd.gsma.rcs-ft-http+xml") == 0 )) { /* it's a file transfer, content type shall be set to application/cipher.vnd.gsma.rcs-ft-http+xml*/ + sal_message_send(op, identity, cr->peer, "application/cipher.vnd.gsma.rcs-ft-http+xml", msg->message, linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr))); + } else { + sal_message_send(op, identity, cr->peer, "xml/cipher", msg->message, linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr))); + } } else { - sal_message_send(op, identity, cr->peer, msg->content_type, msg->message); - // Remove the message to prevent the xml from the file uplaod to be stored in the database - ms_free(msg->message); - msg->message = NULL; + if (msg->content_type == NULL) { + sal_text_send(op, identity, cr->peer,msg->message); + } else { /* rcs file transfer */ + sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL); + } } } + msg->dir=LinphoneChatMessageOutgoing; msg->from=linphone_address_new(identity); msg->storage_id=linphone_chat_message_store(msg); @@ -752,6 +860,18 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag file_url = xmlGetProp(cur, (const xmlChar *)"url"); } + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) { /* there is a key in the message: file has been encrypted */ + /* convert the key from base 64 */ + xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + int keyLength = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0); + uint8_t *keyBuffer = (uint8_t *)malloc(keyLength); + /* decode the key into local key buffer */ + b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength); + linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength); /* duplicate key value into the linphone content private structure */ + xmlFree(keyb64); + free(keyBuffer); + } + cur=cur->next; } xmlFree(typeAttribute); @@ -1069,7 +1189,7 @@ static void linphone_chat_room_send_is_composing_notification(LinphoneChatRoom * } content = linphone_chat_room_create_is_composing_xml(cr); if (content != NULL) { - sal_message_send(op, identity, cr->peer, "application/im-iscomposing+xml", content); + sal_message_send(op, identity, cr->peer, "application/im-iscomposing+xml", content, NULL); ms_free(content); } } @@ -1217,14 +1337,36 @@ static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; LinphoneCore *lc = chatMsg->chat_room->lc; - if (linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)) { - LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size); - linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, lb); - linphone_buffer_unref(lb); - } else { - /* Legacy: call back given by application level */ - linphone_core_notify_file_transfer_recv(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); + /* first call may be with a zero size, ignore it */ + if (size == 0) { + return; } + + if (linphone_content_get_key(chatMsg->file_transfer_information) != NULL) { /* we have a key, we must decrypt the file */ + /* get data from callback to a plainBuffer */ + char *plainBuffer = (char *)malloc(size); + lime_decryptFile(linphone_content_get_cryptoContext_address(chatMsg->file_transfer_information), (unsigned char *)linphone_content_get_key(chatMsg->file_transfer_information), size, plainBuffer, (char *)buffer); + if (linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)) { + LinphoneBuffer *lb = linphone_buffer_new_from_data((unsigned char *)plainBuffer, size); + linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, lb); + linphone_buffer_unref(lb); + } else { + /* legacy: call back given by application level */ + linphone_core_notify_file_transfer_recv(lc, chatMsg, chatMsg->file_transfer_information, plainBuffer, size); + } + free(plainBuffer); + } else { /* regular file, no deciphering */ + if (linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)) { + LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size); + linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)(chatMsg, chatMsg->file_transfer_information, lb); + linphone_buffer_unref(lb); + } else { + /* Legacy: call back given by application level */ + linphone_core_notify_file_transfer_recv(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); + } + } + + return; } @@ -1293,6 +1435,10 @@ static void linphone_chat_process_response_from_get_file(void *data, const belle if (code==200) { LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; LinphoneCore *lc = chatMsg->chat_room->lc; + /* if the file was encrypted, finish the decryption and free context */ + if (linphone_content_get_key(chatMsg->file_transfer_information) != NULL) { + lime_decryptFile(linphone_content_get_cryptoContext_address(chatMsg->file_transfer_information), NULL, 0, NULL, NULL); + } /* file downloaded succesfully, call again the callback with size at zero */ if (linphone_chat_message_cbs_get_file_transfer_recv(chatMsg->callbacks)) { LinphoneBuffer *lb = linphone_buffer_new(); diff --git a/coreapi/content.c b/coreapi/content.c index 36ebbb0a7..9f5cd4fee 100644 --- a/coreapi/content.c +++ b/coreapi/content.c @@ -29,6 +29,8 @@ static void linphone_content_destroy(LinphoneContent *content) { if (content->lcp.data) belle_sip_free(content->lcp.data); if (content->lcp.encoding) belle_sip_free(content->lcp.encoding); if (content->lcp.name) belle_sip_free(content->lcp.name); + if (content->lcp.key) belle_sip_free(content->lcp.key); + /* note : crypto context is allocated/destroyed by the encryption function */ } } @@ -38,6 +40,7 @@ static void linphone_content_clone(LinphoneContent *obj, const LinphoneContent * linphone_content_set_subtype(obj, linphone_content_get_subtype(ref)); linphone_content_set_encoding(obj, linphone_content_get_encoding(ref)); linphone_content_set_name(obj, linphone_content_get_name(ref)); + linphone_content_set_key(obj, linphone_content_get_key(ref), linphone_content_get_key_size(ref)); if (linphone_content_get_buffer(ref) != NULL) { linphone_content_set_buffer(obj, linphone_content_get_buffer(ref), linphone_content_get_size(ref)); } else { @@ -161,12 +164,36 @@ void linphone_content_set_name(LinphoneContent *content, const char *name) { } } +size_t linphone_content_get_key_size(const LinphoneContent *content) { + return content->lcp.keyLength; +} + +const char * linphone_content_get_key(const LinphoneContent *content) { + return content->lcp.key; +} + +void linphone_content_set_key(LinphoneContent *content, const char *key, const size_t keyLength) { + if (content->lcp.key != NULL) { + belle_sip_free(content->lcp.key); + content->lcp.key = NULL; + } + if (key != NULL) { + content->lcp.key = belle_sip_malloc(keyLength); + memcpy(content->lcp.key, key, keyLength); + } +} + +/* crypto context is managed(allocated/freed) by the encryption function, so provide the address of field in the private structure */ +void ** linphone_content_get_cryptoContext_address(LinphoneContent *content) { + return &(content->lcp.cryptoContext); +} LinphoneContent * linphone_content_new(void) { LinphoneContent *content = belle_sip_object_new(LinphoneContent); belle_sip_object_ref(content); content->owned_fields = TRUE; + content->lcp.cryptoContext = NULL; /* this field is managed externally by encryption/decryption functions so be careful to initialise it to NULL */ return content; } diff --git a/coreapi/content.h b/coreapi/content.h index e3e0166ce..1e21138eb 100644 --- a/coreapi/content.h +++ b/coreapi/content.h @@ -51,6 +51,9 @@ struct _LinphoneContentPrivate{ When provided by callback #LinphoneCoreFileTransferSendCb or #LinphoneCoreFileTransferRecvCb, it states the total number of bytes of the transfered file*/ char *encoding; /** + +#if POLARSSL_VERSION_NUMBER >= 0x01030000 /* for Polarssl version 1.3 */ +#include "polarssl/sha256.h" +#else /* for Polarssl version 1.2 */ +#include "polarssl/sha2.h" +#endif +/** + * @brief convert an hexa char [0-9a-fA-F] into the corresponding unsigned integer value + * Any invalid char will be converted to zero without any warning + * + * @param[in] inputChar a char which shall be in range [0-9a-fA-F] + * + * @return the unsigned integer value in range [0-15] + */ +uint8_t lime_charToByte(uint8_t inputChar) { + /* 0-9 */ + if (inputChar>0x29 && inputChar<0x3A) { + return inputChar - 0x30; + } + + /* a-f */ + if (inputChar>0x60 && inputChar<0x67) { + return inputChar - 0x57; /* 0x57 = 0x61(a) + 0x0A*/ + } + + /* A-F */ + if (inputChar>0x40 && inputChar<0x47) { + return inputChar - 0x37; /* 0x37 = 0x41(a) + 0x0A*/ + } + + /* shall never arrive here, string is not Hex*/ + return 0; + +} + +/** + * @brief convert a byte which value is in range [0-15] into an hexa char [0-9a-fA-F] + * + * @param[in] inputByte an integer which shall be in range [0-15] + * + * @return the hexa char [0-9a-f] corresponding to the input + */ +uint8_t lime_byteToChar(uint8_t inputByte) { + inputByte &=0x0F; /* restrict the input value to range [0-15] */ + /* 0-9 */ + if(inputByte<0x0A) { + return inputByte+0x30; + } + /* a-f */ + return inputByte + 0x57; +} + + +/** + * @brief Convert an hexadecimal string into the corresponding byte buffer + * + * @param[out] outputBytes The output bytes buffer, must have a length of half the input string buffer + * @param[in] inputString The input string buffer, must be hexadecimal(it is not checked by function, any non hexa char is converted to 0) + * @param[in] inputStringLength The lenght in chars of the string buffer, output is half this length + */ +void lime_strToUint8(uint8_t *outputBytes, uint8_t *inputString, uint16_t inputStringLength) { + int i; + for (i=0; i>4)&0x0F); + outputString[2*i+1] = lime_byteToChar(inputBytes[i]&0x0F); + } +} + + + +int lime_getSelfZid(xmlDocPtr cacheBuffer, uint8_t selfZid[25]) { + xmlNodePtr cur; + xmlChar *selfZidHex; + + if (cacheBuffer == NULL ) { + return LIME_INVALID_CACHE; + } + + cur = xmlDocGetRootElement(cacheBuffer); + /* if we found a root element, parse its children node */ + if (cur!=NULL) + { + cur = cur->xmlChildrenNode; + } + selfZidHex = NULL; + while (cur!=NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar *)"selfZID"))){ /* self ZID found, extract it */ + selfZidHex = xmlNodeListGetString(cacheBuffer, cur->xmlChildrenNode, 1); + /* copy it to the output buffer and add the null termination */ + memcpy(selfZid, selfZidHex, 24); + selfZid[24]='\0'; + break; + } + cur = cur->next; + } + + /* did we found a ZID? */ + if (selfZidHex == NULL) { + return LIME_INVALID_CACHE; + } + + xmlFree(selfZidHex); + return 0; +} + +int lime_getCachedSndKeysByURI(xmlDocPtr cacheBuffer, limeURIKeys_t *associatedKeys) { + xmlNodePtr cur; + + /* parse the file to get all peer matching the sipURI given in associatedKeys*/ + if (cacheBuffer == NULL ) { /* there is no cache return error */ + return LIME_INVALID_CACHE; + } + + /* reset number of associated keys and their buffer */ + associatedKeys->associatedZIDNumber = 0; + associatedKeys->peerKeys = NULL; + + cur = xmlDocGetRootElement(cacheBuffer); + /* if we found a root element, parse its children node */ + if (cur!=NULL) + { + cur = cur->xmlChildrenNode; + } + while (cur!=NULL) { /* loop on all peer nodes */ + uint8_t matchingURIFlag = 0; /* this flag is set to one if we found the requested sipURI in the current peer node */ + if ((!xmlStrcmp(cur->name, (const xmlChar *)"peer"))) { /* found a peer node, check if there is a matching sipURI node in it */ + xmlNodePtr peerNodeChildren = cur->xmlChildrenNode; + matchingURIFlag = 0; + + /* loop on children nodes until the end or we found the matching sipURI */ + while (peerNodeChildren!=NULL && matchingURIFlag==0) { + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"uri")) { /* found a peer an URI node, check the content */ + xmlChar *uriNodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + if (!xmlStrcmp(uriNodeContent, (const xmlChar *)associatedKeys->peerURI)) { /* found a match with requested URI */ + matchingURIFlag=1; + } + xmlFree(uriNodeContent); + } + peerNodeChildren = peerNodeChildren->next; + } + + if (matchingURIFlag == 1) { /* we found a match for the URI in this peer node, extract the keys, session Id and index values */ + /* allocate a new limeKey_t structure to hold the retreived keys */ + limeKey_t *currentPeerKeys = (limeKey_t *)malloc(sizeof(limeKey_t)); + uint8_t itemFound = 0; /* count the item found, we must get all of the requested infos: 5 nodes*/ + uint8_t pvs = 0; + + peerNodeChildren = cur->xmlChildrenNode; /* reset peerNodeChildren to the first child of node */ + while (peerNodeChildren!=NULL && itemFound<5) { + xmlChar *nodeContent = NULL; + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"ZID")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(currentPeerKeys->peerZID, nodeContent, 24); + itemFound++; + } + + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndKey")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(currentPeerKeys->key, nodeContent, 64); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndSId")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(currentPeerKeys->sessionId, nodeContent, 64); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndIndex")) { + uint8_t sessionIndexBuffer[4]; /* session index is a uint32_t but we first retrieved it as an hexa string, convert it to a 4 uint8_t buffer */ + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(sessionIndexBuffer, nodeContent, 8); + /* convert it back to a uint32_t (MSByte first)*/ + currentPeerKeys->sessionIndex = sessionIndexBuffer[3] + (sessionIndexBuffer[2]<<8) + (sessionIndexBuffer[1]<<16) + (sessionIndexBuffer[0]<<24); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"pvs")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(&pvs, nodeContent, 2); /* pvs is retrieved as a 2 characters hexa string, convert it to an int8 */ + itemFound++; + } + + xmlFree(nodeContent); + peerNodeChildren = peerNodeChildren->next; + } + + /* check if we have all the requested information and the PVS flag is set to 1 */ + if (itemFound == 5 && pvs == 1) { + associatedKeys->associatedZIDNumber +=1; + /* extend array of pointer to limeKey_t structures to add the one we found */ + associatedKeys->peerKeys = (limeKey_t **)realloc(associatedKeys->peerKeys, (associatedKeys->associatedZIDNumber)*sizeof(limeKey_t *)); + + /* add the new entry at the end */ + associatedKeys->peerKeys[associatedKeys->associatedZIDNumber-1] = currentPeerKeys; + + } else { + free(currentPeerKeys); + } + } + } + cur = cur->next; + } + return 0; +} + +int lime_getCachedRcvKeyByZid(xmlDocPtr cacheBuffer, limeKey_t *associatedKey) { + uint8_t peerZidHex[25]; + /* to check we collect all the information needed from the cache and that pvs(boolean for previously verified Sas) is set in cache */ + uint8_t itemFound = 0; + uint8_t pvs = 0; + xmlNodePtr cur; + + if (cacheBuffer == NULL ) { /* there is no cache return error */ + return LIME_INVALID_CACHE; + } + + /* get the given ZID into hex format */ + lime_int8ToStr(peerZidHex, associatedKey->peerZID, 12); + peerZidHex[24]='\0'; /* must be a null terminated string */ + + cur = xmlDocGetRootElement(cacheBuffer); + /* if we found a root element, parse its children node */ + if (cur!=NULL) + { + cur = cur->xmlChildrenNode; + + } + + while (cur!=NULL) { /* loop on all peer nodes */ + if ((!xmlStrcmp(cur->name, (const xmlChar *)"peer"))){ /* found a peer, check his ZID element */ + xmlChar *currentZidHex = xmlNodeListGetString(cacheBuffer, cur->xmlChildrenNode->xmlChildrenNode, 1); /* ZID is the first element of peer */ + if (!xmlStrcmp(currentZidHex, (const xmlChar *)peerZidHex)) { /* we found the peer element we are looking for */ + xmlNodePtr peerNodeChildren = cur->xmlChildrenNode->next; + while (peerNodeChildren != NULL && itemFound<4) { /* look for the tag we want to read : rcvKey, rcvSId, rcvIndex and pvs*/ + xmlChar *nodeContent = NULL; + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvKey")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(associatedKey->key, nodeContent, 64); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvSId")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(associatedKey->sessionId, nodeContent, 64); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvIndex")) { + uint8_t sessionIndexBuffer[4]; /* session index is a uint32_t but we first retrieved it as an hexa string, convert it to a 4 uint8_t buffer */ + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(sessionIndexBuffer, nodeContent, 8); + /* convert it back to a uint32_t (MSByte first)*/ + associatedKey->sessionIndex = sessionIndexBuffer[3] + (sessionIndexBuffer[2]<<8) + (sessionIndexBuffer[1]<<16) + (sessionIndexBuffer[0]<<24); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"pvs")) { + nodeContent = xmlNodeListGetString(cacheBuffer, peerNodeChildren->xmlChildrenNode, 1); + lime_strToUint8(&pvs, nodeContent, 2); /* pvs is retrieved as a 2 characters hexa string, convert it to an int8 */ + itemFound++; + } + xmlFree(nodeContent); + peerNodeChildren = peerNodeChildren->next; + } + xmlFree(currentZidHex); + break; /* we parsed the peer node we were looking for, get out of the main while */ + } + xmlFree(currentZidHex); + } + cur = cur->next; + } + + /* if we manage to find the correct key information and that pvs is set to 1, return 0 (success) */ + if ((pvs == 1) && (itemFound == 4)) { + return 0; + } + + /* otherwise, key wasn't found or is invalid */ + return LIME_NO_VALID_KEY_FOUND_FOR_PEER; +} + +int lime_setCachedKey(xmlDocPtr cacheBuffer, limeKey_t *associatedKey, uint8_t role) { + xmlNodePtr cur; + uint8_t peerZidHex[25]; + uint8_t keyHex[65]; /* key is 32 bytes long -> 64 bytes string + null termination */ + uint8_t sessionIdHex[65]; /* sessionId is 32 bytes long -> 64 bytes string + null termination */ + uint8_t sessionIndexHex[9]; /* sessionInedx is an uint32_t : 4 bytes long -> 8 bytes string + null termination */ + uint8_t itemFound = 0; + + if (cacheBuffer == NULL ) { /* there is no cache return error */ + return LIME_INVALID_CACHE; + } + + /* get the given ZID into hex format */ + lime_int8ToStr(peerZidHex, associatedKey->peerZID, 12); + peerZidHex[24]='\0'; /* must be a null terminated string */ + + cur = xmlDocGetRootElement(cacheBuffer); + /* if we found a root element, parse its children node */ + if (cur!=NULL) + { + cur = cur->xmlChildrenNode; + + } + + /* convert the given tag content to null terminated Hexadecimal strings */ + lime_int8ToStr(keyHex, associatedKey->key, 32); + keyHex[64] = '\0'; + lime_int8ToStr(sessionIdHex, associatedKey->sessionId, 32); + sessionIdHex[64] = '\0'; + sessionIndexHex[0] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>28)&0x0F)); + sessionIndexHex[1] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>24)&0x0F)); + sessionIndexHex[2] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>20)&0x0F)); + sessionIndexHex[3] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>16)&0x0F)); + sessionIndexHex[4] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>12)&0x0F)); + sessionIndexHex[5] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>8)&0x0F)); + sessionIndexHex[6] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex>>4)&0x0F)); + sessionIndexHex[7] = lime_byteToChar((uint8_t)((associatedKey->sessionIndex)&0x0F)); + sessionIndexHex[8] = '\0'; + + while (cur!=NULL && itemFound<3) { /* loop on all peer nodes */ + if ((!xmlStrcmp(cur->name, (const xmlChar *)"peer"))){ /* found a peer, check his ZID element */ + xmlChar *currentZidHex = xmlNodeListGetString(cacheBuffer, cur->xmlChildrenNode->xmlChildrenNode, 1); /* ZID is the first element of peer */ + if (!xmlStrcmp(currentZidHex, (const xmlChar *)peerZidHex)) { /* we found the peer element we are looking for */ + xmlNodePtr peerNodeChildren = cur->xmlChildrenNode->next; + while (peerNodeChildren != NULL && itemFound<3) { /* look for the tag we want to write */ + if (role == LIME_RECEIVER) { /* writing receiver key */ + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvKey")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)keyHex); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvSId")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)sessionIdHex); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"rcvIndex")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)sessionIndexHex); + itemFound++; + } + } else { /* writing sender key */ + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndKey")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)keyHex); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndSId")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)sessionIdHex); + itemFound++; + } + if (!xmlStrcmp(peerNodeChildren->name, (const xmlChar *)"sndIndex")) { + xmlNodeSetContent(peerNodeChildren, (const xmlChar *)sessionIndexHex); + itemFound++; + } + } + peerNodeChildren = peerNodeChildren->next; + } + } + xmlFree(currentZidHex); + } + cur = cur->next; + } + + + return 0; +} + +int lime_deriveKey(limeKey_t *key) { + uint8_t inputData[55]; + uint8_t derivedKey[32]; + + if (key == NULL) { + return LIME_UNABLE_TO_DERIVE_KEY; + } + + if ((key->key == NULL) || (key->sessionId == NULL)) { + return LIME_UNABLE_TO_DERIVE_KEY; + } + + /* Derivation is made derived Key = HMAC_SHA256(Key, 0x0000001||"MessageKey"||0x00||SessionId||SessionIndex||0x00000100)*/ + /* total data to be hashed is 55 bytes : 4 + 10 + 1 + 32 + 4 + 4 */ + inputData[0] = 0x00; + inputData[1] = 0x00; + inputData[2] = 0x00; + inputData[3] = 0x01; + + memcpy(inputData+4, "MessageKey", 10); + + inputData[14] = 0x00; + + memcpy(inputData+15, key->sessionId, 32); + + inputData[47] = (uint8_t)((key->sessionIndex>>24)&0x000000FF); + inputData[48] = (uint8_t)((key->sessionIndex>>16)&0x000000FF); + inputData[49] = (uint8_t)((key->sessionIndex>>8)&0x000000FF); + inputData[50] = (uint8_t)(key->sessionIndex&0x000000FF); + + inputData[51] = 0x00; + inputData[52] = 0x00; + inputData[53] = 0x01; + inputData[54] = 0x00; + + /* derive the key in a temp buffer */ +#if POLARSSL_VERSION_NUMBER >= 0x01030000 /* for Polarssl version 1.3 */ + sha256_hmac(key->key, 32, inputData, 55, derivedKey, 0); /* last param to zero to select SHA256 and not SHA224 */ +#else /* for Polarssl version 1.2 */ + sha2_hmac(key->key, 32, inputData, 55, derivedKey, 0); /* last param to zero to select SHA256 and not SHA224 */ +#endif /* POLARSSL_VERSION_NUMBER */ + + /* overwrite the old key with the derived one */ + memcpy(key->key, derivedKey, 32); + + /* increment the session Index */ + key->sessionIndex += 1; + return 0; +} + +void lime_freeKeys(limeURIKeys_t associatedKeys) { + int i; + + /* free all associated keys */ + for (i=0; i< associatedKeys.associatedZIDNumber; i++) { + if (associatedKeys.peerKeys[i] != NULL) { + free(associatedKeys.peerKeys[i]); + associatedKeys.peerKeys[i] = NULL; + } + } + + free(associatedKeys.peerKeys); + + /* free sipURI string */ + free(associatedKeys.peerURI); +} + +int lime_encryptMessage(limeKey_t *key, uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) { + uint8_t authenticatedData[28]; + gcm_context gcmContext; + /* Authenticated data is senderZID(12 bytes)||receiverZID(12 bytes)||sessionIndex(4 bytes) */ + memcpy(authenticatedData, selfZID, 12); + memcpy(authenticatedData+12, key->peerZID, 12); + authenticatedData[24] = (uint8_t)((key->sessionIndex>>24)&0x000000FF); + authenticatedData[25] = (uint8_t)((key->sessionIndex>>16)&0x000000FF); + authenticatedData[26] = (uint8_t)((key->sessionIndex>>8)&0x000000FF); + authenticatedData[27] = (uint8_t)(key->sessionIndex&0x000000FF); + + /* AES-GCM : key is 192 bits long, Init Vector 64 bits. 256 bits key given is AES key||IV */ + /* tag is 16 bytes long and is set in the 16 first bytes of the encrypted message */ + gcm_init(&gcmContext, POLARSSL_CIPHER_ID_AES, key->key, 192); + gcm_crypt_and_tag(&gcmContext, GCM_ENCRYPT, messageLength, key->key+24, 8, authenticatedData, 28, plainMessage, encryptedMessage+16, 16, encryptedMessage); + gcm_free(&gcmContext); + + return 0; +} + +int lime_encryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher) { + gcm_context *gcmContext; + + if (*cryptoContext == NULL) { /* first call to the function, allocate a crypto context and initialise it */ + gcmContext = (gcm_context *)malloc(sizeof(gcm_context)); + *cryptoContext = (void *)gcmContext; + gcm_init(gcmContext, POLARSSL_CIPHER_ID_AES, key, 192); + gcm_starts(gcmContext, GCM_ENCRYPT, key+24, 8, NULL, 0); /* key contains 192bits of key || 64 bits of Initialisation Vector */ + } else { /* this is not the first call, get the context */ + gcmContext = (gcm_context *)*cryptoContext; + } + + if (length != 0) { + gcm_update(gcmContext, length, (const unsigned char *)plain, (unsigned char *)cipher); + } else { /* lenght is 0, finish the stream */ + gcm_finish(gcmContext, NULL, 0); /* do not generate tag */ + gcm_free(gcmContext); + free(*cryptoContext); + *cryptoContext = NULL; + } + + return 0; +} + +int lime_decryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher) { + gcm_context *gcmContext; + + if (*cryptoContext == NULL) { /* first call to the function, allocate a crypto context and initialise it */ + gcmContext = (gcm_context *)malloc(sizeof(gcm_context)); + *cryptoContext = (void *)gcmContext; + gcm_init(gcmContext, POLARSSL_CIPHER_ID_AES, key, 192); + gcm_starts(gcmContext, GCM_DECRYPT, key+24, 8, NULL, 0); /* key contains 192bits of key || 64 bits of Initialisation Vector */ + } else { /* this is not the first call, get the context */ + gcmContext = (gcm_context *)*cryptoContext; + } + + if (length != 0) { + gcm_update(gcmContext, length, (const unsigned char *)cipher, (unsigned char *)plain); + } else { /* lenght is 0, finish the stream */ + gcm_finish(gcmContext, NULL, 0); /* do not generate tag */ + gcm_free(gcmContext); + free(*cryptoContext); + *cryptoContext = NULL; + } + + return 0; +} + + +int lime_decryptMessage(limeKey_t *key, uint8_t *encryptedMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *plainMessage) { + uint8_t authenticatedData[28]; + gcm_context gcmContext; + int retval; + + /* Authenticated data is senderZID(12 bytes)||receiverZID(12 bytes)||sessionIndex(4 bytes) */ + memcpy(authenticatedData, key->peerZID, 12); + memcpy(authenticatedData+12, selfZID, 12); + authenticatedData[24] = (uint8_t)((key->sessionIndex>>24)&0x000000FF); + authenticatedData[25] = (uint8_t)((key->sessionIndex>>16)&0x000000FF); + authenticatedData[26] = (uint8_t)((key->sessionIndex>>8)&0x000000FF); + authenticatedData[27] = (uint8_t)(key->sessionIndex&0x000000FF); + + /* AES-GCM : key is 192 bits long, Init Vector 64 bits. 256 bits key given is AES key||IV */ + /* tag is 16 bytes long and is the 16 first bytes of the encrypted message */ + gcm_init(&gcmContext, POLARSSL_CIPHER_ID_AES, key->key, 192); + /* messageLength-16 is the length of encrypted data, messageLength include the 16 bytes tag included at the begining of encryptedMessage */ + retval = gcm_auth_decrypt(&gcmContext, messageLength-16, key->key+24, 8, authenticatedData, 28, encryptedMessage, 16, encryptedMessage+16, plainMessage); + gcm_free(&gcmContext); + /* add the null termination char */ + plainMessage[messageLength-16] = '\0'; + + return retval; +} + +int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t *peerURI, uint8_t **output) { + uint8_t selfZidHex[25]; + uint8_t selfZid[12]; /* same data but in byte buffer */ + uint32_t encryptedMessageLength; + limeURIKeys_t associatedKeys; + xmlDocPtr xmlOutputMessage; + xmlNodePtr rootNode; + int i; + int xmlStringLength; + + /* retrieve selfZIDHex from cache(return a 24 char hexa string + null termination) */ + if (lime_getSelfZid(cacheBuffer, selfZidHex) != 0) { + return LIME_UNABLE_TO_ENCRYPT_MESSAGE; + } + lime_strToUint8(selfZid, selfZidHex, 24); + + /* encrypted message length is plaintext + 16 for tag */ + encryptedMessageLength = strlen((char *)message) + 16; + + /* retrieve keys associated to the peer URI */ + associatedKeys.peerURI = (uint8_t *)malloc(strlen((char *)peerURI)+1); + strcpy((char *)(associatedKeys.peerURI), (char *)peerURI); + associatedKeys.associatedZIDNumber = 0; + associatedKeys.peerKeys = NULL; + + if (lime_getCachedSndKeysByURI(cacheBuffer, &associatedKeys) != 0) { + lime_freeKeys(associatedKeys); + return LIME_UNABLE_TO_ENCRYPT_MESSAGE; + } + + if (associatedKeys.associatedZIDNumber == 0) { + lime_freeKeys(associatedKeys); + return LIME_NO_VALID_KEY_FOUND_FOR_PEER; + } + + /* create an xml doc to hold the multipart message */ + xmlOutputMessage = xmlNewDoc((const xmlChar *)"1.0"); + /* root tag is "doc" */ + rootNode = xmlNewDocNode(xmlOutputMessage, NULL, (const xmlChar *)"doc", NULL); + xmlDocSetRootElement(xmlOutputMessage, rootNode); + /* add the self ZID child */ + xmlNewTextChild(rootNode, NULL, (const xmlChar *)"ZID", selfZidHex); + + /* loop on all keys found */ + for (i=0; i + * peerZID + * session index + * ciphertext + * */ + msgNode = xmlNewDocNode(xmlOutputMessage, NULL, (const xmlChar *)"msg", NULL); + lime_int8ToStr(peerZidHex, currentKey->peerZID, 12); + peerZidHex[24] = '\0'; + sessionIndexHex[0] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>28)&0x0F)); + sessionIndexHex[1] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>24)&0x0F)); + sessionIndexHex[2] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>20)&0x0F)); + sessionIndexHex[3] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>16)&0x0F)); + sessionIndexHex[4] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>12)&0x0F)); + sessionIndexHex[5] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>8)&0x0F)); + sessionIndexHex[6] = lime_byteToChar((uint8_t)((currentKey->sessionIndex>>4)&0x0F)); + sessionIndexHex[7] = lime_byteToChar((uint8_t)((currentKey->sessionIndex)&0x0F)); + sessionIndexHex[8] = '\0'; + + xmlNewTextChild(msgNode, NULL, (const xmlChar *)"pzid", peerZidHex); + xmlNewTextChild(msgNode, NULL, (const xmlChar *)"index", sessionIndexHex); + + /* convert the cipherText to base 64 */ + b64Size = b64_encode(NULL, encryptedMessageLength, NULL, 0); + encryptedMessageb64 = (char *)malloc(b64Size+1); + b64Size = b64_encode(encryptedMessage, encryptedMessageLength, encryptedMessageb64, b64Size); + encryptedMessageb64[b64Size] = '\0'; /* libxml need a null terminated string */ + xmlNewTextChild(msgNode, NULL, (const xmlChar *)"text", (const xmlChar *)encryptedMessageb64); + free(encryptedMessage); + free(encryptedMessageb64); + + /* add the message Node into the doc */ + xmlAddChild(rootNode, msgNode); + + /* update the key used */ + lime_deriveKey(currentKey); + lime_setCachedKey(cacheBuffer, currentKey, LIME_SENDER); + } + + /* dump the whole message doc into the output */ + xmlDocDumpFormatMemoryEnc(xmlOutputMessage, output, &xmlStringLength, "UTF-8", 0); + xmlFreeDoc(xmlOutputMessage); + + lime_freeKeys(associatedKeys); + + return 0; +} + +int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output) { + int retval; + uint8_t selfZidHex[25]; + uint8_t selfZid[12]; /* same data but in byte buffer */ + limeKey_t associatedKey; + xmlChar *peerZidHex = NULL; + xmlNodePtr cur; + uint8_t *encryptedMessage = NULL; + uint32_t encryptedMessageLength = 0; + uint32_t usedSessionIndex = 0; + xmlDocPtr xmlEncryptedMessage; + + if (cacheBuffer == NULL) { + return LIME_INVALID_CACHE; + } + /* retrieve selfZIDHex from cache(return a 24 char hexa string + null termination) */ + if (lime_getSelfZid(cacheBuffer, selfZidHex) != 0) { + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + lime_strToUint8(selfZid, selfZidHex, 24); + + /* parse the message into an xml doc */ + /* make sure we have a valid xml message before trying to parse it */ + if (memcmp(message, "", 38) != 0 ) { + return LIME_INVALID_ENCRYPTED_MESSAGE; + } + xmlEncryptedMessage = xmlParseDoc((const xmlChar *)message); + if (xmlEncryptedMessage == NULL) { + return LIME_INVALID_ENCRYPTED_MESSAGE; + } + + /* retrieve the sender ZID which is the first child of root */ + cur = xmlDocGetRootElement(xmlEncryptedMessage); + if (cur != NULL) { + cur = cur->xmlChildrenNode; + if ((!xmlStrcmp(cur->name, (const xmlChar *)"ZID"))){ /* sender ZID found, extract it */ + peerZidHex = xmlNodeListGetString(xmlEncryptedMessage, cur->xmlChildrenNode, 1); + /* convert it from hexa string to bytes string and set the result in the associatedKey structure */ + lime_strToUint8(associatedKey.peerZID, peerZidHex, strlen((char *)peerZidHex)); + cur = cur->next; + } + } + + if (peerZidHex != NULL) { + /* get from cache the matching key */ + retval = lime_getCachedRcvKeyByZid(cacheBuffer, &associatedKey); + + if (retval != 0) { + xmlFree(peerZidHex); + xmlFreeDoc(xmlEncryptedMessage); + return retval; + } + + /* retrieve the portion of message which is encrypted with our key */ + while (cur != NULL) { /* loop on all "msg" node in the message */ + xmlNodePtr msgChildrenNode = cur->xmlChildrenNode; + xmlChar *currentZidHex = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); /* pZID is the first element of msg */ + if (!xmlStrcmp(currentZidHex, (const xmlChar *)selfZidHex)) { /* we found the msg node we are looking for */ + /* get the index (second node in the msg one) */ + xmlChar *sessionIndexHex; + xmlChar *encryptedMessageb64; + + msgChildrenNode = msgChildrenNode->next; + sessionIndexHex = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); + usedSessionIndex = (((uint32_t)lime_charToByte(sessionIndexHex[0]))<<28) + | (((uint32_t)lime_charToByte(sessionIndexHex[1]))<<24) + | (((uint32_t)lime_charToByte(sessionIndexHex[2]))<<20) + | (((uint32_t)lime_charToByte(sessionIndexHex[3]))<<16) + | (((uint32_t)lime_charToByte(sessionIndexHex[4]))<<12) + | (((uint32_t)lime_charToByte(sessionIndexHex[5]))<<8) + | (((uint32_t)lime_charToByte(sessionIndexHex[6]))<<4) + | (((uint32_t)lime_charToByte(sessionIndexHex[7]))); + xmlFree(sessionIndexHex); + /* get the encrypted message */ + msgChildrenNode = msgChildrenNode->next; + /* convert the cipherText from base 64 */ + encryptedMessageb64 = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); + encryptedMessageLength = b64_decode((char *)encryptedMessageb64, strlen((char *)encryptedMessageb64), NULL, 0); + encryptedMessage = (uint8_t *)malloc(encryptedMessageLength); + encryptedMessageLength = b64_decode((char *)encryptedMessageb64, strlen((char *)encryptedMessageb64), encryptedMessage, encryptedMessageLength); + xmlFree(encryptedMessageb64); + } + + cur = cur->next; + xmlFree(currentZidHex); + } + } + + xmlFree(peerZidHex); + xmlFreeDoc(xmlEncryptedMessage); + + /* do we have retrieved correctly all the needed data */ + if (encryptedMessage == NULL) { + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + + /* shall we derive our key before going for decryption */ + if (usedSessionIndex < associatedKey.sessionIndex) { + /* something wen't wrong with the cache, this shall never happend */ + free(encryptedMessage); + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + + if ((usedSessionIndex - associatedKey.sessionIndex > MAX_DERIVATION_NUMBER) ) { + /* we missed to many messages, ask for a cache reset via a ZRTP call */ + free(encryptedMessage); + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + + while (usedSessionIndex>associatedKey.sessionIndex) { + lime_deriveKey(&associatedKey); + } + + /* decrypt the message */ + *output = (uint8_t *)malloc(encryptedMessageLength - 16 +1); /* plain message is same length than encrypted one with 16 bytes less for the tag + 1 to add the null termination char */ + retval = lime_decryptMessage(&associatedKey, encryptedMessage, encryptedMessageLength, selfZid, *output); + + free(encryptedMessage); + + if (retval!=0 ) { + free(*output); + *output = NULL; + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + + /* update used key */ + lime_deriveKey(&associatedKey); + lime_setCachedKey(cacheBuffer, &associatedKey, LIME_RECEIVER); + + return 0; +} + +char *lime_error_code_to_string(int errorCode) { + switch (errorCode) { + case LIME_INVALID_CACHE: return "Invalid ZRTP cache"; + case LIME_UNABLE_TO_DERIVE_KEY: return "Unable to derive Key"; + case LIME_UNABLE_TO_ENCRYPT_MESSAGE: return "Unable to encrypt message"; + case LIME_UNABLE_TO_DECRYPT_MESSAGE: return "Unable to decrypt message"; + case LIME_NO_VALID_KEY_FOUND_FOR_PEER: return "No valid key found"; + case LIME_INVALID_ENCRYPTED_MESSAGE: return "Invalid encrypted message"; + } + return "Unknow error"; + +} diff --git a/coreapi/lime.h b/coreapi/lime.h new file mode 100644 index 000000000..861c9acd7 --- /dev/null +++ b/coreapi/lime.h @@ -0,0 +1,192 @@ +#ifndef LIME_H +#define LIME_H + +#define LIME_INVALID_CACHE 0x1001 +#define LIME_UNABLE_TO_DERIVE_KEY 0x1002 +#define LIME_UNABLE_TO_ENCRYPT_MESSAGE 0x1004 +#define LIME_UNABLE_TO_DECRYPT_MESSAGE 0x1008 +#define LIME_NO_VALID_KEY_FOUND_FOR_PEER 0x1010 +#define LIME_INVALID_ENCRYPTED_MESSAGE 0x1020 + +/* this define the maximum key derivation number allowed to get the caches back in sync in case of missed messages */ +#define MAX_DERIVATION_NUMBER 100 + +#define LIME_SENDER 0x01 +#define LIME_RECEIVER 0x02 +#include +#include +#include +#include + +/** + * @brief Structure holding all needed material to encrypt/decrypt Messages */ +typedef struct limeKey_struct { + uint8_t key[32]; /**< a 256 bit key used to encrypt/decrypt message */ + uint8_t sessionId[32]; /**< a session id used to derive key */ + uint32_t sessionIndex; /**< an index to count number of derivation */ + uint8_t peerZID[12]; /**< the ZID associated to this key */ +} limeKey_t; + +/** + * @brief Store the differents keys associated to a sipURI */ +typedef struct limeURIKeys_struct { + limeKey_t **peerKeys; /**< an array of all the key material associated to each ZID matching the specified URI */ + uint16_t associatedZIDNumber; /**< previous array length */ + uint8_t *peerURI; /**< the sip URI associated to all the keys, must be a null terminated string */ +} limeURIKeys_t; + +/** + * @brief Retrieve selfZID from cache + * + * @param[in] cacheBuffer The xmlDoc containing current cache + * @param[out] selfZid The ZID found as a 24 hexa char string null terminated + * + * @return 0 on success, error code otherwise + */ +__attribute__ ((visibility ("default"))) int lime_getSelfZid(xmlDocPtr cacheBuffer, uint8_t selfZid[25]); + +/** + * @brief Get from cache all the senders keys associated to the given URI + * peerKeys field from associatedKeys param must be NULL when calling this function. + * Structure content must then be freed using lime_freeKeys function + * + * @param[in] cacheBuffer The xmlDoc containing current cache + * @param[in/out] associatedKeys Structure containing the peerURI. After this call contains all key material associated to the given URI. Must be then freed through lime_freeKeys function + * + * @return 0 on success, error code otherwise + */ +__attribute__ ((visibility ("default"))) int lime_getCachedSndKeysByURI(xmlDocPtr cacheBuffer, limeURIKeys_t *associatedKeys); + +/** + * @brief Get the receiver key associated to the ZID given in the associatedKey parameter + * + * @param[in] cacheBuffer The xmlDoc containing current cache + * @param[in/out] associatedKey Structure containing the peerZID and will store the retrieved key + * + * @return 0 on success, error code otherwise + */ +__attribute__ ((visibility ("default"))) int lime_getCachedRcvKeyByZid(xmlDocPtr cacheBuffer, limeKey_t *associatedKey); + +/** + * @brief Set in cache the given key material, association is made by ZID contained in the associatedKey parameter + * + * @param[out] cacheBuffer The xmlDoc containing current cache to be updated + * @param[in/out] associatedKey Structure containing the key and ZID to identify the peer node to be updated + * @param[in] role Can be LIME_SENDER or LIME_RECEIVER, specify which key we want to update + * + * @return 0 on success, error code otherwise + */ + +__attribute__ ((visibility ("default"))) int lime_setCachedKey(xmlDocPtr cacheBuffer, limeKey_t *associatedKey, uint8_t role); + +/** + * @brief Free all allocated data in the associated keys structure + * Note, this will also free the peerURI string which then must have been allocated + * + * @param[in/out] associatedKeys The structure to be cleaned + * + */ +__attribute__ ((visibility ("default"))) void lime_freeKeys(limeURIKeys_t associatedKeys); + +/** + * @brief Derive in place the key given in parameter and increment session index + * Derivation is made derived Key = HMAC_SHA256(Key, 0x0000001||"MessageKey"||0x00||SessionId||SessionIndex||256) + * + * @param[in/out] key The structure containing the original key which will be overwritten, the sessionId and SessionIndex + * + * @return 0 on success, error code otherwise + */ +__attribute__ ((visibility ("default"))) int lime_deriveKey(limeKey_t *key); + +/** + * @brief encrypt a message with the given key + * + * @param[in] key Key to use: first 192 bits are used as key, last 64 bits as init vector + * @param[in] message The string to be encrypted + * @param[in] messageLength The length in bytes of the message to be encrypted + * @param[in] selfZID The self ZID is use in authentication tag computation + * @param[out] encryptedMessage A buffer to hold the output, ouput length is input's one + 16 for the authentication tag + * Authentication tag is set at the begining of the encrypted Message + * + * @return 0 on success, error code otherwise + * + */ +__attribute__ ((visibility ("default"))) int lime_encryptMessage(limeKey_t *key, uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage); + +/** + * @brief Encrypt a file before transfering it to the server, encryption is done in several call, first one will be done with cryptoContext null, last one with length = 0 + * + * @param[in/out] cryptoContext The context used to encrypt the file using AES-GCM. Is created at first call(if null) + * @param[in] key 256 bits : 192 bits of key || 64 bits of Initial Vector + * @param[in] length Length of data to be encrypted, if 0 it will conclude the encryption + * @param[in] plain Plain data to be encrypted (length bytes) + * @param[out] cipher Output to a buffer allocated by caller, at least length bytes available + * + * @return 0 on success, error code otherwise + * + */ +__attribute__ ((visibility ("default"))) int lime_encryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher); + +/** + * @brief Decrypt a file retrieved from server, decryption is done in several call, first one will be done with cryptoContext null, last one with length = 0 + * + * @param[in/out] cryptoContext The context used to decrypt the file using AES-GCM. Is created at first call(if null) + * @param[in] key 256 bits : 192 bits of key || 64 bits of Initial Vector + * @param[in] length Length of data to be decrypted, if 0 it will conclude the decryption + * @param[out] plain Output to a buffer allocated by caller, at least length bytes available + * @param[in] cipher Cipher text to be decrypted(length bytes) + * + * @return 0 on success, error code otherwise + * + */ +__attribute__ ((visibility ("default"))) int lime_decryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher); + +/** + * @brief decrypt and authentify a message with the given key + * + * @param[in] key Key to use: first 192 bits are used as key, last 64 bits as init vector + * @param[in] message The string to be decrypted + * @param[in] messageLength The length in bytes of the message to be decrypted (this include the 16 bytes tag at the begining of the message) + * @param[in] selfZID The self ZID is use in authentication tag computation + * @param[out] plainMessage A buffer to hold the output, ouput length is input's one - 16 for the authentication tag + 1 for null termination char + * Authentication tag is retrieved at the begining of the encrypted Message + * + * @return 0 on success, error code otherwise + * + */ + +__attribute__ ((visibility ("default"))) int lime_decryptMessage(limeKey_t *key, uint8_t *encryptedMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *plainMessage); + +/** + * @brief create the encrypted multipart xml message from plain text and destination URI + * Retrieve in cache the needed keys which are then updated. Output buffer is allocated and must be freed by caller + * + * @param[in/out] cacheBuffer The xmlDoc containing current cache, get the keys and selfZID from it, updated by this function with derivated keys + * @param[in] message The plain text message to be encrypted + * @param[in] peerURI The destination URI, associated keys will be found in cache + * @param[out] output The output buffer, allocated and set with the encrypted message xml body(null terminated string). Must be freed by caller + * + * @return 0 on success, error code otherwise + */ +__attribute__ ((visibility ("default"))) int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t *peerURI, uint8_t **output); + +/** + * @brief decrypt a multipart xml message + * Retrieve in cache the needed key which is then updated. Output buffer is allocated and must be freed by caller + * + * @param[in/out] cacheBuffer The xmlDoc containing current cache, get the key and selfZID from it, updated by this function with derivated keys + * @param[in] message The multipart message, contain one or several part identified by destination ZID, one shall match the self ZID retrieved from cache + * @param[out] output The output buffer, allocated and set with the decrypted message(null terminated string). Must be freed by caller + * + * @return 0 on success, error code otherwise + */ + +__attribute__ ((visibility ("default"))) int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output); + +/** + * @brief given a readable version of error code generated by Lime functions + * @param[in] errorCode The error code + * @return a string containing the error description + */ +char *lime_error_code_to_string(int errorCode); +#endif /* LIME_H */ diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 00a0b7f3c..5b550a245 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -2752,6 +2752,7 @@ void linphone_call_start_media_streams(LinphoneCall *call, bool_t all_inputs_mut memset(¶ms,0,sizeof(MSZrtpParams)); /*call->current_params.media_encryption will be set later when zrtp is activated*/ params.zid_file=lc->zrtp_secrets_cache; + params.uri= linphone_address_as_string_uri_only((call->dir==LinphoneCallIncoming) ? call->log->from : call->log->to); setZrtpCryptoTypesParameters(¶ms,call->core); audio_stream_enable_zrtp(call->audiostream,¶ms); #if VIDEO_ENABLED diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index f9a0bf612..1d05b4054 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -891,6 +891,8 @@ static void sip_config_read(LinphoneCore *lc) tmp=lp_config_get_int(lc->config,"sip","guess_hostname",1); linphone_core_set_guess_hostname(lc,tmp); + tmp=lp_config_get_int(lc->config,"sip","lime",0); + linphone_core_set_lime(lc,tmp); tmp=lp_config_get_int(lc->config,"sip","inc_timeout",30); linphone_core_set_inc_timeout(lc,tmp); @@ -1814,6 +1816,15 @@ bool_t linphone_core_get_guess_hostname(LinphoneCore *lc){ return lc->sip_conf.guess_hostname; } +/** + * Tells to LinphoneCore to use Linphone Instant Messaging encryption + * + */ +void linphone_core_set_lime(LinphoneCore *lc, bool_t val){ + lc->lime=val; + lp_config_set_int(lc->config,"sip","lime",val); +} + /** * Same as linphone_core_get_primary_contact() but the result is a LinphoneAddress object * instead of const char* diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 5452523eb..b9afb2a1a 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -2249,6 +2249,8 @@ LINPHONE_PUBLIC const char * linphone_core_get_identity(LinphoneCore *lc); LINPHONE_PUBLIC void linphone_core_set_guess_hostname(LinphoneCore *lc, bool_t val); LINPHONE_PUBLIC bool_t linphone_core_get_guess_hostname(LinphoneCore *lc); +LINPHONE_PUBLIC void linphone_core_set_lime(LinphoneCore *lc, bool_t val); + LINPHONE_PUBLIC bool_t linphone_core_ipv6_enabled(LinphoneCore *lc); LINPHONE_PUBLIC void linphone_core_enable_ipv6(LinphoneCore *lc, bool_t val); diff --git a/coreapi/private.h b/coreapi/private.h index baef0ae6e..796eb750f 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -801,6 +801,8 @@ struct _LinphoneCore belle_tls_verify_policy_t *http_verify_policy; MSList *tones; LinphoneReason chat_deny_code; + bool_t lime; + char *file_transfer_server; const char **supported_formats; LinphoneContent *log_collection_upload_information; LinphoneCoreVTable *current_vtable; // the latest vtable to call a callback, see linphone_core_get_current_vtable @@ -1114,6 +1116,37 @@ void set_playback_gain_db(AudioStream *st, float gain); LinphoneMediaDirection media_direction_from_sal_stream_dir(SalStreamDir dir); SalStreamDir sal_dir_from_call_params_dir(LinphoneMediaDirection cpdir); +/***************************************************************************** + * LINPHONE CONTENT PRIVATE ACCESSORS * + ****************************************************************************/ +/** + * Get the key associated with a RCS file transfer message if encrypted + * @param[in] content LinphoneContent object. + * @return The key to encrypt/decrypt the file associated to this content. + */ +const char *linphone_content_get_key(const LinphoneContent *content); + +/** + * Get the size of key associated with a RCS file transfer message if encrypted + * @param[in] content LinphoneContent object. + * @return The key size in bytes + */ +size_t linphone_content_get_key_size(const LinphoneContent *content); +/** + * Set the key associated with a RCS file transfer message if encrypted + * @param[in] content LinphoneContent object. + * @param[in] key The key to be used to encrypt/decrypt file associated to this content. + */ +void linphone_content_set_key(LinphoneContent *content, const char *key, const size_t keyLength); + +/** + * Get the address of the crypto context associated with a RCS file transfer message if encrypted + * @param[in] content LinphoneContent object. + * @return The address of the pointer to the crypto context. Crypto context is managed(alloc/free) + * by the encryption/decryption functions, so we give the address to store/retrieve the pointer + */ +void ** linphone_content_get_cryptoContext_address(LinphoneContent *content); + #ifdef ANDROID void linphone_core_wifi_lock_acquire(LinphoneCore *lc); void linphone_core_wifi_lock_release(LinphoneCore *lc); diff --git a/include/sal/sal.h b/include/sal/sal.h index 6d82b6c68..a663c16cd 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -710,7 +710,7 @@ int sal_unregister(SalOp *h); /*Messaging */ int sal_text_send(SalOp *op, const char *from, const char *to, const char *text); -int sal_message_send(SalOp *op, const char *from, const char *to, const char* content_type, const char *msg); +int sal_message_send(SalOp *op, const char *from, const char *to, const char* content_type, const char *msg, const char *peer_uri); int sal_message_reply(SalOp *op, SalReason reason); /*presence Subscribe/notify*/ diff --git a/mediastreamer2 b/mediastreamer2 index 531a8a80e..82f9cf317 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 531a8a80e4cba81468ba4a79905f1a7a054649d0 +Subproject commit 82f9cf317da9199454f722f37a493c3a228876cc diff --git a/tester/ZIDCache.xml b/tester/ZIDCache.xml new file mode 100644 index 000000000..d5bc40a8b --- /dev/null +++ b/tester/ZIDCache.xml @@ -0,0 +1,2 @@ + +ef7692d0792a67491ae2d44e005dbe0399643d953a2202dd9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899pipo1@pipo.com963c57bb28e62068d2df23e8f9b771932d3c57bb28e62068d2df23e8f9b77193ffd9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b771935f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193fcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b7719300000069000001e2011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899pipo1@pipo.com123456789012345678901234567890123456765431262068d2df23e8f9b7719325d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b77193000000010000000001 diff --git a/tester/ZIDCacheAlice.xml b/tester/ZIDCacheAlice.xml new file mode 100644 index 000000000..d0b2be2fb --- /dev/null +++ b/tester/ZIDCacheAlice.xml @@ -0,0 +1,2 @@ + +ef7692d0792a67491ae2d44e005dbe0399643d953a2202dd9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899sip:pauline@sip.example.orgeeee4fe260a61bb2ab85740cce180f66096c29167b4b10806ff42c964b1884a260f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000007a000001cf011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899sip:pauline@sip.example.org72d80ab1cad243cf45634980c1d02cfb2df81ce0dd5dfcf1ebeacfc5345a917625d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000000f00000000 diff --git a/tester/ZIDCacheBob.xml b/tester/ZIDCacheBob.xml new file mode 100644 index 000000000..07b62aaaa --- /dev/null +++ b/tester/ZIDCacheBob.xml @@ -0,0 +1,4 @@ + +005dbe0399643d953a2202dd + ef7692d0792a67491ae2d44e9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899sip:marie@sip.example.orgeeee4fe260a61bb2ab85740cce180f66096c29167b4b10806ff42c964b1884a260f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000007a000001cf01 + 1234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899sip:marie@sip.example.org81e6e6362c34dc974263d1f77cbb9a8d6d6a718330994379099a8fa19fb12faa25d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000002e0000000001 diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 0aab3f30d..8df680545 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -243,6 +243,9 @@ typedef struct _stats { int number_of_video_windows_created; int number_of_LinphoneFileTransferDownloadSuccessful; + int number_of_LinphoneCoreLogCollectionUploadStateDelivered; + int number_of_LinphoneCoreLogCollectionUploadStateNotDelivered; + int number_of_LinphoneCoreLogCollectionUploadStateInProgress; }stats; diff --git a/tester/log_collection_tester.c b/tester/log_collection_tester.c index 13c036bde..ba6311497 100644 --- a/tester/log_collection_tester.c +++ b/tester/log_collection_tester.c @@ -244,12 +244,54 @@ static void collect_files_changing_size() { collect_cleanup(marie); } +static void logCollectionUploadStateChangedCb(LinphoneCore *lc, LinphoneCoreLogCollectionUploadState state, const char *info) { + + stats* counters = get_stats(lc); + switch(state) { + case LinphoneCoreLogCollectionUploadStateInProgress: + counters->number_of_LinphoneCoreLogCollectionUploadStateInProgress++; + break; + case LinphoneCoreLogCollectionUploadStateDelivered: + counters->number_of_LinphoneCoreLogCollectionUploadStateDelivered++; + CU_ASSERT_TRUE(strlen(info)>0) + break; + case LinphoneCoreLogCollectionUploadStateNotDelivered: + counters->number_of_LinphoneCoreLogCollectionUploadStateNotDelivered++; + break; + } +} +static void upload_collected_traces() { + LinphoneCoreManager* marie = setup(TRUE); + int waiting = 100; + LinphoneCoreVTable *v_table = linphone_core_v_table_new(); + v_table->log_collection_upload_state_changed = logCollectionUploadStateChangedCb; + linphone_core_add_listener(marie->lc, v_table); + + linphone_core_set_log_collection_max_file_size(5000); + linphone_core_set_log_collection_upload_server_url(marie->lc,"https://www.linphone.org:444/lft.php"); + // Generate some logs + while (--waiting) ms_error("(test error)Waiting %d...", waiting); + linphone_core_compress_log_collection(marie->lc); + linphone_core_upload_log_collection(marie->lc); + CU_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphoneCoreLogCollectionUploadStateDelivered,1)); + + /*try 2 times*/ + waiting=100; + linphone_core_reset_log_collection(marie->lc); + while (--waiting) ms_error("(test error)Waiting %d...", waiting); + linphone_core_compress_log_collection(marie->lc); + linphone_core_upload_log_collection(marie->lc); + CU_ASSERT_TRUE(wait_for(marie->lc,marie->lc,&marie->stat.number_of_LinphoneCoreLogCollectionUploadStateDelivered,2)); + + collect_cleanup(marie); +} test_t log_collection_tests[] = { { "No file when disabled", collect_files_disabled}, { "Collect files filled when enabled", collect_files_filled}, { "Logs collected into small file", collect_files_small_size}, { "Logs collected when decreasing max size", collect_files_changing_size}, + { "Upload collected traces", upload_collected_traces} }; test_suite_t log_collection_test_suite = { diff --git a/tester/message_tester.c b/tester/message_tester.c index 5d75b87a4..658bea376 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -22,6 +22,7 @@ #include "linphonecore.h" #include "private.h" #include "liblinphone_tester.h" +#include "lime.h" #ifdef MSG_STORAGE_ENABLED #include @@ -87,7 +88,7 @@ void file_transfer_received(LinphoneChatMessage *message, const LinphoneContent* } } -static char big_file [128000]; /* a buffer to simulate a big file for the file transfer message test */ +char big_file[128000]; /* a buffer to simulate a big file for the file transfer message test */ /* * function called when the file transfer is initiated. file content should be feed into object LinphoneContent @@ -179,9 +180,11 @@ void liblinphone_tester_chat_message_msg_state_changed(LinphoneChatMessage *msg, static void text_message(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + char* to; + LinphoneChatRoom* chat_room; - char* to = linphone_address_as_string(marie->identity); - LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); ms_free(to); { int dummy=0; @@ -269,11 +272,15 @@ static void text_message_with_credential_from_auth_cb(void) { } static void text_message_with_privacy(void) { + char *to; + LinphoneChatRoom* chat_room; + + LinphoneProxyConfig* pauline_proxy; LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - LinphoneProxyConfig* pauline_proxy; - char* to = linphone_address_as_string(marie->identity); - LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); + + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); ms_free(to); /*test proxy config privacy*/ @@ -343,12 +350,16 @@ static void text_message_compatibility_mode(void) { static void text_message_with_ack(void) { int leaked_objects; int begin; + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + belle_sip_object_enable_leak_detector(TRUE); begin=belle_sip_object_get_object_count(); + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_rc"); + { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); char* to = linphone_address_as_string(marie->identity); LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); @@ -376,11 +387,13 @@ static void text_message_with_ack(void) { static void text_message_with_external_body(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - char* to = linphone_address_as_string(marie->identity); + + char *to = linphone_address_as_string(marie->identity); LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc,to); LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(message); linphone_chat_message_set_external_body_url(message,message_external_body_url="http://www.linphone.org"); + { int dummy=0; wait_for_until(marie->lc,pauline->lc,&dummy,1,100); /*just to have time to purge message stored in the server*/ @@ -453,6 +466,7 @@ static void file_transfer_message(void) { char *receive_filepath = ms_strdup_printf("%s/receive_file.dump", liblinphone_tester_writable_dir_prefix); LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -571,6 +585,88 @@ static void small_file_transfer_message(void) { linphone_core_manager_destroy(pauline); } +static void lime_file_transfer_message(void) { + int i; + char *to; + FILE *ZIDCacheMarieFD, *ZIDCachePaulineFD; + LinphoneCoreManager *marie, *pauline; + LinphoneChatRoom *chat_room; + LinphoneContent *content; + LinphoneChatMessage *message; + LinphoneChatMessageCbs *cbs; + + /* setting dummy file content to something */ + const char* big_file_content="big file"; + for (i=0;istat); + reset_counters(&pauline->stat); + + /* make sure lime is enabled */ + linphone_core_set_lime(marie->lc, 1); + linphone_core_set_lime(pauline->lc, 1); + + /* set the zid caches files : create two ZID cache from this valid one inserting the auto-generated sip URI for the peer account as keys in ZID cache are indexed by peer sip uri */ + ZIDCacheMarieFD = fopen("tmpZIDCacheMarie.xml", "w"); + ZIDCachePaulineFD = fopen("tmpZIDCachePauline.xml", "w"); + fprintf(ZIDCacheMarieFD, "\nef7692d0792a67491ae2d44e005dbe0399643d953a2202dd9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s08df5907d30959b8cb70f6fff2d8febd88fb41b0c8afc39e4b972f86dd5cfe2d60f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b7719300000078000001cf011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s72d80ab1cad243cf45634980c1d02cfb2df81ce0dd5dfcf1ebeacfc5345a917625d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000000f00000000", linphone_address_as_string_uri_only(pauline->identity), linphone_address_as_string_uri_only(pauline->identity)); + fprintf(ZIDCachePaulineFD, "\n005dbe0399643d953a2202ddef7692d0792a67491ae2d44e9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s08df5907d30959b8cb70f6fff2d8febd88fb41b0c8afc39e4b972f86dd5cfe2d60f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b7719300000078000001cf011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s81e6e6362c34dc974263d1f77cbb9a8d6d6a718330994379099a8fa19fb12faa25d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000002e0000000001", linphone_address_as_string_uri_only(marie->identity), linphone_address_as_string_uri_only(marie->identity)); + fclose(ZIDCacheMarieFD); + fclose(ZIDCachePaulineFD); + linphone_core_set_zrtp_secrets_file(marie->lc, "tmpZIDCacheMarie.xml"); + linphone_core_set_zrtp_secrets_file(pauline->lc, "tmpZIDCachePauline.xml"); + + /* Globally configure an http file transfer server. */ + linphone_core_set_file_transfer_server(pauline->lc,"https://www.linphone.org:444/lft.php"); + + /* create a chatroom on pauline's side */ + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + ms_free(to); + /* create a file transfer message */ + content = linphone_core_create_content(pauline->lc); + linphone_content_set_type(content,"text"); + linphone_content_set_subtype(content,"plain"); + linphone_content_set_size(content,sizeof(big_file)); /*total size to be transfered*/ + linphone_content_set_name(content,"big_file.txt"); + + message = linphone_chat_room_create_file_transfer_message(chat_room, content); + { + int dummy=0; + wait_for_until(marie->lc,pauline->lc,&dummy,1,100); /*just to have time to purge message stored in the server*/ + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + } + + cbs = linphone_chat_message_get_callbacks(message); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_message_cbs_set_file_transfer_send(cbs, memory_file_transfer_send); + linphone_chat_room_send_chat_message(chat_room,message); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); + if (marie->stat.last_received_chat_message ) { + cbs = linphone_chat_message_get_callbacks(marie->stat.last_received_chat_message); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_message_cbs_set_file_transfer_recv(cbs, file_transfer_received); + linphone_chat_message_download_file(marie->stat.last_received_chat_message); + } + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageExtBodyReceived,1)); + + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageDelivered,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1); + + linphone_content_unref(content); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + +} + static void file_transfer_message_io_error_upload(void) { int i; char* to; @@ -581,6 +677,7 @@ static void file_transfer_message_io_error_upload(void) { const char* big_file_content="big file"; /* setting dummy file content to something */ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -647,6 +744,7 @@ static void file_transfer_message_io_error_download(void) { const char* big_file_content="big file"; /* setting dummy file content to something */ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -712,6 +810,7 @@ static void file_transfer_message_upload_cancelled(void) { const char* big_file_content="big file"; /* setting dummy file content to something */ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -772,6 +871,7 @@ static void file_transfer_message_download_cancelled(void) { const char* big_file_content="big file"; /* setting dummy file content to something */ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); reset_counters(&pauline->stat); @@ -837,6 +937,10 @@ static void file_transfer_using_external_body_url(void) { reset_counters(&marie->stat); reset_counters(&pauline->stat); + /* make sure lime is disabled */ + linphone_core_set_lime(marie->lc, 0); + linphone_core_set_lime(pauline->lc, 0); + /* create a chatroom on pauline's side */ to = linphone_address_as_string(marie->identity); chat_room = linphone_core_create_chat_room(pauline->lc,to); @@ -865,7 +969,8 @@ static void file_transfer_using_external_body_url(void) { static void text_message_with_send_error(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - char* to = linphone_address_as_string(pauline->identity); + + char *to = linphone_address_as_string(pauline->identity); LinphoneChatRoom* chat_room = linphone_core_create_chat_room(marie->lc,to); LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(message); @@ -903,7 +1008,8 @@ static void text_message_with_send_error(void) { static void text_message_denied(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - char* to = linphone_address_as_string(pauline->identity); + + char *to = linphone_address_as_string(pauline->identity); LinphoneChatRoom* chat_room = linphone_core_create_chat_room(marie->lc,to); LinphoneChatMessage* message = linphone_chat_room_create_message(chat_room,"Bli bli bli \n blu"); LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(message); @@ -941,12 +1047,13 @@ void info_message_received(LinphoneCore *lc, LinphoneCall* call, const LinphoneI static void info_message_with_args(bool_t with_content) { - LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); - LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneInfoMessage *info; const LinphoneContent *content; const char *hvalue; + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + CU_ASSERT_TRUE(call(pauline,marie)); info=linphone_core_create_info_message(marie->lc); @@ -1003,11 +1110,15 @@ static void info_message_with_body(){ } static void is_composing_notification(void) { + char* to; + LinphoneChatRoom* chat_room; + int dummy = 0; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - char* to = linphone_address_as_string(marie->identity); - LinphoneChatRoom* chat_room = linphone_core_create_chat_room(pauline->lc, to); - int dummy = 0; + + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc, to); ms_free(to); { @@ -1026,6 +1137,211 @@ static void is_composing_notification(void) { linphone_core_manager_destroy(pauline); } +void printHex(char *title, uint8_t *data, uint32_t length) { + int i; + printf ("%s : ", title); + for (i=0; ipeerZID, 12); + printHex("key", associatedKeys.peerKeys[i]->key, 32); + printHex("sessionID", associatedKeys.peerKeys[i]->sessionId, 32); + printf("session index %d\n", associatedKeys.peerKeys[i]->sessionIndex); + } + + /* get data from cache : receiver */ + memcpy(associatedKey.peerZID, targetZID, 12); + retval = lime_getCachedRcvKeyByZid(cacheBuffer, &associatedKey); + printf("getCachedKey by ZID return %d\n", retval); + + printHex("Key", associatedKey.key, 32); + printHex("sessionID", associatedKey.sessionId, 32); + printf("session index %d\n", associatedKey.sessionIndex); + + /* encrypt/decrypt a message */ + lime_encryptMessage(associatedKeys.peerKeys[0], (uint8_t *)"bla Bla bla b! Pipo", 20, senderZID, encryptedMessage); + printHex("Ciphered", encryptedMessage, 32); + /* invert sender and receiverZID to decrypt/authenticate */ + memcpy(receiverZID, associatedKeys.peerKeys[0]->peerZID, 12); + memcpy(associatedKeys.peerKeys[0]->peerZID, senderZID, 12); + retval = lime_decryptMessage(associatedKeys.peerKeys[0], encryptedMessage, 36, receiverZID, plainMessage); + printf("Decrypt and auth returned %d\nPlain: %s\n", retval, plainMessage); + + /* update receiver data */ + associatedKey.sessionIndex++; + associatedKey.key[0]++; + associatedKey.sessionId[0]++; + retval = lime_setCachedKey(cacheBuffer, &associatedKey, LIME_RECEIVER); + printf("setCachedKey return %d\n", retval); + + /* update sender data */ + associatedKeys.peerKeys[0]->sessionIndex++; + associatedKeys.peerKeys[0]->key[0]++; + associatedKeys.peerKeys[0]->sessionId[0]++; + retval = lime_setCachedKey(cacheBuffer, associatedKeys.peerKeys[0], LIME_SENDER); + printf("setCachedKey return %d\n", retval); + + /* free memory */ + lime_freeKeys(associatedKeys); + + /* write the file */ + /* dump the xml document into a string */ + xmlDocDumpFormatMemoryEnc(cacheBuffer, &xmlStringOutput, &xmlStringLength, "UTF-8", 0); + /* write it to the file */ + CACHE = fopen("ZIDCache.xml", "w+"); + fwrite(xmlStringOutput, 1, xmlStringLength, CACHE); + xmlFree(xmlStringOutput); + fclose(CACHE); + xmlFreeDoc(cacheBuffer); +} + +static void lime_text_message(void) { + char* to; + FILE *ZIDCacheMarieFD, *ZIDCachePaulineFD; + LinphoneChatRoom* chat_room; + LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + + /* make sure lime is enabled */ + linphone_core_set_lime(marie->lc, 1); + linphone_core_set_lime(pauline->lc, 1); + + /* set the zid caches files : create two ZID cache from this valid one inserting the auto-generated sip URI for the peer account as keys in ZID cache are indexed by peer sip uri */ + ZIDCacheMarieFD = fopen("tmpZIDCacheMarie.xml", "w"); + ZIDCachePaulineFD = fopen("tmpZIDCachePauline.xml", "w"); + fprintf(ZIDCacheMarieFD, "\nef7692d0792a67491ae2d44e005dbe0399643d953a2202dd9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s08df5907d30959b8cb70f6fff2d8febd88fb41b0c8afc39e4b972f86dd5cfe2d60f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b7719300000078000001cf011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s72d80ab1cad243cf45634980c1d02cfb2df81ce0dd5dfcf1ebeacfc5345a917625d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000000f00000000", linphone_address_as_string_uri_only(pauline->identity), linphone_address_as_string_uri_only(pauline->identity)); + fprintf(ZIDCachePaulineFD, "\n005dbe0399643d953a2202ddef7692d0792a67491ae2d44e9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s08df5907d30959b8cb70f6fff2d8febd88fb41b0c8afc39e4b972f86dd5cfe2d60f020a3fe11dc2cc0e1e8ed9341b4cd14944db806ca4fc95456bbe45d95c43a5f9aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b77193bcffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b7719300000078000001cf011234567889643d953a2202ee9b5c8f06f3b6c2c695f2dfc3c26f31f5fef8661f8c5fe7c95aeb5c5b0435b045f8324dd18ea905171ec2be89f879d01d5994132048d92ea020778cbdf31c605e2fdcef69380937c2cf221f7d11526f286c39f49641452ba9012521c705094899%s81e6e6362c34dc974263d1f77cbb9a8d6d6a718330994379099a8fa19fb12faa25d9ac653a83c4559cb0ae7394e7cd3b2d3c57bb28e62068d2df23e8f9b77193f69aa1e5e4c7ec88fa389a9f6b8879b42d3c57bb28e62068d2df23e8f9b7719322ffd51e7316a6c6f53a50fcf01b01bf2d3c57bb28e62068d2df23e8f9b771930000002e0000000001", linphone_address_as_string_uri_only(marie->identity), linphone_address_as_string_uri_only(marie->identity)); + fclose(ZIDCacheMarieFD); + fclose(ZIDCachePaulineFD); + linphone_core_set_zrtp_secrets_file(marie->lc, "tmpZIDCacheMarie.xml"); + linphone_core_set_zrtp_secrets_file(pauline->lc, "tmpZIDCachePauline.xml"); + + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + ms_free(to); + + linphone_chat_room_send_message(chat_room,"Bla bla bla bla"); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageReceivedLegacy,1); + + CU_ASSERT_PTR_NOT_NULL(linphone_core_get_chat_room(marie->lc,pauline->identity)); + /* TODO : check the message arrived correctly deciphered */ + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + + #ifdef MSG_STORAGE_ENABLED /* @@ -1224,6 +1540,7 @@ static void history_messages_count() { #endif test_t message_tests[] = { + { "Lime Text Message", lime_text_message }, { "Text message", text_message }, { "Text message within call's dialog", text_message_within_dialog}, { "Text message with credentials from auth info cb", text_message_with_credential_from_auth_cb}, @@ -1238,11 +1555,13 @@ test_t message_tests[] = { /* { "File transfer message with io error at download", file_transfer_message_io_error_download },*/ { "File transfer message upload cancelled", file_transfer_message_upload_cancelled }, { "File transfer message download cancelled", file_transfer_message_download_cancelled }, + { "Lime File transfer message", lime_file_transfer_message }, { "File transfer message using external body url", file_transfer_using_external_body_url }, { "Text message denied", text_message_denied }, { "Info message", info_message }, { "Info message with body", info_message_with_body }, - { "IsComposing notification", is_composing_notification } + { "IsComposing notification", is_composing_notification }, + { "Lime Unitary", lime_unit } #ifdef MSG_STORAGE_ENABLED ,{ "Database migration", message_storage_migration } ,{ "History count", history_messages_count }