lime_localStorage.cpp 47.4 KB
Newer Older
johan's avatar
johan committed
1 2
/*
	lime_x3dh.cpp
johan's avatar
johan committed
3 4
	@author Johan Pascal
	@copyright	Copyright (C) 2017  Belledonne Communications SARL
johan's avatar
johan committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

	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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define BCTBX_LOG_DOMAIN "lime"
#include <bctoolbox/logging.h>

#include "lime/lime.hpp"
#include "lime_localStorage.hpp"
#include "lime_double_ratchet.hpp"
#include "lime_impl.hpp"
#include "bctoolbox/exception.hh"

#include "soci/sqlite3/soci-sqlite3.h"
using namespace::std;
using namespace::soci;
using namespace::lime;

namespace lime {

johan's avatar
johan committed
39 40 41 42 43 44 45 46 47 48
/******************************************************************************/
/*                                                                            */
/* Db public API                                                              */
/*                                                                            */
/******************************************************************************/
/**
 * @brief Constructor, open and check DB validity, create or update db schema is needed
 *
 * @param[in]	filename	The path to DB file
 */
49
Db::Db(string filename) : sql{sqlite3, filename}{
50 51 52
	constexpr int db_module_table_not_holding_lime_row = -1;

	int userVersion=db_module_table_not_holding_lime_row;
Johan Pascal's avatar
Johan Pascal committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
	sql<<"PRAGMA foreign_keys = ON;"; // make sure this connection enable foreign keys
	transaction tr(sql);
	// CREATE OR INGORE TABLE db_module_version(
	sql<<"CREATE TABLE IF NOT EXISTS db_module_version("
		"name VARCHAR(16) PRIMARY KEY,"
		"version UNSIGNED INTEGER NOT NULL"
		")";
	sql<<"SELECT version FROM db_module_version WHERE name='lime'", into(userVersion);

	// Enforce value in case there is no lime version number in table db_module_version
	if (!sql.got_data()) {
		userVersion=db_module_table_not_holding_lime_row;
	}

	if (userVersion == lime::settings::DBuserVersion) {
		return;
	}

	if (userVersion > lime::settings::DBuserVersion) { /* nothing to do if we encounter a superior version number than expected, just hope it is compatible */
		BCTBX_SLOGE<<"Lime module database schema version found in DB(v "<<userVersion<<") is more recent than the one currently supported by the lime module(v "<<static_cast<unsigned int>(lime::settings::DBuserVersion)<<")";
		return;
	}

	/* Perform update if needed */
	// update the schema version in DB
	if (userVersion == db_module_table_not_holding_lime_row) { // but not any lime row in it
		sql<<"INSERT INTO db_module_version(name,version) VALUES('lime',:DbVersion)", use(lime::settings::DBuserVersion);
	} else { // and we had an older version
		sql<<"UPDATE db_module_version SET version = :DbVersion WHERE name='lime'", use(lime::settings::DBuserVersion);
		/* Do the update here */
		tr.commit(); // commit all the previous queries
		return;
	}

	// create the lime DB:

	/*** Double Ratchet tables ***/
	/* DR Session:
	*  - DId : link to lime_PeerDevices table, identify which peer is associated to this session
	*  - Uid: link to LocalUsers table, identify which local device is associated to this session
	*  - SessionId(primary key)
	*  - Ns, Nr, PN : index for sending, receivind and previous sending chain
	*  - DHr : peer current public ECDH key
	*  - DHs : self current ECDH key. (public || private keys)
	*  - RK, CKs, CKr : Root key, sender and receiver chain keys
	*  - AD : Associated data : provided once at session creation by X3DH, is derived from initiator public Ik and id, receiver public Ik and id
	*  - Status : 0 is for stale and 1 is for active, only one session shall be active for a peer device, by default created as active
	*  - timeStamp : is updated when session change status and is used to remove stale session after determined time in cleaning operation
	*  - X3DHInit : when we are initiator, store the generated X3DH init message and keep sending it until we've got at least a reply from peer
	*/
	sql<<"CREATE TABLE DR_sessions( \
				Did INTEGER NOT NULL DEFAULT 0, \
				Uid INTEGER NOT NULL DEFAULT 0, \
				sessionId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
				Ns UNSIGNED INTEGER NOT NULL, \
				Nr UNSIGNED INTEGER NOT NULL, \
				PN UNSIGNED INTEGER NOT NULL, \
				DHr BLOB NOT NULL, \
				DHs BLOB NOT NULL, \
				RK BLOB NOT NULL, \
				CKs BLOB NOT NULL, \
				CKr BLOB NOT NULL, \
				AD BLOB NOT NULL, \
				Status INTEGER NOT NULL DEFAULT 1, \
				timeStamp DATETIME DEFAULT CURRENT_TIMESTAMP, \
				X3DHInit BLOB DEFAULT NULL, \
				FOREIGN KEY(Did) REFERENCES lime_PeerDevices(Did) ON UPDATE CASCADE ON DELETE CASCADE, \
				FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";

	/* DR Message Skipped DH : Store chains of skipped message keys, this table store the DHr identifying the chain
	*  - DHid(primary key)
	*  - SessionId : foreign key, link to the DR session the skipped keys are attached
	*  - DHr : the peer ECDH public key used in this key chain
	*  - received : count messages successfully decoded since the last MK insertion in that chain, allow to delete chains that are too old
	*/
	sql<<"CREATE TABLE DR_MSk_DHr( \
				DHid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
				sessionId INTEGER NOT NULL DEFAULT 0, \
				DHr BLOB NOT NULL, \
				received UNSIGNED INTEGER NOT NULL DEFAULT 0, \
				FOREIGN KEY(sessionId) REFERENCES DR_sessions(sessionId) ON UPDATE CASCADE ON DELETE CASCADE);";

	/* DR Message Skipped MK : Store chains of skipped message keys, this table store the message keys with their index in the chain
	*  - DHid : foreign key, link to the key chain table: DR_Message_Skipped_DH
	*  - Nr : the id in the key chain
	*  - MK : the message key stored
	*  primary key is [DHid,Nr]
	*/
	sql<<"CREATE TABLE DR_MSk_MK( \
				DHid INTEGER NOT NULL, \
				Nr INTEGER NOT NULL, \
				MK BLOB NOT NULL, \
				PRIMARY KEY( DHid , Nr ), \
				FOREIGN KEY(DHid) REFERENCES DR_MSk_DHr(DHid) ON UPDATE CASCADE ON DELETE CASCADE);";

	/*** Lime tables : local user identities, peer devices identities ***/
	/* List each self account enable on device :
	*  - Uid : primary key, used to make link with Peer Devices, SPk and OPk tables
	*  - User Id : shall be the GRUU
	*  - Ik : public||private indentity key (EdDSA key)
	*  - server : the URL of key Server
	*  - curveId : identifies the curve used by this user - MUST be in sync with server
	*/
	sql<<"CREATE TABLE lime_LocalUsers( \
				Uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
				UserId TEXT NOT NULL, \
				Ik BLOB NOT NULL, \
				server TEXT NOT NULL, \
				curveId INTEGER NOT NULL DEFAULT 0);"; // default the curveId value to 0 which is not one of the possible values(defined in lime.hpp)

	/* Peer Devices :
	* - Did : primary key, used to make link with DR_sessions table.
	* - DeviceId: peer device id (shall be its GRUU)
	* - Ik : Peer device Identity public key, got it from X3DH server or X3DH init message
	* - Verified : a flag, 0 : peer identity was not verified, 1 : peer identity confirmed
	*
	* Note: peer device information is shared by all local device, hence they are not linked to particular local devices from lime_LocalUsers table
	*/
	sql<<"CREATE TABLE lime_PeerDevices( \
				Did INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
				DeviceId TEXT NOT NULL, \
				Ik BLOB NOT NULL, \
				Verified UNSIGNED INTEGER DEFAULT 0);";

	/*** X3DH tables ***/
	/* Signed pre-key :
	* - SPKid : the primary key must be a random number as it is public, so avoid leaking information on number of key used
	* - SPK : Public key||Private Key (ECDH keys)
	* - timeStamp : Application shall renew SPK regurlarly(SPK_LifeTime). Old key are disactivated and deleted after a period(SPK_LimboTime))
	* - Status : a boolean: can be active(1) or stale(0), by default any newly inserted key is set to active
	* - Uid : User Id from lime_LocalUsers table: who's key is this
	*/
	sql<<"CREATE TABLE X3DH_SPK( \
				SPKid UNSIGNED INTEGER PRIMARY KEY NOT NULL, \
				SPK BLOB NOT NULL, \
				timeStamp DATETIME DEFAULT CURRENT_TIMESTAMP, \
				Status INTEGER NOT NULL DEFAULT 1, \
				Uid INTEGER NOT NULL, \
				FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";

	/* One time pre-key : deleted after usage, generated at user creation and on X3DH server request
	* - OPKid : the primary key must be a random number as it is public, so avoid leaking information on number of key used
	* - OPK : Public key||Private Key (ECDH keys)
	* - Uid : User Id from lime_LocalUsers table: who's key is this
	* - Status : a boolean: can be published on X3DH Server(1) or not anymore on X3DH server(0), by default any newly inserted key is set to published
	* - timeStamp : timeStamp is set during update if we found out a key is no more on server(and we didn't used it as usage delete key).
	*   		So after a limbo period, key is considered missing in action and removed from storage.
	*/
	sql<<"CREATE TABLE X3DH_OPK( \
				OPKid UNSIGNED INTEGER PRIMARY KEY NOT NULL, \
				OPK BLOB NOT NULL, \
				Uid INTEGER NOT NULL, \
				Status INTEGER NOT NULL DEFAULT 1, \
				timeStamp DATETIME DEFAULT CURRENT_TIMESTAMP, \
				FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";

	tr.commit(); // commit all the previous queries
