lime.c 42.8 KB
Newer Older
1 2
/*
linphone
3
Copyright (C) 2017  Belledonne Communications SARL
4 5 6 7 8 9 10 11 12 13 14 15 16

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 19
*/

20 21
#include "linphone/api/c-content.h"

22
#include "bctoolbox/crypto.h"
23
#include "lime.h"
24 25 26 27 28
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_LIME
29
#include "private.h"
johan's avatar
johan committed
30
#include "bctoolbox/port.h"
31
#include "bzrtp/bzrtp.h"
32

33 34
#define FILE_TRANSFER_KEY_SIZE 32

35 36 37 38 39
/**
 * @brief check at runtime if LIME is available
 *
 * @return TRUE when Lime was fully compiled, FALSE when it wasn't
 */
40
bool_t lime_is_available(void) { return TRUE; }
41

42 43
int lime_getCachedSndKeysByURI(void *cachedb, limeURIKeys_t *associatedKeys) {
	sqlite3 *db = (sqlite3 *)cachedb;
johan's avatar
johan committed
44
	size_t keysFound = 0; /* used to detect the no key found error because of validity expired */
45 46 47 48 49
	char *stmt = NULL;
	int ret;
	sqlite3_stmt *sqlStmt = NULL;
	int length =0;
	uint8_t pvsOne[1] = {0x01}; /* used to bind this specific byte value to a blob WHERE constraint in the query */
50

51
	if (cachedb == NULL ) { /* there is no cache return error */
52 53 54 55 56 57 58
		return LIME_INVALID_CACHE;
	}

	/* reset number of associated keys and their buffer */
	associatedKeys->associatedZIDNumber = 0;
	associatedKeys->peerKeys = NULL;

59 60 61 62
	/* query the DB: join ziduri, lime and zrtp tables : retrieve zuid(for easier key update in cache), peerZID, sndKey, sndSId, sndIndex, valid where self and peer ZIDs are matching constraint and pvs is raised */
	/* Note: retrieved potentially expired keys, just to be able to send a different status to caller(no keys found is not expired key found) */
	/* if we do not have self uri in associatedKeys, just retrieve any available key matching peer URI */
	if (associatedKeys->selfURI == NULL) {
63
		stmt = sqlite3_mprintf("SELECT zu.zuid, zu.zid as peerZID, l.sndkey, l.sndSId, l.sndIndex, l.valid FROM ziduri as zu INNER JOIN zrtp as z ON z.zuid=zu.zuid INNER JOIN lime as l ON z.zuid=l.zuid WHERE zu.peeruri=? AND z.pvs=?;");
64 65 66 67 68 69 70 71
		ret = sqlite3_prepare_v2(db, stmt, -1, &sqlStmt, NULL);
		sqlite3_free(stmt);
		if (ret != SQLITE_OK) {
			return LIME_INVALID_CACHE;
		}
		sqlite3_bind_text(sqlStmt, 1, associatedKeys->peerURI,-1, SQLITE_TRANSIENT);
		sqlite3_bind_blob(sqlStmt, 2, pvsOne, 1, SQLITE_TRANSIENT);
	} else { /* we have a self URI, so include it in the query */
72
		stmt = sqlite3_mprintf("SELECT zu.zuid, zu.zid as peerZID, l.sndkey, l.sndSId, l.sndIndex, l.valid FROM ziduri as zu INNER JOIN zrtp as z ON z.zuid=zu.zuid INNER JOIN lime as l ON z.zuid=l.zuid WHERE zu.selfuri=? AND zu.peeruri=? AND z.pvs=?;");
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
		ret = sqlite3_prepare_v2(db, stmt, -1, &sqlStmt, NULL);
		sqlite3_free(stmt);
		if (ret != SQLITE_OK) {
			return LIME_INVALID_CACHE;
		}
		sqlite3_bind_text(sqlStmt, 1, associatedKeys->selfURI,-1, SQLITE_TRANSIENT);
		sqlite3_bind_text(sqlStmt, 2, associatedKeys->peerURI,-1, SQLITE_TRANSIENT);
		sqlite3_bind_blob(sqlStmt, 3, pvsOne, 1, SQLITE_TRANSIENT);
	}

	/* parse all retrieved rows */
	while ((ret = sqlite3_step(sqlStmt)) == SQLITE_ROW) {
		/* allocate a new limeKey_t structure to hold the retreived keys */
		limeKey_t *currentPeerKey = (limeKey_t *)bctbx_malloc0(sizeof(limeKey_t));
		bctoolboxTimeSpec currentTimeSpec;
		bctoolboxTimeSpec validityTimeSpec;
		validityTimeSpec.tv_sec=0;
		validityTimeSpec.tv_nsec=0;

		/* get zuid from column 0 */
		currentPeerKey->zuid = sqlite3_column_int(sqlStmt, 0);

		/* retrieve values : peerZid, sndKey, sndSId, sndIndex, valid from columns 1,2,3,4,5 */
		length = sqlite3_column_bytes(sqlStmt, 1);
		if (length==12) { /* peerZID */
Benjamin REIS's avatar
Benjamin REIS committed
98
			memcpy(currentPeerKey->peerZID, sqlite3_column_blob(sqlStmt, 1), (size_t)length);
99 100 101
		} else { /* something wrong with that one, skip it */
			continue;
		}
102

103 104
		length = sqlite3_column_bytes(sqlStmt, 2);
		if (length==32) { /* sndKey */
Benjamin REIS's avatar
Benjamin REIS committed
105
			memcpy(currentPeerKey->key, sqlite3_column_blob(sqlStmt, 2), (size_t)length);
106 107 108
		} else { /* something wrong with that one, skip it */
			continue;
		}
109

110 111
		length = sqlite3_column_bytes(sqlStmt, 3);
		if (length==32) { /* sndSId */
Benjamin REIS's avatar
Benjamin REIS committed
112
			memcpy(currentPeerKey->sessionId, sqlite3_column_blob(sqlStmt, 3), (size_t)length);
113 114 115
		} else { /* something wrong with that one, skip it */
			continue;
		}
116

117 118 119 120 121 122 123 124 125 126
		length = sqlite3_column_bytes(sqlStmt, 4);
		if (length==4) { /* sndIndex : 4 bytes of a uint32_t, stored as a blob in big endian */
			uint8_t *sessionId = (uint8_t *)sqlite3_column_blob(sqlStmt, 4);
			currentPeerKey->sessionIndex = ((uint32_t)(sessionId[0]))<<24 |
							((uint32_t)(sessionId[1]))<<16 |
							((uint32_t)(sessionId[2]))<<8 |
							((uint32_t)(sessionId[3]));
		} else { /* something wrong with that one, skip it */
			continue;
		}
127

128 129 130
		length = sqlite3_column_bytes(sqlStmt, 5);
		if (length==8) { /* sndIndex : 8 bytes of a int64_t, stored as a blob in big endian */
			uint8_t *validity = (uint8_t *)sqlite3_column_blob(sqlStmt, 5);
Benjamin REIS's avatar
Benjamin REIS committed
131
			validityTimeSpec.tv_sec = (int64_t)(((uint64_t)(validity[0]))<<56 |
132 133 134 135 136 137
							((uint64_t)(validity[1]))<<48 |
							((uint64_t)(validity[2]))<<40 |
							((uint64_t)(validity[3]))<<32 |
							((uint64_t)(validity[4]))<<24 |
							((uint64_t)(validity[5]))<<16 |
							((uint64_t)(validity[6]))<<8 |
Benjamin REIS's avatar
Benjamin REIS committed
138
							((uint64_t)(validity[7])));
139 140 141
		} else { /* something wrong with that one, skip it */
			continue;
		}
142

143 144 145 146 147 148
		/* count is a found even if it may be expired */
		keysFound++;

		/* check validity */
		bctbx_get_utc_cur_time(&currentTimeSpec);
		if (validityTimeSpec.tv_sec == 0 || bctbx_timespec_compare(&currentTimeSpec, &validityTimeSpec)<0) {
149
			associatedKeys->associatedZIDNumber++;
150 151 152 153 154 155 156
			/* extend array of pointer to limeKey_t structures to add the one we found */
			associatedKeys->peerKeys = (limeKey_t **)bctbx_realloc(associatedKeys->peerKeys, (associatedKeys->associatedZIDNumber)*sizeof(limeKey_t *));

			/* add the new entry at the end */
			associatedKeys->peerKeys[associatedKeys->associatedZIDNumber-1] = currentPeerKey;
		} else {
			free(currentPeerKey);
157 158
		}
	}
159 160 161 162 163 164 165 166 167

	sqlite3_finalize(sqlStmt);

	/* something is wrong with the cache? */
	if (ret!=SQLITE_DONE) {
		return LIME_INVALID_CACHE;
	}

	/* we're done, check what we have */
johan's avatar
johan committed
168 169 170 171 172 173 174
	if (associatedKeys->associatedZIDNumber == 0) {
		if (keysFound == 0) {
			return LIME_NO_VALID_KEY_FOUND_FOR_PEER;
		} else {
			return LIME_PEER_KEY_HAS_EXPIRED;
		}
	}
175
	return 0;
176

177 178
}

