lime-v2.cpp 17.5 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 73
	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);
		belle_sip_auth_event_set_username(event, auth_username);
74
		belle_sip_auth_event_set_passwd(event, auth_password);
75
	}
76 77
}

78
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) {
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
	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;
96 97
	cbs.process_auth_requested = processAuthRequested;
	X3DHServerPostContext *userData = new X3DHServerPostContext(responseProcess, from, lc);
98
	l=belle_http_request_listener_create_from_callbacks(&cbs, userData);
99
	belle_sip_object_data_set(BELLE_SIP_OBJECT(req), "http_request_listener", l, belle_sip_object_unref);
100 101 102 103
	belle_http_provider_send_request(prov,req,l);
}) {
}

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

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

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

121
ChatMessageModifier::Result LimeV2::processOutgoingMessage (const shared_ptr<ChatMessage> &message, int &errorCode) {
122
	ChatMessageModifier::Result result = ChatMessageModifier::Result::Suspended;
Matthieu Tanon's avatar
Matthieu Tanon committed
123

124 125
	cout << endl << "[ENCRYPT]" << endl;

126
    shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
127
	const string &localDeviceId = chatRoom->getLocalAddress().asString();
128
	const IdentityAddress &peerAddress = chatRoom->getPeerAddress();
129

130
	shared_ptr<const string> recipientUserId = make_shared<const string>(peerAddress.getAddressWithoutGruu().asString());
131

132
	// Add participants to the recipient list
133
	auto recipients = make_shared<vector<lime::RecipientData>>();
134
	const list<shared_ptr<Participant>> participants = chatRoom->getParticipants();
135 136 137 138 139 140 141 142 143 144 145 146
	for (const shared_ptr<Participant> &participant : participants) {
		const list<shared_ptr<ParticipantDevice>> devices = participant->getPrivate()->getDevices();
		for (const shared_ptr<ParticipantDevice> &device : devices) {
			recipients->emplace_back(device->getAddress().asString());
		}
	}

	// 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());
147 148 149
		}
	}

150 151
	const string &plainStringMessage = message->getInternalContent().getBodyAsUtf8String();
	shared_ptr<const vector<uint8_t>> plainMessage = make_shared<const vector<uint8_t>>(plainStringMessage.begin(), plainStringMessage.end());
152
	shared_ptr<vector<uint8_t>> cipherMessage = make_shared<vector<uint8_t>>();
153

154
	belleSipLimeManager->encrypt(localDeviceId, recipientUserId, recipients, plainMessage, cipherMessage, [localDeviceId, recipients, cipherMessage, message, &result] (lime::CallbackReturn returnCode, string errorMessage) {
155
		if (returnCode == lime::CallbackReturn::success) {
156
			list<Content *> contents;
157

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
			for (const auto &recipient : *recipients) {
				cout << "recipient " << recipient.deviceId << " status = ";
				switch (recipient.peerStatus) {
					case lime::PeerDeviceStatus::unknown:
						BCTBX_SLOGI << "LIMEv2 peer device unkown";
						cout << "unknown" << endl;
						break;
					case lime::PeerDeviceStatus::untrusted:
						BCTBX_SLOGI << "LIMEv2 peer device untrusted";
						cout << "untrusted" << endl;
						break;
					case lime::PeerDeviceStatus::trusted:
						BCTBX_SLOGI << "LIMEv2 peer device trusted";
						cout << "trusted" << endl;
						break;
					default:
						break;
				}
			}

178 179 180
			// ---------------------------------------------- SIPFRAG

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

185
			// ---------------------------------------------- HEADERS
186

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

199 200
			// ---------------------------------------------- MESSAGE

201 202 203
			const vector<uint8_t> *binaryCipherMessage = cipherMessage.get();
			vector<uint8_t> encodedMessage = encodeBase64(*binaryCipherMessage);
			vector<char> cipherMessageB64(encodedMessage.begin(), encodedMessage.end());
204 205 206 207 208
			Content *cipherMessage = new Content();
			cipherMessage->setBody(cipherMessageB64);
			cipherMessage->setContentType(ContentType::OctetStream);
			cipherMessage->addHeader("Content-Description", "Encrypted message");
			contents.push_back(move(cipherMessage));
209

210
			Content finalContent = ContentManager::contentListToMultipart(contents, MultipartBoundary, true);
211

212
			message->setInternalContent(finalContent);
213 214
			message->send();
			result = ChatMessageModifier::Result::Done;
215 216 217 218 219

			// TODO can be improved
			for (const auto &content : contents) {
				delete content;
			}
220
		} else {
221
			BCTBX_SLOGE << "Lime operation failed: " << errorMessage;
222
			result = ChatMessageModifier::Result::Error;
223
		}
224
	}, lime::EncryptionPolicy::cipherMessage);