johan's avatar
johan committed
210
};
johan's avatar
johan committed
211

johan's avatar
johan committed
212 213 214
/**
 * @brief Check for existence, retrieve Uid for local user based on its userId(GRUU) and curve from table lime_LocalUsers
 *
johan's avatar
johan committed
215
 * @param[in]	deviceId	a string holding the user to look for in DB, shall be its GRUU
johan's avatar
johan committed
216 217
 * @param[out]	Uid		the DB internal Id matching given userId(if find in DB, 0 otherwise)
 * @param[out]	curveId		the curve selected at user creation
johan's avatar
johan committed
218
 * @param[out]	url		the url of the X3DH server this user is registered on
johan's avatar
johan committed
219 220
 *
 */
johan's avatar
johan committed
221
void Db::load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url)
johan's avatar
johan committed
222 223
{
	int curve=0;
johan's avatar
johan committed
224
	sql<<"SELECT Uid,CurveId,server FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), into(curve), into(url), use(deviceId);
johan's avatar
johan committed
225 226 227 228 229 230 231 232 233 234 235 236 237 238

	if (sql.got_data()) { // we found someone
		// turn back integer value retrieved from DB into a lime::CurveId
		switch (curve) {
			case static_cast<uint8_t>(lime::CurveId::c25519):
				curveId=lime::CurveId::c25519;
				break;
			case static_cast<uint8_t>(lime::CurveId::c448):
				curveId=lime::CurveId::c448;
				break;
			case static_cast<uint8_t>(lime::CurveId::unset):
			default: // we got an unknow or unset curve Id, DB is either corrupted or a future version
				curveId=lime::CurveId::unset;
				Uid=0;
johan's avatar
johan committed
239
				throw BCTBX_EXCEPTION << "Lime DB either corrupted or back from the future. User "<<deviceId<<" claim to run with unknown or unset Curve Id "<< curve;
johan's avatar
johan committed
240 241 242
		}
	} else { // no match: throw an execption
		Uid = 0; // be sure to reset the db_Uid to 0
johan's avatar
johan committed
243
		throw BCTBX_EXCEPTION << "Cannot find Lime User "<<deviceId<<" in DB";
johan's avatar
johan committed
244 245 246
	}
}

247
/**
johan's avatar
johan committed
248
 * @brief Delete old stale sessions and old stored message key. Apply to all users in localStorage
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
 * 	- DR Session in stale status for more than DRSession_limboTime are deleted
 * 	- MessageKey stored linked to a session who received more than maxMessagesReceivedAfterSkip are deleted
 * 	Note : The messagekeys count is on a chain, so if we have in a chain
 * 	Received1 Skip1 Skip2 Received2 Received3 Skip3 Received4
 * 	The counter will be reset to 0 when we insert Skip3 (when Received4 arrives) so Skip1 and Skip2 won't be deleted until we got the counter above max on this chain
 * 	Once we moved to next chain(as soon as peer got an answer from us and replies), the count won't be reset anymore
 */
void Db::clean_DRSessions() {
	// WARNIMG: not sure this code is portable it may work with sqlite3 only
	// delete stale sessions considered to old
	sql<<"DELETE FROM DR_sessions WHERE Status=0 AND timeStamp < date('now', '-"<<lime::settings::DRSession_limboTime_days<<" day');";

	// clean Message keys (MK will be cascade deleted when the DHr is deleted )
	sql<<"DELETE FROM DR_MSk_DHr WHERE received > "<<lime::settings::maxMessagesReceivedAfterSkip<<";";
}
johan's avatar
johan committed
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

/**
 * @brief Delete old stale SPk. Apply to all users in localStorage
 * 	- SPk in stale status for more than SPK_limboTime_days are deleted
 */
void Db::clean_SPk() {
	// WARNIMG: not sure this code is portable it may work with sqlite3 only
	// delete stale sessions considered to old
	sql<<"DELETE FROM X3DH_SPK WHERE Status=0 AND timeStamp < date('now', '-"<<lime::settings::SPK_limboTime_days<<" day');";
}

/**
 * @brief Get a list of deviceIds of all local users present in localStorage
 *
 * @param[out]	deviceIds	the list of all local users (their device Id)
 */
