Commit 70a58fed authored by johan's avatar johan Committed by johan
Browse files

Better user management and republishing to server on failure

parent c0844d40
......@@ -127,7 +127,7 @@ namespace lime {
std::unordered_map<std::string, std::shared_ptr<LimeGeneric>> m_users_cache; // cache of already opened Lime Session, identified by user Id (GRUU)
std::string m_db_access; // DB access information forwarded to SOCI to correctly access database
limeX3DHServerPostData m_X3DH_post_data; // send data to the X3DH key server
void load_user(std::shared_ptr<LimeGeneric> &user, const std::string &localDeviceId); // helper function, get from m_users_cache of local Storage the requested Lime object
void load_user(std::shared_ptr<LimeGeneric> &user, const std::string &localDeviceId, const bool allStatus=false); // helper function, get from m_users_cache of local Storage the requested Lime object
public :
......
......@@ -93,15 +93,15 @@ namespace lime {
void Lime<Curve>::publish_user(const limeCallback &callback, const uint16_t OPkInitialBatchSize) {
auto userData = make_shared<callbackUserData<Curve>>(this->shared_from_this(), callback, OPkInitialBatchSize);
get_SelfIdentityKey(); // make sure our Ik is loaded in object
/* Generate the SPk */
/* Generate (or load if they already are in base when publishing an inactive user) the SPk */
X<Curve, lime::Xtype::publicKey> SPk{};
DSA<Curve, lime::DSAtype::signature> SPk_sig{};
uint32_t SPk_id=0;
X3DH_generate_SPk(SPk, SPk_sig, SPk_id);
// Generate the OPks
X3DH_generate_SPk(SPk, SPk_sig, SPk_id, true);
// Generate (or load if they already are in base when publishing an inactive user) the OPks
std::vector<X<Curve, lime::Xtype::publicKey>> OPks{};
std::vector<uint32_t> OPk_ids{};
X3DH_generate_OPks(OPks, OPk_ids, OPkInitialBatchSize);
X3DH_generate_OPks(OPks, OPk_ids, OPkInitialBatchSize, true);
// Build and post the message to server
std::vector<uint8_t> X3DHmessage{};
......@@ -243,7 +243,7 @@ namespace lime {
auto db_sessionIdInCache = 0; // this would be the db_sessionId of the session stored in cache if there is one, no session has the Id 0
if (sessionElem != m_DR_sessions_cache.end()) { // session is in cache, it is the active one, just give it a try
db_sessionIdInCache = sessionElem->second->dbSessionId();
std::vector<std::shared_ptr<DR<Curve>>> DRSessions{1, sessionElem->second}; // copy the session pointer into a vector as the decryot function ask for it
std::vector<std::shared_ptr<DR<Curve>>> DRSessions{1, sessionElem->second}; // copy the session pointer into a vector as the decrypt function ask for it
if (decryptMessage<Curve>(senderDeviceId, m_selfDeviceId, recipientUserId, DRSessions, DRmessage, cipherMessage, plainMessage) != nullptr) {
// we manage to decrypt the message with the current active session loaded in cache
return senderDeviceStatus;
......@@ -292,8 +292,8 @@ namespace lime {
/* These extern templates are defines in lime_localStorage.cpp */
extern template bool Lime<C255>::create_user();
extern template void Lime<C255>::get_SelfIdentityKey();
extern template void Lime<C255>::X3DH_generate_SPk(X<C255, lime::Xtype::publicKey> &publicSPk, DSA<C255, DSAtype::signature> &SPk_sig, uint32_t &SPk_id);
extern template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
extern template void Lime<C255>::X3DH_generate_SPk(X<C255, lime::Xtype::publicKey> &publicSPk, DSA<C255, DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load);
extern template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load);
extern template void Lime<C255>::cache_DR_sessions(std::vector<RecipientInfos<C255>> &internal_recipients, std::vector<std::string> &missing_devices);
extern template void Lime<C255>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C255>>> &DRSessions);
extern template void Lime<C255>::X3DH_get_SPk(uint32_t SPk_id, Xpair<C255> &SPk);
......@@ -315,8 +315,8 @@ namespace lime {
/* These extern templates are defines in lime_localStorage.cpp */
extern template bool Lime<C448>::create_user();
extern template void Lime<C448>::get_SelfIdentityKey();
extern template void Lime<C448>::X3DH_generate_SPk(X<C448, lime::Xtype::publicKey> &publicSPk, DSA<C448, DSAtype::signature> &SPk_sig, uint32_t &SPk_id);
extern template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
extern template void Lime<C448>::X3DH_generate_SPk(X<C448, lime::Xtype::publicKey> &publicSPk, DSA<C448, DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load);
extern template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load);
extern template void Lime<C448>::cache_DR_sessions(std::vector<RecipientInfos<C448>> &internal_recipients, std::vector<std::string> &missing_devices);
extern template void Lime<C448>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C448>>> &DRSessions);
extern template void Lime<C448>::X3DH_get_SPk(uint32_t SPk_id, Xpair<C448> &SPk);
......@@ -411,14 +411,16 @@ namespace lime {
* @brief : Load user from database and return a pointer to the control class instanciating the appropriate Lime children class
*
* Fail to find the user will raise an exception
* If allStatus flag is set to false (default value), raise an exception on inactive users otherwise load inactive user.
*
* @param[in] dbFilename Path to filename to use
* @param[in] deviceId User to lookup in DB, deviceId shall be the GRUU
* @param[in] X3DH_post_data A function used to communicate with the X3DH server
* @param[in] allStatus allow loading of inactive user if set to true
*
* @return a pointer to the LimeGeneric class allowing access to API declared in lime_lime.hpp
*/
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &deviceId, const limeX3DHServerPostData &X3DH_post_data) {
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &deviceId, const limeX3DHServerPostData &X3DH_post_data, const bool allStatus) {
/* open DB and load user */
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(dbFilename)); // create as unique ptr, ownership is then passed to the Lime structure when instanciated
......@@ -426,7 +428,7 @@ namespace lime {
long int Uid=0;
std::string x3dh_server_url;
localStorage->load_LimeUser(deviceId, Uid, curve, x3dh_server_url); // this one will throw an exception if user is not found, just let it rise
localStorage->load_LimeUser(deviceId, Uid, curve, x3dh_server_url, allStatus); // this one will throw an exception if user is not found, just let it rise
LIME_LOGI<<"Load Lime user "<<deviceId;
/* check the curve id retrieved from DB is instanciable and return an exception if not */
......
......@@ -259,7 +259,6 @@ namespace lime {
} else {
m_payload_direct_encryption = false;
}
if (messageType & static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::X3DH_init_flag)) {
// header is : Version<1 byte> ||
// message type <1 byte> ||
......@@ -292,7 +291,7 @@ namespace lime {
// curve id <1 byte> ||
// Ns<2 bytes> || PN <2 bytes> ||
// DHs < X<Curve, lime::Xtype::publicKey>::ssize() >
m_size = headerSize<Curve>(); // headerSize is the size when no X3DJ init is present
m_size = headerSize<Curve>(); // headerSize is the size when no X3DH init is present
if (header.size() >= m_size) { //header shall be actually longer because buffer pass is the whole message
m_Ns = header[3]<<8|header[4];
m_PN = header[5]<<8|header[6];
......
......@@ -80,8 +80,8 @@ namespace lime {
void get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector<std::shared_ptr<DR<Curve>>> &DRSessions); // load from local storage in DRSessions all DR session matching the peerDeviceId, ignore the one picked by id in 2nd arg
/* X3DH related - part related to exchange with server or localStorage - implemented in lime_x3dh_protocol.cpp or lime_localStorage.cpp */
void X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk, DSA<Curve, lime::DSAtype::signature> &SPk_sig, uint32_t &SPk_id); // generate a new Signed Pre-Key key pair, store it in DB and set its public key, signature and Id in given params
void X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number); // generate a new batch of OPks, store them in base and fill the vector with information to be sent to X3DH server
void X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk, DSA<Curve, lime::DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load=false); // generate a new Signed Pre-Key key pair, store it in DB and set its public key, signature and Id in given params
void X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load=false); // generate a new batch of OPks, store them in base and fill the vector with information to be sent to X3DH server
void X3DH_get_SPk(uint32_t SPk_id, Xpair<Curve> &SPk); // retrieve matching SPk from localStorage, throw an exception if not found
bool is_currentSPk_valid(void); // check validity of current SPk
void X3DH_get_OPk(uint32_t OPk_id, Xpair<Curve> &OPk); // retrieve matching OPk from localStorage, throw an exception if not found
......
......@@ -150,7 +150,7 @@ namespace lime {
std::shared_ptr<LimeGeneric> insert_LimeUser(const std::string &dbFilename, const std::string &deviceId, const std::string &url, const lime::CurveId curve, const uint16_t OPkInitialBatchSize,
const limeX3DHServerPostData &X3DH_post_data, const limeCallback &callback);
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &deviceId, const limeX3DHServerPostData &X3DH_post_data);
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &deviceId, const limeX3DHServerPostData &X3DH_post_data, const bool allStatus=false);
}
#endif // lime_lime_hpp
......@@ -218,21 +218,24 @@ Db::Db(std::string filename) : sql{"sqlite3", filename}{
* @brief Check for existence, retrieve Uid for local user based on its userId (GRUU) and curve from table lime_LocalUsers
*
* @param[in] deviceId a string holding the user to look for in DB, shall be its GRUU
* @param[out] Uid the DB internal Id matching given userId (if find in DB, 0 otherwise)
* @param[out] Uid the DB internal Id matching given userId (if find in DB, 0 if not find, -1 if found but not active)
* @param[out] curveId the curve selected at user creation
* @param[out] url the url of the X3DH server this user is registered on
* @param[in] allStatus allow loading of inactive user if set to true(default is false)
*
*/
void Db::load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url)
void Db::load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url, const bool allStatus)
{
int curve=0;
sql<<"SELECT Uid,curveId,server FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), into(curve), into(url), use(deviceId);
if (sql.got_data()) { // we found someone
// Check if the user has been activated
if (curve&lime::settings::DBInactiveUserBit) { // user is inactive
Uid = 0; // be sure to reset the db_Uid to 0
throw BCTBX_EXCEPTION << "Lime User "<<deviceId<<" is in DB but has not been activated yet, call create_user again to try to activate";
if (allStatus == false) { // do not allow inactive users to be loaded
// Check if the user has been activated
if (curve&lime::settings::DBInactiveUserBit) { // user is inactive
Uid = -1; // be sure to reset the db_Uid to -1
throw BCTBX_EXCEPTION << "Lime User "<<deviceId<<" is in DB but has not been activated yet, call create_user again to try to activate";
}
}
// turn back integer value retrieved from DB into a lime::CurveId
......@@ -350,7 +353,7 @@ void Db::set_peerDeviceStatus(const std::string &peerDeviceId, const std::vector
// Do we have this peerDevice in lime_PeerDevices
blob Ik_blob(sql);
long long id;
sql<<"SELECT Did, Ik FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId;", into(id), into(Ik_blob), use(peerDeviceId);
sql<<"SELECT Did, Ik FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId LIMIT 1;", into(id), into(Ik_blob), use(peerDeviceId);
if (sql.got_data()) { // Found it
auto IkSize = Ik_blob.get_len();
std::vector<uint8_t> storedIk;
......@@ -679,7 +682,7 @@ bool DR<DHKey>::session_save() {
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);
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
......@@ -797,18 +800,23 @@ bool DR<Curve>::trySkippedMessageKeys(const uint16_t Nr, const X<Curve, lime::Xt
*
* @return true if user was created successfully, exception is thrown otherwise.
*
* @exception BCTBX_EXCEPTION thrown if user already exists in the database
* @exception BCTBX_EXCEPTION thrown if user already exists and is active in the database
*/
template <typename Curve>
bool Lime<Curve>::create_user()
{
// check if the user is not already in the DB
int Uid;
int curve;
m_localStorage->sql<<"SELECT Uid,curveId FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), use(m_selfDeviceId);
// check if the user is not already in the DB
m_localStorage->sql<<"SELECT Uid,curveId FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), into(curve), use(m_selfDeviceId);
if (m_localStorage->sql.got_data()) {
// TODO: if already there but not active, try to publish it.
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";
if (curve&lime::settings::DBInactiveUserBit) { // user is there but inactive, just return true, the insert_LimeUser will try to publish it again
m_db_Uid = Uid;
return true;
} else {
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 Signature key pair
......@@ -908,11 +916,35 @@ void Lime<Curve>::get_SelfIdentityKey() {
}
/**
* @brief Generate (or load) a SPk and its signature by Ik.
* The generated SPk is stored in local storage with active Status, any exiting SPk are set to inactive.
*
* @param[out] publicSPk The generated (or loaded) active SPk public key
* @param[out] SPk_sig The signature by Ik of the SPk public key
* @param[out] SPk_id The SPk id
* @param[in] load Flag, if set first try to load key from storage to return them and if none is found, generate it
*/
template <typename Curve>
void Lime<Curve>::X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk, DSA<Curve, lime::DSAtype::signature> &SPk_sig, uint32_t &SPk_id) {
void Lime<Curve>::X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk, DSA<Curve, lime::DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load) {
// check Identity key is loaded in Lime object context
get_SelfIdentityKey();
// if the load flag is on, try to load a existing active key instead of generating it
if (load) {
blob SPk_blob(m_localStorage->sql);
m_localStorage->sql<<"SELECT SPk, SPKid FROM X3DH_SPk WHERE Uid = :Uid AND Status = 1 LIMIT 1;", into(SPk_blob), into(SPk_id), use(m_db_Uid);
if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
SPk_blob.read(0, (char *)(publicSPk.data()), publicSPk.size()); // Read the public key
// Sign the public key with our identity key
auto SPkSign = make_Signature<Curve>();
SPkSign->set_public(m_Ik.publicKey());
SPkSign->set_secret(m_Ik.privateKey());
SPkSign->sign(publicSPk, SPk_sig);
return;
}
}
// Generate a new ECDH Key pair
auto DH = make_keyExchange<Curve>();
DH->createKeyPair(m_RNG);
......@@ -959,8 +991,16 @@ void Lime<Curve>::X3DH_generate_SPk(X<Curve, lime::Xtype::publicKey> &publicSPk,
}
}
/**
* @brief Generate (or load) a batch of OPks, store them in local storage and return their public keys with their ids.
*
* @param[out] publicOPks A vector of all the generated (or loaded) OPks public keys, length shall be OPk_number unless keys are loaded from storage
* @param[out] OPk_ids A vector of all keys ids, order match the one of the previous vector
* @param[in] OPk_number How many keys shall we generate. This parameter is ignored if the load flag is set and we find some keys to load
* @param[in] load Flag, if set first try to load keys from storage to return them and if none found just generate the requested amount
*/
template <typename Curve>
void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number) {
void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load) {
// make room for OPk and OPk ids
OPk_ids.clear();
......@@ -978,6 +1018,29 @@ void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey
activeOPkIds.insert(OPk_id);
}
// Shall we try to just load OPks before generating them?
if (load) {
blob OPk_blob(m_localStorage->sql);
uint32_t OPk_id;
// Prepare DB statement: add a filter on current user Id as we'll target all retrieved OPk_ids (soci doesn't allow rowset and blob usage together)
statement st = (m_localStorage->sql.prepare << "SELECT OPk FROM X3DH_OPK WHERE Uid = :Uid AND OPKid = :OPkId;", into(OPk_blob), use(m_db_Uid), use(OPk_id));
for (uint32_t id : activeOPkIds) { // We already have all the active OPK ids, loop on them
OPk_id = id; // copy the id into the bind variable
st.execute(true);
if (m_localStorage->sql.got_data()) {
OPk_ids.push_back(OPk_id);
X<Curve, lime::Xtype::publicKey> OPk;
OPk_blob.read(0, (char *)(OPk.data()), OPk.size()); // Read the public key
publicOPks.push_back(OPk);
}
}
if (OPk_ids.size()>0) { // We found some OPks, all set then
return;
}
}
// we must create OPk_number new Ids
while (OPk_ids.size() < OPk_number){
// Generate a random OPk Id
......@@ -1200,8 +1263,8 @@ void Lime<Curve>::X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds) {
template bool Lime<C255>::create_user();
template bool Lime<C255>::activate_user();
template void Lime<C255>::get_SelfIdentityKey();
template void Lime<C255>::X3DH_generate_SPk(X<C255, lime::Xtype::publicKey> &publicSPk, DSA<C255, DSAtype::signature> &SPk_sig, uint32_t &SPk_id);
template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
template void Lime<C255>::X3DH_generate_SPk(X<C255, lime::Xtype::publicKey> &publicSPk, DSA<C255, DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load);
template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load);
template void Lime<C255>::cache_DR_sessions(std::vector<RecipientInfos<C255>> &internal_recipients, std::vector<std::string> &missing_devices);
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, Xpair<C255> &SPk);
......@@ -1214,8 +1277,8 @@ void Lime<Curve>::X3DH_updateOPkStatus(const std::vector<uint32_t> &OPkIds) {
template bool Lime<C448>::create_user();
template bool Lime<C448>::activate_user();
template void Lime<C448>::get_SelfIdentityKey();
template void Lime<C448>::X3DH_generate_SPk(X<C448, lime::Xtype::publicKey> &publicSPk, DSA<C448, DSAtype::signature> &SPk_sig, uint32_t &SPk_id);
template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
template void Lime<C448>::X3DH_generate_SPk(X<C448, lime::Xtype::publicKey> &publicSPk, DSA<C448, DSAtype::signature> &SPk_sig, uint32_t &SPk_id, const bool load);
template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448, lime::Xtype::publicKey>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number, const bool load);
template void Lime<C448>::cache_DR_sessions(std::vector<RecipientInfos<C448>> &internal_recipients, std::vector<std::string> &missing_devices);
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, Xpair<C448> &SPk);
......
......@@ -45,7 +45,7 @@ namespace lime {
Db(std::string filename);
~Db(){sql.close();};
void load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url);
void load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url, const bool allStatus=false);
void delete_LimeUser(const std::string &deviceId);
void clean_DRSessions();
void clean_SPk();
......
......@@ -27,11 +27,11 @@
using namespace::std;
namespace lime {
void LimeManager::load_user(std::shared_ptr<LimeGeneric> &user, const std::string &localDeviceId) {
void LimeManager::load_user(std::shared_ptr<LimeGeneric> &user, const std::string &localDeviceId, const bool allStatus) {
// Load user object
auto userElem = m_users_cache.find(localDeviceId);
if (userElem == m_users_cache.end()) { // not in cache, load it from DB
user = load_LimeUser(m_db_access, localDeviceId, m_X3DH_post_data);
user = load_LimeUser(m_db_access, localDeviceId, m_X3DH_post_data, allStatus);
m_users_cache[localDeviceId]=user;
} else {
user = userElem->second;
......@@ -77,7 +77,7 @@ namespace lime {
// Load user object
std::shared_ptr<LimeGeneric> user;
LimeManager::load_user(user, localDeviceId);
LimeManager::load_user(user, localDeviceId, true); // load user even if inactive as we are deleting it anyway
user->delete_user(managerDeleteCallback);
}
......
......@@ -39,6 +39,16 @@ using namespace::lime;
static belle_sip_stack_t *bc_stack=NULL;
static belle_http_provider_t *prov=NULL;
/**
* An enumeration to control simulated http transmission failure
*/
enum class HttpLinkStatus : uint8_t {
ok,
sending_fail,
reception_fail
};
static HttpLinkStatus httpLink = HttpLinkStatus::ok;
static int http_before_all(void) {
bc_stack=belle_sip_stack_new(NULL);
......@@ -129,6 +139,32 @@ static limeX3DHServerPostData X3DHServerPost([](const std::string &url, const st
belle_http_provider_send_request(prov,req,l);
});
/** @brief Post data to X3DH server: use the previous function but is able to simulate emission or reception failure.
* uses the httpLink global variable to simulate transmission error:
*
*
* @param[in] url The URL of X3DH server
* @param[in] from The local device id, used to identify user on the X3DH server, user identification and credential verification is out of lib lime scope.
* Here identification is performed on test server via belle-sip authentication mechanism and providing the test user credentials
* @param[in] message The data to be sent to the X3DH server
* @param[in] responseProcess The function to be called when response from server arrives. Function prototype is defined in lime.hpp: (void)(int responseCode, std::vector<uint8_t>response)
*/
static limeX3DHServerPostData X3DHServerPost_Failing_Simulation([](const std::string &url, const std::string &from, const std::vector<uint8_t> &message, const limeX3DHServerResponseProcess &responseProcess){
switch (httpLink) {
case HttpLinkStatus::reception_fail :
X3DHServerPost(url, from, message, [](int response_code, const std::vector<uint8_t> &responseBody){
// This is a dummy responseProcessing, just swallow the server answer and do nothing
});
break;
case HttpLinkStatus::sending_fail :
// Just do nothing, swallow the packet and do not give any answer.
break;
case HttpLinkStatus::ok :
default:
X3DHServerPost(url, from, message, responseProcess);
break;
}
});
/* This function will destroy and recreate managers given in parameter, force deleting all internal cache and start back from what is in local Storage */
static void managersClean(std::unique_ptr<LimeManager> &alice, std::unique_ptr<LimeManager> &bob, std::string aliceDb, std::string bobDb) {
......@@ -3175,9 +3211,174 @@ static void user_management(void) {
#endif
}
/**
* Scenario:
* - Create a user in local DB but do not send any message to the X3DH server
* - Check the user is there and inactive then delete it from local only
* - Create the same user with different keys on both local and server to check server didn't get it in the first place
* - Clean DBs (local and remote)
* - Repeat the previous 3 steps but let the message reach the server this time. At the second creation we shall get a fail from server as we are creating again a user but with different keys
* - Clean DBs (local and remote)
* - Create a local user but do not send anything to the server
* - Create it again letting the message go, it shall be a success
* - Clean DBs (local and remote)
* - Create a user but discard the response from server (so it is stored as inactive)
* - Create it again with normal connectivity, server is happy as we get the same Ik and SPk, user is activated on local base
*/
static void user_registration_failure_test(const lime::CurveId curve, const std::string &dbBaseFilename, const std::string &x3dh_server_url ) {
std::string dbFilenameAlice{dbBaseFilename};
dbFilenameAlice.append(".alice.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
remove(dbFilenameAlice.data()); // delete the database file if already exists
lime_tester::events_counters_t counters={};
int expected_success=0;
int expected_failed=0;
// reset the global setting for Http Link
httpLink = HttpLinkStatus::ok;
limeCallback callback([&counters](lime::CallbackReturn returnCode, std::string anythingToSay) {
if (returnCode == lime::CallbackReturn::success) {
counters.operation_success++;
} else {
counters.operation_failed++;
LIME_LOGI <<"Insert Lime user failed : "<< anythingToSay.data() ;
}
});
// we need a LimeManager
auto Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation));
auto aliceDeviceId = lime_tester::makeRandomDeviceName("alice.");
try {
/* create a user in a fresh database but discard the message to server */
httpLink = HttpLinkStatus::sending_fail;
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE(counters.operation_failed == expected_failed); // We shall have no failure either, the server never answer so the callback is never called (in a real situation the timeout on answer may be forwarded to the lime engine)
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
} catch (BctbxException &e) {
LIME_LOGE << e;
BC_FAIL("");
}
bool gotExpectedException = false;
long int Uid=0;
try {
/* now we have the user in local base but not active and not in remote, lets check that */
/* load alice from DB(using insider functions) : user is not active, we shall get an exception */
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(dbFilenameAlice));
auto curve = CurveId::unset;
std::string x3dh_server_url;
localStorage->load_LimeUser(*aliceDeviceId, Uid, curve, x3dh_server_url); // this one will throw an exception if user is not found, just let it rise
} catch (BctbxException &e) {
gotExpectedException = true;
}
BC_ASSERT(Uid == -1); // when user is not active, the db::load_LimeUser set the Uid to -1 before generating the exception
BC_ASSERT(gotExpectedException == true);
try {
// Delete local user from base and create it letting it flow to the server
httpLink = HttpLinkStatus::sending_fail; // make sure our delete never reach the server
Manager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
BC_ASSERT_TRUE(counters.operation_failed == expected_failed);
httpLink = HttpLinkStatus::ok; // restore htpp link
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback); // This one has different keys but the same user Id
// if this userId was already registered on server(with the old Ik) we would have an error
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// and delete it again
Manager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// Start again but this time let the message flow to the server, just block the answer
httpLink = HttpLinkStatus::reception_fail;
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE(counters.operation_failed == expected_failed); // We shall have no failure either, the server never answer so the callback is never called (in a real situation the timeout on answer may be forwarded to the lime engine)
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
// Now we have the user inactive in local but set on server. Delete the local one
httpLink = HttpLinkStatus::sending_fail; // make sure our delete never reach the server
Manager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
httpLink = HttpLinkStatus::ok; // restore htpp link
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback); // This one has different keys but the same user Id
// this userId is already registered on server(with the old Ik) we shall have an error -> the error is deleted from local base
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_failed,++expected_failed,lime_tester::wait_for_timeout));
// Now the user is on the server but no more in local, to delete it from server, we must create it in local only and then delete
httpLink = HttpLinkStatus::sending_fail; // make sure our delete never reach the server
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE(counters.operation_failed == expected_failed); // We shall have no failure either, the server never answer so the callback is never called (in a real situation the timeout on answer may be forwarded to the lime engine)
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
httpLink = HttpLinkStatus::ok; // restore htpp link
Manager->delete_user(*aliceDeviceId, callback); // delete from local and remote
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// Create again on local only, and then call the create_user again with the same id, with restored connectivity to get an active user
httpLink = HttpLinkStatus::sending_fail; // make sure our delete never reach the server
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE(counters.operation_failed == expected_failed); // We shall have no failure either, the server never answer so the callback is never called (in a real situation the timeout on answer may be forwarded to the lime engine)
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
// create one more time, it shall this time publish on the server without error
httpLink = HttpLinkStatus::ok; // restore htpp link
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// Clean
Manager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// Same than previous but this time we block the server answer and then call again create
httpLink = HttpLinkStatus::reception_fail; // make sure our delete never reach the server
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_FALSE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+1,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE(counters.operation_failed == expected_failed); // We shall have no failure either, the server never answer so the callback is never called (in a real situation the timeout on answer may be forwarded to the lime engine)
Manager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost_Failing_Simulation)); // reload manager from db after the timeout
// create one more time, it shall this time publish on the server without error
httpLink = HttpLinkStatus::ok; // restore htpp link
Manager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
} catch (BctbxException &e) {
LIME_LOGE << e;
BC_FAIL("");
}
try {
if (cleanDatabase) {
// delete Alice, wait for callback confirmation from server
httpLink = HttpLinkStatus::ok; // restore http link functionality
Manager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
remove(dbFilenameAlice.data()); // delete the database file
}
} catch (BctbxException &e) {
LIME_LOGE << e;
BC_FAIL("");
}
}
static void user_registration_failure(void) {
#ifdef EC25519_ENABLED
user_registration_failure_test(lime::CurveId::c25519, "lime_user_management", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c25519_server_port).data());
#endif
#ifdef EC448_ENABLED
user_registration_failure_test(lime::CurveId::c448, "lime_user_management", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c448_server_port).data());
#endif
}
static test_t tests[] = {
TEST_NO_TAG("User Management", user_management),
TEST_NO_TAG("Basic", x3dh_basic),
TEST_NO_TAG("User Management", user_management),
TEST_NO_TAG("User registration failure", user_registration_failure),
TEST_NO_TAG("User not found", x3dh_user_not_found),
TEST_NO_TAG("Queued encryption", x3dh_operation_queue),
TEST_NO_TAG("Multi devices queued encryption", x3dh_multidev_operation_queue),
......
......@@ -184,9 +184,6 @@ db.get("PRAGMA user_version;", function(err, row) {
}
});