Matthieu Tanon's avatar
Matthieu Tanon committed
225

226
	return result;
227 228
}

229
ChatMessageModifier::Result LimeV2::processIncomingMessage (const shared_ptr<ChatMessage> &message, int &errorCode) {
Matthieu Tanon's avatar
Matthieu Tanon committed
230
	const shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
231
	const string &localDeviceId = chatRoom->getLocalAddress().asString();
232
	const string &recipientUserId = chatRoom->getPeerAddress().getAddressWithoutGruu().asString();
233

234 235
	cout << endl << "[DECRYPT]" << endl;

236 237
	Content internalContent;
	message->getContents();
238
	if (message->getInternalContent().isEmpty()) {
239
		BCTBX_SLOGE << "LIMEv2 no internal content";
240
		if (message->getContents().front()->isEmpty()) {
241
			BCTBX_SLOGE << "LIMEv2 no content in received message";
242
		}
243
		internalContent = *message->getContents().front();
244
	}
245
	internalContent = message->getInternalContent();
246

247 248 249
	ContentType expectedContentType = ContentType::Encrypted;
	expectedContentType.addParameter("boundary", MultipartBoundary);
	if (internalContent.getContentType() != expectedContentType) {
250
		BCTBX_SLOGE << "LIMEv2 unexpected content-type: " << internalContent.getContentType();
251 252 253
		return ChatMessageModifier::Result::Error;
	}
	list<Content> contentList = ContentManager::multipartToContentList(internalContent);
254

255 256 257 258 259 260 261 262 263 264 265 266
	// ---------------------------------------------- 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
267
		BCTBX_SLOGE << "LIMEv2 no sipfrag found";
268 269 270 271
		const string &result = senderDeviceId;
		return result;
	}();

272
	// ---------------------------------------------- HEADERS
273

274
	const vector<uint8_t> &cipherHeader = [contentList, localDeviceId]() {
275
		for (const auto &content : contentList) {
276 277 278
			if (content.getContentType() != ContentType::LimeKey)
				continue;

279 280 281 282 283 284 285 286 287 288 289 290 291
			// 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;
292 293
			}
		}
294
		// TODO return nothing or null value
295
		BCTBX_SLOGE << "LIMEv2 no cipher header found";
296
		const vector<uint8_t> cipherHeader;
297 298 299
		return cipherHeader;
	}();

300 301
	// ---------------------------------------------- MESSAGE

302
	const vector<uint8_t> &cipherMessage = [contentList]() {
303
		for (const auto &content : contentList) {
304
			if (content.getContentType() == ContentType::OctetStream) {
305 306 307 308
				const vector<uint8_t> &cipherMessage = vector<uint8_t>(content.getBody().begin(), content.getBody().end());
				return cipherMessage;
			}
		}
309
		// TODO return nothing or null value
310
		BCTBX_SLOGE << "LIMEv2 no cipher message found";
311
		const vector<uint8_t> cipherMessage;
312 313 314
		return cipherMessage;
	}();

315 316
	vector<uint8_t> decodedCipherHeader = decodeBase64(cipherHeader);
	vector<uint8_t> decodedCipherMessage = decodeBase64(cipherMessage);
317 318
	vector<uint8_t> plainMessage{};

319
	lime::PeerDeviceStatus peerStatus = lime::PeerDeviceStatus::fail;
320
	try {
321
		 peerStatus = belleSipLimeManager->decrypt(localDeviceId, recipientUserId, senderDeviceId, decodedCipherHeader, decodedCipherMessage, plainMessage);
322
	} catch (const exception &e) {
323
		ms_message("%s while decrypting message\n", e.what());
324 325
	}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
	cout << "decrypt status = ";
	switch (peerStatus) {
		case lime::PeerDeviceStatus::unknown:
			BCTBX_SLOGI << "LIMEv2 peer device unkown";
			cout << "unknown" << endl;
			break;
		case lime::PeerDeviceStatus::untrusted:
			BCTBX_SLOGI << "LIMEv2 peer device untrusted";
			cout << "untrusted" << endl;
			break;
		case lime::PeerDeviceStatus::trusted:
			BCTBX_SLOGI << "LIMEv2 peer device trusted";
			cout << "trusted" << endl;
			break;
		case lime::PeerDeviceStatus::fail:
			BCTBX_SLOGE << "LIMEv2 decryption failure";
			cout << "fail" << endl;
			return ChatMessageModifier::Result::Error;
	}