void Db::get_allLocalDevices(std::vector<std::string> &deviceIds) {
	deviceIds.clear();
	rowset<row> rs = (sql.prepare << "SELECT UserId FROM lime_LocalUsers;");
Johan Pascal's avatar
Johan Pascal committed
283
	for (const auto &r : rs) {
johan's avatar
johan committed
284 285 286 287
		deviceIds.push_back(r.get<string>(0));
	}
}

288 289 290 291 292 293 294 295 296 297 298 299 300
/**
 * @brief set the identity verified flag for peer device
 *
 * @param[in]	peerDeviceId	The device Id of peer, shall be its GRUU
 * @param[in]	Ik		the EdDSA peer public identity key, formatted as in RFC8032
 * @param[in]	status		value of flag to set
 *
 * throw an exception if given key doesn't match the one present in local storage
 * if peer Device is not present in local storage and status is true, it is added, if status is false, it is just ignored
 */
void Db::set_PeerDevicesVerifiedStatus(const std::string &peerDeviceId, const std::vector<uint8_t> &Ik, bool status) {
	// Do we have this peerDevice in lime_PeerDevices
	blob Ik_blob(sql);
301
	long long id;
302
	sql<<"SELECT Did, Ik FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId;", into(id), into(Ik_blob), use(peerDeviceId);
303 304 305 306 307 308
	if (sql.got_data()) { // Found it
		auto IkSize = Ik_blob.get_len();
		std::vector<uint8_t> storedIk;
		storedIk.resize(IkSize);
		Ik_blob.read(0, (char *)(storedIk.data()), IkSize); // Read the public key
		if (storedIk == Ik) {
309
			sql<<"UPDATE Lime_PeerDevices SET Verified = :Verified WHERE Did = :id;", use((status==true)?1:0), use(id);
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
		} else { // Ik in local Storage differs than the one given... raise an exception
			throw BCTBX_EXCEPTION << "Trying to insert an Identity key for peer device "<<peerDeviceId<<" which differs from one already in local storage";
		}
	} else { // peer is not in local Storage
		if (status) { // insert it only if status is true, if false just ignore the request
			blob Ik_insert_blob(sql);
			Ik_insert_blob.write(0, (char *)(Ik.data()), Ik.size());
			sql<<"INSERT INTO Lime_PeerDevices(DeviceId, Ik, Verified) VALUES(:peerDeviceId, :Ik, 1);", use(peerDeviceId), use(Ik_insert_blob);
		}
	}
}

/**
 * @brief get the identity verified flag for peer device
 *
 * @param[in]	peerDeviceId	The device Id of peer, shall be its GRUU
 *
 * @return the stored Verified status, false if peer Device is not present in local Storage
 */
bool Db::get_PeerDevicesIdentityVerifiedStatus(const std::string &peerDeviceId) {
	int verified;
	sql<<"SELECT Verified FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId LIMIT 1;", into(verified), use(peerDeviceId);
	if (sql.got_data()) { // Found it
		if (verified == 1) {
			return true;
		}
		return false; // verified is stored as false
	}

	// peerDeviceId not found in local storage: return false
	return false;
}

johan's avatar
johan committed
343 344 345
/**
 * @brief if exists, delete user
 *
johan's avatar
johan committed
346
 * @param[in]	deviceId	a string holding the user to look for in DB, shall be its GRUU
johan's avatar
johan committed
347 348
 *
 */
johan's avatar
johan committed
349
void Db::delete_LimeUser(const std::string &deviceId)
johan's avatar
johan committed
350
{
johan's avatar
johan committed
351
	sql<<"DELETE FROM lime_LocalUsers WHERE UserId = :userId;", use(deviceId);
johan's avatar
johan committed
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
}

