/* lime_x3dh.cpp Copyright (C) 2017 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 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define BCTBX_LOG_DOMAIN "lime" #include #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 { Db::Db(std::string filename) : sql{sqlite3, filename}{ int userVersion=-1; sql<<"PRAGMA user_version;",into(userVersion); sql<<"PRAGMA foreign_keys = ON;"; // make sure this connection enable foreign keys if (userVersion != lime::settings::DBuserVersion) { if (userVersion > lime::settings::DBuserVersion) { /* nothing to do if we encounter a superior version number than expected, just hope it is compatible */ //TODO: Log this event, throw an execption? //TODO: use a table for versionning not the user_version pragma } else { /* Perform update if needed */ // update the schema version in DB metadata, the above line shall work but if fails at runtime in soci lib on syntax error //sql<<"PRAGMA user_version = :versionNumber;", use(lime::settings::DBuserVersion); sql<<"PRAGMA user_version = "<(lime::CurveId::c25519): curveId=lime::CurveId::c25519; break; case static_cast(lime::CurveId::c448): curveId=lime::CurveId::c448; break; case static_cast(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; throw BCTBX_EXCEPTION << "Lime DB either corrupted or back from the future. User "< bool DR::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()); 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()); 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()); // make sure we have no other session active with this peer DiD m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid); 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()); m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Did,X3DHInit) VALUES(:Ns,:Nr,:PN,:DHr,:DHs,:RK,:CKs,:CKr,:AD,:Did,: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(X3DH_initMessage); } else { m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Did) VALUES(:Ns,:Nr,:PN,:DHr,:DHs,:RK,:CKs,:CKr,:AD,:Did);", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs), use(RK), use(CKs), use(CKr), use(AD), use(m_peerDid); } // 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 { // make sure we have no other session active with this peer DiD if (m_active_status == false) { m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid); m_active_status = true; } // Build blobs from DR session blob DHr(m_localStorage->sql); DHr.write(0, (char *)(m_DHr.data()), m_DHr.size()); 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()); 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()); 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); } 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) { // make sure we have no other session active with this peer DiD if (m_active_status == false) { m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid); 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()); 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); } break; case DRSessionDbStatus::clean: // Session is clean? So why have we been called? default: BCTBX_SLOGE<<"Double ratchet session saved call on sessionId "<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 } } // Shall we insert some skipped Message keys? for ( 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 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 } // insert all the skipped key in the chain uint16_t Nr; 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) { uint16_t Nr; 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 bool DR::session_load() { // blobs to store DR session data blob DHr(m_localStorage->sql); blob DHs(m_localStorage->sql); 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 m_localStorage->sql<<"SELECT Did,Ns,Nr,PN,DHr,DHs,RK,CKs,CKr,AD,Status,X3DHInit FROM DR_sessions WHERE sessionId = :sessionId LIMIT 1", into(m_peerDid), 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); 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()); 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()); 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 bool DR::trySkippedMessageKeys(const uint16_t Nr, const X &DHr, DRMKey &MK) { 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::session_load(); template bool DR::session_save(); template bool DR::trySkippedMessageKeys(const uint16_t Nr, const X &DHr, DRMKey &MK); #endif #ifdef EC448_ENABLED template bool DR::session_load(); template bool DR::session_save(); template bool DR::trySkippedMessageKeys(const uint16_t Nr, const X &DHr, DRMKey &MK); #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 bool Lime::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 "<(); 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>{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(Curve::curveId())); } catch (exception const &e) { throw BCTBX_EXCEPTION << "Lime user insertion failed. DB backend says : "<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 void Lime::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 void Lime::X3DH_generate_SPk(X &publicSPk, Signature &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(); 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(); 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); // Generate a random SPk Id std::array randomId; bctbx_rng_get(m_RNG, randomId.data(), randomId.size()); SPk_id = static_cast(randomId[0])<<24 | static_cast(randomId[1])<<16 | static_cast(randomId[2])<<8 | static_cast(randomId[3]); // 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 m_localStorage->sql<<"UPDATE X3DH_SPK SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Uid = :Uid", use(m_db_Uid); 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 : "<{ECDH_Context->selfPublic}); // destroy contexts bctbx_DestroyECDHContext(ECDH_Context); bctbx_DestroyEDDSAContext(EDDSA_Context); } template void Lime::X3DH_generate_OPks(std::vector> &publicOPks, std::vector &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(); try { for (uint16_t i=0; i randomId; bctbx_rng_get(m_RNG, randomId.data(), randomId.size()); OPk_id = static_cast(randomId[0])<<24 | static_cast(randomId[1])<<16 | static_cast(randomId[2])<<8 | static_cast(randomId[3]); // 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 : "< void Lime::cache_DR_sessions(std::vector> &internal_recipients, std::vector &missing_devices) { /* 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 */ std::string sqlString_requestedDevices{""}; std::unordered_map>> requestedDevices; // found session will be loaded and temp stored in this size_t requestedDevicesCount = 0; for (auto &recipient : internal_recipients) { if (recipient.DRSession == nullptr) { sqlString_requestedDevices.append("'").append(recipient.deviceId).append("',"); requestedDevicesCount++; } } if (requestedDevicesCount==0) return; // we already got them all sqlString_requestedDevices.pop_back(); // remove the last ',' /* fetch them from DB */ rowset 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 d.Uid= :Uid AND s.Status=1 AND d.DeviceId IN ("<(0); auto peerDeviceId = r.get(1); auto DRsession = std::make_shared>(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 } /* loop on internal recipient and fill it with the found ones, store the missing ones in the missing_devices vector */ 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 recipient.DRSession = std::move(retrievedElem->second); // don't need this pointer in tmp comtainer anymore } } } } /** * @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 long int Lime::store_peerDevice(const std::string &peerDeviceId, const ED &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 m_localStorage->sql<<"SELECT Ik,Did FROM lime_peerDevices WHERE DeviceId = :DeviceId AND Uid = :Uid LIMIT 1;", into(Ik_blob), into(Did), use(peerDeviceId), use(m_db_Uid); if (m_localStorage->sql.got_data()) { // Found one ED stored_Ik; 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 BCTBX_SLOGE<<"It appears that peer device "<sql); Ik_blob.write(0, (char *)(Ik.data()), Ik.size()); m_localStorage->sql<<"INSERT INTO lime_PeerDevices(DeviceId,Uid,Ik) VALUES (:deviceId,:Uid,:Ik) ", use(peerDeviceId), use(m_db_Uid), use(Ik_blob); m_localStorage->sql<<"select last_insert_rowid()",into(Did); tr.commit(); bctbx_debug("user %s store peerDevice %s with device id %x", m_selfDeviceId.data(), peerDeviceId.data(), Did); return Did; } } catch (exception const &e) { throw BCTBX_EXCEPTION << "Peer device "< void Lime::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector>> &DRSessions) { rowset 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.sessionId <> :ignoreThisDRSessionId ORDER BY s.Status DESC, timeStamp ASC;", use(senderDeviceId), use(ignoreThisDRSessionId)); for (auto sessionId : rs) { /* load session in cache DRSessions */ DRSessions.push_back(make_shared>(m_localStorage.get(), sessionId)); // load session from cache } }; /** * @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 void Lime::X3DH_get_SPk(uint32_t SPk_id, KeyPair> &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 "< void Lime::X3DH_get_OPk(uint32_t OPk_id, KeyPair> &OPk) { blob OPk_blob(m_localStorage->sql); 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); 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 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 } else { throw BCTBX_EXCEPTION << "X3DH "<::create_user(); template void Lime::get_SelfIdentityKey(); template void Lime::X3DH_generate_SPk(X &publicSPk, Signature &SPk_sig, uint32_t &SPk_id); template void Lime::X3DH_generate_OPks(std::vector> &publicOPks, std::vector &OPk_ids, const uint16_t OPk_number); template void Lime::cache_DR_sessions(std::vector> &internal_recipients, std::vector &missing_devices); template long int Lime::store_peerDevice(const std::string &peerDeviceId, const ED &Ik); template void Lime::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector>> &DRSessions); template void Lime::X3DH_get_SPk(uint32_t SPk_id, KeyPair> &SPk); template void Lime::X3DH_get_OPk(uint32_t OPk_id, KeyPair> &SPk); #endif #ifdef EC448_ENABLED template bool Lime::create_user(); template void Lime::get_SelfIdentityKey(); template void Lime::X3DH_generate_SPk(X &publicSPk, Signature &SPk_sig, uint32_t &SPk_id); template void Lime::X3DH_generate_OPks(std::vector> &publicOPks, std::vector &OPk_ids, const uint16_t OPk_number); template void Lime::cache_DR_sessions(std::vector> &internal_recipients, std::vector &missing_devices); template long int Lime::store_peerDevice(const std::string &peerDeviceId, const ED &Ik); template void Lime::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector>> &DRSessions); template void Lime::X3DH_get_SPk(uint32_t SPk_id, KeyPair> &SPk); template void Lime::X3DH_get_OPk(uint32_t OPk_id, KeyPair> &SPk); #endif } // namespace lime