179 180 181 182 183 184 185
int lime_getCachedRcvKeyByZid(void *cachedb, limeKey_t *associatedKey, const char *selfURI, const char *peerURI) {
	sqlite3 *db = (sqlite3 *)cachedb;
	char *stmt = NULL;
	int ret;
	sqlite3_stmt *sqlStmt = NULL;
	int length =0;
	uint8_t pvsOne[1] = {0x01}; /* used to bind this specific byte value to a blob WHERE constraint in the query */
johan's avatar
johan committed
186

187 188

	if (db == NULL) { /* there is no cache return error */
189
		ms_error("[LIME] Get Cached Rcv Key by Zid : no cache found");
190 191 192
		return LIME_INVALID_CACHE;
	}

193 194
	/* query the DB: join ziduri, lime and zrtp tables : */
	/* retrieve zuid(for easier key update in cache), rcvKey, rcvSId, rcvIndex where self/peer uris and peer zid are matching constraint(unique row) and pvs is raised */
195
	stmt = sqlite3_mprintf("SELECT zu.zuid, l.rcvkey, l.rcvSId, l.rcvIndex FROM ziduri as zu INNER JOIN zrtp as z ON z.zuid=zu.zuid INNER JOIN lime as l ON z.zuid=l.zuid WHERE zu.selfuri=? AND zu.peeruri=? AND zu.zid=? AND z.pvs=? LIMIT 1;");
196 197 198
	ret = sqlite3_prepare_v2(db, stmt, -1, &sqlStmt, NULL);
	sqlite3_free(stmt);
	if (ret != SQLITE_OK) {
199
		ms_error("[LIME] Get Cached Rcv Key by Zid can't prepare statement to retrieve key");
200 201 202 203 204 205
		return LIME_INVALID_CACHE;
	}
	sqlite3_bind_text(sqlStmt, 1, selfURI,-1, SQLITE_TRANSIENT);
	sqlite3_bind_text(sqlStmt, 2, peerURI,-1, SQLITE_TRANSIENT);
	sqlite3_bind_blob(sqlStmt, 3, associatedKey->peerZID, 12, SQLITE_TRANSIENT);
	sqlite3_bind_blob(sqlStmt, 4, pvsOne, 1, SQLITE_TRANSIENT);
206 207


208 209 210
	if ((ret = sqlite3_step(sqlStmt)) == SQLITE_ROW) { /* we found a row */
		/* get zuid from column 0 */
		associatedKey->zuid = sqlite3_column_int(sqlStmt, 0);
211

212 213 214
		/* retrieve values : rcvKey, rcvSId, rcvIndex from columns 1,2,3 */
		length = sqlite3_column_bytes(sqlStmt, 1);
		if (length==32) { /* rcvKey */
Benjamin REIS's avatar
Benjamin REIS committed
215
			memcpy(associatedKey->key, sqlite3_column_blob(sqlStmt, 1), (size_t)length);
216
		} else { /* something wrong */
217
			ms_error("[LIME] Get Cached Rcv Key by Zid fetched a rcvKey with wrong length");
218
			sqlite3_finalize(sqlStmt);
219
			return LIME_INVALID_CACHE;
220 221 222 223
		}

		length = sqlite3_column_bytes(sqlStmt, 2);
		if (length==32) { /* rcvSId */
Benjamin REIS's avatar
Benjamin REIS committed
224
			memcpy(associatedKey->sessionId, sqlite3_column_blob(sqlStmt, 2), (size_t)length);
225
		} else { /* something wrong */
226
			ms_error("[LIME] Get Cached Rcv Key by Zid fetched a rcvSid with wrong length");
227
			sqlite3_finalize(sqlStmt);
228
			return LIME_INVALID_CACHE;
229 230
		}

231
		length = sqlite3_column_bytes(sqlStmt, 3);
232
		if (length==4) { /* rcvIndex */
233 234 235 236 237 238 239
			uint8_t *sessionId = (uint8_t *)sqlite3_column_blob(sqlStmt, 3);
			associatedKey->sessionIndex = ((uint32_t)(sessionId[0]))<<24 |
							((uint32_t)(sessionId[1]))<<16 |
							((uint32_t)(sessionId[2]))<<8 |
							((uint32_t)(sessionId[3]));
		} else { /* something wrong */
			sqlite3_finalize(sqlStmt);
240 241
			ms_error("[LIME] Get Cached Rcv Key by Zid fetched a rcvIndex with wrong length");
			return LIME_INVALID_CACHE;
242 243 244
		}

		sqlite3_finalize(sqlStmt);
245 246 247
		return 0;
	}

248 249
	/* something is wrong with the cache? */
	if (ret!=SQLITE_DONE) {
250
		ms_error("[LIME] Get Cached Rcv Key by Zid : request gone bad");
251 252 253 254
		return LIME_INVALID_CACHE;
	}

	/* reach here if the query executed correctly but returned no result */
255
	return LIME_NO_VALID_KEY_FOUND_FOR_PEER;
256 257
}