/******************************************************************************/
/*                                                                            */
/* Double ratchet member functions                                            */
/*                                                                            */
/******************************************************************************/
template <typename DHKey>
bool DR<DHKey>::session_save() {

	// open transaction
	transaction tr(m_localStorage->sql);

	// shall we try to insert or update?
	bool MSk_DHr_Clean = false; // flag use to signal the need for late cleaning in DR_MSk_DHr table
	if (m_dbSessionId==0) { // We have no id for this session row, we shall insert a new one
		// Build blobs from DR session
		blob DHr(m_localStorage->sql);
		DHr.write(0, (char *)(m_DHr.data()), m_DHr.size());
371 372 373
		blob DHs(m_localStorage->sql);
		DHs.write(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size()); // DHs holds Public || Private keys in the same field
		DHs.write(m_DHs.publicKey().size(), (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
johan's avatar
johan committed
374 375 376 377 378 379 380 381 382 383
		blob RK(m_localStorage->sql);
		RK.write(0, (char *)(m_RK.data()), m_RK.size());
		blob CKs(m_localStorage->sql);
		CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
		blob CKr(m_localStorage->sql);
		CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());
		/* this one is written in base only at creation and never updated again */
		blob AD(m_localStorage->sql);
		AD.write(0, (char *)(m_sharedAD.data()), m_sharedAD.size());

384 385
		// make sure we have no other session active with this pair local,peer DiD
		m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did AND Uid = :Uid", use(m_peerDid), use(m_db_Uid);
johan's avatar
johan committed
386 387 388 389

		if (m_X3DH_initMessage.size()>0) {
			blob X3DH_initMessage(m_localStorage->sql);
			X3DH_initMessage.write(0, (char *)(m_X3DH_initMessage.data()), m_X3DH_initMessage.size());
390
			m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Did,Uid,X3DHInit) VALUES(:Ns,:Nr,:PN,:DHr,:DHs,:RK,:CKs,:CKr,:AD,:Did,:Uid,:X3DHinit);", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs), use(RK), use(CKs), use(CKr), use(AD), use(m_peerDid), use(m_db_Uid), use(X3DH_initMessage);
johan's avatar
johan committed
391
		} else {
392
			m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Did,Uid) VALUES(:Ns,:Nr,:PN,:DHr,:DHs,:RK,:CKs,:CKr,:AD,:Did,:Uid);", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs), use(RK), use(CKs), use(CKr), use(AD), use(m_peerDid), use(m_db_Uid);
johan's avatar
johan committed
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
		}
		// if insert went well we shall be able to retrieve the last insert id to save it in the Session object
		/*** WARNING: unportable section of code, works only with sqlite3 backend ***/
		m_localStorage->sql<<"select last_insert_rowid()",into(m_dbSessionId);
		/*** above could should work but it doesn't, consistently return false from .get_last_insert_id... ***/
		/*if (!(sql.get_last_insert_id("DR_sessions", m_dbSessionId))) {
			throw;
		} */
	} else { // we have an id, it shall already be in the db
		// Try to update an existing row
		try{ //TODO: make sure the update was a success, or we shall signal it
			switch (m_dirty) {
				case DRSessionDbStatus::dirty: // dirty case shall actually never occurs as a dirty is set only at creation not loading, first save is processed above
				case DRSessionDbStatus::dirty_ratchet: // ratchet&decrypt modifies all but also request to delete X3DHInit from storage
				{
408
					// make sure we have no other session active with this pair local,peer DiD
johan's avatar
johan committed
409
					if (m_active_status == false) {
410
						m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did AND Uid = :Uid", use(m_peerDid), use(m_db_Uid);
johan's avatar
johan committed
411 412 413 414 415 416
						m_active_status = true;
					}

					// Build blobs from DR session
					blob DHr(m_localStorage->sql);
					DHr.write(0, (char *)(m_DHr.data()), m_DHr.size());
417 418 419
					blob DHs(m_localStorage->sql);
					DHs.write(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size()); // DHs holds Public || Private keys in the same field
					DHs.write(m_DHs.publicKey().size(), (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
johan's avatar
johan committed
420 421 422 423 424 425 426
					blob RK(m_localStorage->sql);
					RK.write(0, (char *)(m_RK.data()), m_RK.size());
					blob CKs(m_localStorage->sql);
					CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
					blob CKr(m_localStorage->sql);
					CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());

427
					m_localStorage->sql<<"UPDATE DR_sessions SET Ns= :Ns, Nr= :Nr, PN= :PN, DHr= :DHr,DHs= :DHs, RK= :RK, CKs= :CKs, CKr= :CKr, Status = 1,  X3DHInit = NULL WHERE sessionId = :sessionId;", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs), use(RK), use(CKs), use(CKr), use(m_dbSessionId);
johan's avatar
johan committed
428 429 430 431
				}
					break;
				case DRSessionDbStatus::dirty_decrypt: // decrypt modifies: CKr and Nr. Also set Status to active and clear X3DH init message if there is one(it is actually useless as our first reply from peer shall trigger a ratchet&decrypt)
				{
432
					// make sure we have no other session active with this pair local,peer DiD
johan's avatar
johan committed
433
					if (m_active_status == false) {
434
						m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did AND Uid = :Uid", use(m_peerDid), use(m_db_Uid);
johan's avatar
johan committed
435 436 437 438 439 440 441 442 443 444 445 446
						m_active_status = true;
					}

					blob CKr(m_localStorage->sql);
					CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());
					m_localStorage->sql<<"UPDATE DR_sessions SET Nr= :Nr, CKr= :CKr, Status = 1, X3DHInit = NULL WHERE sessionId = :sessionId;", use(m_Nr), use(CKr), use(m_dbSessionId);
				}
					break;
				case DRSessionDbStatus::dirty_encrypt: // encrypt modifies: CKs and Ns
				{
					blob CKs(m_localStorage->sql);
					CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
447
					m_localStorage->sql<<"UPDATE DR_sessions SET Ns= :Ns, CKs= :CKs, Status = :active_status WHERE sessionId = :sessionId;", use(m_Ns), use(CKs), use((m_active_status==true)?0x01:0x00), use(m_dbSessionId);
johan's avatar
johan committed
448 449
				}
					break;
450 451
				case DRSessionDbStatus::clean: // Session is clean? So why have we been called?
				default:
johan's avatar
johan committed
452
					BCTBX_SLOGE<<"Double ratchet session saved call on sessionId "<<m_dbSessionId<<" but sessions appears to be clean";
johan's avatar
johan committed
453 454 455 456 457 458 459 460 461
					break;
			}
		} catch (...) {
			throw;
		}
		// updatesert went well, do we have any mkskipped row to modify
		if (m_usedDHid !=0 ) { // ok, we consumed a key, remove it from db
			m_localStorage->sql<<"DELETE from DR_MSk_MK WHERE DHid = :DHid AND Nr = :Nr;", use(m_usedDHid), use(m_usedNr);
			MSk_DHr_Clean = true; // flag the cleaning needed in DR_MSk_DH table, we may have to remove a row in it if no more row are linked to it in DR_MSk_MK
462 463 464 465 466
		} else { // we did not consume a key
			if (m_dirty == DRSessionDbStatus::dirty_decrypt || m_dirty == DRSessionDbStatus::dirty_ratchet) { // if we did a message decrypt :
				// update the count of posterior messages received in the stored skipped messages keys for this session(all stored chains)
				m_localStorage->sql<<"UPDATE DR_MSk_DHr SET received = received + 1 WHERE sessionId = :sessionId", use(m_dbSessionId);
			}
johan's avatar
johan committed
467 468 469 470
		}
	}

	// Shall we insert some skipped Message keys?
Johan Pascal's avatar
Johan Pascal committed
471
	for ( const auto &rChain : m_mkskipped) { // loop all chains of message keys, each one is a DHr associated to an unordered map of MK indexed by Nr to be saved
johan's avatar
johan committed
472 473 474 475 476 477 478
		blob DHr(m_localStorage->sql);
		DHr.write(0, (char *)(rChain.DHr.data()), rChain.DHr.size());
		long DHid=0;
		m_localStorage->sql<<"SELECT DHid FROM DR_MSk_DHr WHERE sessionId = :sessionId AND DHr = :DHr LIMIT 1",into(DHid), use(m_dbSessionId), use(DHr);
		if (!m_localStorage->sql.got_data()) { // There is no row in DR_MSk_DHr matching this key, we must add it
			m_localStorage->sql<<"INSERT INTO DR_MSk_DHr(sessionId, DHr) VALUES(:sessionId, :DHr)", use(m_dbSessionId), use(DHr);
			m_localStorage->sql<<"select last_insert_rowid()",into(DHid); // WARNING: unportable code, sqlite3 only, see above for more details on similar issue
479 480
		} else { // the chain already exists in storage, just reset its counter of newer message received
			m_localStorage->sql<<"UPDATE DR_MSk_DHr SET received = 0 WHERE DHid = :DHid", use(DHid);
johan's avatar
johan committed
481 482
		}
		// insert all the skipped key in the chain
483
		uint16_t Nr;
johan's avatar
johan committed
484 485 486 487 488 489 490 491 492 493 494 495
		blob MK(m_localStorage->sql);
		statement st = (m_localStorage->sql.prepare << "INSERT INTO DR_MSk_MK(DHid,Nr,MK) VALUES(:DHid,:Nr,:Mk)", use(DHid), use(Nr), use(MK));

		for (const auto &kv : rChain.messageKeys) { // messageKeys is an unordered map of MK indexed by Nr.
			Nr=kv.first;
			MK.write(0, (char *)kv.second.data(), kv.second.size());
			st.execute(true);
		}
	}

	// Now do the cleaning(remove unused row from DR_MKs_DHr table) if needed
	if (MSk_DHr_Clean == true) {
496
		uint16_t Nr;
johan's avatar
johan committed
497 498 499 500 501 502 503 504 505 506 507 508 509 510
		m_localStorage->sql<<"SELECT Nr from DR_MSk_MK WHERE DHid = :DHid LIMIT 1;", into(Nr), use(m_usedDHid);
		if (!m_localStorage->sql.got_data()) { // no more MK with this DHid, remove it
			m_localStorage->sql<<"DELETE from DR_MSk_DHr WHERE DHid = :DHid;", use(m_usedDHid);
		}
	}

	tr.commit();
	return true;
};

