lime-v2.cpp 17.6 KB
Newer Older
1
/*
2
 * lime-v2.cpp
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * Copyright (C) 2010-2018 Belledonne Communications SARL
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

20 21
#include "chat/chat-message/chat-message-p.h"
#include "chat/chat-room/chat-room.h"
22
#include "content/content-manager.h"
23
#include "content/header/header-param.h"
24
#include "conference/participant-p.h"
25
#include "conference/participant-device.h"
26
#include "lime-v2.h"
27
#include "private.h"
28 29 30 31 32

using namespace std;

LINPHONE_BEGIN_NAMESPACE

33
struct X3DHServerPostContext {
34 35
	const lime::limeX3DHServerResponseProcess responseProcess;
	const string username;
36 37
	LinphoneCore *lc;
	X3DHServerPostContext(const lime::limeX3DHServerResponseProcess &response, const string &username, LinphoneCore *lc) : responseProcess(response), username{username}, lc{lc} {};
38 39 40 41 42 43 44 45
};

void BelleSipLimeManager::processIoError (void *data, const belle_sip_io_error_event_t *event) noexcept {
	X3DHServerPostContext *userData = static_cast<X3DHServerPostContext *>(data);
	(userData->responseProcess)(0, vector<uint8_t>{});
	delete(userData);
}

46
void BelleSipLimeManager::processResponse (void *data, const belle_http_response_event_t *event) noexcept {
47 48 49 50 51 52 53 54 55 56 57 58 59
	X3DHServerPostContext *userData = static_cast<X3DHServerPostContext *>(data);
	if (event->response){
		auto code=belle_http_response_get_status_code(event->response);
		belle_sip_message_t *message = BELLE_SIP_MESSAGE(event->response);
		auto body = reinterpret_cast<const uint8_t *>(belle_sip_message_get_body(message));
		auto bodySize = belle_sip_message_get_body_size(message);
		(userData->responseProcess)(code, vector<uint8_t>{body, body+bodySize});
	} else {
		(userData->responseProcess)(0, vector<uint8_t>{});
	}
	delete(userData);
}

60 61 62 63 64 65 66
void BelleSipLimeManager::processAuthRequested (void *data, belle_sip_auth_event_t *event) noexcept {
	X3DHServerPostContext *userData = static_cast<X3DHServerPostContext *>(data);
	LinphoneCore *lc = userData->lc;

	const char *realm = belle_sip_auth_event_get_realm(event);
	const char *username = belle_sip_auth_event_get_username(event);
	const char *domain = belle_sip_auth_event_get_domain(event);
67

68 69
	const LinphoneAuthInfo *auth_info = linphone_core_find_auth_info(lc, realm, username, domain);

70 71 72
	if (auth_info) {
		const char *auth_username = linphone_auth_info_get_username(auth_info);
		const char *auth_password = linphone_auth_info_get_password(auth_info);
Erwan Croze's avatar
Erwan Croze committed
73
		const char *auth_ha1 = linphone_auth_info_get_ha1(auth_info);
74
		belle_sip_auth_event_set_username(event, auth_username);
75
		belle_sip_auth_event_set_passwd(event, auth_password);
Erwan Croze's avatar
Erwan Croze committed
76
		belle_sip_auth_event_set_ha1(event, auth_ha1);
77
	}
78 79
}

80
BelleSipLimeManager::BelleSipLimeManager (const string &db_access, belle_http_provider_t *prov, LinphoneCore *lc) : LimeManager(db_access, [prov, lc](const string &url, const string &from, const vector<uint8_t> &message, const lime::limeX3DHServerResponseProcess &responseProcess) {
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
	belle_http_request_listener_callbacks_t cbs= {};
	belle_http_request_listener_t *l;
	belle_generic_uri_t *uri;
	belle_http_request_t *req;
	belle_sip_memory_body_handler_t *bh;

	bh = belle_sip_memory_body_handler_new_copy_from_buffer(message.data(), message.size(), NULL, NULL);
	uri=belle_generic_uri_parse(url.data());
	req=belle_http_request_create("POST", uri,
			belle_http_header_create("User-Agent", "lime"),
			belle_http_header_create("Content-type", "x3dh/octet-stream"),
			belle_http_header_create("From", from.data()),
			NULL);

	belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req),BELLE_SIP_BODY_HANDLER(bh));
	cbs.process_response = processResponse;
	cbs.process_io_error = processIoError;
98 99
	cbs.process_auth_requested = processAuthRequested;
	X3DHServerPostContext *userData = new X3DHServerPostContext(responseProcess, from, lc);
100
	l=belle_http_request_listener_create_from_callbacks(&cbs, userData);
101
	belle_sip_object_data_set(BELLE_SIP_OBJECT(req), "http_request_listener", l, belle_sip_object_unref);
102 103 104 105
	belle_http_provider_send_request(prov,req,l);
}) {
}

Ghislain MARY's avatar
Ghislain MARY committed
106
LimeV2::LimeV2 (const std::string &db_access, belle_http_provider_t *prov, LinphoneCore *lc) {
107
	engineType = EncryptionEngineListener::EngineType::LimeV2;
108
	x3dhServerUrl = linphone_config_get_string(linphone_core_get_config(lc), "misc", "x3dh_server_url", "");
109
	curve = lime::CurveId::c25519; // c448
110
	belleSipLimeManager = unique_ptr<BelleSipLimeManager>(new BelleSipLimeManager(db_access, prov, lc));
111
	lastLimeUpdate = linphone_config_get_int(lc->config, "misc", "last_lime_update_time", 0);
112 113
}

114 115 116 117 118 119 120 121
string LimeV2::getX3dhServerUrl () const {
	return x3dhServerUrl;
}

lime::CurveId LimeV2::getCurveId () const {
	return curve;
}

122
ChatMessageModifier::Result LimeV2::processOutgoingMessage (const shared_ptr<ChatMessage> &message, int &errorCode) {
123 124
	// We use a shared ptr here due to non synchronism with the lambda in the encrypt method
	shared_ptr<ChatMessageModifier::Result> result =  make_shared<ChatMessageModifier::Result>(ChatMessageModifier::Result::Suspended);
125
    shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
126
	const string &localDeviceId = chatRoom->getLocalAddress().asString();
127 128
	const IdentityAddress &peerAddress = chatRoom->getPeerAddress();
	shared_ptr<const string> recipientUserId = make_shared<const string>(peerAddress.getAddressWithoutGruu().asString());
129

130
	// Add participants to the recipient list
131
	bool isMultidevice = FALSE;
132
	auto recipients = make_shared<vector<lime::RecipientData>>();
133
	const list<shared_ptr<Participant>> participants = chatRoom->getParticipants();
134
	for (const shared_ptr<Participant> &participant : participants) {
135
		int nbDevice = 0;
136 137
		const list<shared_ptr<ParticipantDevice>> devices = participant->getPrivate()->getDevices();
		for (const shared_ptr<ParticipantDevice> &device : devices) {
138
			nbDevice++;
139 140
			recipients->emplace_back(device->getAddress().asString());
		}
141
		if (nbDevice > 1) isMultidevice = TRUE;
142 143 144 145 146 147 148
	}

	// Add potential other devices of the sender
	const list<shared_ptr<ParticipantDevice>> senderDevices = chatRoom->getMe()->getPrivate()->getDevices();
	for (const auto &senderDevice : senderDevices) {
		if (senderDevice->getAddress() != chatRoom->getLocalAddress()) {
			recipients->emplace_back(senderDevice->getAddress().asString());
149
			isMultidevice = TRUE;
150 151 152
		}
	}

153 154 155 156 157 158
	// TODO warning when multiple devices for the same participant
	if (isMultidevice) {
		// TODO add policies to adapt behaviour when multiple devices
		lWarning() << "Sending encrypted message to multidevice participant";
	}

159 160
	const string &plainStringMessage = message->getInternalContent().getBodyAsUtf8String();
	shared_ptr<const vector<uint8_t>> plainMessage = make_shared<const vector<uint8_t>>(plainStringMessage.begin(), plainStringMessage.end());
161
	shared_ptr<vector<uint8_t>> cipherMessage = make_shared<vector<uint8_t>>();
162

163
	try {
164
		belleSipLimeManager->encrypt(localDeviceId, recipientUserId, recipients, plainMessage, cipherMessage, [localDeviceId, recipients, cipherMessage, message, result] (lime::CallbackReturn returnCode, string errorMessage) {
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
			if (returnCode == lime::CallbackReturn::success) {
				list<Content *> contents;

				// ---------------------------------------------- SIPFRAG

				Content *sipfrag = new Content();
				sipfrag->setBody(localDeviceId); // "From: " +
				sipfrag->setContentType(ContentType::SipFrag);
				contents.push_back(move(sipfrag));

				// ---------------------------------------------- HEADERS

				for (const auto &recipient : *recipients) {
					vector<uint8_t> encodedCipher = encodeBase64(recipient.DRmessage);
					vector<char> cipherHeaderB64(encodedCipher.begin(), encodedCipher.end());
					Content *cipherHeader = new Content();
					cipherHeader->setBody(cipherHeaderB64);
					cipherHeader->setContentType(ContentType::LimeKey);
					cipherHeader->addHeader("Content-Id", recipient.deviceId);
					Header contentDescription("Content-Description", "Cipher key");
					cipherHeader->addHeader(contentDescription);
					contents.push_back(move(cipherHeader));
				}
188

189
				// ---------------------------------------------- MESSAGE
190

191 192 193 194 195 196 197 198
				const vector<uint8_t> *binaryCipherMessage = cipherMessage.get();
				vector<uint8_t> encodedMessage = encodeBase64(*binaryCipherMessage);
				vector<char> cipherMessageB64(encodedMessage.begin(), encodedMessage.end());
				Content *cipherMessage = new Content();
				cipherMessage->setBody(cipherMessageB64);
				cipherMessage->setContentType(ContentType::OctetStream);
				cipherMessage->addHeader("Content-Description", "Encrypted message");
				contents.push_back(move(cipherMessage));
199

200
				Content finalContent = ContentManager::contentListToMultipart(contents, MultipartBoundary, true);
201

202
				message->setInternalContent(finalContent);
DanmeiChen's avatar
DanmeiChen committed
203
				message->getPrivate()->send(); // seems to leak when called for the second time
204
				*result = ChatMessageModifier::Result::Done;
205

206 207 208 209 210
				// TODO can be improved
				for (const auto &content : contents) {
					delete content;
				}
			} else {
211
				lError() << "Lime operation failed: " << errorMessage;
212
				*result = ChatMessageModifier::Result::Error;
213
			}
214 215
		}, lime::EncryptionPolicy::cipherMessage);
	} catch (const exception &e) {
216
		lError() << "test" << " while encrypting message";
217
		*result = ChatMessageModifier::Result::Error;
218
	}
Matthieu Tanon's avatar
Matthieu Tanon committed
219

220
	return *result;
221 222
}

223
ChatMessageModifier::Result LimeV2::processIncomingMessage (const shared_ptr<ChatMessage> &message, int &errorCode) {
Matthieu Tanon's avatar
Matthieu Tanon committed
224
	const shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
225
	const string &localDeviceId = chatRoom->getLocalAddress().asString();
226
	const string &recipientUserId = chatRoom->getPeerAddress().getAddressWithoutGruu().asString();
227

228
	// Get message internal content if there is one
229 230
	Content internalContent;
	message->getContents();
231
	if (message->getInternalContent().isEmpty()) {
232
		lError() << "LIMEv2 no internal content";
233
		if (message->getContents().front()->isEmpty()) {
234
			lError() << "LIMEv2 no content in received message";
235
		}
236
		internalContent = *message->getContents().front();
237
	}
238
	internalContent = message->getInternalContent();
239

240
	// Check if message if encrypted and unwrap the multipart
241 242 243
	ContentType expectedContentType = ContentType::Encrypted;
	expectedContentType.addParameter("boundary", MultipartBoundary);
	if (internalContent.getContentType() != expectedContentType) {
244
		lError() << "LIMEv2 unexpected content-type: " << internalContent.getContentType();
245 246 247
		return ChatMessageModifier::Result::Error;
	}
	list<Content> contentList = ContentManager::multipartToContentList(internalContent);
248

249 250 251 252 253 254 255 256 257 258 259 260
	// ---------------------------------------------- SIPFRAG

	const string &senderDeviceId = [contentList]() {
		string senderDeviceId;
		for (const auto &content : contentList) {
			if (content.getContentType() != ContentType::SipFrag)
				continue;
			senderDeviceId = content.getBodyAsUtf8String();
			const string &result = senderDeviceId;
			return result;
		}
		// TODO return nothing or null value
261
		lError() << "LIMEv2 no sipfrag found";
262 263 264 265
		const string &result = senderDeviceId;
		return result;
	}();

266
	// ---------------------------------------------- HEADERS
267

268
	const vector<uint8_t> &cipherHeader = [contentList, localDeviceId]() {
269
		for (const auto &content : contentList) {
270 271 272
			if (content.getContentType() != ContentType::LimeKey)
				continue;

273 274 275 276 277 278 279 280 281 282 283 284 285
			// TODO workaround because GRUU is parsed as a parameter by content-manager
			Header headerDeviceId = content.getHeader("Content-Id");
			list<HeaderParam> params = headerDeviceId.getParameters();
			HeaderParam gruuParam;
			for (const auto &param : params) {
				if (param.getName() == "gr")
					gruuParam = param;
			}

			const string &recomposedGruu = headerDeviceId.getValue() + gruuParam.asString();
			if (recomposedGruu == localDeviceId) {
				const vector<uint8_t> &cipherHeader = vector<uint8_t>(content.getBody().begin(), content.getBody().end());
				return cipherHeader;
286 287
			}
		}
288
		// TODO return nothing or null value
289
		lError() << "LIMEv2 no cipher header found";
290
		const vector<uint8_t> cipherHeader;
291 292 293
		return cipherHeader;
	}();

294 295
	// ---------------------------------------------- MESSAGE

296
	const vector<uint8_t> &cipherMessage = [contentList]() {
297
		for (const auto &content : contentList) {
298
			if (content.getContentType() == ContentType::OctetStream) {
299 300 301 302
				const vector<uint8_t> &cipherMessage = vector<uint8_t>(content.getBody().begin(), content.getBody().end());
				return cipherMessage;
			}
		}
303
		// TODO return nothing or null value
304
		lError() << "LIMEv2 no cipher message found";
305
		const vector<uint8_t> cipherMessage;
306 307 308
		return cipherMessage;
	}();

309 310
	vector<uint8_t> decodedCipherHeader = decodeBase64(cipherHeader);
	vector<uint8_t> decodedCipherMessage = decodeBase64(cipherMessage);
311 312
	vector<uint8_t> plainMessage{};

313
	lime::PeerDeviceStatus peerDeviceStatus = lime::PeerDeviceStatus::fail;
314
	try {
315
		 peerDeviceStatus = belleSipLimeManager->decrypt(localDeviceId, recipientUserId, senderDeviceId, decodedCipherHeader, decodedCipherMessage, plainMessage);
316
	} catch (const exception &e) {
317
		lError() << e.what() << " while decrypting message";
318
	}
319

320 321
	if (peerDeviceStatus == lime::PeerDeviceStatus::fail) lError() << "Failed to decrypt message from " << senderDeviceId;

322
	// Prepare decrypted message for next modifier
323 324
	string plainMessageString(plainMessage.begin(), plainMessage.end());
	Content finalContent;
325
	ContentType finalContentType = ContentType::Cpim; // TODO should be the content-type of the decrypted message
326 327
	finalContent.setContentType(finalContentType);
	finalContent.setBodyFromUtf8(plainMessageString);
328 329
	message->setInternalContent(finalContent);

330 331 332
	// Set the contact in sipfrag as the authenticatedFromAddress for sender authentication
	IdentityAddress sipfragAddress(senderDeviceId);
	message->getPrivate()->setAuthenticatedFromAddress(sipfragAddress);
333

Matthieu Tanon's avatar
Matthieu Tanon committed
334
	// Test errorCode
335
	return ChatMessageModifier::Result::Done;
336 337
}

338 339 340 341 342 343
void LimeV2::update (LinphoneConfig *lpconfig) {
	lime::limeCallback callback = setLimeCallback("Keys update");
	belleSipLimeManager->update(callback);
	lp_config_set_int(lpconfig, "misc", "last_lime_update_time", (int)lastLimeUpdate);
}

344
bool LimeV2::encryptionEnabledForFileTransferCb (const shared_ptr<AbstractChatRoom> &chatRoom) {
345
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
346
	return false;
347 348
}

349
void LimeV2::generateFileTransferKeyCb (const shared_ptr<AbstractChatRoom> &chatRoom, const shared_ptr<ChatMessage> &message) {
350
	// TODO Work in progress
351 352
}

353
int LimeV2::downloadingFileCb (const shared_ptr<ChatMessage> &message, size_t offset, const uint8_t *buffer, size_t size, uint8_t *decrypted_buffer) {
354
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
355
	return 0;
356 357
}

358
int LimeV2::uploadingFileCb (const shared_ptr<ChatMessage> &message, size_t offset, const uint8_t *buffer, size_t size, uint8_t *encrypted_buffer) {
359
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
360
	return 0;
361 362
}

363 364 365
EncryptionEngineListener::EngineType LimeV2::getEngineType () {
	return engineType;
}
366

367
AbstractChatRoom::SecurityLevel LimeV2::getSecurityLevel (string deviceId) const {
368 369 370
	lime::PeerDeviceStatus status = belleSipLimeManager->get_peerDeviceStatus(deviceId);
	switch (status) {
		case lime::PeerDeviceStatus::unknown:
371
			return AbstractChatRoom::SecurityLevel::Encrypted;
372
		case lime::PeerDeviceStatus::untrusted:
373
			return AbstractChatRoom::SecurityLevel::Encrypted;
374
		case lime::PeerDeviceStatus::trusted:
375
			return AbstractChatRoom::SecurityLevel::Safe;
376
		default:
377
			return AbstractChatRoom::SecurityLevel::Unsafe;
378 379 380
	}
}

381
void LimeV2::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
382
	// TODO Work in progress
383 384
}

385 386 387 388
std::shared_ptr<BelleSipLimeManager> LimeV2::getLimeManager () {
	return belleSipLimeManager;
}

389
lime::limeCallback LimeV2::setLimeCallback (string operation) {
390 391
	lime::limeCallback callback([operation](lime::CallbackReturn returnCode, string anythingToSay) {
		if (returnCode == lime::CallbackReturn::success) {
392
			lInfo() << "Lime operation successful: " << operation;
393
		} else {
394
			lInfo() << "Lime operation failed: " << operation;
395 396
		}
	});
397
	return callback;
398 399
}

400 401
void LimeV2::onRegistrationStateChanged (LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const string &message) {
	if (state == LinphoneRegistrationState::LinphoneRegistrationOk) {
402

Matthieu Tanon's avatar
Matthieu Tanon committed
403
		char *contactAddress = linphone_address_as_string_uri_only(linphone_proxy_config_get_contact(cfg));
404 405
		IdentityAddress identityAddress = IdentityAddress(contactAddress);
		string localDeviceId = identityAddress.asString();
Matthieu Tanon's avatar
Matthieu Tanon committed
406 407
		if (contactAddress)
			ms_free(contactAddress);
408

409 410 411 412
		stringstream operation;
		operation << "create user " << localDeviceId;
		lime::limeCallback callback = setLimeCallback(operation.str());

413
		LinphoneConfig *lpconfig = linphone_core_get_config(linphone_proxy_config_get_core(cfg));
414
		lastLimeUpdate = linphone_config_get_int(lpconfig, "misc", "last_lime_update_time", -1); // TODO should be done by the tester
415

416
		try {
417
			// create user if not exist
418
			belleSipLimeManager->create_user(localDeviceId, x3dhServerUrl, curve, callback);
419
			lastLimeUpdate = ms_time(NULL);
420
			lp_config_set_int(lpconfig, "misc", "last_lime_update_time", (int)lastLimeUpdate);
421
		} catch (const exception &e) {
422
			lInfo() << e.what() << " while creating lime user";
423

424
			// update keys if necessary
425 426
			int limeUpdateThreshold = lp_config_get_int(lpconfig, "misc", "lime_update_threshold", 86400);
			if (ms_time(NULL) - lastLimeUpdate > limeUpdateThreshold) { // 24 hours = 86400 ms
427 428 429 430
				update(lpconfig);
				lastLimeUpdate = ms_time(NULL);
			} else {
			}
431
		}
432 433 434
	}
}

435
LINPHONE_END_NAMESPACE