swarm: handle device's expiration

If a device expires, a migration will regenerate the device's
certificate. In this case, the device certificate in swarms must
be updated.
So, the idea of this patch is to verify that the current certificate
in the repository for this device is still correct. If the device or
the member got invalidated, it tries to replace the current certificate
(updated via the migration). So that the new commits will still be valid.

Change-Id: I75b19b0edbb5601a758a73a4c4a44678d77295e1
GitLab: #684
This commit is contained in:
Sébastien Blin
2022-01-06 12:02:21 -05:00
parent 6e83fc2a45
commit d2d18bdedc
5 changed files with 319 additions and 60 deletions

View File

@ -64,10 +64,12 @@ public:
private:
void testLoadExpiredAccount();
void testMigrationAfterRevokation();
void testExpiredDeviceInSwarm();
CPPUNIT_TEST_SUITE(MigrationTest);
CPPUNIT_TEST(testLoadExpiredAccount);
CPPUNIT_TEST(testMigrationAfterRevokation);
CPPUNIT_TEST(testExpiredDeviceInSwarm);
CPPUNIT_TEST_SUITE_END();
};
@ -223,6 +225,142 @@ MigrationTest::testMigrationAfterRevokation()
CPPUNIT_ASSERT(bob2Device != bob2Account->currentDeviceId());
}
void
MigrationTest::testExpiredDeviceInSwarm()
{
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
auto aliceDevice = std::string(aliceAccount->currentDeviceId());
auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
auto bobUri = bobAccount->getUsername();
std::mutex mtx;
std::unique_lock<std::mutex> lk {mtx};
std::condition_variable cv;
std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
auto messageBobReceived = 0, messageAliceReceived = 0;
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
[&](const std::string& accountId,
const std::string& /* conversationId */,
std::map<std::string, std::string> /*message*/) {
if (accountId == bobId) {
messageBobReceived += 1;
} else {
messageAliceReceived += 1;
}
cv.notify_one();
}));
bool requestReceived = false;
confHandlers.insert(
DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
[&](const std::string& /*accountId*/,
const std::string& /* conversationId */,
std::map<std::string, std::string> /*metadatas*/) {
requestReceived = true;
cv.notify_one();
}));
bool conversationReady = false;
confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
[&](const std::string& accountId, const std::string& /* conversationId */) {
if (accountId == bobId) {
conversationReady = true;
cv.notify_one();
}
}));
auto aliceMigrated = false;
confHandlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::MigrationEnded>(
[&](const std::string& accountId, const std::string& state) {
if (accountId == aliceId && state == "SUCCESS") {
aliceMigrated = true;
}
cv.notify_one();
}));
bool aliceStopped = false, aliceAnnounced = false, aliceRegistered = false;
confHandlers.insert(
DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
[&](const std::string&, const std::map<std::string, std::string>&) {
auto details = aliceAccount->getVolatileAccountDetails();
auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
if (daemonStatus == "UNREGISTERED")
aliceStopped = true;
else if (daemonStatus == "REGISTERED")
aliceRegistered = true;
auto announced = details[DRing::Account::VolatileProperties::DEVICE_ANNOUNCED];
if (announced == "true")
aliceAnnounced = true;
cv.notify_one();
}));
DRing::registerSignalHandlers(confHandlers);
CPPUNIT_ASSERT(aliceAccount->setValidity("", {}, 90));
auto now = std::chrono::system_clock::now();
aliceRegistered = false;
aliceAccount->forceReloadAccount();
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceRegistered; }));
CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
aliceStopped = false;
Manager::instance().sendRegister(aliceId, false);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&]() { return aliceStopped; }));
aliceAnnounced = false;
Manager::instance().sendRegister(aliceId, true);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return aliceAnnounced; }));
CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
// Create conversation
auto convId = DRing::startConversation(aliceId);
DRing::addConversationMember(aliceId, convId, bobUri);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return requestReceived; }));
messageAliceReceived = 0;
DRing::acceptConversationRequest(bobId, convId);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return conversationReady; }));
// Assert that repository exists
auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
+ DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
// Wait that alice sees Bob
cv.wait_for(lk, 20s, [&]() { return messageAliceReceived == 1; });
messageBobReceived = 0;
DRing::sendMessage(aliceId, convId, "hi"s, "");
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageBobReceived == 1; }));
// Wait for certificate to expire
std::this_thread::sleep_until(now + 100s);
// Check migration is triggered and expiration updated
aliceAnnounced = false;
aliceAccount->forceReloadAccount();
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceAnnounced; }));
CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
// check that certificate in conversation is expired
auto devicePath = fmt::format("{}/devices/{}.crt", repoPath, aliceAccount->currentDeviceId());
CPPUNIT_ASSERT(fileutils::isFile(devicePath));
auto cert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
now = std::chrono::system_clock::now();
CPPUNIT_ASSERT(cert.getExpiration() < now);
// Resend a new message
messageBobReceived = 0;
DRing::sendMessage(aliceId, convId, "hi again"s, "");
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageBobReceived == 1; }));
messageAliceReceived = 0;
DRing::sendMessage(bobId, convId, "hi!"s, "");
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageAliceReceived == 1; }));
// check that certificate in conversation is updated
CPPUNIT_ASSERT(fileutils::isFile(devicePath));
cert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
now = std::chrono::system_clock::now();
CPPUNIT_ASSERT(cert.getExpiration() > now);
// Check same device as begining
CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
}
} // namespace test
} // namespace jami

View File

@ -1084,7 +1084,8 @@ ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account)
}
// /devices
std::string devicePath = devicesPath + DIR_SEPARATOR_STR + cert->getId().toString() + ".crt";
std::string devicePath = devicesPath + DIR_SEPARATOR_STR + cert->getLongId().toString()
+ ".crt";
file = fileutils::ofstream(devicePath, std::ios::trunc | std::ios::binary);
if (!file.is_open()) {
JAMI_ERR("Could not write data to %s", devicePath.c_str());