template <typename DHKey>
bool DR<DHKey>::session_load() {
	// blobs to store DR session data
	blob DHr(m_localStorage->sql);
511
	blob DHs(m_localStorage->sql);
johan's avatar
johan committed
512 513 514 515 516 517 518 519 520
	blob RK(m_localStorage->sql);
	blob CKs(m_localStorage->sql);
	blob CKr(m_localStorage->sql);
	blob AD(m_localStorage->sql);
	blob X3DH_initMessage(m_localStorage->sql);

	// create an empty DR session
	indicator ind;
	int status; // retrieve an int from DB, turn it into a bool to store in object
521
	m_localStorage->sql<<"SELECT Did,Uid,Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Status,X3DHInit FROM DR_sessions WHERE sessionId = :sessionId LIMIT 1", into(m_peerDid), into(m_db_Uid), into(m_Ns), into(m_Nr), into(m_PN), into(DHr), into(DHs), into(RK), into(CKs), into(CKr), into(AD), into(status), into(X3DH_initMessage,ind), use(m_dbSessionId);
johan's avatar
johan committed
522 523 524

	if (m_localStorage->sql.got_data()) { // TODO : some more specific checks on length of retrieved data?
		DHr.read(0, (char *)(m_DHr.data()), m_DHr.size());
525 526
		DHs.read(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size());
		DHs.read(m_DHs.publicKey().size(), (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
johan's avatar
johan committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
		RK.read(0, (char *)(m_RK.data()), m_RK.size());
		CKs.read(0, (char *)(m_CKs.data()), m_CKs.size());
		CKr.read(0, (char *)(m_CKr.data()), m_CKr.size());
		AD.read(0, (char *)(m_sharedAD.data()), m_sharedAD.size());
		if (ind == i_ok && X3DH_initMessage.get_len()>0) {
			m_X3DH_initMessage.resize(X3DH_initMessage.get_len());
			X3DH_initMessage.read(0, (char *)(m_X3DH_initMessage.data()), m_X3DH_initMessage.size());
		}
		if (status==1) {
			m_active_status = true;
		} else {
			m_active_status = false;
		}
		return true;
	} else { // something went wrong with the DB, we cannot retrieve the session
		return false;
	}
};

template <typename Curve>
547
bool DR<Curve>::trySkippedMessageKeys(const uint16_t Nr, const X<Curve> &DHr, DRMKey &MK) {
johan's avatar
johan committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
	blob MK_blob(m_localStorage->sql);
	blob DHr_blob(m_localStorage->sql);
	DHr_blob.write(0, (char *)(DHr.data()), DHr.size());

	indicator ind;
	m_localStorage->sql<<"SELECT m.MK, m.DHid FROM DR_MSk_MK as m INNER JOIN DR_MSk_DHr as d ON d.DHid=m.DHid WHERE d.sessionId = :sessionId AND d.DHr = :DHr AND m.Nr = :Nr LIMIT 1", into(MK_blob,ind), into(m_usedDHid), use(m_dbSessionId), use(DHr_blob), use(Nr);
	// we didn't find anything
	if (!m_localStorage->sql.got_data() || ind != i_ok || MK_blob.get_len()!=MK.size()) {
		m_usedDHid=0; // make sure the DHid is not set when we didn't find anything as it is later used to remove confirmed used key from DB
		return false;
	}
	// record the Nr of extracted to be able to delete it fron base later(if decrypt ends well)
	m_usedNr=Nr;

	MK_blob.read(0, (char *)(MK.data()), MK.size());
	return true;
};
/* template instanciations for Curves 25519 and 448 */
#ifdef EC25519_ENABLED
	template bool DR<C255>::session_load();
	template bool DR<C255>::session_save();
569
	template bool DR<C255>::trySkippedMessageKeys(const uint16_t Nr, const X<C255> &DHr, DRMKey &MK);
johan's avatar
johan committed
570 571 572 573 574
#endif

#ifdef EC448_ENABLED
	template bool DR<C448>::session_load();
	template bool DR<C448>::session_save();
575
	template bool DR<C448>::trySkippedMessageKeys(const uint16_t Nr, const X<C448> &DHr, DRMKey &MK);
johan's avatar
johan committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
#endif

/******************************************************************************/
/*                                                                            */
/*  Lime members functions                                                    */
/*                                                                            */
/******************************************************************************/
/**
 * @brief Create a new local user based on its userId(GRUU) from table lime_LocalUsers
 *
 * use m_selfDeviceId as input, use m_X3DH_Server as input
 * populate m_db_Uid
 *
 * @return true if user was created successfully, exception is thrown otherwise.
 *
 * @exception BCTBX_EXCEPTION	thrown if user already exists in the database
 */
template <typename Curve>
bool Lime<Curve>::create_user()
{
	// check if the user is not already in the DB
	int dummy_Uid;
	m_localStorage->sql<<"SELECT Uid FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(dummy_Uid), use(m_selfDeviceId);
	if (m_localStorage->sql.got_data()) {
		throw BCTBX_EXCEPTION << "Lime user "<<m_selfDeviceId<<" cannot be created: it is already in Database - delete it before if you really want to replace it";
	}

	// generate an identity EDDSA key pair
	auto EDDSAContext = EDDSAInit<Curve>();
	bctbx_EDDSACreateKeyPair(EDDSAContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
	// store it in a blob : Public||Private
	blob Ik(m_localStorage->sql);
	Ik.write(0, (const char *)EDDSAContext->publicKey, EDDSAContext->pointCoordinateLength);
	Ik.write(EDDSAContext->pointCoordinateLength, (const char *)EDDSAContext->secretKey, EDDSAContext->secretLength);
	/* set the Ik in Lime object */
	//m_Ik = std::move(KeyPair<ED<Curve>>{EDDSAContext->publicKey, EDDSAContext->secretKey});
	bctbx_DestroyEDDSAContext(EDDSAContext);


	// insert in DB
	try {
		m_localStorage->sql<<"INSERT INTO lime_LocalUsers(UserId,Ik,server,curveId) VALUES (:userId,:Ik,:server,:curveId) ", use(m_selfDeviceId), use(Ik), use(m_X3DH_Server_URL), use(static_cast<uint8_t>(Curve::curveId()));
	} catch (exception const &e) {
		throw BCTBX_EXCEPTION << "Lime user insertion failed. DB backend says : "<<e.what();
	}
	// get the Id of inserted row
	m_localStorage->sql<<"select last_insert_rowid()",into(m_db_Uid);
	/* WARNING: previous line break portability of DB backend, specific to sqlite3.
	Following code shall work but consistently returns false and do not set m_db_Uid...*/
	/*
	if (!(m_localStorage->sql.get_last_insert_id("lime_LocalUsers", m_db_Uid)))
		throw BCTBX_EXCEPTION << "Lime user insertion failed. Couldn't retrieve last insert DB";
	}
	*/
	/* all went fine set the Ik loaded flag */
	//m_Ik_loaded = true;


	return true;
}

template <typename Curve>
void Lime<Curve>::get_SelfIdentityKey() {
	if (m_Ik_loaded == false) {
		blob Ik_blob(m_localStorage->sql);
		m_localStorage->sql<<"SELECT Ik FROM Lime_LocalUsers WHERE Uid = :UserId LIMIT 1;", into(Ik_blob), use(m_db_Uid);
		if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
			Ik_blob.read(0, (char *)(m_Ik.publicKey().data()), m_Ik.publicKey().size()); // Read the public key
			Ik_blob.read(m_Ik.publicKey().size(), (char *)(m_Ik.privateKey().data()), m_Ik.privateKey().size()); // Read the private key
			m_Ik_loaded = true; // set the flag
		}
	}
}


template <typename Curve>
void Lime<Curve>::X3DH_generate_SPk(X<Curve> &publicSPk, Signature<Curve> &SPk_sig, uint32_t &SPk_id) {
	// check Identity key is loaded in Lime object context
	get_SelfIdentityKey();

	// Generate a new ECDH Key pair
	auto ECDH_Context = ECDHInit<Curve>();
	bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);

	// Sign the public key with our identity key
	auto EDDSA_Context = EDDSAInit<Curve>();
	bctbx_EDDSA_setPublicKey(EDDSA_Context, m_Ik.publicKey().data(), m_Ik.publicKey().size());
	bctbx_EDDSA_setSecretKey(EDDSA_Context, m_Ik.privateKey().data(), m_Ik.privateKey().size());
	auto sig_size=SPk_sig.size();
	bctbx_EDDSA_sign(EDDSA_Context, ECDH_Context->selfPublic, ECDH_Context->pointCoordinateLength, nullptr, 0, SPk_sig.data(), &sig_size);

667 668
	// Generate a random SPk Id: Sqlite doesn't really support unsigned value.
	// Be sure the MSbit is set to zero to avoid problem as even if declared unsigned this Id will be treated by sqlite as signed(but still unsigned in this lib)
johan's avatar
johan committed
669 670
	std::array<uint8_t,4> randomId;
	bctbx_rng_get(m_RNG, randomId.data(), randomId.size());
671
	SPk_id = static_cast<uint32_t>(randomId[0])<<23 | static_cast<uint32_t>(randomId[1])<<16 | static_cast<uint32_t>(randomId[2])<<8 | static_cast<uint32_t>(randomId[3]);
johan's avatar
johan committed
672 673 674 675 676 677 678

	// insert all this in DB
	try {
		// open a transaction as both modification shall be done or none
		transaction tr(m_localStorage->sql);

		// We must first update potential existing SPK in base from active to stale status
johan's avatar
johan committed
679
		m_localStorage->sql<<"UPDATE X3DH_SPK SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Uid = :Uid AND Status = 1;", use(m_db_Uid);
johan's avatar
johan committed
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721

		blob SPk_blob(m_localStorage->sql);
		SPk_blob.write(0, (const char *)ECDH_Context->selfPublic, ECDH_Context->pointCoordinateLength);
		SPk_blob.write(ECDH_Context->pointCoordinateLength, (const char *)ECDH_Context->secret, ECDH_Context->secretLength);
		m_localStorage->sql<<"INSERT INTO X3DH_SPK(SPKid,SPK,Uid) VALUES (:SPKid,:SPK,:Uid) ", use(SPk_id), use(SPk_blob), use(m_db_Uid);

		tr.commit();
	} catch (exception const &e) {
		bctbx_DestroyECDHContext(ECDH_Context);
		bctbx_DestroyEDDSAContext(EDDSA_Context);
		throw BCTBX_EXCEPTION << "SPK insertion in DB failed. DB backend says : "<<e.what();
	}

	// get SPk public key in output param
	publicSPk = std::move(X<Curve>{ECDH_Context->selfPublic});

	// destroy contexts
	bctbx_DestroyECDHContext(ECDH_Context);
	bctbx_DestroyEDDSAContext(EDDSA_Context);
}

template <typename Curve>
void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number) {

	// make room for OPk and OPk ids
	publicOPks.reserve(OPk_number);
	OPk_ids.reserve(OPk_number);

	// Prepare DB statement
	transaction tr(m_localStorage->sql);
	blob OPk(m_localStorage->sql);
	uint32_t OPk_id;
	statement st = (m_localStorage->sql.prepare << "INSERT INTO X3DH_OPK(OPKid, OPK,Uid) VALUES(:OPKid,:OPK,:Uid)", use(OPk_id), use(OPk), use(m_db_Uid));

	// Create an ECDH context to create key pairs
	auto ECDH_Context = ECDHInit<Curve>();

	try {
		for (uint16_t i=0; i<OPk_number; i++) {
			// Generate a new ECDH Key pair
			bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);

722 723
			// Generate a random OPk Id: Sqlite doesn't really support unsigned value.
			// Be sure the MSbit is set to zero to avoid problem as even if declared unsigned this Id will be treated by sqlite as signed(but still unsigned in this lib)
johan's avatar
johan committed
724 725
			std::array<uint8_t,4> randomId;
			bctbx_rng_get(m_RNG, randomId.data(), randomId.size());
726
			OPk_id = static_cast<uint32_t>(randomId[0])<<23 | static_cast<uint32_t>(randomId[1])<<16 | static_cast<uint32_t>(randomId[2])<<8 | static_cast<uint32_t>(randomId[3]);
johan's avatar
johan committed
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748

			// Insert in DB: store Public Key || Private Key
			OPk.write(0, (char *)(ECDH_Context->selfPublic), ECDH_Context->pointCoordinateLength);
			OPk.write(ECDH_Context->pointCoordinateLength, (char *)(ECDH_Context->secret), ECDH_Context->secretLength);
			st.execute(true);

			// set in output vectors
			publicOPks.emplace_back(ECDH_Context->selfPublic);
			OPk_ids.push_back(OPk_id);
		}
	} catch (exception &e) {
		bctbx_DestroyECDHContext(ECDH_Context);
		throw BCTBX_EXCEPTION << "OPK insertion in DB failed. DB backend says : "<<e.what();
	}
	// commit changes to DB
	tr.commit();

	bctbx_DestroyECDHContext(ECDH_Context);
}

