Commit da09a28c authored by johan's avatar johan

Update function manages SPk

To be completed with
- OPk management
parent 53d7692f
......@@ -130,6 +130,25 @@ namespace lime {
postToX3DHServer(userData, X3DHmessage);
}
template <typename Curve>
void Lime<Curve>::update_SPk(const limeCallback &callback) {
// Do we need to update the SPk
if (!is_currentSPk_valid()) {
BCTBX_SLOGI<<"User "<<m_selfDeviceId<<" updates its SPk";
callbackUserData<Curve> *userData = new callbackUserData<Curve>{this->shared_from_this(), callback};
// generate and publish the SPk
X<Curve> SPk{};
Signature<Curve> SPk_sig{};
uint32_t SPk_id=0;
X3DH_generate_SPk(SPk, SPk_sig, SPk_id);
std::vector<uint8_t> X3DHmessage{};
x3dh_protocol::buildMessage_publishSPk(X3DHmessage, SPk, SPk_sig, SPk_id);
postToX3DHServer(userData, X3DHmessage);
} else { // nothing to do but caller expect a callback
if (callback) callback(lime::callbackReturn::success, "");
}
}
template <typename Curve>
void Lime<Curve>::encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) {
bctbx_debug("encrypt from %s to %ld recipients", m_selfDeviceId.data(), recipients->size());
......@@ -223,13 +242,18 @@ namespace lime {
}
// parse the X3DH init message, get keys from localStorage, compute the shared secrets, create DR_Session and return a shared pointer to it
std::shared_ptr<DR<Curve>> DRSession{X3DH_init_receiver_session(X3DH_initMessage, senderDeviceId)}; // would just throw an exception in case of failure, let it flow up
DRSessions.clear();
DRSessions.push_back(DRSession);
try {
std::shared_ptr<DR<Curve>> DRSession{X3DH_init_receiver_session(X3DH_initMessage, senderDeviceId)}; // would just throw an exception in case of failure, let it flow up
DRSessions.clear();
DRSessions.push_back(DRSession);
} catch (BctbxException &e) {
BCTBX_SLOGE<<"Fail to create the DR session from the X3DH init message : "<<e;
return false;
}
if (decryptMessage<Curve>(senderDeviceId, m_selfDeviceId, recipientUserId, DRSessions, cipherHeader, cipherMessage, plainMessage) != 0) {
// we manage to decrypt the message with this session, set it in cache
m_DR_sessions_cache[senderDeviceId] = std::move(DRSession);
m_DR_sessions_cache[senderDeviceId] = std::move(DRSessions.front());
return true;
}
return false;
......
......@@ -80,6 +80,7 @@ namespace lime {
void X3DH_generate_SPk(X<Curve> &publicSPk, Signature<Curve> &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>> &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_get_SPk(uint32_t SPk_id, KeyPair<X<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, KeyPair<X<Curve>> &SPk); // retrieve matching OPk from localStorage, throw an exception if not found
/* X3DH related - part related to X3DH DR session initiation, implemented in lime_x3dh.cpp */
void X3DH_init_sender_session(const std::vector<X3DH_peerBundle<Curve>> &peersBundle); // compute a sender X3DH using the data from peer bundle, then create and load the DR_Session
......@@ -108,6 +109,13 @@ namespace lime {
void publish_user(const limeCallback &callback) override;
void delete_user(const limeCallback &callback) override;
/**
* @brief Check if the current SPk needs to be updated, if yes, generate a new one and publish it on server
*
* @param[in] callback Called with success or failure when operation is completed.
*/
void update_SPk(const limeCallback &callback) override;
void encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) override;
bool decrypt(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) override;
};
......
......@@ -36,6 +36,7 @@ namespace lime {
virtual bool decrypt(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) = 0;
virtual void publish_user(const limeCallback &callback) = 0;
virtual void delete_user(const limeCallback &callback) = 0;
virtual void update_SPk(const limeCallback &callback) = 0;
virtual ~LimeGeneric() {};
};
......
......@@ -35,6 +35,16 @@ using namespace::lime;
namespace lime {
/******************************************************************************/
/* */
/* Db public API */
/* */
/******************************************************************************/
/**
* @brief Constructor, open and check DB validity, create or update db schema is needed
*
* @param[in] filename The path to DB file
*/
Db::Db(std::string filename) : sql{sqlite3, filename}{
constexpr int db_module_table_not_in_base = -2;
constexpr int db_module_table_not_holding_lime_row = -1;
......@@ -190,23 +200,20 @@ Db::Db(std::string filename) : sql{sqlite3, filename}{
}
}
};
/******************************************************************************/
/* */
/* Db public API */
/* */
/******************************************************************************/
/**
* @brief Check for existence, retrieve Uid for local user based on its userId(GRUU) and curve from table lime_LocalUsers
*
* @param[in] userId a string holding the user to look for in DB, shall be its GRUU
* @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] curveId the curve selected at user creation
* @param[out] url the url of the X3DH server this user is registered on
*
*/
void Db::load_LimeUser(const std::string &userId, 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)
{
int curve=0;
sql<<"SELECT Uid,CurveId,server FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), into(curve), into(url), use(userId);
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
// turn back integer value retrieved from DB into a lime::CurveId
......@@ -221,16 +228,16 @@ void Db::load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &
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 "<<userId<<" claim to run with unknown or unset Curve Id "<< curve;
throw BCTBX_EXCEPTION << "Lime DB either corrupted or back from the future. User "<<deviceId<<" claim to run with unknown or unset Curve Id "<< curve;
}
} else { // no match: throw an execption
Uid = 0; // be sure to reset the db_Uid to 0
throw BCTBX_EXCEPTION << "Cannot find Lime User "<<userId<<" in DB";
throw BCTBX_EXCEPTION << "Cannot find Lime User "<<deviceId<<" in DB";
}
}
/**
* @brief Delete old stale sessions and old stored message key
* @brief Delete old stale sessions and old stored message key. Apply to all users in localStorage
* - 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
......@@ -241,21 +248,44 @@ void Db::load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &
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 Delete old stale SPk. Apply to all users in localStorage
* - SPk in stale status for more than SPK_limboTime_days are deleted
*/
void Db::clean_SPk() {
// WARNIMG: not sure this code is portable it may work with sqlite3 only
// delete stale sessions considered to old
sql<<"DELETE FROM X3DH_SPK WHERE Status=0 AND timeStamp < date('now', '-"<<lime::settings::SPK_limboTime_days<<" day');";
}
/**
* @brief Get a list of deviceIds of all local users present in localStorage
*
* @param[out] deviceIds the list of all local users (their device Id)
*/
void Db::get_allLocalDevices(std::vector<std::string> &deviceIds) {
deviceIds.clear();
rowset<row> rs = (sql.prepare << "SELECT UserId FROM lime_LocalUsers;");
for (auto &r : rs) {
deviceIds.push_back(r.get<string>(0));
}
}
/**
* @brief if exists, delete user
*
* @param[in] userId a string holding the user to look for in DB, shall be its GRUU
* @param[in] deviceId a string holding the user to look for in DB, shall be its GRUU
*
*/
void Db::delete_LimeUser(const std::string &userId)
void Db::delete_LimeUser(const std::string &deviceId)
{
sql<<"DELETE FROM lime_LocalUsers WHERE UserId = :userId;", use(userId);
sql<<"DELETE FROM lime_LocalUsers WHERE UserId = :userId;", use(deviceId);
}
/******************************************************************************/
......@@ -582,7 +612,7 @@ void Lime<Curve>::X3DH_generate_SPk(X<Curve> &publicSPk, Signature<Curve> &SPk_s
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);
m_localStorage->sql<<"UPDATE X3DH_SPK SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Uid = :Uid AND Status = 1;", use(m_db_Uid);
blob SPk_blob(m_localStorage->sql);
SPk_blob.write(0, (const char *)ECDH_Context->selfPublic, ECDH_Context->pointCoordinateLength);
......@@ -758,6 +788,20 @@ void Lime<Curve>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<Curve>> &SPk) {
}
}
/**
* @brief is current SPk valid?
*/
template <typename Curve>
bool Lime<Curve>::is_currentSPk_valid(void) {
// Do we have an active SPk for this user which is younger than SPK_lifeTime_days
int dummy;
m_localStorage->sql<<"SELECT SPKid FROM X3DH_SPk WHERE Uid = :Uid AND Status = 1 AND timeStamp > date('now', '-"<<lime::settings::SPK_lifeTime_days<<" day') LIMIT 1;", into(dummy), use(m_db_Uid);
if (m_localStorage->sql.got_data()) {
return true;
} else {
return false;
}
}
/**
* @brief retrieve matching OPk from localStorage, throw an exception if not found
......@@ -789,6 +833,7 @@ void Lime<Curve>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<Curve>> &OPk) {
template long int Lime<C255>::store_peerDevice(const std::string &peerDeviceId, const ED<C255> &Ik);
template void Lime<C255>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C255>>> &DRSessions);
template void Lime<C255>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C255>> &SPk);
template bool Lime<C255>::is_currentSPk_valid(void);
template void Lime<C255>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C255>> &SPk);
#endif
......@@ -801,6 +846,7 @@ void Lime<Curve>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<Curve>> &OPk) {
template long int Lime<C448>::store_peerDevice(const std::string &peerDeviceId, const ED<C448> &Ik);
template void Lime<C448>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C448>>> &DRSessions);
template void Lime<C448>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C448>> &SPk);
template bool Lime<C448>::is_currentSPk_valid(void);
template void Lime<C448>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C448>> &SPk);
#endif
......
......@@ -29,12 +29,57 @@ namespace lime {
soci::session sql;
Db()=delete; // we can't create a new DB holder without DB filename
/**
* @brief Constructor, open and check DB validity, create or update db schema is needed
*
* @param[in] filename The path to DB file
*/
Db(std::string filename);
~Db(){sql.close();};
void load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &curveId, std::string &url);
void delete_LimeUser(const std::string &userId);
/**
* @brief Check for existence, retrieve Uid for local user based on its deviceId(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 found in DB, 0 otherwise)
* @param[out] curveId the curve selected at user creation
* @param[out] url the url of the X3DH server this user is registered on
*
*/
void load_LimeUser(const std::string &deviceId, long int &Uid, lime::CurveId &curveId, std::string &url);
/**
* @brief if exists, delete user
*
* @param[in] userId a string holding the user to look for in DB, shall be its GRUU
*
*/
void delete_LimeUser(const std::string &deviceId);
/**
* @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 clean_DRSessions();
/**
* @brief Delete old stale SPk. Apply to all users in localStorage
* - SPk in stale status for more than SPK_limboTime_days are deleted
*/
void clean_SPk();
/**
* @brief Get a list of deviceIds of all local users present in localStorage
*
* @param[out] deviceIds the list of all local users (their device Id)
*/
void get_allLocalDevices(std::vector<std::string> &deviceIds);
};
#ifdef EC25519_ENABLED
......
......@@ -109,13 +109,55 @@ namespace lime {
// open local DB
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(m_db_access));
/* DR sessions cleaning */
/* DR sessions and old stale SPk cleaning */
localStorage->clean_DRSessions();
localStorage->clean_SPk();
// get all users from localStorage
std::vector<std::string> deviceIds{};
localStorage->get_allLocalDevices(deviceIds);
//This counter will trace number of callbacks, to trace how many operation did end,
auto callbackCounts = make_shared<size_t>(0);
// we expect two callback per local user account: one for update SPk, one for get OPk number on server
size_t expectedCallbacks = deviceIds.size();
auto globalReturnCode = make_shared<lime::callbackReturn>(lime::callbackReturn::success);
limeCallback managerUpdateCallback([callbackCounts, expectedCallbacks, globalReturnCode, callback](lime::callbackReturn returnCode, std::string errorMessage) {
(*callbackCounts)++;
if (returnCode == lime::callbackReturn::fail) {
*globalReturnCode = lime::callbackReturn::fail; // if one fail, return fail at the end of it
}
if (*callbackCounts == expectedCallbacks) {
if (callback) callback(*globalReturnCode, "");
}
});
// for each user
for (auto deviceId : deviceIds) {
BCTBX_SLOGI<<"Lime update user "<<deviceId;
//load user
auto userElem = m_users_cache.find(deviceId);
std::shared_ptr<LimeGeneric> user;
if (userElem == m_users_cache.end()) { // not in cache, load it from DB
user = load_LimeUser(m_db_access, deviceId, m_http_provider, m_user_auth);
m_users_cache[deviceId]=user;
} else {
user = userElem->second;
}
// send a request to X3DH server to check how many OPk are left on server
// update the SPk(if needed)
user->update_SPk(managerUpdateCallback);
}
/* OPk check : ask X3DH server how many keys are left */
/* SPk check */
if (callback) callback(lime::callbackReturn::success, "");
//if (callback) callback(lime::callbackReturn::success, "");
}
} // namespace lime
......@@ -86,9 +86,8 @@ 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
constexpr unsigned int SPK_lifeTime_days=7; // in days, Life time of a signed pre-key, it will be set to stale after that period
constexpr unsigned int SPK_limboTime_days=30; // in days, How long shall we keep a signed pre-key once it has been replaced by a new one
}
}
......
......@@ -316,6 +316,25 @@ bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8
return true;
}
/* return true if the message buffer is a valid DR message holding an X3DH init message, copy its SPk id in the given parameter */
bool DR_message_extractX3DHInit_SPkId(std::vector<uint8_t> &message, uint32_t &SPkId) {
if (DR_message_holdsX3DHInit(message) == false) return false;
// compute position of SPkId in message
size_t SPkIdPos = 3+1; // DR message header + OPK flag
if (message[2] == static_cast<uint8_t>(lime::CurveId::c25519)) { // curve 25519
SPkIdPos += ED<C255>::keyLength() + X<C255>::keyLength();
} else { // curve 448
SPkIdPos += ED<C448>::keyLength() + X<C448>::keyLength();
}
SPkId = message[SPkIdPos]<<24 |
message[SPkIdPos+1]<<16 |
message[SPkIdPos+2]<<8 |
message[SPkIdPos+3];
return true;
}
/* Open provided DB and look for DRSessions established between selfDevice and peerDevice
* Populate the sessionsId vector with the Ids of sessions found
......@@ -369,6 +388,33 @@ unsigned int get_StoredMessageKeyCount(const std::string &dbFilename, const std:
}
}
/* For the given deviceId, count the number of associated SPk and return the Id of the active one(if any)
* return true if an active one was found
*/
bool get_SPks(const std::string &dbFilename, const std::string &selfDeviceId, size_t &count, uint32_t &activeId) noexcept{
try {
soci::session sql(sqlite3, dbFilename); // open the DB
count=0;
sql<< "SELECT count(SPKid) FROM X3DH_SPK as s INNER JOIN lime_LocalUsers as u on u.Uid = s.Uid WHERE u.UserId = :selfId;", into(count), use(selfDeviceId);
if (sql.got_data()) {
sql<< "SELECT SPKid FROM X3DH_SPK as s INNER JOIN lime_LocalUsers as u on u.Uid = s.Uid WHERE u.UserId = :selfId AND s.Status=1 LIMIT 1;", into(activeId), use(selfDeviceId);
if (sql.got_data()) {
return true;
} else {
return false;
}
} else {
return false;
}
} catch (exception &e) { // swallow any error on DB
BCTBX_SLOGE<<"Got an error while getting the MK count in DB: "<<e.what();
count=0;
return false;
}
}
/* Move back in time all timeStamps by the given amout of days
* DB holds timeStamps in DR_sessions and X3DH_SPK tables
*/
......
......@@ -78,6 +78,9 @@ bool DR_message_holdsX3DHInit(std::vector<uint8_t> &message, bool &haveOPk);
/* return true if the message buffer is a valid DR message holding a X3DH init one in its header and copy the X3DH init message in the provided buffer */
bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage);
/* return true if the message buffer is a valid DR message holding an X3DH init message, copy its SPk id in the given parameter */
bool DR_message_extractX3DHInit_SPkId(std::vector<uint8_t> &message, uint32_t &SPkId);
/* 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 */
......@@ -89,6 +92,11 @@ long int get_DRsessionsId(const std::string &dbFilename, const std::string &self
*/
unsigned int get_StoredMessageKeyCount(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId) noexcept;
/* For the given deviceId, count the number of associated SPk and return the Id of the active one(if any)
* return true if an active one was found
*/
bool get_SPks(const std::string &dbFilename, const std::string &selfDeviceId, size_t &count, uint32_t &activeId) noexcept;
/* Move back in time all timeStamps by the given amout of days
* DB holds timeStamps in DR_sessions and X3DH_SPK tables
*/
......
......@@ -142,7 +142,7 @@ static void helloworld_basic_test(const lime::CurveId curve, const std::string &
// Any application using Lime shall instantiate one LimeManager only, even in case of multiple users managed by the application.
auto aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, prov,
// closure copy from context its way to access users credentials
[&aliceCredentials](belle_sip_auth_event_t *event){
[aliceCredentials](belle_sip_auth_event_t *event){
// the deviceId is set in the event username(accessible via belle_sip_auth_event_get_username(event);
BCTBX_SLOGI<<"Accessing credentials for user "<<std::string(belle_sip_auth_event_get_username(event))<<endl;
......@@ -156,7 +156,7 @@ static void helloworld_basic_test(const lime::CurveId curve, const std::string &
auto bobManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameBob, prov,
// closure copy from context its way to access users credentials
[&bobCredentials](belle_sip_auth_event_t *event){
[bobCredentials](belle_sip_auth_event_t *event){
// the deviceId is set in the event username(accessible via belle_sip_auth_event_get_username(event);
BCTBX_SLOGI<<"Accessing credentials for user "<<std::string(belle_sip_auth_event_get_username(event))<<endl;
......@@ -207,14 +207,16 @@ static void helloworld_basic_test(const lime::CurveId curve, const std::string &
// - a callback (prototype: void(lime::callbackReturn, std::string))
aliceManager->encrypt(*aliceDeviceId, make_shared<const std::string>("bob"), recipients, message, cipherMessage,
// lambda to get the results, it captures :
// - counter : relative to the test, real application won't need this, it's local and used to wait for this macro so can't be destroyed before the call to this closure
// - counter : relative to the test, real application won't need this, it's local and used to wait for completion and can't be destroyed before the call to this closure
// - recipients : It will hold the same list of deviceIds we set as input with their corresponding cipherHeader.
// - cipherMessage : It will hold the cipher message to be sent to all recipients devices.
// IMPORTANT : recipients and cipherMessage are captured by copy not reference. They are shared_ptr, their original scope is likely to be the function where the encrypt is called.
// they shall then be destroyed when getting out of this function and thus won't be valid anymore when this closure is called. By getting a copy we just increase their
// use count and are sure to still have them valid when we are called.
// It may be wise to use weak_ptr instead of shared ones so if any problem occurs resulting in callback never being called, it won't held this buffer from being destroyed
// In normal operation, the shared_ptr given to encrypt function is internally owned at least until the callback is called.
// After this closure is called it is destroyed(internal reference is dropped) decreasing the count and allowing the release of the buffer.
//
// It may be wise to use weak_ptr instead of shared ones so if any problem occurs resulting in callback never being called/destroyed, it won't held this buffer from being destroyed
// In normal operation, the shared_ptrs to recipients and cipherMessage given to encrypt function are internally owned at least until the callback is called.
[&counters,
recipients, cipherMessage](lime::callbackReturn returnCode, std::string errorMessage){
// counters is related to this test environment only, not to be considered for real usage
......
......@@ -206,6 +206,172 @@ static void lime_session_establishment(const lime::CurveId curve, const std::str
}
}
/**
* Scenario:
* - Create a user alice
* - simulate a move forward in time
* - check we changed SPk and still got the old one
* - repeat until we reach the epoch in which SPk shall be deleted, check it is the case
*/
static void lime_update_SPk_test(const lime::CurveId curve, const std::string &dbBaseFilename, const std::string &x3dh_server_url) {
// create DB
std::string dbFilenameAlice{dbBaseFilename};
std::string dbFilenameBob{dbBaseFilename};
dbFilenameAlice.append(".alice.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
dbFilenameBob.append(".bob.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
remove(dbFilenameAlice.data()); // delete the database file if already exists
remove(dbFilenameBob.data()); // delete the database file if already exists
lime_tester::events_counters_t counters={};
int expected_success=0;
limeCallback callback([&counters](lime::callbackReturn returnCode, std::string anythingToSay) {
if (returnCode == lime::callbackReturn::success) {
counters.operation_success++;
} else {
counters.operation_failed++;
BCTBX_SLOGE<<"Lime operation failed : "<<anythingToSay;
}
});
try {
// starting epoch nad number of SPk keys in localStorage
unsigned int epoch=0;
auto SPkExpectedCount=1;
size_t patternIndex = 0;
// create Manager and device for alice
auto aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, prov, user_auth_callback));
auto aliceDeviceId = lime_tester::makeRandomDeviceName("alice.d1.");
aliceManager->create_user(*aliceDeviceId, x3dh_server_url, curve, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
size_t SPkCount=0;
uint32_t activeSPkId=0;
BC_ASSERT_TRUE(lime_tester::get_SPks(dbFilenameAlice, *aliceDeviceId, SPkCount, activeSPkId));
BC_ASSERT_EQUAL(SPkCount, SPkExpectedCount, int, "%d");
// We will create a bob device and encrypt for each new epoch
std::vector<std::unique_ptr<LimeManager>> bobManagers{};
std::vector<std::shared_ptr<std::string>> bobDeviceIds{};
std::vector<std::shared_ptr<std::vector<recipientData>>> bobRecipients{};
std::vector<std::shared_ptr<std::vector<uint8_t>>> bobCipherMessages{};
// create a device for bob and encrypt to alice
bobManagers.push_back(std::unique_ptr<LimeManager>(new LimeManager(dbFilenameBob, prov, user_auth_callback)));
bobDeviceIds.push_back(lime_tester::makeRandomDeviceName("bob.d"));
bobManagers.back()->create_user(*(bobDeviceIds.back()), x3dh_server_url, curve, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
bobRecipients.push_back(make_shared<std::vector<recipientData>>());
bobRecipients.back()->emplace_back(*aliceDeviceId);
auto plainMessage = make_shared<const std::vector<uint8_t>>(lime_tester::messages_pattern[patternIndex].begin(), lime_tester::messages_pattern[patternIndex].end());
bobCipherMessages.push_back(make_shared<std::vector<uint8_t>>());
bobManagers.back()->encrypt(*(bobDeviceIds.back()), make_shared<const std::string>("alice"), bobRecipients.back(), plainMessage, bobCipherMessages.back(), callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
uint32_t SPkIdMessage=0;
// extract SPKid from bob's message, and check it matches the current one from Alice DB
BC_ASSERT_TRUE(lime_tester::DR_message_extractX3DHInit_SPkId((*(bobRecipients.back()))[0].cipherHeader, SPkIdMessage));
BC_ASSERT_EQUAL(SPkIdMessage, activeSPkId, uint32_t, "%x");
patternIndex++;
patternIndex %= lime_tester::messages_pattern.size();
// steping by SPK_lifeTime_days go ahead in time and check the update is performed correctly
while (epoch<=lime::settings::SPK_limboTime_days) {
// forward time by SPK_lifeTime_days
aliceManager=nullptr; // destroy manager before modifying DB
lime_tester::forwardTime(dbFilenameAlice, lime::settings::SPK_lifeTime_days);
epoch+=lime::settings::SPK_lifeTime_days;
aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, prov, user_auth_callback));
// call the update, it shall create and upload a new SPk but keep the old ones
aliceManager->update(callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
SPkExpectedCount++;
// check we have the correct count of keys in local
SPkCount=0;
activeSPkId=0;
BC_ASSERT_TRUE(lime_tester::get_SPks(dbFilenameAlice, *aliceDeviceId, SPkCount, activeSPkId));
BC_ASSERT_EQUAL(SPkCount, SPkExpectedCount, int, "%d");
// create a device for bob and use it to encrypt
bobManagers.push_back(std::unique_ptr<LimeManager>(new LimeManager(dbFilenameBob, prov, user_auth_callback)));
bobDeviceIds.push_back(lime_tester::makeRandomDeviceName("bob.d"));
bobManagers.back()->create_user(*(bobDeviceIds.back()), x3dh_server_url, curve, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
bobRecipients.push_back(make_shared<std::vector<recipientData>>());
bobRecipients.back()->emplace_back(*aliceDeviceId);
auto plainMessage = make_shared<const std::vector<uint8_t>>(lime_tester::messages_pattern[patternIndex].begin(), lime_tester::messages_pattern[patternIndex].end());
bobCipherMessages.push_back(make_shared<std::vector<uint8_t>>());
bobManagers.back()->encrypt(*(bobDeviceIds.back()), make_shared<const std::string>("alice"), bobRecipients.back(), plainMessage, bobCipherMessages.back(), callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
// extract SPKid from bob's message, and check it matches the current one from Alice DB
SPkIdMessage=0;
BC_ASSERT_TRUE(lime_tester::DR_message_extractX3DHInit_SPkId((*(bobRecipients.back()))[0].cipherHeader, SPkIdMessage));
BC_ASSERT_EQUAL(SPkIdMessage, activeSPkId, uint32_t, "%x");
patternIndex++;
patternIndex %= lime_tester::messages_pattern.size();
}
// forward time once more by SPK_lifeTime_days, our first SPk shall now be out of limbo and ready to be deleted
aliceManager=nullptr; // destroy manager before modifying DB
lime_tester::forwardTime(dbFilenameAlice, lime::settings::SPK_lifeTime_days);
epoch+=lime::settings::SPK_lifeTime_days;
aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, prov, user_auth_callback));
// call the update, it shall create and upload a new SPk but keep the old ones and delete one
aliceManager->update(callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
// there shall not be any rise in the number of SPk keys found in DB, check that
SPkCount=0;
activeSPkId=0;
BC_ASSERT_TRUE(lime_tester::get_SPks(dbFilenameAlice, *aliceDeviceId, SPkCount, activeSPkId));
BC_ASSERT_EQUAL(SPkCount, SPkExpectedCount, int, "%d");
// Try to decrypt all message: the first message must fail to decrypt as we just deleted the SPk needed to create the session
std::vector<uint8_t> receivedMessage{};
BC_ASSERT_FALSE(aliceManager->decrypt(*aliceDeviceId, "alice", *(bobDeviceIds[0]), (*bobRecipients[0])[0].cipherHeader, *(bobCipherMessages[0]), receivedMessage));
// other shall be Ok.
for (size_t i=1; i<bobManagers.size(); i++) {
receivedMessage.clear();
BC_ASSERT_TRUE(aliceManager->decrypt(*aliceDeviceId, "alice", *(bobDeviceIds[i]), (*bobRecipients[i])[0].cipherHeader, *(bobCipherMessages[i]), receivedMessage));
auto receivedMessageString = std::string{receivedMessage.begin(), receivedMessage.end()};
BC_ASSERT_TRUE(receivedMessageString == lime_tester::messages_pattern[i%lime_tester::messages_pattern.size()]);
}
if (cleanDatabase) {
auto i=0;
for (auto &bobManager : bobManagers) {
bobManager->delete_user(*(bobDeviceIds[i]), callback);
i++;
}
aliceManager->delete_user(*aliceDeviceId, callback);
expected_success += 1+bobManagers.size();
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success,expected_success,lime_tester::wait_for_timeout));
remove(dbFilenameAlice.data());
remove(dbFilenameBob.data());
}
} catch (BctbxException &e) {
BCTBX_SLOGE <<e;;
BC_FAIL();
}
}
static void lime_update_SPk() {
#ifdef EC25519_ENABLED
lime_update_SPk_test(lime::CurveId::c25519, "lime_update_SPk", std::string("https://").append(test_x3dh_server_url).append(":").append(test_x3dh_c25519_server_port).data());
#endif
#ifdef EC448_ENABLED
//lime_update_SPk_test(lime::CurveId::c448, "lime_update_SPk", std::string("https://").append(test_x3dh_server_url).append(":").append(test_x3dh_c448_server_port).data());
#endif
}
/**
* Scenario:
* - Establish a session between alice and bob
......@@ -213,6 +379,7 @@ static void lime_session_establishment(const lime::CurveId curve, const std::str
* - Check we have two message key in localStorage
* - decrypt a message
* - Check we have one message key in localStorage
* - simulate a move forward in time
* - call the update
* - Check we have no more message key in storage
* - try to decrypt the message, it shall fail
......@@ -1470,6 +1637,7 @@ static test_t tests[] = {
TEST_NO_TAG("Sending chain limit", x3dh_sending_chain_limit),
TEST_NO_TAG("Without OPk", x3dh_without_OPk),
TEST_NO_TAG("Update - clean MK", lime_update_clean_MK),
TEST_NO_TAG("Update - SPk", lime_update_SPk)
};
test_suite_t lime_lime_test_suite = {
......
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