/*
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