template <typename Curve>
void Lime<Curve>::cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal_recipients, std::vector<std::string> &missing_devices) {
749 750
	// build a user list of missing ones : produce a list ready to be sent to SQL query: 'user','user','user',... also build a map to store shared_ptr to sessions
	// build also a list of all peer devices used to fetch from DB the ones with identity not verified
johan's avatar
johan committed
751
	std::string sqlString_requestedDevices{""};
752
	std::string sqlString_allDevices{""};
johan's avatar
johan committed
753 754

	size_t requestedDevicesCount = 0;
755
	size_t allDevicesCount = 0;
Johan Pascal's avatar
Johan Pascal committed
756
	for (const auto &recipient : internal_recipients) {
johan's avatar
johan committed
757 758 759 760
		if (recipient.DRSession == nullptr) {
			sqlString_requestedDevices.append("'").append(recipient.deviceId).append("',");
			requestedDevicesCount++;
		}
761 762 763 764 765 766 767 768 769 770 771
		sqlString_allDevices.append("'").append(recipient.deviceId).append("',");
		allDevicesCount++;
	}

	// fetch devices with identity verified flag to false
	if (allDevicesCount==0) return; // the device list was empty... this is very strange

	sqlString_allDevices.pop_back(); // remove the last ','
	// fetch all the verified devices (we don't directly fetch unverified device as some devices may not be in local storage at all)
	rowset<row> rs_devices = (m_localStorage->sql.prepare << "SELECT d.DeviceId FROM lime_PeerDevices as d WHERE d.Verified = 1 AND d.DeviceId IN ("<<sqlString_allDevices<<");");
	std::vector<std::string> verifiedDevices{}; // vector of verified deviceId
Johan Pascal's avatar
Johan Pascal committed
772
	for (const auto &r : rs_devices) {
773 774 775 776 777 778
		verifiedDevices.push_back(r.get<string>(0));
	}

	// loop on internal recipient and mark the one verified as verified
	for (auto &recipient : internal_recipients) {
		recipient.identityVerified = false;
Johan Pascal's avatar
Johan Pascal committed
779
		for (const auto &verifiedDevice : verifiedDevices) {
780 781 782 783 784
			if (verifiedDevice == recipient.deviceId) {
				recipient.identityVerified = true;
				break;
			}
		}
johan's avatar
johan committed
785
	}
786 787 788


	// Now do we have sessions to load?
johan's avatar
johan committed
789 790 791 792
	if (requestedDevicesCount==0) return; // we already got them all

	sqlString_requestedDevices.pop_back(); // remove the last ','

793
	// fetch them from DB
794
	rowset<row> rs = (m_localStorage->sql.prepare << "SELECT s.sessionId, d.DeviceId FROM DR_sessions as s INNER JOIN lime_PeerDevices as d ON s.Did=d.Did WHERE s.Uid= :Uid AND s.Status=1 AND d.DeviceId IN ("<<sqlString_requestedDevices<<");", use(m_db_Uid));
795 796

	std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> requestedDevices; // found session will be loaded and temp stored in this
Johan Pascal's avatar
Johan Pascal committed
797
	for (const auto &r : rs) {
798 799 800 801 802 803
		auto sessionId = r.get<int>(0);
		auto peerDeviceId = r.get<string>(1);

		auto DRsession = std::make_shared<DR<Curve>>(m_localStorage.get(), sessionId); // load session from local storage
		requestedDevices[peerDeviceId] = DRsession; // store found session in a our temp container
		m_DR_sessions_cache[peerDeviceId] = DRsession; // session is also stored in cache
johan's avatar
johan committed
804 805
	}

806
	// loop on internal recipient and fill it with the found ones, store the missing ones in the missing_devices vector
johan's avatar
johan committed
807 808 809 810 811 812
	for (auto &recipient : internal_recipients) {
		if (recipient.DRSession == nullptr) { // they are missing
			auto retrievedElem = requestedDevices.find(recipient.deviceId);
			if (retrievedElem == requestedDevices.end()) { // we didn't found this one
				missing_devices.push_back(recipient.deviceId);
			} else { // we got this one
813
				recipient.DRSession = std::move(retrievedElem->second); // don't need this pointer in tmp comtainer anymore
johan's avatar
johan committed
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
			}
		}
	}
}