258 259 260
int lime_setCachedKey(void *cachedb, limeKey_t *associatedKey, uint8_t role, uint64_t validityTimeSpan) {
	bctoolboxTimeSpec currentTime;
	/* columns to be written in cache */
261 262
	const char *colNamesSender[] = {"sndKey", "sndSId", "sndIndex"}; /* Sender never update the validity period */
	const char *colNamesReceiver[] = {"rcvKey", "rcvSId", "rcvIndex", "valid"};
263 264 265
	uint8_t *colValues[4];
	uint8_t sessionIndex[4]; /* buffer to hold the uint32_t buffer index in big endian */
	size_t colLength[] = {32, 32, 4, 8}; /* data length: keys and session ID : 32 bytes, Index: 4 bytes(uint32_t), validity : 8 bytes(UTC time as int64_t) */
266
	uint8_t colNums;
267 268

	if (cachedb == NULL  || associatedKey == NULL) { /* there is no cache return error */
269 270 271
		return LIME_INVALID_CACHE;
	}

272
	/* wrap values to be written */
273
	sessionIndex[0] = (uint8_t)((associatedKey->sessionIndex>>24)&0xFF);
274 275 276 277 278 279
	sessionIndex[1] = (associatedKey->sessionIndex>>16)&0xFF;
	sessionIndex[2] = (associatedKey->sessionIndex>>8)&0xFF;
	sessionIndex[3] = (associatedKey->sessionIndex)&0xFF;
	colValues[0] = associatedKey->key;
	colValues[1] = associatedKey->sessionId;
	colValues[2] = sessionIndex;
280

281
	/* shall we update valid column? Enforce only when receiver, if timeSpan is 0, just ignore */
johan's avatar
johan committed
282
	if (validityTimeSpan > 0 && role == LIME_RECEIVER) {
283
		bctbx_get_utc_cur_time(&currentTime);
Benjamin REIS's avatar
Benjamin REIS committed
284
		bctbx_timespec_add(&currentTime, (int64_t)validityTimeSpan);
285
		/* store the int64_t in big endian in the cache(cache is not typed, all data seen as blob) */
286 287 288 289 290 291 292 293
		colValues[3][0] = (uint8_t)((currentTime.tv_sec>>56)&0xFF);
		colValues[3][1] = (uint8_t)((currentTime.tv_sec>>48)&0xFF);
		colValues[3][2] = (uint8_t)((currentTime.tv_sec>>40)&0xFF);
		colValues[3][3] = (uint8_t)((currentTime.tv_sec>>32)&0xFF);
		colValues[3][4] = (uint8_t)((currentTime.tv_sec>>24)&0xFF);
		colValues[3][5] = (uint8_t)((currentTime.tv_sec>>16)&0xFF);
		colValues[3][6] = (uint8_t)((currentTime.tv_sec>>8)&0xFF);
		colValues[3][7] = (uint8_t)((currentTime.tv_sec)&0xFF);
294 295 296 297 298 299 300

		colNums = 4;
	} else {
		colNums = 3; /* do not write the valid column*/
	}

	/* update cache */
301 302 303 304 305
	return bzrtp_cache_write(
		cachedb, associatedKey->zuid, "lime",
		role == LIME_SENDER ? colNamesSender : colNamesReceiver,
		colValues, colLength, colNums
	);
306 307
}

308 309 310 311 312 313 314 315 316
/**
 * @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
 */
static int lime_deriveKey(limeKey_t *key) {
johan's avatar
johan committed
317 318 319
	uint8_t inputData[55];
	uint8_t derivedKey[32];

320 321 322 323
	if (key == NULL) {
		return LIME_UNABLE_TO_DERIVE_KEY;
	}

324
	/* Derivation is made derived Key = HMAC_SHA256(Key, 0x0000001||"MessageKey"||0x00||SessionId||SessionIndex||0x00000100)*/
325 326 327 328 329
	/* 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;
330

331 332 333
	memcpy(inputData+4, "MessageKey", 10);

	inputData[14] = 0x00;
334

335
	memcpy(inputData+15, key->sessionId, 32);
336

337 338 339 340
	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);
341

342 343 344 345 346 347
	inputData[51] = 0x00;
	inputData[52] = 0x00;
	inputData[53] = 0x01;
	inputData[54] = 0x00;

	/* derive the key in a temp buffer */
jehan's avatar
jehan committed
348
	bctbx_hmacSha256(key->key, 32, inputData, 55, 32, derivedKey);
349 350 351 352 353 354 355 356 357

	/* overwrite the old key with the derived one */
	memcpy(key->key, derivedKey, 32);

	/* increment the session Index */
	key->sessionIndex += 1;
	return 0;
}

358
void lime_freeKeys(limeURIKeys_t *associatedKeys) {
359 360 361
	int i;

	/* free all associated keys */
362 363
	for (i=0; i< associatedKeys->associatedZIDNumber; i++) {
		if (associatedKeys->peerKeys[i] != NULL) {
Simon Morlat's avatar
Simon Morlat committed
364
			/*shouldn't we memset to zero the content of peerKeys[i] in order clear keys?*/
365 366
			free(associatedKeys->peerKeys[i]);
			associatedKeys->peerKeys[i] = NULL;
367 368 369
		}
	}

370
	bctbx_free(associatedKeys->peerKeys);
Simon Morlat's avatar
Simon Morlat committed
371
	associatedKeys->peerKeys = NULL;
372

373 374 375 376
	/* free sipURI strings */
	bctbx_free(associatedKeys->selfURI);
	associatedKeys->selfURI = NULL;
	bctbx_free(associatedKeys->peerURI);
Simon Morlat's avatar
Simon Morlat committed
377
	associatedKeys->peerURI = NULL;
378 379
}

Ghislain MARY's avatar
Ghislain MARY committed
380
int lime_encryptMessage(limeKey_t *key, const uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) {
381
	uint8_t authenticatedData[28];
johan's avatar
johan committed
382
	/* Authenticated data is senderZID(12 bytes)||receiverZID(12 bytes)||sessionIndex(4 bytes) */
383 384 385 386 387 388 389 390 391
	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 */
jehan's avatar
jehan committed
392
	return bctbx_aes_gcm_encrypt_and_tag(key->key, 24,
johan's avatar
johan committed
393 394 395 396 397
			plainMessage, messageLength,
			authenticatedData, 28,
			key->key+24, 8, /* IV is at the end(last 64 bits) of the given key buffer */
			encryptedMessage, 16, /* the first 16 bytes of output are the authentication tag */
			encryptedMessage+16); /* actual encrypted message starts after 16 bytes of authentication tag */
398 399 400 401 402 403

	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];
johan's avatar
johan committed
404 405 406
	int retval;

	/* Authenticated data is senderZID(12 bytes)||receiverZID(12 bytes)||sessionIndex(4 bytes) */
407 408 409 410 411 412 413 414 415
	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 */
jehan's avatar
jehan committed
416
	retval = bctbx_aes_gcm_decrypt_and_auth(key->key, 24, /* key is 192 bits long */
johan's avatar
johan committed
417 418 419 420 421 422
			encryptedMessage+16,  messageLength-16, /* encrypted message first 16 bytes store the authentication tag, then is the actual message */
			authenticatedData, 28, /* additionnal data needed for authentication */
			key->key+24, 8, /* last 8 bytes of key is the initialisation vector */
			encryptedMessage, 16, /* first 16 bytes of message is the authentication tag */
			plainMessage);

423 424 425 426 427 428
	/* add the null termination char */
	plainMessage[messageLength-16] = '\0';

	return retval;
}

429
int lime_createMultipartMessage(void *cachedb, const char *contentType, uint8_t *message, const char *selfURI, const char *peerURI, uint8_t **output) {
430
	uint8_t selfZidHex[25];
johan's avatar
johan committed
431 432
	uint8_t selfZid[12]; /* same data but in byte buffer */
	uint32_t encryptedMessageLength;
Ghislain MARY's avatar
Ghislain MARY committed
433
	uint32_t encryptedContentTypeLength;
johan's avatar
johan committed
434 435 436
	limeURIKeys_t associatedKeys;
	xmlDocPtr xmlOutputMessage;
	xmlNodePtr rootNode;
johan's avatar
johan committed
437
	int i,ret;
johan's avatar
johan committed
438
	int xmlStringLength;
439
	xmlChar *local_output = NULL;
johan's avatar
johan committed
440

441 442
	/* retrieve selfZIDHex from cache */
	if (bzrtp_getSelfZID(cachedb, selfURI, selfZid, NULL) != 0) {
443 444
		return LIME_UNABLE_TO_ENCRYPT_MESSAGE;
	}
445 446 447
	if (message == NULL || contentType == NULL) {
		return LIME_UNABLE_TO_ENCRYPT_MESSAGE;
	}
448 449

	/* encrypted message length is plaintext + 16 for tag */
450
	encryptedMessageLength = (uint32_t)strlen((char *)message) + 16;
Ghislain MARY's avatar
Ghislain MARY committed
451
	encryptedContentTypeLength = (uint32_t)strlen((char *)contentType) + 16;
452 453

	/* retrieve keys associated to the peer URI */
454 455
	associatedKeys.peerURI = bctbx_strdup(peerURI);
	associatedKeys.selfURI = bctbx_strdup(selfURI);
456 457 458
	associatedKeys.associatedZIDNumber  = 0;
	associatedKeys.peerKeys = NULL;

459
	if ((ret = lime_getCachedSndKeysByURI(cachedb, &associatedKeys)) != 0) {
460
		lime_freeKeys(&associatedKeys);
johan's avatar
johan committed
461
		return ret;
462 463 464
	}

	/* create an xml doc to hold the multipart message */
johan's avatar
johan committed
465
	xmlOutputMessage = xmlNewDoc((const xmlChar *)"1.0");
466
	/* root tag is "doc" */
johan's avatar
johan committed
467
	rootNode = xmlNewDocNode(xmlOutputMessage, NULL, (const xmlChar *)"doc", NULL);
468
	xmlDocSetRootElement(xmlOutputMessage, rootNode);
469
	/* add the self ZID child, convert it to an hexa string  */
470
	bctbx_int8_to_str(selfZidHex, selfZid, 12);
471
	selfZidHex[24] = '\0'; /* add a NULL termination for libxml */
472 473 474 475
	xmlNewTextChild(rootNode, NULL, (const xmlChar *)"ZID", selfZidHex);

	/* loop on all keys found */
	for (i=0; i<associatedKeys.associatedZIDNumber; i++) {
johan's avatar
johan committed
476 477 478
		uint8_t peerZidHex[25];
		uint8_t sessionIndexHex[9];
		xmlNodePtr msgNode;
johan's avatar
johan committed
479 480
		size_t b64Size = 0;
		unsigned char *encryptedMessageb64;
Ghislain MARY's avatar
Ghislain MARY committed
481
		unsigned char *encryptedContentTypeb64;
johan's avatar
johan committed
482

483 484 485
		/* encrypt message with current key */
		limeKey_t *currentKey = associatedKeys.peerKeys[i];
		/* encrypted message include a 16 bytes tag */
486
		uint8_t *encryptedMessage = (uint8_t *)ms_malloc(encryptedMessageLength);
Ghislain MARY's avatar
Ghislain MARY committed
487
		uint8_t *encryptedContentType = (uint8_t *)ms_malloc(encryptedContentTypeLength);
488
		lime_encryptMessage(currentKey, message, (uint32_t)strlen((char *)message), selfZid, encryptedMessage);
Ghislain MARY's avatar
Ghislain MARY committed
489
		lime_encryptMessage(currentKey, (const uint8_t *)contentType, (uint32_t)strlen((char *)contentType), selfZid, encryptedContentType);
490 491 492 493 494
		/* add a "msg" node the the output message, doc node is :
		 * <msg>
		 * 		<pzid>peerZID</pzid>
		 * 		<index>session index</index>
		 * 		<text>ciphertext</text>
Ghislain MARY's avatar
Ghislain MARY committed
495
		 * 		<content-type>ciphertext</content-type>
496
		 * </msg> */
johan's avatar
johan committed
497
		msgNode = xmlNewDocNode(xmlOutputMessage, NULL, (const xmlChar *)"msg", NULL);
498
		bctbx_int8_to_str(peerZidHex, currentKey->peerZID, 12);
499
		peerZidHex[24] = '\0';
500
		bctbx_uint32_to_str(sessionIndexHex, currentKey->sessionIndex);
501 502 503 504 505

		xmlNewTextChild(msgNode, NULL, (const xmlChar *)"pzid", peerZidHex);
		xmlNewTextChild(msgNode, NULL, (const xmlChar *)"index", sessionIndexHex);

		/* convert the cipherText to base 64 */
jehan's avatar
jehan committed
506
		bctbx_base64_encode(NULL, &b64Size, encryptedMessage, encryptedMessageLength); /* b64Size is 0, so it is set to the requested output buffer size */
507
		encryptedMessageb64 = reinterpret_cast<unsigned char *>(ms_malloc(b64Size+1)); /* allocate a buffer of requested size +1 for NULL termination */
jehan's avatar
jehan committed
508
		bctbx_base64_encode(encryptedMessageb64, &b64Size, encryptedMessage, encryptedMessageLength); /* b64Size is 0, so it is set to the requested output buffer size */
509 510
		encryptedMessageb64[b64Size] = '\0'; /* libxml need a null terminated string */
		xmlNewTextChild(msgNode, NULL, (const xmlChar *)"text", (const xmlChar *)encryptedMessageb64);
511 512
		ms_free(encryptedMessage);
		ms_free(encryptedMessageb64);
513

Ghislain MARY's avatar
Ghislain MARY committed
514 515 516
		/* convert the encrypted content-type to base 64 */
		b64Size = 0;
		bctbx_base64_encode(NULL, &b64Size, encryptedContentType, encryptedContentTypeLength); /* b64Size is 0, so it is set to the requested output buffer size */
517
		encryptedContentTypeb64 = reinterpret_cast<unsigned char *>(ms_malloc(b64Size+1)); /* allocate a buffer of requested size +1 for NULL termination */
Ghislain MARY's avatar
Ghislain MARY committed
518 519 520 521 522 523
		bctbx_base64_encode(encryptedContentTypeb64, &b64Size, encryptedContentType, encryptedContentTypeLength); /* b64Size is 0, so it is set to the requested output buffer size */
		encryptedContentTypeb64[b64Size] = '\0'; /* libxml need a null terminated string */
		xmlNewTextChild(msgNode, NULL, (const xmlChar *)"content-type", (const xmlChar *)encryptedContentTypeb64);
		ms_free(encryptedContentType);
		ms_free(encryptedContentTypeb64);

524 525
		/* add the message Node into the doc */
		xmlAddChild(rootNode, msgNode);
526

527 528
		/* update the key used */
		lime_deriveKey(currentKey);
529
		lime_setCachedKey(cachedb, currentKey, LIME_SENDER, 0); /* never update validity when sending a message */
530 531 532
	}

	/* dump the whole message doc into the output */
533 534
	xmlDocDumpFormatMemoryEnc(xmlOutputMessage, &local_output, &xmlStringLength, "UTF-8", 0);

Benjamin REIS's avatar
Benjamin REIS committed
535 536
	*output = (uint8_t *)ms_malloc((size_t)xmlStringLength + 1);
	memcpy(*output, local_output, (size_t)xmlStringLength);
537
	(*output)[xmlStringLength] = '\0';
538

539 540
	xmlFree(local_output);
	xmlFreeDoc(xmlOutputMessage);
541
	lime_freeKeys(&associatedKeys);
542 543 544 545

	return 0;
}

