Commit 3c2bd37d authored by johan's avatar johan

Add update function to LimeManager API

Clean DR session in localStorage
To be completed with:
- generate and upload new SPk to server
- generate and upload new OPks to server
parent 6f1baee6
......@@ -127,6 +127,19 @@ namespace lime {
*/
bool decrypt(const std::string &localDeviceId, const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage);
/**
* @brief Update: shall be called once a day at least, performs checks, updates and cleaning operations
* - check if we shall update a new SPk to X3DH server(SPk lifetime is set in settings)
* - check if we need to upload OPks to X3DH server
* - remove old SPks, clean double ratchet sessions (remove staled, clean their stored keys for skipped messages)
*
* Is performed for all users founds in local storage
*
* @param[in] callback This operation may contact the X3DH server and is thus asynchronous, when server responds,
* this callback will be called giving the exit status and an error message in case of failure.
*/
void update(const limeCallback &callback);
LimeManager() = delete; // no manager without Database and http provider
LimeManager(const LimeManager&) = delete; // no copy constructor
LimeManager operator=(const LimeManager &) = delete; // nor copy operator
......
......@@ -103,18 +103,31 @@ Db::Db(std::string filename) : sql{sqlite3, filename}{
X3DHInit BLOB DEFAULT NULL, \
FOREIGN KEY(Did) REFERENCES lime_PeerDevices(Did) ON UPDATE CASCADE ON DELETE CASCADE)";
/* DR Message Skipped DH : DHid(primary key), SessionId, DHr */
/* 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);";
/* DR Message Skipped MK : [DHid,NR](primary key), MK */
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 ));";
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 :
......@@ -136,12 +149,14 @@ Db::Db(std::string filename) : sql{sqlite3, filename}{
* - DeviceId: peer device id (shall be its GRUU)
* - Uid: link to LocalUsers table, identify which localUser registered this peer Device
* - 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
*/
sql<<"CREATE TABLE lime_PeerDevices( \
Did INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
DeviceId TEXT NOT NULL, \
Uid INTEGER NOT NULL, \
Ik BLOB NOT NULL, \
Verified UNSIGNED INTEGER DEFAULT 0,\
FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";
/*** X3DH tables ***/
......@@ -214,6 +229,24 @@ void Db::load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &
}
}
/**
* @brief Delete old stale sessions and old stored message key
* - 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<<";";
}
/**
* @brief if exists, delete user
*
......@@ -333,6 +366,11 @@ bool DR<DHKey>::session_save() {
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
} 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);
}
}
}
......@@ -345,6 +383,8 @@ bool DR<DHKey>::session_save() {
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
} 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);
}
// insert all the skipped key in the chain
uint16_t Nr;
......
......@@ -34,6 +34,7 @@ namespace lime {
void load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &curveId, std::string &url);
void delete_LimeUser(const std::string &userId);
void clean_DRSessions();
};
#ifdef EC25519_ENABLED
......
......@@ -104,4 +104,18 @@ namespace lime {
// call the decryption function
return user->decrypt(recipientUserId, senderDeviceId, cipherHeader, cipherMessage, plainMessage);
}
void LimeManager::update(const limeCallback &callback) {
// open local DB
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(m_db_access));
/* DR sessions cleaning */
localStorage->clean_DRSessions();
/* OPk check : ask X3DH server how many keys are left */
/* SPk check */
if (callback) callback(lime::callbackReturn::success, "");
}
} // namespace lime
......@@ -57,11 +57,18 @@ namespace settings {
// Maximum number of Message we can skip(and store their keys) at reception of one message
constexpr std::uint16_t maxMessageSkip=1024;
// after a message key is stored, count how many messages we can receive from peer before deleting the key
// Note: implemented by receiving key chain, so any new skipped message in a chain will reset the counter to 0
constexpr std::uint16_t maxMessagesReceivedAfterSkip = 128;
// Maximum length of Sending chain: is this count is reached without any return from peer,
// the DR session is set to stale and we must create another one to send messages
// Can't be more than 2^16 as message number is send on 2 bytes
constexpr std::uint16_t maxSendingChain=1000;
// Lifetime of a session once not active anymore, unit is day
constexpr unsigned int DRSession_limboTime_days=30;
/******************************************************************************/
/* */
/* Local Storage related definitions */
......@@ -78,6 +85,10 @@ namespace settings {
/******************************************************************************/
const std::string X3DH_SK_info{"Lime"}; // shall be an ASCII string identifying the application (X3DH spec section 2.1)
const std::string X3DH_AD_info{"X3DH Associated Data"}; // used to generate a shared AD based on Ik and deviceID
// use time period definitions as in bctoolbox/port.h bctbx_time_string_to_sec function
const std::string SPK_lifeTime{"1W"}; // Life tine of a signed pre-key, it will be set to stale after that period
const std::string SPK_limboTime{"1W"}; // How long shall we keep a signed pre-key once it has been replaced by a new one
}
}
......
......@@ -316,12 +316,12 @@ bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8
/* Open provided DB and look for DRSessions established between selfDevice and peerDevice
* Populate the sessionsId vector with the Ids of sessions found
* return the id of the active session if one is found, 0 otherwise */
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId) {
soci::session sql(sqlite3, dbFilename); // open the DB
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId) noexcept{
sessionsId.clear();
sessionsId.resize(25); // no more than 25 sessions id fetched
std::vector<int> status(25);
try {
soci::session sql(sqlite3, dbFilename); // open the DB
soci::statement st = (sql.prepare << "SELECT s.sessionId, s.Status FROM DR_sessions as s INNER JOIN lime_PeerDevices as d on s.Did = d.Did INNER JOIN lime_LocalUsers as u on u.Uid = d.Uid WHERE u.UserId = :selfId AND d.DeviceId = :peerId ORDER BY s.Status DESC, s.Did;", into(sessionsId), into(status), use(selfDeviceId), use(peerDeviceId));
st.execute();
if (st.fetch()) { // all retrieved session shall fit in the arrays no need to go on several fetch
......@@ -345,6 +345,40 @@ long int get_DRsessionsId(const std::string &dbFilename, const std::string &self
}
}
/* Open provided DB, look for DRSessions established between selfDevice and peerDevice, count the stored message keys in all these sessions
* return 0 if no sessions found or no user found
*/
unsigned int get_StoredMessageKeyCount(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId) noexcept{
try {
soci::session sql(sqlite3, dbFilename); // open the DB
unsigned int mkCount=0;
sql<< "SELECT count(m.MK) FROM DR_sessions as s INNER JOIN lime_PeerDevices as d on s.Did = d.Did INNER JOIN lime_LocalUsers as u on u.Uid = d.Uid INNER JOIN DR_MSk_DHr as c on c.sessionId = s.sessionId INNER JOIN DR_MSk_Mk as m ON m.DHid=c.DHid WHERE u.UserId = :selfId AND d.DeviceId = :peerId ORDER BY s.Status DESC, s.Did;", into(mkCount), use(selfDeviceId), use(peerDeviceId);
if (sql.got_data()) {
return mkCount;
} else {
return 0;
}
} catch (exception &e) { // swallow any error on DB
BCTBX_SLOGE<<"Got an error while getting the MK count in DB: "<<e.what();
return 0;
}
}
/* Move back in time all timeStamps by the given amout of days
* DB holds timeStamps in DR_sessions and X3DH_SPK tables
*/
void forwardTime(const std::string &dbFilename, int days) noexcept {
try {
soci::session sql(sqlite3, dbFilename); // open the DB
/* move back by days all timeStamp, we have some in DR_sessions and X3DH_SPk tables */
sql<<"UPDATE DR_sessions SET timeStamp = date (timeStamp, '-"<<days<<" day');";
sql<<"UPDATE X3DH_SPK SET timeStamp = date (timeStamp, '-"<<days<<" day');";
} catch (exception &e) { // swallow any error on DB
BCTBX_SLOGE<<"Got an error forwarding time in DB: "<<e.what();
}
}
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
......
......@@ -76,7 +76,18 @@ bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8
/* Open provided DB and look for DRSessions established between selfDevice and peerDevice
* Populate the sessionsId vector with the Ids of sessions found
* return the id of the active session if one is found, 0 otherwise */
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId);
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId) noexcept;
/* Open provided DB, look for DRSessions established between selfDevice and peerDevice, count the stored message keys in all these sessions
* return 0 if no sessions found or no user found
*/
unsigned int get_StoredMessageKeyCount(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId) noexcept;
/* Move back in time all timeStamps by the given amout of days
* DB holds timeStamps in DR_sessions and X3DH_SPK tables
*/
void forwardTime(const std::string &dbFilename, int days) noexcept;
/**
* @brief append a random suffix to user name to avoid collision if test server is user by several tests runs
......
......@@ -38,12 +38,6 @@
using namespace::std;
using namespace::lime;
static const std::string AD{"alice@sip.linphone.org;opaque=user:epid:UghFocauauCHBHoLhAAA;gruu;bob@sip.linphone.org;opaque=user:epid:CTboShduoSCdauSEUDbka;gruu;"};
/*****************************************************************************/
/* Direct use of DR encrypt/decrypt, is not what is done in real usage */
/*****************************************************************************/
/**
* @param[in] period altern sended each <period> messages (sequence will anyways always start with alice send - bob receive - bob send)
* @param[in] skip_period same than above but for reception skipping: at each begining of skip_period, skip reception of skip_length messages
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment