Commit 075c8d48 authored by Johan Pascal's avatar Johan Pascal

Manage verified status for peer devices

parent 436cad78
......@@ -33,8 +33,9 @@ namespace lime {
/* Struct used to manage recipient list for encrypt function input: give a recipient GRUU and get it back with the header which must be sent to recipient with the cipher text*/
struct recipientData {
std::string deviceId; // recipient deviceId (shall be GRUU)
bool identityVerified; // after encrypt calls back, it will hold the status of this peer device: identity verified or not
std::vector<uint8_t> cipherHeader; // after encrypt calls back, it will hold the header targeted to the specified recipient. This header may contain an X3DH init message.
recipientData(std::string deviceId) : deviceId{deviceId}, cipherHeader{} {};
recipientData(std::string deviceId) : deviceId{deviceId}, identityVerified{false}, cipherHeader{} {};
};
/* Enum of what a Lime callback could possibly say */
......@@ -166,6 +167,27 @@ namespace lime {
*/
void get_selfIdentityKey(const std::string &localDeviceId, std::vector<uint8_t> &Ik);
/**
* @brief set the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
* @param[in] Ik the EdDSA peer public identity key, formatted as in RFC8032
* @param[in] status value of flag to set
*
* throw an exception if given key doesn't match the one present in local storage
* if peer Device is not present in local storage and status is true, it is added, if status is false, it is just ignored
*/
void set_peerIdentityVerifiedStatus(const std::string &peerDeviceId, const std::vector<uint8_t> &Ik, bool status);
/**
* @brief get the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
*
* @return the stored Verified status, false if peer Device is not present in local Storage
*/
bool get_peerIdentityVerifiedStatus(const std::string &peerDeviceId);
LimeManager() = delete; // no manager without Database and http provider
LimeManager(const LimeManager&) = delete; // no copy constructor
LimeManager operator=(const LimeManager &) = delete; // nor copy operator
......
......@@ -208,13 +208,12 @@ namespace lime {
x3dh_protocol::buildMessage_getPeerBundles<Curve>(X3DHmessage, missing_devices);
// use the raw pointer to userData as it is passed to belle-sip C callbacks but store it in current object so it is not destroyed
postToX3DHServer(userData.get(), X3DHmessage);
// use the raw pointer to userData as it is passed to belle-sip C callbacks but store it in current object so it is not destroyed
//X3DH_get_peerBundles(userData.get(), missing_devices);
} else { // got everyone, encrypt
encryptMessage(internal_recipients, *plainMessage, *recipientUserId, m_selfDeviceId, *cipherMessage);
// move cipher headers to the input/output structure
for (size_t i=0; i<recipients->size(); i++) {
(*recipients)[i].cipherHeader = std::move(internal_recipients[i].cipherHeader);
(*recipients)[i].identityVerified = internal_recipients[i].identityVerified;
}
if (callback) callback(lime::callbackReturn::success, "");
// is there no one in an asynchronous encryption process and do we have something in encryption queue to process
......@@ -261,7 +260,7 @@ 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
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
std::shared_ptr<DR<Curve>> DRSession{X3DH_init_receiver_session(X3DH_initMessage, senderDeviceId)}; // would just throw an exception in case of failure
DRSessions.clear();
DRSessions.push_back(DRSession);
} catch (BctbxException &e) {
......
......@@ -113,10 +113,11 @@ namespace lime {
struct recipientInfos {
std::shared_ptr<DR<Curve>> DRSession; // Session to reach recipient
std::string deviceId; // recipient device Id(gruu)
bool identityVerified; // will hold the status of peer: did we already verified his identity
std::vector<uint8_t> cipherHeader; // will hold the header targeted to this recipient after encryption
recipientInfos() : DRSession{nullptr}, deviceId{}, cipherHeader{} {};
recipientInfos(std::string deviceId) : DRSession{nullptr}, deviceId{deviceId}, cipherHeader{} {};
recipientInfos(std::string deviceId, std::shared_ptr<DR<Curve>> session) : DRSession{session}, deviceId{deviceId}, cipherHeader{} {};
recipientInfos() : DRSession{nullptr}, deviceId{}, identityVerified{false}, cipherHeader{} {};
recipientInfos(std::string deviceId) : DRSession{nullptr}, deviceId{deviceId}, identityVerified{false}, cipherHeader{} {};
recipientInfos(std::string deviceId, std::shared_ptr<DR<Curve>> session) : DRSession{session}, deviceId{deviceId}, identityVerified{false}, cipherHeader{} {};
};
// helpers function wich are the one to be used to encrypt/decrypt messages
......
......@@ -285,6 +285,60 @@ void Db::get_allLocalDevices(std::vector<std::string> &deviceIds) {
}
}
/**
* @brief set the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
* @param[in] Ik the EdDSA peer public identity key, formatted as in RFC8032
* @param[in] status value of flag to set
*
* throw an exception if given key doesn't match the one present in local storage
* if peer Device is not present in local storage and status is true, it is added, if status is false, it is just ignored
*/
void Db::set_PeerDevicesVerifiedStatus(const std::string &peerDeviceId, const std::vector<uint8_t> &Ik, bool status) {
// Do we have this peerDevice in lime_PeerDevices
blob Ik_blob(sql);
sql<<"SELECT Ik FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId LIMIT 1;", into(Ik_blob), use(peerDeviceId);
if (sql.got_data()) { // Found it
auto IkSize = Ik_blob.get_len();
std::vector<uint8_t> storedIk;
storedIk.resize(IkSize);
Ik_blob.read(0, (char *)(storedIk.data()), IkSize); // Read the public key
if (storedIk == Ik) {
sql<<"UPDATE Lime_PeerDevices SET Verified = :Verified WHERE DeviceId = :peerDeviceId LIMIT 1;", use((status==true)?1:0), use(peerDeviceId);
} else { // Ik in local Storage differs than the one given... raise an exception
throw BCTBX_EXCEPTION << "Trying to insert an Identity key for peer device "<<peerDeviceId<<" which differs from one already in local storage";
}
} else { // peer is not in local Storage
if (status) { // insert it only if status is true, if false just ignore the request
blob Ik_insert_blob(sql);
Ik_insert_blob.write(0, (char *)(Ik.data()), Ik.size());
sql<<"INSERT INTO Lime_PeerDevices(DeviceId, Ik, Verified) VALUES(:peerDeviceId, :Ik, 1);", use(peerDeviceId), use(Ik_insert_blob);
}
}
}
/**
* @brief get the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
*
* @return the stored Verified status, false if peer Device is not present in local Storage
*/
bool Db::get_PeerDevicesIdentityVerifiedStatus(const std::string &peerDeviceId) {
int verified;
sql<<"SELECT Verified FROM Lime_PeerDevices WHERE DeviceId = :peerDeviceId LIMIT 1;", into(verified), use(peerDeviceId);
if (sql.got_data()) { // Found it
if (verified == 1) {
return true;
}
return false; // verified is stored as false
}
// peerDeviceId not found in local storage: return false
return false;
}
/**
* @brief if exists, delete user
*
......@@ -691,23 +745,54 @@ void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve>> &publicOPks, std::vec
template <typename Curve>
void Lime<Curve>::cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal_recipients, std::vector<std::string> &missing_devices) {
/* build a user list of missing ones : produce a list ready to be sent to SQL query: 'user','user','user',... also build a map to store shared_ptr to sessions */
// build a user list of missing ones : produce a list ready to be sent to SQL query: 'user','user','user',... also build a map to store shared_ptr to sessions
// build also a list of all peer devices used to fetch from DB the ones with identity not verified
std::string sqlString_requestedDevices{""};
std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> requestedDevices; // found session will be loaded and temp stored in this
std::string sqlString_allDevices{""};
size_t requestedDevicesCount = 0;
size_t allDevicesCount = 0;
for (auto &recipient : internal_recipients) {
if (recipient.DRSession == nullptr) {
sqlString_requestedDevices.append("'").append(recipient.deviceId).append("',");
requestedDevicesCount++;
}
sqlString_allDevices.append("'").append(recipient.deviceId).append("',");
allDevicesCount++;
}
// fetch devices with identity verified flag to false
if (allDevicesCount==0) return; // the device list was empty... this is very strange
sqlString_allDevices.pop_back(); // remove the last ','
// fetch all the verified devices (we don't directly fetch unverified device as some devices may not be in local storage at all)
rowset<row> rs_devices = (m_localStorage->sql.prepare << "SELECT d.DeviceId FROM lime_PeerDevices as d WHERE d.Verified = 1 AND d.DeviceId IN ("<<sqlString_allDevices<<");");
std::vector<std::string> verifiedDevices{}; // vector of verified deviceId
for (auto &r : rs_devices) {
verifiedDevices.push_back(r.get<string>(0));
}
// loop on internal recipient and mark the one verified as verified
for (auto &recipient : internal_recipients) {
recipient.identityVerified = false;
for (auto &verifiedDevice : verifiedDevices) {
if (verifiedDevice == recipient.deviceId) {
recipient.identityVerified = true;
break;
}
}
}
// Now do we have sessions to load?
if (requestedDevicesCount==0) return; // we already got them all
sqlString_requestedDevices.pop_back(); // remove the last ','
/* fetch them from DB */
// fetch them from DB
rowset<row> rs = (m_localStorage->sql.prepare << "SELECT s.sessionId, d.DeviceId FROM DR_sessions as s INNER JOIN lime_PeerDevices as d ON s.Did=d.Did WHERE s.Uid= :Uid AND s.Status=1 AND d.DeviceId IN ("<<sqlString_requestedDevices<<");", use(m_db_Uid));
std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> requestedDevices; // found session will be loaded and temp stored in this
for (auto &r : rs) {
auto sessionId = r.get<int>(0);
auto peerDeviceId = r.get<string>(1);
......@@ -717,7 +802,7 @@ void Lime<Curve>::cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal
m_DR_sessions_cache[peerDeviceId] = DRsession; // session is also stored in cache
}
/* loop on internal recipient and fill it with the found ones, store the missing ones in the missing_devices vector */
// loop on internal recipient and fill it with the found ones, store the missing ones in the missing_devices vector
for (auto &recipient : internal_recipients) {
if (recipient.DRSession == nullptr) { // they are missing
auto retrievedElem = requestedDevices.find(recipient.deviceId);
......
......@@ -81,6 +81,28 @@ namespace lime {
* @param[out] deviceIds the list of all local users (their device Id)
*/
void get_allLocalDevices(std::vector<std::string> &deviceIds);
/**
* @brief set the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
* @param[in] Ik the EdDSA peer public identity key, formatted as in RFC8032
* @param[in] status value of flag to set
*
* throw an exception if given key doesn't match the one present in local storage
* if peer Device is not present in local storage and status is true, it is added, if status is false, it is just ignored
*/
void set_PeerDevicesVerifiedStatus(const std::string &peerDeviceId, const std::vector<uint8_t> &Ik, bool status);
/**
* @brief get the identity verified flag for peer device
*
* @param[in] peerDeviceId The device Id of peer, shall be its GRUU
*
* @return the stored Verified status, false if peer Device is not present in local Storage
*/
bool get_PeerDevicesIdentityVerifiedStatus(const std::string &peerDeviceId);
};
#ifdef EC25519_ENABLED
......
......@@ -160,4 +160,18 @@ namespace lime {
user->get_Ik(Ik);
}
void LimeManager::set_peerIdentityVerifiedStatus(const std::string &peerDeviceId, const std::vector<uint8_t> &Ik, bool status) {
// open local DB
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(m_db_access));
localStorage->set_PeerDevicesVerifiedStatus(peerDeviceId, Ik, status);
}
bool LimeManager::get_peerIdentityVerifiedStatus(const std::string &peerDeviceId) {
// open local DB
auto localStorage = std::unique_ptr<lime::Db>(new lime::Db(m_db_access));
return localStorage->get_PeerDevicesIdentityVerifiedStatus(peerDeviceId);
}
} // namespace lime
......@@ -206,7 +206,159 @@ static void lime_session_establishment(const lime::CurveId curve, const std::str
throw;
}
}
/**
* Scenario:
* - create Bob and Alice devices
* - retrieve their respective Identity keys
* - check if they are verified -> they shall not be
* - set alice key as verified in bob's context
* - check it is now verified
* - set it as non verified and check
* - try to set a different alice identity key in bob's context, we shall have an exception
* - bob encrypts a message to alice -> check return status give NOT all recipients trusted
* - set alice key as verified in bob's context
* - bob encrypts a message to alice -> check return status give all recipients trusted
* - set a fake bob key in alice context
* - try to decrypt bob's message, it shall fail
* - alice try to encrypt a message to bob, it shall fail
*/
static void lime_identityVerifiedStatus_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;
}
});
// declare variable outside the try block as we will generate exceptions during the test
std::unique_ptr<LimeManager> aliceManager = nullptr;
std::unique_ptr<LimeManager> bobManager = nullptr;
std::shared_ptr<std::string> aliceDeviceId = nullptr;
std::shared_ptr<std::string> bobDeviceId = nullptr;
std::vector<uint8_t> aliceIk{};
std::vector<uint8_t> bobIk{};
try {
// create Manager and device for alice and bob
aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, prov, user_auth_callback));
aliceDeviceId = lime_tester::makeRandomDeviceName("alice.d1.");
aliceManager->create_user(*aliceDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
bobManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameBob, prov, user_auth_callback));
bobDeviceId = lime_tester::makeRandomDeviceName("bob.d1.");
bobManager->create_user(*bobDeviceId, x3dh_server_url, curve, lime_tester::OPkInitialBatchSize, callback);
expected_success += 2;
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_success, expected_success,lime_tester::wait_for_timeout));
// retrieve their respective Ik
aliceManager->get_selfIdentityKey(*aliceDeviceId, aliceIk);
bobManager->get_selfIdentityKey(*bobDeviceId, bobIk);
// check their status
BC_ASSERT_FALSE(aliceManager->get_peerIdentityVerifiedStatus(*bobDeviceId));
BC_ASSERT_FALSE(bobManager->get_peerIdentityVerifiedStatus(*aliceDeviceId));
// set alice Id key as vertified in Bob's Manager and check it worked
bobManager->set_peerIdentityVerifiedStatus(*aliceDeviceId, aliceIk, true);
BC_ASSERT_TRUE(bobManager->get_peerIdentityVerifiedStatus(*aliceDeviceId));
// reset it and check it worked
bobManager->set_peerIdentityVerifiedStatus(*aliceDeviceId, aliceIk, false);
BC_ASSERT_FALSE(bobManager->get_peerIdentityVerifiedStatus(*aliceDeviceId));
} catch (BctbxException &e) {
BCTBX_SLOGE <<e;;
BC_FAIL();
}
// try to set another key for alice in bob's context, it shall generate an exception
auto gotException = false;
try {
// copy alice Ik but modify it
std::vector<uint8_t> fakeIk = aliceIk;
fakeIk[0] ^= 0xFF;
bobManager->set_peerIdentityVerifiedStatus(*aliceDeviceId, fakeIk, true);
} catch (BctbxException &e) {
BC_PASS();
gotException = true;
}
BC_ASSERT_TRUE(gotException);
try {
// Bob encrypts a message for Alice, alice identity verified status shall be : not verified
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(stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
BC_ASSERT_FALSE((*bobRecipients)[0].identityVerified);
// set again the key as verified in bob's context
bobManager->set_peerIdentityVerifiedStatus(*aliceDeviceId, aliceIk, true);
BC_ASSERT_TRUE(bobManager->get_peerIdentityVerifiedStatus(*aliceDeviceId));
// Bob encrypts a message for Alice, alice identity verified status shall be : verified
bobRecipients = make_shared<std::vector<recipientData>>();
bobRecipients->emplace_back(*aliceDeviceId);
bobMessage = make_shared<const std::vector<uint8_t>>(lime_tester::messages_pattern[1].begin(), lime_tester::messages_pattern[1].end());
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(stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
BC_ASSERT_TRUE((*bobRecipients)[0].identityVerified);
// set a fake bob key in alice context(set is as verified otherwise the request is just ignored)
std::vector<uint8_t> fakeIk = bobIk;
fakeIk[0] ^= 0xFF;
aliceManager->set_peerIdentityVerifiedStatus(*bobDeviceId, fakeIk, true);
// alice decrypt but it will fail as the identity key in X3DH init packet is not matching the one we assert as verified
std::vector<uint8_t> receivedMessage{};
BC_ASSERT_FALSE (aliceManager->decrypt(*aliceDeviceId, "alice", *bobDeviceId, (*bobRecipients)[0].cipherHeader, *bobCipherMessage, receivedMessage));
// alice now try to encrypt to Bob but it will fail as key fetched from X3DH server won't match the one we assert as verified
auto aliceRecipients = make_shared<std::vector<recipientData>>();
aliceRecipients->emplace_back(*bobDeviceId);
auto aliceMessage = make_shared<const std::vector<uint8_t>>(lime_tester::messages_pattern[2].begin(), lime_tester::messages_pattern[2].end());
auto aliceCipherMessage = make_shared<std::vector<uint8_t>>();
aliceManager->encrypt(*aliceDeviceId, make_shared<const std::string>("bob"), aliceRecipients, aliceMessage, aliceCipherMessage, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(stack,&counters.operation_failed,1,lime_tester::wait_for_timeout));
} catch (BctbxException &e) {
BCTBX_SLOGE <<e;;
BC_FAIL();
}
}
static void lime_identityVerifiedStatus() {
#ifdef EC25519_ENABLED
lime_identityVerifiedStatus_test(lime::CurveId::c25519, "lime_identityVerifiedStatus", std::string("https://").append(test_x3dh_server_url).append(":").append(test_x3dh_c25519_server_port).data());
#endif
#ifdef EC448_ENABLED
lime_identityVerifiedStatus_test(lime::CurveId::c448, "lime_identityVerifiedStatus", std::string("https://").append(test_x3dh_server_url).append(":").append(test_x3dh_c448_server_port).data());
#endif
}
/**
* scenario :
* - check the Ik in pattern_db is retrieved as expected
* - try asking for an unknown user, we shall get an exception
*/
static void lime_getSelfIk_test(const lime::CurveId curve, const std::string &dbFilename, const std::vector<uint8_t> &pattern) {
// retrieve the Ik and check it matches given pattern
std::unique_ptr<LimeManager> aliceManager = nullptr;
......@@ -1819,7 +1971,8 @@ 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("get self Identity Key", lime_getSelfIk)
TEST_NO_TAG("get self Identity Key", lime_getSelfIk),
TEST_NO_TAG("Verified Status", lime_identityVerifiedStatus)
};
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