546
int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *selfURI, const char *peerURI, uint8_t **output, char **content_type, uint64_t validityTimeSpan) {
Ghislain MARY's avatar
Ghislain MARY committed
547
	int retval = 0;
johan's avatar
johan committed
548 549
	uint8_t selfZidHex[25];
	uint8_t selfZid[12]; /* same data but in byte buffer */
Ghislain MARY's avatar
Ghislain MARY committed
550
	char xpath_str[MAX_XPATH_LENGTH];
johan's avatar
johan committed
551
	limeKey_t associatedKey;
Simon Morlat's avatar
Simon Morlat committed
552
	char *peerZidHex = NULL;
Ghislain MARY's avatar
Ghislain MARY committed
553
	char *sessionIndexHex = NULL;
Ghislain MARY's avatar
Ghislain MARY committed
554 555
	xmlparsing_context_t *xml_ctx;
	xmlXPathObjectPtr msg_object;
johan's avatar
johan committed
556
	uint8_t *encryptedMessage = NULL;
johan's avatar
johan committed
557
	size_t encryptedMessageLength = 0;
Ghislain MARY's avatar
Ghislain MARY committed
558 559
	uint8_t *encryptedContentType = NULL;
	size_t encryptedContentTypeLength = 0;
johan's avatar
johan committed
560
	uint32_t usedSessionIndex = 0;
Ghislain MARY's avatar
Ghislain MARY committed
561
	int i;
562

563
	if (cachedb == NULL) {
564 565
		return LIME_INVALID_CACHE;
	}
566 567 568

	/* retrieve selfZID from cache, and convert it to an Hexa buffer to easily match it against hex string containg in xml message as pzid */
	if (bzrtp_getSelfZID(cachedb, selfURI, selfZid, NULL) != 0) {
569
		ms_error("[LIME] Couldn't get self ZID");
570 571
		return LIME_UNABLE_TO_DECRYPT_MESSAGE;
	}
572
	bctbx_int8_to_str(selfZidHex, selfZid, 12);
573
	selfZidHex[24]='\0';
574

Ghislain MARY's avatar
Ghislain MARY committed
575 576 577 578
	xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)message, 0, NULL, 0);
	if (xml_ctx->doc == NULL) {
579
		ms_error("[LIME] XML doc is null");
Ghislain MARY's avatar
Ghislain MARY committed
580 581
		retval = LIME_INVALID_ENCRYPTED_MESSAGE;
		goto error;
582
	}
583

Ghislain MARY's avatar
Ghislain MARY committed
584
	if (linphone_create_xml_xpath_context(xml_ctx) < 0) {
585
		ms_error("[LIME] Couldn't create xml xpath context");
Ghislain MARY's avatar
Ghislain MARY committed
586 587
		retval = LIME_INVALID_ENCRYPTED_MESSAGE;
		goto error;
588 589
	}

Ghislain MARY's avatar
Ghislain MARY committed
590 591
	/* Retrieve the sender ZID */
	peerZidHex = linphone_get_xml_text_content(xml_ctx, "/doc/ZID");
592
	if (peerZidHex != NULL) {
Ghislain MARY's avatar
Ghislain MARY committed
593
		/* Convert it from hexa string to bytes string and set the result in the associatedKey structure */
594
		bctbx_str_to_uint8(associatedKey.peerZID, (const uint8_t *)peerZidHex, (uint16_t)strlen(peerZidHex));
595

Ghislain MARY's avatar
Ghislain MARY committed
596
		/* Get the matching key from cache */
597
		retval = lime_getCachedRcvKeyByZid(cachedb, &associatedKey, selfURI, peerURI);
598
		if (retval != 0) {
599 600
			ms_error("[LIME] Couldn't get cache rcv key by ZID. Returns %04x. PeerZid %s peerURI %s selfURI %s", retval, peerZidHex, peerURI, selfURI);
			linphone_free_xml_text_content(peerZidHex);
Ghislain MARY's avatar
Ghislain MARY committed
601
			goto error;
602
		}
603
		linphone_free_xml_text_content(peerZidHex);
604

605
		/* Retrieve the portion of message which is encrypted with our key(seek for a pzid matching our) */
Ghislain MARY's avatar
Ghislain MARY committed
606 607 608
		msg_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/doc/msg");
		if ((msg_object != NULL) && (msg_object->nodesetval != NULL)) {
			for (i = 1; i <= msg_object->nodesetval->nodeNr; i++) {
Simon Morlat's avatar
Simon Morlat committed
609
				char *currentZidHex;
610

Simon Morlat's avatar
Simon Morlat committed
611 612
				char *encryptedMessageb64;
				char *encryptedContentTypeb64;
Ghislain MARY's avatar
Ghislain MARY committed
613 614 615
				snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/pzid", i);
				currentZidHex = linphone_get_xml_text_content(xml_ctx, xpath_str);
				if ((currentZidHex != NULL) && (strcmp(currentZidHex, (char *)selfZidHex) == 0)) {
Simon Morlat's avatar
Simon Morlat committed
616
					linphone_free_xml_text_content(currentZidHex);
Ghislain MARY's avatar
Ghislain MARY committed
617 618 619 620
					/* We found the msg node we are looking for */
					snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/index", i);
					sessionIndexHex = linphone_get_xml_text_content(xml_ctx, xpath_str);
					if (sessionIndexHex != NULL) {
621
						usedSessionIndex = bctbx_str_to_uint32((const unsigned char *)sessionIndexHex);
Ghislain MARY's avatar
Ghislain MARY committed
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
					}
					snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/text", i);
					encryptedMessageb64 = linphone_get_xml_text_content(xml_ctx, xpath_str);
					if (encryptedMessageb64 != NULL) {
						bctbx_base64_decode(NULL, &encryptedMessageLength, (const unsigned char *)encryptedMessageb64, strlen(encryptedMessageb64)); /* encryptedMessageLength is 0, so it will be set to the requested buffer length */
						encryptedMessage = (uint8_t *)ms_malloc(encryptedMessageLength);
						bctbx_base64_decode(encryptedMessage, &encryptedMessageLength, (const unsigned char *)encryptedMessageb64, strlen(encryptedMessageb64));
						linphone_free_xml_text_content(encryptedMessageb64);
					}
					snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/content-type", i);
					encryptedContentTypeb64 = linphone_get_xml_text_content(xml_ctx, xpath_str);
					if (encryptedContentTypeb64 != NULL) {
						bctbx_base64_decode(NULL, &encryptedContentTypeLength, (const unsigned char *)encryptedContentTypeb64, strlen(encryptedContentTypeb64)); /* encryptedContentTypeLength is 0, so it will be set to the requested buffer length */
						encryptedContentType = (uint8_t *)ms_malloc(encryptedContentTypeLength);
						bctbx_base64_decode(encryptedContentType, &encryptedContentTypeLength, (const unsigned char *)encryptedContentTypeb64, strlen(encryptedContentTypeb64));
						linphone_free_xml_text_content(encryptedContentTypeb64);
					}
					break;
				}
641
			}
Ghislain MARY's avatar
Ghislain MARY committed
642
		}
Simon Morlat's avatar
Simon Morlat committed
643
		if (msg_object != NULL) xmlXPathFreeObject(msg_object);
644 645 646 647
	}

	/* do we have retrieved correctly all the needed data */
	if (encryptedMessage == NULL) {
648
		ms_error("[LIME] Encrypted message is null, something went wrong...");
649 650
		retval = LIME_UNABLE_TO_DECRYPT_MESSAGE;
		goto error;
651 652 653
	}

	/* shall we derive our key before going for decryption */