/**
 * @brief Store peer device information(DeviceId - GRUU -, public Ik, Uid to link it to a user) in local storage
 *
 * @param[in] peerDeviceId	The device id to insert
 * @param[in] Ik		The public EDDSA identity key of this device
 *
 * @return the id internally used by db to store this row
 */
template <typename Curve>
long int Lime<Curve>::store_peerDevice(const std::string &peerDeviceId, const ED<Curve> &Ik) {
	blob Ik_blob(m_localStorage->sql);

	try {
		long int Did=0;
		// make sure this device wasn't already here, if it was, check they have the same Ik
834
		m_localStorage->sql<<"SELECT Ik,Did FROM lime_PeerDevices WHERE DeviceId = :DeviceId LIMIT 1;", into(Ik_blob), into(Did), use(peerDeviceId);
johan's avatar
johan committed
835 836
		if (m_localStorage->sql.got_data()) { // Found one
			ED<Curve> stored_Ik;
837 838 839 840
			if (Ik_blob.get_len() != stored_Ik.size()) { // can't match they are not the same size
				BCTBX_SLOGE<<"It appears that peer device "<<peerDeviceId<<" was known with an identity key but is trying to use another one now";
				throw BCTBX_EXCEPTION << "Peer device "<<peerDeviceId<<" changed its Ik";
			}
johan's avatar
johan committed
841 842 843 844
			Ik_blob.read(0, (char *)(stored_Ik.data()), stored_Ik.size()); // Read it to compare it to the given one
			if (stored_Ik == Ik) { // they match, so we just return the Did
				return Did;
			} else { // Ik are not matching, peer device changed its Ik!?! Reject
johan's avatar
johan committed
845
				BCTBX_SLOGE<<"It appears that peer device "<<peerDeviceId<<" was known with an identity key but is trying to use another one now";
johan's avatar
johan committed
846 847 848 849 850
				throw BCTBX_EXCEPTION << "Peer device "<<peerDeviceId<<" changed its Ik";
			}
		} else { // not found in local Storage
			transaction tr(m_localStorage->sql);
			Ik_blob.write(0, (char *)(Ik.data()), Ik.size());
851
			m_localStorage->sql<<"INSERT INTO lime_PeerDevices(DeviceId,Ik) VALUES (:deviceId,:Ik) ", use(peerDeviceId), use(Ik_blob);
johan's avatar
johan committed
852 853
			m_localStorage->sql<<"select last_insert_rowid()",into(Did);
			tr.commit();
854
			bctbx_debug("store peerDevice %s with device id %x", peerDeviceId.data(), Did);
johan's avatar
johan committed
855 856 857 858 859 860 861 862 863 864
			return Did;
		}
	} catch (exception const &e) {
		throw BCTBX_EXCEPTION << "Peer device "<<peerDeviceId<<" insertion failed. DB backend says : "<<e.what();
	}
}

// load from local storage in DRSessions all DR session matching the peerDeviceId, ignore the one picked by id in 2nd arg
template <typename Curve>
void Lime<Curve>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector<std::shared_ptr<DR<Curve>>> &DRSessions) {
865
	rowset<int> rs = (m_localStorage->sql.prepare << "SELECT s.sessionId FROM DR_sessions as s INNER JOIN lime_PeerDevices as d ON s.Did=d.Did WHERE d.DeviceId = :senderDeviceId AND s.Uid = :Uid AND s.sessionId <> :ignoreThisDRSessionId ORDER BY s.Status DESC, timeStamp ASC;", use(senderDeviceId), use (m_db_Uid), use(ignoreThisDRSessionId));
866

Johan Pascal's avatar
Johan Pascal committed
867
	for (const auto &sessionId : rs) {
868 869
		/* load session in cache DRSessions */
		DRSessions.push_back(make_shared<DR<Curve>>(m_localStorage.get(), sessionId)); // load session from cache
johan's avatar
johan committed
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
	}
};

