Commit af311a2a authored by johan's avatar johan
Browse files

Republish user on update when deleted from server

- allow server database recovery
parent eb9147f2
......@@ -148,7 +148,9 @@ namespace lime {
template <typename Curve>
void Lime<Curve>::update_OPk(const limeCallback &callback, uint16_t OPkServerLowLimit, uint16_t OPkBatchSize) {
// Request Server for the count of our OPk it still holds
auto userData = make_shared<callbackUserData<Curve>>(this->shared_from_this(), callback, OPkServerLowLimit, OPkBatchSize);
// OPk server low limit cannot be zero, it must be at least one as we test the userData on this to check the server request was a getSelfOPks
// and republish the user if not found
auto userData = make_shared<callbackUserData<Curve>>(this->shared_from_this(), callback, std::max(OPkServerLowLimit,static_cast<uint16_t>(1)), OPkBatchSize);
std::vector<uint8_t> X3DHmessage{};
x3dh_protocol::buildMessage_getSelfOPks<Curve>(X3DHmessage);
postToX3DHServer(userData, X3DHmessage); // in the response from server, if more OPks are needed, it will generate and post them before calling the callback
......
......@@ -1023,7 +1023,8 @@ void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve, lime::Xtype::publicKey
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));
// Get Keys matching the currend user and that are not set as dispatched yet (Status = 1)
statement st = (m_localStorage->sql.prepare << "SELECT OPk FROM X3DH_OPK WHERE Uid = :Uid AND Status = 1 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
......
......@@ -886,7 +886,8 @@ namespace lime {
// generate and publish the OPks
std::vector<X<Curve, lime::Xtype::publicKey>> OPks{};
std::vector<uint32_t> OPk_ids{};
X3DH_generate_OPks(OPks, OPk_ids, userData->OPkBatchSize);
// Generate OPks OPkBatchSize (or more if we need more to reach ServerLowLimit)
X3DH_generate_OPks(OPks, OPk_ids, std::max(userData->OPkBatchSize, static_cast<uint16_t>(userData->OPkServerLowLimit - selfOPkIds.size())) );
std::vector<uint8_t> X3DHmessage{};
x3dh_protocol::buildMessage_publishOPks(X3DHmessage, OPks, OPk_ids);
postToX3DHServer(userData, X3DHmessage);
......@@ -899,8 +900,18 @@ namespace lime {
case x3dh_protocol::x3dh_message_type::error: {
// error messages are logged inside the parseMessage_getType function, just return failure to callback
if (callback) callback(lime::CallbackReturn::fail, "X3DH server error");
cleanUserData(userData);
// Check if the error message is a user_not_found and we were trying to get our self OPks(OPkServerLowLimit > 0)
if (error_code == lime::x3dh_protocol::x3dh_error_code::user_not_found && userData->OPkServerLowLimit > 0) {
// We must republish the user, something went terribly wrong on server side and we're not there anymore
LIME_LOGW<<"Something went terribly wrong on server "<<m_X3DH_Server_URL<<". Republish user "<<m_selfDeviceId;
X3DH_updateOPkStatus(std::vector<uint32_t>{}); // set all OPks to dispatched status as we don't know if some of them where dispatched or not
// republish the user, it will keep same Ik and SPk but generate new OPks as we just set all our OPk to dispatched
publish_user(callback, userData->OPkServerLowLimit);
cleanUserData(userData);
} else {
if (callback) callback(lime::CallbackReturn::fail, "X3DH server error");
cleanUserData(userData);
}
}
return;
......
......@@ -1656,6 +1656,103 @@ static void lime_update_clean_MK() {
#endif
}
/**
* Scenario:
* - Create Alice and Bob users
* - Backup local Alice DB.
* - Delete Alice user (so it is not anymore on the server)
* - restore local Alice DB and do an update, it shall republish the user on server(using new OPks)
* - Bob encrypt a message to Alice, it shall work as the user has been republished
* - Alice decrypt just to be sure we're good with the OPks
*/
static void lime_update_republish_test(const lime::CurveId curve, const std::string &dbBaseFilename, const std::string &x3dh_server_url) {
// create DB
std::string dbFilenameAlice{dbBaseFilename};
std::string dbFilenameAliceBackup{dbBaseFilename};
dbFilenameAlice.append(".alice.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
dbFilenameAliceBackup.append(".alice.backup.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
std::string dbFilenameBob{dbBaseFilename};
dbFilenameBob.append(".bob.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
remove(dbFilenameAlice.data()); // delete the database file if already exists
remove(dbFilenameAliceBackup.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++;
LIME_LOGE<<"Lime operation failed : "<<anythingToSay;
}
});
try {
// create Managers and devices for alice and Bob
auto aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost));
auto aliceDeviceId = lime_tester::makeRandomDeviceName("alice.");
aliceManager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
auto bobManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameBob, X3DHServerPost));
auto bobDeviceId = lime_tester::makeRandomDeviceName("bob.");
bobManager->create_user(*bobDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
expected_success += 2;
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, expected_success,lime_tester::wait_for_timeout));
// Backup Alice Database
std::ifstream src(dbFilenameAlice, std::ios::binary);
std::ofstream dst(dbFilenameAliceBackup, std::ios::binary);
dst << src.rdbuf();
src.close();
dst.close();
// Delete Alice user
aliceManager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
// Start a new manager using the backuped local base, so the user is present in local but no more on remote
aliceManager = nullptr;
aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAliceBackup, X3DHServerPost));
// Update: that shall set all current OPk as dispatched, create a new batch of default creation size and republish the user on server
aliceManager->update(callback, lime_tester::OPkInitialBatchSize, lime_tester::OPkInitialBatchSize);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
// Bob encrypt a message to Alice, it will fetch keys from server
auto bobRecipients = make_shared<std::vector<RecipientData>>();
bobRecipients->emplace_back(*aliceDeviceId);
auto bobMessage = make_shared<const std::vector<uint8_t>>(lime_tester::messages_pattern[0].begin(), lime_tester::messages_pattern[0].end());
auto bobCipherMessage = make_shared<std::vector<uint8_t>>();
bobManager->encrypt(*bobDeviceId, make_shared<const std::string>("alice"), bobRecipients, bobMessage, bobCipherMessage, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
// Alice decrypt
std::vector<uint8_t> receivedMessage{};
BC_ASSERT_TRUE(aliceManager->decrypt(*aliceDeviceId, "alice", *bobDeviceId, (*bobRecipients)[0].DRmessage, *bobCipherMessage, receivedMessage) == lime::PeerDeviceStatus::unknown);
auto receivedMessageString = std::string{receivedMessage.begin(), receivedMessage.end()};
BC_ASSERT_TRUE(receivedMessageString == lime_tester::messages_pattern[0]);
if (cleanDatabase) {
aliceManager->delete_user(*aliceDeviceId, callback);
bobManager->delete_user(*bobDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,expected_success+2,lime_tester::wait_for_timeout));
remove(dbFilenameAlice.data());
remove(dbFilenameAliceBackup.data());
remove(dbFilenameBob.data());
}
} catch (BctbxException &e) {
LIME_LOGE << e;
BC_FAIL("");
}
}
static void lime_update_republish() {
#ifdef EC25519_ENABLED
lime_update_republish_test(lime::CurveId::c25519, "lime_update_republish", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c25519_server_port).data());
#endif
#ifdef EC448_ENABLED
lime_update_republish_test(lime::CurveId::c448, "lime_update_republish", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c448_server_port).data());
#endif
}
/** Scenario
* - Create one device for alice
* - Create OPk_batch_size devices for bob, they will all fetch a key and encrypt a messaage to alice, server shall not have anymore OPks
......@@ -3401,6 +3498,7 @@ static test_t tests[] = {
TEST_NO_TAG("Update - clean MK", lime_update_clean_MK),
TEST_NO_TAG("Update - SPk", lime_update_SPk),
TEST_NO_TAG("Update - OPk", lime_update_OPk),
TEST_NO_TAG("Update - Republish", lime_update_republish),
TEST_NO_TAG("get self Identity Key", lime_getSelfIk),
TEST_NO_TAG("Verified Status", lime_identityVerifiedStatus),
TEST_NO_TAG("Peer Device Status", lime_peerDeviceStatus),
......
......@@ -679,31 +679,38 @@ https.createServer(options, (req, res) => {
*/
case enum_messageTypes.getSelfOPks:
console.log("Process a getSelfOPks Message from "+userId);
db.all("SELECT o.OPk_id as OPk_id FROM Users as u INNER JOIN OPk as o ON u.Uid=o.Uid WHERE UserId = ?;", userId, function (err, rows) {
if (err) {
returnError(enum_errorCodes.db_error, " Database error in getSelfOPks by "+userId+" : "+err);
return;
}
// check we have a matching user in DB
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (errU, row) {
if (row == undefined) { // user not found in DB
returnError(enum_errorCodes.user_not_found, "Get Self OPks but "+userId+" not found in db");
} else {
// create the return message
let selfOPKsBuffer = Buffer.allocUnsafe(X3DH_headerSize+2);
selfOPKsBuffer.writeUInt8(X3DH_protocolVersion, 0);
selfOPKsBuffer.writeUInt8(enum_messageTypes.selfOPks, 1);
selfOPKsBuffer.writeUInt8(curveId, 2);
db.all("SELECT o.OPk_id as OPk_id FROM Users as u INNER JOIN OPk as o ON u.Uid=o.Uid WHERE UserId = ?;", userId, function (err, rows) {
if (err) {
returnError(enum_errorCodes.db_error, " Database error in getSelfOPks by "+userId+" : "+err);
return;
}
if (rows == undefined || rows.length == 0) { // no Id founds
selfOPKsBuffer.writeUInt16BE(0, 3); // peers bundle count on 2 bytes in Big Endian
// create the return message
let selfOPKsBuffer = Buffer.allocUnsafe(X3DH_headerSize+2);
selfOPKsBuffer.writeUInt8(X3DH_protocolVersion, 0);
selfOPKsBuffer.writeUInt8(enum_messageTypes.selfOPks, 1);
selfOPKsBuffer.writeUInt8(curveId, 2);
} else {
selfOPKsBuffer.writeUInt16BE(rows.length, 3); // peers bundle count on 2 bytes in Big Endian
if (rows == undefined || rows.length == 0) { // no Id founds
selfOPKsBuffer.writeUInt16BE(0, 3); // peers bundle count on 2 bytes in Big Endian
} else {
selfOPKsBuffer.writeUInt16BE(rows.length, 3); // peers bundle count on 2 bytes in Big Endian
for (let i=0; i<rows.length; i++) {
let OPk_idBuffer = Buffer.allocUnsafe(4);
OPk_idBuffer.writeUInt32BE(rows[i]['OPk_id'], 0); // OPk id on 4 bytes in Big Endian
selfOPKsBuffer = Buffer.concat([selfOPKsBuffer, OPk_idBuffer]);
}
for (let i=0; i<rows.length; i++) {
let OPk_idBuffer = Buffer.allocUnsafe(4);
OPk_idBuffer.writeUInt32BE(rows[i]['OPk_id'], 0); // OPk id on 4 bytes in Big Endian
selfOPKsBuffer = Buffer.concat([selfOPKsBuffer, OPk_idBuffer]);
}
}
returnOk(selfOPKsBuffer);
});
}
returnOk(selfOPKsBuffer);
});
break;
......
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