654
	if (usedSessionIndex < associatedKey.sessionIndex) {
655 656 657
		/* something wen't wrong with the cache, this shall never happen */
		uint8_t associatedKeyIndexHex[9];
		bctbx_uint32_to_str(associatedKeyIndexHex, associatedKey.sessionIndex);
658
		ms_error("[LIME] Session index [%s] < associated key's session index [%s], should not happen !", sessionIndexHex, associatedKeyIndexHex);
659
		ms_free(encryptedMessage);
660 661
		retval = LIME_UNABLE_TO_DECRYPT_MESSAGE;
		goto error;
662 663 664 665
	}

	if ((usedSessionIndex - associatedKey.sessionIndex > MAX_DERIVATION_NUMBER) ) {
		/* we missed to many messages, ask for a cache reset via a ZRTP call */
666
		ms_error("[LIME] Too many messages missed (%i), cache should be reset by ZRTP call", usedSessionIndex - associatedKey.sessionIndex);
667
		ms_free(encryptedMessage);
668 669
		retval = LIME_UNABLE_TO_DECRYPT_MESSAGE;
		goto error;
670 671
	}

672
	if (associatedKey.sessionIndex != usedSessionIndex) {
673 674
		uint8_t associatedKeyIndexHex[9];
		bctbx_uint32_to_str(associatedKeyIndexHex, associatedKey.sessionIndex);
675
		ms_warning("LIME] unexpected session index [%s] received from [%s] (expected [%s]), [%i] messages will be discarded",
676
			sessionIndexHex, peerURI, associatedKeyIndexHex, usedSessionIndex-associatedKey.sessionIndex);
677
	}
678

679 680
	while (usedSessionIndex>associatedKey.sessionIndex) {
		lime_deriveKey(&associatedKey);
681 682
	}

Ghislain MARY's avatar
Ghislain MARY committed
683 684
	/* Decrypt the message */
	*output = (uint8_t *)ms_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 */
685
	retval = lime_decryptMessage(&associatedKey, encryptedMessage, (uint32_t)encryptedMessageLength, selfZid, *output);
686 687 688
	ms_free(encryptedMessage);
	if (retval != 0) {
		ms_free(*output);
689
		*output = NULL;
690
		ms_error("[LIME] Couldn't decrypt message");
691 692
		retval = LIME_UNABLE_TO_DECRYPT_MESSAGE;
		goto error;
693 694
	}

Ghislain MARY's avatar
Ghislain MARY committed
695 696 697 698 699 700 701 702
	/* Decrypt the content-type */
	if (encryptedContentType != NULL) {
		*content_type = (char *)ms_malloc(encryptedContentTypeLength - 16 + 1); /* content-type is same length than encrypted one with 16 bytes less for the tag + 1 to add the null termination char */
		retval = lime_decryptMessage(&associatedKey, encryptedContentType, (uint32_t)encryptedContentTypeLength, selfZid, *((uint8_t **)content_type));
		ms_free(encryptedContentType);
		if (retval != 0) {
			ms_free(*content_type);
			*content_type = NULL;
703
			ms_error("[LIME] Couldn't decrypt content type");
704 705
			retval = LIME_UNABLE_TO_DECRYPT_MESSAGE;
			goto error;
Ghislain MARY's avatar
Ghislain MARY committed
706 707 708
		}
	}

709 710
	/* update used key */
	lime_deriveKey(&associatedKey);
711
	lime_setCachedKey(cachedb, &associatedKey, LIME_RECEIVER, validityTimeSpan);
712

Ghislain MARY's avatar
Ghislain MARY committed
713
error:
714 715 716
	if (sessionIndexHex != NULL) {
		linphone_free_xml_text_content(sessionIndexHex);
	}
Ghislain MARY's avatar
Ghislain MARY committed
717 718
	linphone_xmlparsing_context_destroy(xml_ctx);
	return retval;
719
}
720

721 722
bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr) {
	if (cr) {
Ghislain MARY's avatar
Ghislain MARY committed
723
		switch (linphone_core_lime_enabled(linphone_chat_room_get_core(cr))) {
724
			case LinphoneLimeDisabled: return FALSE;
725
			case LinphoneLimeMandatory:
726
			case LinphoneLimePreferred: {
Ghislain MARY's avatar
Ghislain MARY committed
727
				void *zrtp_cache_db = linphone_core_get_zrtp_cache_db(linphone_chat_room_get_core(cr));
728 729 730
				if (zrtp_cache_db != NULL) {
					bool_t res;
					limeURIKeys_t associatedKeys;
Benjamin REIS's avatar
Benjamin REIS committed
731 732 733 734 735 736 737
					const LinphoneAddress *peerAddr = linphone_chat_room_get_peer_address(cr);
					char *peer = ms_strdup_printf(
						"%s:%s@%s",
						linphone_address_get_scheme(peerAddr),
						linphone_address_get_username(peerAddr),
						linphone_address_get_domain(peerAddr)
					);
738 739 740 741 742 743 744 745 746
					/* retrieve keys associated to the peer URI */
					associatedKeys.peerURI = bctbx_strdup(peer);
					associatedKeys.selfURI = NULL; /* TODO : there is no sender associated to chatroom so check for any local URI available, shall we add sender to chatroom? */
					associatedKeys.associatedZIDNumber  = 0;
					associatedKeys.peerKeys = NULL;
					/* with NULL is selfURI, just retrieve keys for any local uri found in cache, shall we use a dedicated function which would
					return the list of possible uris and store the selected one in the chatroom ? */
					res = (lime_getCachedSndKeysByURI(zrtp_cache_db, &associatedKeys) == 0);
					lime_freeKeys(&associatedKeys);
Simon Morlat's avatar
Simon Morlat committed
747
					ms_free(peer);
748
					return res;
749 750 751 752 753
				}
			}
		}
	}
	return FALSE;
754 755
}