345 346 347 348

	string plainMessageString(plainMessage.begin(), plainMessage.end());

	Content finalContent;
349
	ContentType finalContentType = ContentType::Cpim; // TODO should be the content-type of the decrypted message
350 351
	finalContent.setContentType(finalContentType);
	finalContent.setBodyFromUtf8(plainMessageString);
352 353
	message->setInternalContent(finalContent);

354 355 356
	// Set the contact in sipfrag as the authenticatedFromAddress for sender authentication
	IdentityAddress sipfragAddress(senderDeviceId);
	message->getPrivate()->setAuthenticatedFromAddress(sipfragAddress);
357

Matthieu Tanon's avatar
Matthieu Tanon committed
358
	// Test errorCode
359
	return ChatMessageModifier::Result::Done;
360 361
}

362 363 364 365 366 367
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);
}

368
bool LimeV2::encryptionEnabledForFileTransferCb (const shared_ptr<AbstractChatRoom> &chatRoom) {
369
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
370
	return false;
371 372
}

373
void LimeV2::generateFileTransferKeyCb (const shared_ptr<AbstractChatRoom> &chatRoom, const shared_ptr<ChatMessage> &message) {
374
	// TODO Work in progress
375 376
}

377
int LimeV2::downloadingFileCb (const shared_ptr<ChatMessage> &message, size_t offset, const uint8_t *buffer, size_t size, uint8_t *decrypted_buffer) {
378
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
379
	return 0;
380 381
}

382
int LimeV2::uploadingFileCb (const shared_ptr<ChatMessage> &message, size_t offset, const uint8_t *buffer, size_t size, uint8_t *encrypted_buffer) {
383
	// TODO Work in progress
Matthieu Tanon's avatar
Matthieu Tanon committed
384
	return 0;
385 386
}

387 388 389
EncryptionEngineListener::EngineType LimeV2::getEngineType () {
	return engineType;
}
390
void LimeV2::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
391
	// TODO Work in progress
392 393
}

394 395 396 397
std::shared_ptr<BelleSipLimeManager> LimeV2::getLimeManager () {
	return belleSipLimeManager;
}

398
lime::limeCallback LimeV2::setLimeCallback (string operation) {
399 400
	lime::limeCallback callback([operation](lime::CallbackReturn returnCode, string anythingToSay) {
		if (returnCode == lime::CallbackReturn::success) {
401
			BCTBX_SLOGI << "Lime operation successful: " << operation;
402
		} else {
403
			BCTBX_SLOGE << "Lime operation failed: " << anythingToSay;
404 405
		}
	});
406
	return callback;
407 408
}

409 410
void LimeV2::onRegistrationStateChanged (LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const string &message) {
	if (state == LinphoneRegistrationState::LinphoneRegistrationOk) {
411

Matthieu Tanon's avatar
Matthieu Tanon committed
412 413
		char *contactAddress = linphone_address_as_string_uri_only(linphone_proxy_config_get_contact(cfg));
		IdentityAddress ia = IdentityAddress(contactAddress);
414
		IdentityAddress ia = IdentityAddress(linphone_address_as_string_uri_only(linphone_proxy_config_get_contact(cfg)));
415
		string localDeviceId = ia.asString();
Matthieu Tanon's avatar
Matthieu Tanon committed
416 417
		if (contactAddress)
			ms_free(contactAddress);
418

419 420 421 422
		stringstream operation;
		operation << "create user " << localDeviceId;
		lime::limeCallback callback = setLimeCallback(operation.str());

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

426
		try {
427
			// create user if not exist
428
			belleSipLimeManager->create_user(localDeviceId, x3dhServerUrl, curve, callback);
429
			lastLimeUpdate = ms_time(NULL);
430
			lp_config_set_int(lpconfig, "misc", "last_lime_update_time", (int)lastLimeUpdate);
431
		} catch (const exception &e) {
432
			ms_message("%s while creating lime user\n", e.what());
433

434
			// update keys if necessary
435 436
			int limeUpdateThreshold = lp_config_get_int(lpconfig, "misc", "lime_update_threshold", 86400);
			if (ms_time(NULL) - lastLimeUpdate > limeUpdateThreshold) { // 24 hours = 86400 ms
437 438 439 440
				update(lpconfig);
				lastLimeUpdate = ms_time(NULL);
			} else {
			}
441
		}
442 443 444
	}
}

445
LINPHONE_END_NAMESPACE