/**
 * @brief retrieve matching SPk from localStorage, throw an exception if not found
 *
 * @param[in]	SPk_id	Id of the SPk we're trying to fetch
 * @param[out]	SPk	The SPk if found
 */
template <typename Curve>
void Lime<Curve>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<Curve>> &SPk) {
	blob SPk_blob(m_localStorage->sql);
	m_localStorage->sql<<"SELECT SPk FROM X3DH_SPk WHERE Uid = :Uid AND SPKid = :SPk_id LIMIT 1;", into(SPk_blob), use(m_db_Uid), use(SPk_id);
	if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
		SPk_blob.read(0, (char *)(SPk.publicKey().data()), SPk.publicKey().size()); // Read the public key
		SPk_blob.read(SPk.publicKey().size(), (char *)(SPk.privateKey().data()), SPk.privateKey().size()); // Read the private key
	} else {
		throw BCTBX_EXCEPTION << "X3DH "<<m_selfDeviceId<<"look up for SPk id "<<SPk_id<<" failed";
	}
}

johan's avatar
johan committed
891 892 893 894 895 896 897 898 899 900 901 902 903 904
/**
 * @brief is current SPk valid?
 */
template <typename Curve>
bool Lime<Curve>::is_currentSPk_valid(void) {
	// Do we have an active SPk for this user which is younger than SPK_lifeTime_days
	int dummy;
	m_localStorage->sql<<"SELECT SPKid FROM X3DH_SPk WHERE Uid = :Uid AND Status = 1 AND timeStamp > date('now', '-"<<lime::settings::SPK_lifeTime_days<<" day') LIMIT 1;", into(dummy), use(m_db_Uid);
	if (m_localStorage->sql.got_data()) {
		return true;
	} else {
		return false;
	}
}
johan's avatar
johan committed
905 906 907 908 909 910 911 912 913 914 915

/**
 * @brief retrieve matching OPk from localStorage, throw an exception if not found
 * 	Note: once fetch, the OPk is deleted from localStorage
 *
 * @param[in]	OPk_id	Id of the OPk we're trying to fetch
 * @param[out]	OPk	The OPk if found
 */
template <typename Curve>
void Lime<Curve>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<Curve>> &OPk) {
	blob OPk_blob(m_localStorage->sql);
916
	m_localStorage->sql<<"SELECT OPk FROM X3DH_OPK WHERE Uid = :Uid AND OPKid = :OPk_id LIMIT 1;", into(OPk_blob), use(m_db_Uid), use(OPk_id);
johan's avatar
johan committed
917 918 919
	if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
		OPk_blob.read(0, (char *)(OPk.publicKey().data()), OPk.publicKey().size()); // Read the public key
		OPk_blob.read(OPk.publicKey().size(), (char *)(OPk.privateKey().data()), OPk.privateKey().size()); // Read the private key
920
		m_localStorage->sql<<"DELETE FROM X3DH_OPK WHERE Uid = :Uid AND OPKid = :OPk_id;", use(m_db_Uid), use(OPk_id); // And remove it from local Storage
johan's avatar
johan committed
921 922 923 924 925
	} else {
		throw BCTBX_EXCEPTION << "X3DH "<<m_selfDeviceId<<"look up for OPk id "<<OPk_id<<" failed";
	}
}

926 927 928 929 930 931 932 933 934 935 936
/**
 * @brief update OPk Status so we can get an idea of what's on server and what was dispatched but not used yet
 * 	get rid of anyone with status 0 and oldest than OPk_limboTime_days
 *
 * @param[in]	OPkIds	List of Ids found on server
 */
template <typename Curve>
void Lime<Curve>::X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds) {
	if (OPkIds.size()>0) { /* we have keys on server */
		// build a comma-separated list of OPk id on server
		std::string sqlString_OPkIds{""};
Johan Pascal's avatar
Johan Pascal committed
937
		for (const auto &OPkId : OPkIds) {
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
			sqlString_OPkIds.append(to_string(OPkId)).append(",");
		}

		sqlString_OPkIds.pop_back(); // remove the last ','

		// Update Status and timeStamp in DB for keys we own and are not anymore on server
		m_localStorage->sql << "UPDATE X3DH_OPK SET Status = 0, timeStamp=CURRENT_TIMESTAMP WHERE Status = 1 AND Uid = :Uid AND OPKid NOT IN ("<<sqlString_OPkIds<<");", use(m_db_Uid);
	} else { /* we have no keys on server */
		m_localStorage->sql << "UPDATE X3DH_OPK SET Status = 0, timeStamp=CURRENT_TIMESTAMP WHERE Status = 1 AND Uid = :Uid;", use(m_db_Uid);
	}

	// Delete keys not anymore on server since too long
	m_localStorage->sql << "DELETE FROM X3DH_OPK WHERE Uid = :Uid AND Status = 0 AND timeStamp < date('now', '-"<<lime::settings::OPk_limboTime_days<<" day');", use(m_db_Uid);
}

johan's avatar
johan committed
953 954 955 956 957 958 959 960 961 962
/* template instanciations for Curves 25519 and 448 */
#ifdef EC25519_ENABLED
	template bool Lime<C255>::create_user();
	template void Lime<C255>::get_SelfIdentityKey();
	template void Lime<C255>::X3DH_generate_SPk(X<C255> &publicSPk, Signature<C255> &SPk_sig, uint32_t &SPk_id);
	template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
	template void Lime<C255>::cache_DR_sessions(std::vector<recipientInfos<C255>> &internal_recipients, std::vector<std::string> &missing_devices);
	template long int Lime<C255>::store_peerDevice(const std::string &peerDeviceId, const ED<C255> &Ik);
	template void Lime<C255>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C255>>> &DRSessions);
	template void Lime<C255>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C255>> &SPk);
johan's avatar
johan committed
963
	template bool Lime<C255>::is_currentSPk_valid(void);
johan's avatar
johan committed
964
	template void Lime<C255>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C255>> &SPk);
965
	template void Lime<C255>::X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds);
johan's avatar
johan committed
966 967 968 969 970 971 972 973 974 975 976
#endif

#ifdef EC448_ENABLED
	template bool Lime<C448>::create_user();
	template void Lime<C448>::get_SelfIdentityKey();
	template void Lime<C448>::X3DH_generate_SPk(X<C448> &publicSPk, Signature<C448> &SPk_sig, uint32_t &SPk_id);
	template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
	template void Lime<C448>::cache_DR_sessions(std::vector<recipientInfos<C448>> &internal_recipients, std::vector<std::string> &missing_devices);
	template long int Lime<C448>::store_peerDevice(const std::string &peerDeviceId, const ED<C448> &Ik);
	template void Lime<C448>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C448>>> &DRSessions);
	template void Lime<C448>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C448>> &SPk);
johan's avatar
johan committed
977
	template bool Lime<C448>::is_currentSPk_valid(void);
johan's avatar
johan committed
978
	template void Lime<C448>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C448>> &SPk);
979
	template void Lime<C448>::X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds);
johan's avatar
johan committed
980 981 982 983
#endif


} // namespace lime