756 757
int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
	LinphoneCore *lc = linphone_im_encryption_engine_get_core(engine);
758 759
	int errcode = -1;
	/* check if we have a xml/cipher message to be decrypted */
760 761
	if (linphone_chat_message_get_content_type(msg) &&
		(strcmp("xml/cipher", linphone_chat_message_get_content_type(msg)) == 0 ||
762
		strcmp("application/cipher.vnd.gsma.rcs-ft-http+xml", linphone_chat_message_get_content_type(msg)) == 0)) {
763
		errcode = 0;
764 765 766 767 768 769 770
		int retval;
		void *zrtp_cache_db = NULL; /* use a void * instead of sqlite3 * to avoid problems and ifdef when SQLITE is not available(the get function shall return NULL in that case) */
		uint8_t *decrypted_body = NULL;
		char *decrypted_content_type = NULL;
		char *peerUri = NULL;
		char *selfUri = NULL;

771 772
		ms_debug("Content type is known (%s), try to decrypt it", linphone_chat_message_get_content_type(msg));

773 774 775
		zrtp_cache_db = linphone_core_get_zrtp_cache_db(lc);
		if (zrtp_cache_db == NULL) {
			ms_warning("Unable to load content of ZRTP ZID cache to decrypt message");
776 777
			errcode = 500;
			return errcode;
778
		}
Benjamin REIS's avatar
Benjamin REIS committed
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
		const LinphoneAddress *fromAddr = linphone_chat_message_get_from_address(msg);
		peerUri = ms_strdup_printf(
			"%s:%s@%s",
			linphone_address_get_scheme(fromAddr),
			linphone_address_get_username(fromAddr),
			linphone_address_get_domain(fromAddr)
		);

		const LinphoneAddress *toAddr = linphone_chat_message_get_to_address(msg);
		selfUri = ms_strdup_printf(
			"%s:%s@%s",
			linphone_address_get_scheme(toAddr),
			linphone_address_get_username(toAddr),
			linphone_address_get_domain(toAddr)
		);
794

795
		retval = lime_decryptMultipartMessage(zrtp_cache_db, (uint8_t *)linphone_chat_message_get_text(msg), selfUri, peerUri, &decrypted_body, &decrypted_content_type,
796
						      bctbx_time_string_to_sec(lp_config_get_string(lc->config, "sip", "lime_key_validity", "0")));
Simon Morlat's avatar
Simon Morlat committed
797 798
		ms_free(peerUri);
		ms_free(selfUri);
799 800 801 802 803
		if (retval != 0) {
			ms_warning("Unable to decrypt message, reason : %s", lime_error_code_to_string(retval));
			if (decrypted_body) ms_free(decrypted_body);
			errcode = 488;
			return errcode;
804
		} else {
805
			/* swap encrypted message with plain text message */
806
			linphone_chat_message_set_text(msg, (char *)decrypted_body);
Ghislain MARY's avatar
Ghislain MARY committed
807
			ms_free(decrypted_body);
808
			if (decrypted_content_type != NULL) {
809
				ms_debug("Decrypted content type is ", decrypted_content_type);
810
				linphone_chat_message_set_content_type(msg, decrypted_content_type);
Simon Morlat's avatar
Simon Morlat committed
811
				ms_free(decrypted_content_type);
812
			} else {
813
				ms_debug("Decrypted content type is unknown, use plain/text or application/vnd.gsma.rcs-ft-http+xml");
814
				if (strcmp("application/cipher.vnd.gsma.rcs-ft-http+xml", linphone_chat_message_get_content_type(msg)) == 0) {
815
					linphone_chat_message_set_content_type(msg, "application/vnd.gsma.rcs-ft-http+xml");
816
				} else {
817
					linphone_chat_message_set_content_type(msg, "text/plain");
818
				}
819 820
			}
		}
821 822
	} else {
		ms_message("Content type is unknown (%s), don't try to decrypt it", linphone_chat_message_get_content_type(msg));
823 824 825 826
	}
	return errcode;
}

827 828
int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
	LinphoneCore *lc = linphone_im_encryption_engine_get_core(engine);
829
	int errcode = -1;
830
	const char *new_content_type = "xml/cipher";
Ghislain MARY's avatar
Ghislain MARY committed
831
	if(linphone_core_lime_enabled(linphone_chat_room_get_core(room))) {
832
		if (linphone_chat_room_lime_available(room)) {
833
			void *zrtp_cache_db = NULL; /* use a void * instead of sqlite3 * to avoid problems and ifdef when SQLITE is not available(the get function shall return NULL in that case) */
834 835
			if (linphone_chat_message_get_content_type(msg)) {
				if (strcmp(linphone_chat_message_get_content_type(msg), "application/vnd.gsma.rcs-ft-http+xml") == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
836
					/* It's a file transfer, content type shall be set to application/cipher.vnd.gsma.rcs-ft-http+xml
837 838 839
						 TODO: As of january 2017, the content type is now included in the encrypted body, this
						 application/cipher.vnd.gsma.rcs-ft-http+xml is kept for compatibility with previous versions,
						 but may be dropped in the future to use xml/cipher instead. */
Ghislain MARY's avatar
Ghislain MARY committed
840
					new_content_type = "application/cipher.vnd.gsma.rcs-ft-http+xml";
841
				} else if (strcmp(linphone_chat_message_get_content_type(msg), "application/im-iscomposing+xml") == 0) {
842 843 844
					/* We don't encrypt composing messages */
					return errcode;
				}
845
			}
Ghislain MARY's avatar
Ghislain MARY committed
846

847
			/* access the zrtp cache to get keys needed to cipher the message */
848
			zrtp_cache_db = linphone_core_get_zrtp_cache_db(lc);
849
			errcode = 0;
850
			if (zrtp_cache_db == NULL) {
851
				ms_warning("Unable to access ZRTP ZID cache to encrypt message");
852 853
				errcode = 488;
			} else {
854 855
				int retval;
				uint8_t *crypted_body = NULL;
Benjamin REIS's avatar
Benjamin REIS committed
856 857 858 859 860 861 862 863 864 865 866 867 868 869
				const LinphoneAddress *peerAddr = linphone_chat_room_get_peer_address(room);
				char *peerUri = ms_strdup_printf(
					"%s:%s@%s",
					linphone_address_get_scheme(peerAddr),
					linphone_address_get_username(peerAddr),
					linphone_address_get_domain(peerAddr)
				);
				const LinphoneAddress *fromAddr = linphone_chat_message_get_from_address(msg);
				char *selfUri = ms_strdup_printf(
					"%s:%s@%s",
					linphone_address_get_scheme(fromAddr),
					linphone_address_get_username(fromAddr),
					linphone_address_get_domain(fromAddr)
				);
870

871
				retval = lime_createMultipartMessage(zrtp_cache_db, linphone_chat_message_get_content_type(msg), (uint8_t *)linphone_chat_message_get_text(msg), selfUri, peerUri, &crypted_body);
872
				if (retval != 0) { /* fail to encrypt */
Ghislain MARY's avatar
Ghislain MARY committed
873
					ms_warning("Unable to encrypt message for %s : %s", peerUri, lime_error_code_to_string(retval));
874 875
					if (crypted_body) ms_free(crypted_body);
					errcode = 488;
876
				} else { /* encryption ok, swap plain text message body by encrypted one */
877
					linphone_chat_message_set_text(msg, (char *)crypted_body);
Ghislain MARY's avatar
Ghislain MARY committed
878
					ms_free(crypted_body);
Simon Morlat's avatar
Simon Morlat committed
879
					linphone_chat_message_set_content_type(msg, new_content_type);
880
				}
881 882
				ms_free(peerUri);
				ms_free(selfUri);
883
			}
884
		} else {
885 886 887 888
			if (linphone_core_lime_enabled(lc) == LinphoneLimeMandatory) {
				ms_warning("Unable to access ZRTP ZID cache to encrypt message");
				errcode = 488;
			}
889 890 891 892
		}
	}
	return errcode;
}
893

894
int lime_im_encryption_engine_process_downloading_file_cb(LinphoneImEncryptionEngine *engine, LinphoneChatMessage *msg, size_t offset, const uint8_t *buffer, size_t size, uint8_t *decrypted_buffer) {
895 896 897
	LinphoneContent *content = linphone_chat_message_get_file_transfer_information(msg);
	if (!content)
		return -1;
898 899

	if (!linphone_content_get_key(content))
900
		return -1;
901

902
	if (!buffer || size == 0)
903
		return bctbx_aes_gcm_decryptFile(linphone_content_get_cryptoContext_address(content), NULL, 0, NULL, NULL);
904

905
	return bctbx_aes_gcm_decryptFile(
906 907 908 909 910 911
		linphone_content_get_cryptoContext_address(content),
		(unsigned char *)linphone_content_get_key(content),
		size,
		(char *)decrypted_buffer,
		(char *)buffer
	);
912 913
}

914
int lime_im_encryption_engine_process_uploading_file_cb(LinphoneImEncryptionEngine *engine, LinphoneChatMessage *msg, size_t offset, const uint8_t *buffer, size_t *size, uint8_t *encrypted_buffer) {
915
	LinphoneContent *content = linphone_chat_message_get_file_transfer_information(msg);
916

917 918
	if (!content)
		return -1;
919

920
	if (!linphone_content_get_key(content))
921
		return -1;
922

923
	if (!buffer || *size == 0)
924
		return bctbx_aes_gcm_encryptFile(linphone_content_get_cryptoContext_address(content), NULL, 0, NULL, NULL);
925

926
	size_t file_size = linphone_content_get_file_size(content);
927 928 929
	if (file_size == 0) {
		ms_warning("File size has not been set, encryption will fail if not done in one step (if file is larger than 16K)");
	} else if (offset + *size < file_size) {
930 931
		*size -= (*size % 16);
	}
932

933
	return bctbx_aes_gcm_encryptFile(
934 935 936 937 938 939
		linphone_content_get_cryptoContext_address(content),
		(unsigned char *)linphone_content_get_key(content),
		*size,
		(char *)buffer,
		(char *)encrypted_buffer
	);
940 941
}

942 943
bool_t lime_im_encryption_engine_is_file_encryption_enabled_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room) {
	LinphoneCore *lc = linphone_im_encryption_engine_get_core(engine);
944 945 946
	return linphone_chat_room_lime_available(room) && linphone_core_lime_for_file_sharing_enabled(lc);
}

947
void lime_im_encryption_engine_generate_file_transfer_key_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
948 949 950 951
	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 msg */
	sal_get_random_bytes((unsigned char *)keyBuffer, FILE_TRANSFER_KEY_SIZE);
952
	LinphoneContent *content = linphone_chat_message_get_file_transfer_information(msg);
953 954
	if (content)
		linphone_content_set_key(content, keyBuffer, FILE_TRANSFER_KEY_SIZE); /* key is duplicated in the content private structure */
955 956
}

957 958
#else /* HAVE_LIME */

959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
bool_t lime_is_available() { return FALSE; }
int lime_decryptMultipartMessage(void *cachedb, uint8_t *message, const char *selfURI, const char *peerURI, uint8_t **output, char **content_type, uint64_t validityTimeSpan) { return LIME_NOT_ENABLED;}
int lime_createMultipartMessage(void *cachedb, const char *contentType, uint8_t *message, const char *selfURI, const char *peerURI, uint8_t **output) { return LIME_NOT_ENABLED;}
void lime_freeKeys(limeURIKeys_t *associatedKeys){
}
int lime_getCachedSndKeysByURI(void *cachedb, limeURIKeys_t *associatedKeys){
	return LIME_NOT_ENABLED;
}
int lime_encryptMessage(limeKey_t *key, const uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) {
	return LIME_NOT_ENABLED;
}
int lime_setCachedKey(void * cacheDb, limeKey_t *associatedKey, uint8_t role, uint64_t validityTimeSpan) {
	return LIME_NOT_ENABLED;
}
int lime_getCachedRcvKeyByZid(void * cacheDb, limeKey_t *associatedKey, const char *selfURI, const char *peerURI) {
	return LIME_NOT_ENABLED;
}
int lime_decryptMessage(limeKey_t *key, uint8_t *encryptedMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *plainMessage) {
	return LIME_NOT_ENABLED;
}
bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr) {
	return FALSE;
}
int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
	return 500;
}
int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {
	return 500;
}
int lime_im_encryption_engine_process_downloading_file_cb(LinphoneImEncryptionEngine *engine, LinphoneChatMessage *msg, size_t offset, const uint8_t *buffer, size_t size, uint8_t *decrypted_buffer) {
	return 500;
}
int lime_im_encryption_engine_process_uploading_file_cb(LinphoneImEncryptionEngine *engine, LinphoneChatMessage *msg, size_t offset, const uint8_t *buffer, size_t *size, uint8_t *encrypted_buffer) {
	return 500;
}
bool_t lime_im_encryption_engine_is_file_encryption_enabled_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room) {
	return FALSE;
}
void lime_im_encryption_engine_generate_file_transfer_key_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) {

}

#endif /* HAVE_LIME */

1003
const char *lime_error_code_to_string(int errorCode) {
1004 1005 1006 1007 1008 1009
	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";
johan's avatar
johan committed
1010
		case LIME_PEER_KEY_HAS_EXPIRED: return "Any key matching peer Uri has expired";
1011
		case LIME_INVALID_ENCRYPTED_MESSAGE: return "Invalid encrypted message";
1012
		case LIME_NOT_ENABLED: return "Lime not enabled at build";
1013 1014 1015 1016
	}
	return "Unknow error";

}