Commit 20b1036d authored by Matthieu Tanon's avatar Matthieu Tanon

Manage SAS issues at end of audio session unless Ik match and SAS validated

parent 59a733d9
......@@ -779,19 +779,15 @@ void ClientGroupChatRoom::onParticipantDeviceAdded (const shared_ptr<ConferenceP
if (getCore()->limeV2Enabled()) {
int nbDevice = int(participant->getPrivate()->getDevices().size());
int maxNbDevicesPerParticipant = linphone_config_get_int(linphone_core_get_config(L_GET_C_BACK_PTR(getCore())), "lime", "max_nb_device_per_participant", 1);
LimeV2 *limeV2Engine = static_cast<LimeV2 *>(getCore()->getEncryptionEngine());
// Check if the new participant device is unexpected, in which case a security alert is created
if (nbDevice >= maxNbDevicesPerParticipant) {
lWarning() << "LIMEv2 alert: maximum number of devices exceeded for " << participant->getAddress();
securityEvent = make_shared<ConferenceSecurityEvent>(
time(nullptr),
d->conferenceId,
ConferenceSecurityEvent::SecurityEventType::MultideviceParticipantDetected,
event->getDeviceAddress()
);
securityEvent = make_shared<ConferenceSecurityEvent>(time(nullptr), d->conferenceId, ConferenceSecurityEvent::SecurityEventType::MultideviceParticipantDetected, event->getDeviceAddress());
limeV2Engine->getLimeManager()->set_peerDeviceStatus(event->getDeviceAddress().asString(), lime::PeerDeviceStatus::unsafe); // WARNING no effect if user not in lime db
// Otherwise check if this new device downgrades the chatroom security level, in which case a security event is created
} else {
LimeV2 *limeV2Engine = static_cast<LimeV2 *>(getCore()->getEncryptionEngine());
lime::PeerDeviceStatus newDeviceStatus = limeV2Engine->getLimeManager()->get_peerDeviceStatus(event->getDeviceAddress().asString());
if (getSecurityLevel() == SecurityLevel::Safe && newDeviceStatus != lime::PeerDeviceStatus::trusted) {
lInfo() << "LIMEv2 chat room security level downgraded by "<< event->getDeviceAddress().asString();
......
......@@ -3138,6 +3138,68 @@ void MediaSessionPrivate::startVideoStream (CallSession::State targetState) {
void MediaSessionPrivate::stopAudioStream () {
L_Q();
if (audioStream) {
// Check auxiliary secret match and SAS validation for LIMEv2 verification purposes
if (ms_zrtp_getAuxiliarySharedSecretMismatch(audioStream->ms.sessions.zrtp_context) != 2) {
// get remote Ik from sdp attributes and decode base64
const string &remoteIkB64_string(sal_custom_sdp_attribute_find(op->getRemoteMediaDescription()->custom_sdp_attributes, "Ik"));
vector<uint8_t> remoteIkB64_vector = vector<uint8_t>(remoteIkB64_string.begin(), remoteIkB64_string.end());
vector<uint8_t> remoteIk_vector = decodeBase64(remoteIkB64_vector);
// Get proxy config
LinphoneProxyConfig *proxy = nullptr;
if (destProxy)
proxy = destProxy;
else
proxy = linphone_core_get_default_proxy_config(q->getCore()->getCCore());
// Get LIMEv2 context
LimeV2 *limeV2Engine;
if (proxy && linphone_core_lime_v2_enabled(linphone_proxy_config_get_core(proxy))) {
limeV2Engine = static_cast<LimeV2 *>(q->getCore()->getEncryptionEngine());
} else {
lWarning() << "LIMEv2 disabled or proxy config unavailable, unable to set peer identity verified status";
}
// Get peer's GRUU
const SalAddress *remoteAddress = getOp()->getRemoteContactAddress();
char *peerDeviceId = sal_address_as_string_uri_only(remoteAddress);
const IdentityAddress faultyDevice = IdentityAddress(peerDeviceId);
// If mismatch = 0 set this peer as trusted with this Ik
if (ms_zrtp_getAuxiliarySharedSecretMismatch(audioStream->ms.sessions.zrtp_context) == 0) {
if (limeV2Engine) {
try {
// If SAS verified, the lime peer is trusted, untrusted otherwise // TODO unsafe + security event otherwise ?
lime::PeerDeviceStatus peerDeviceStatus = authTokenVerified ? lime::PeerDeviceStatus::trusted : lime::PeerDeviceStatus::unsafe;
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, remoteIk_vector, peerDeviceStatus);
if (!authTokenVerified) addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::ManInTheMiddleDetected);
} catch (const exception &e) {
// This Ik doesn't match with the stored Ik
lError() << "LIMEv2 identity theft detected from " << peerDeviceId << " (" << e.what() << ")";
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, lime::PeerDeviceStatus::unsafe);
addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::LimeIdentityKeyChanged);
}
} else {
lError() << "Unable to get LIMEv2 context, unable to set peer device status";
}
} else {
// If mismatch != 0 the Ik exchange went wrong (possible identity theft)
lError() << "LIMEv2 auxiliary secret mismatch: possible identity theft detected from " << peerDeviceId;
try {
// Try to set peer device to untrusted
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, remoteIk_vector, lime::PeerDeviceStatus::untrusted);
lInfo() << "LIMEv2 peer device " << peerDeviceId << " is now untrusted";
} catch (const exception &e) {
// This Ik doesn't match with the stored Ik
lError() << "LIMEv2 identity theft detected from " << peerDeviceId << " (" << e.what() << ")";
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, lime::PeerDeviceStatus::unsafe);
addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::LimeIdentityKeyChanged);
}
}
ms_free(peerDeviceId);
}
if (listener)
listener->onUpdateMediaInfoForReporting(q->getSharedFromThis(), LINPHONE_CALL_STATS_AUDIO);
media_stream_reclaim_sessions(&audioStream->ms, &sessions[mainAudioStreamIndex]);
......@@ -3519,9 +3581,10 @@ void MediaSessionPrivate::propagateEncryptionChanged () {
if (!authToken.empty()) {
/* ZRTP only is using auth_token */
getCurrentParams()->setMediaEncryption(LinphoneMediaEncryptionZRTP);
if (ms_zrtp_getAuxiliarySharedSecretMismatch(audioStream->ms.sessions.zrtp_context) != 2) {
// get remote Ik from sdp attributes and decode base64
} else {
// If auxiliary secret matches and the SAS is verified we can trust this peer device
// Other cases are dealt with at the end of the call in stopAudioStream method
if (ms_zrtp_getAuxiliarySharedSecretMismatch(audioStream->ms.sessions.zrtp_context) == 0 && authTokenVerified) {
const string &remoteIkB64_string(sal_custom_sdp_attribute_find(op->getRemoteMediaDescription()->custom_sdp_attributes, "Ik"));
vector<uint8_t> remoteIkB64_vector = vector<uint8_t>(remoteIkB64_string.begin(), remoteIkB64_string.end());
vector<uint8_t> remoteIk_vector = decodeBase64(remoteIkB64_vector);
......@@ -3546,35 +3609,9 @@ void MediaSessionPrivate::propagateEncryptionChanged () {
char *peerDeviceId = sal_address_as_string_uri_only(remoteAddress);
const IdentityAddress faultyDevice = IdentityAddress(peerDeviceId);
// If mismatch = 0 set this peer as trusted with this Ik
if (ms_zrtp_getAuxiliarySharedSecretMismatch(audioStream->ms.sessions.zrtp_context) == 0) {
if (limeV2Engine) {
try {
// If SAS verified, the lime peer is trusted, untrusted otherwise // TODO unsafe + security event otherwise ?
lime::PeerDeviceStatus peerDeviceStatus = authTokenVerified ? lime::PeerDeviceStatus::trusted : lime::PeerDeviceStatus::unsafe;
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, remoteIk_vector, peerDeviceStatus);
// if (!authTokenVerified) addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::ManInTheMiddleDetected);
// WARNING this event will be triggered even in a call where SAS is validated because we go through here a first time with authTokenVerified anyway
// This might be the reason why during a call the icon color is first red, then orange, then green
} catch (const exception &e) {
// This Ik doesn't match with the stored Ik
lError() << "LIMEv2 identity theft detected from " << peerDeviceId << " (" << e.what() << ")";
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, lime::PeerDeviceStatus::unsafe);
addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::LimeIdentityKeyChanged);
}
} else {
lError() << "Unable to get LIMEv2 context, unable to set peer device status";
}
} else {
// If mismatch != 0 the Ik exchange went wrong (possible identity theft)
// TODO Report the security issue to application level (LimeIdentityKeyChanged chatroom event)
lError() << "LIMEv2 auxiliary secret mismatch: possible identity theft detected from " << peerDeviceId;
if (limeV2Engine) {
try {
// Try to set peer device to untrusted
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, remoteIk_vector, lime::PeerDeviceStatus::untrusted);
lInfo() << "LIMEv2 peer device " << peerDeviceId << " is now untrusted";
limeV2Engine->getLimeManager()->set_peerDeviceStatus(peerDeviceId, remoteIk_vector, lime::PeerDeviceStatus::trusted);
} catch (const exception &e) {
// This Ik doesn't match with the stored Ik
lError() << "LIMEv2 identity theft detected from " << peerDeviceId << " (" << e.what() << ")";
......@@ -3582,9 +3619,8 @@ void MediaSessionPrivate::propagateEncryptionChanged () {
addSecurityEventInChatrooms(faultyDevice, ConferenceSecurityEvent::SecurityEventType::LimeIdentityKeyChanged);
}
}
ms_free(peerDeviceId);
}
} else {
/* Otherwise it must be DTLS as SDES doesn't go through this function */
getCurrentParams()->setMediaEncryption(LinphoneMediaEncryptionDTLS);
}
......
......@@ -4541,16 +4541,14 @@ static void group_chat_lime_v2_chatroom_security_level_downgrade_resetting_zrtp
BC_ASSERT_TRUE((call_ok = simple_zrtp_call_with_sas_validation(pauline, marie, FALSE, TRUE)));
if (!call_ok) goto end;
// WARNING
// Marie's chatroom security level is expected to be downgraded too but we are in a state of ZRTP asynchronism
// There is no ZRTP exchange until next call, where SAS can be validated or invalidated again
// Until then Marie trusts Pauline but Pauline doesn't trust Marie so security levels correspond to this state
// Check the chat room security level got downgraded for Marie and Pauline
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(marieCr), LinphoneChatRoomSecurityLevelSafe, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(paulineCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(paulineCr), LinphoneChatRoomSecurityLevelUnsafe, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(laureCr), LinphoneChatRoomSecurityLevelSafe, int, "%d");
// Check that pauline's chatroom received a security event
BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_ManInTheMiddleDetected, initialPaulineStats.number_of_ManInTheMiddleDetected + 1, 3000));
end:
// Clean local LIMEv2 databases
linphone_core_delete_local_lime_v2_db(marie->lc);
......@@ -4704,22 +4702,35 @@ static void group_chat_lime_v2_chatroom_security_alert (void) {
BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_MultideviceParticipantDetected, initialLaureStats.number_of_MultideviceParticipantDetected + 1, 3000));
// Check the security level was downgraded for Marie and Laure
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(marieCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(laureCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d");
// TODO Check that the lowest security level is reached for everyone because of the security alert and Pauline2 set to unsafe status
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(marieCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d"); // TODO shall be unsafe thanks to new LIMEv2 API
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(laureCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d"); // TODO shall be unsafe thanks to new LIMEv2 API
// Laure sends a messages to trigger a LIMEv2 security alerts because maxNumberOfDevicePerParticipant has been exceeded
if (lp_config_get_int(linphone_core_get_config(laure->lc), "lime", "allow_message_in_unsafe_chatroom", 0) == 0) {
const char *laureMessage = "I'm going to the cinema";
_send_message(laureCr, laureMessage);
wait_for_list(coresList, &dummy, 1, 500);
BC_ASSERT_FALSE((marie->stat.number_of_LinphoneMessageReceived == initialPauline1Stats.number_of_LinphoneMessageReceived + 3));
BC_ASSERT_FALSE((pauline1->stat.number_of_LinphoneMessageReceived == initialPauline1Stats.number_of_LinphoneMessageReceived + 3));
BC_ASSERT_FALSE((pauline2->stat.number_of_LinphoneMessageReceived == initialPauline2Stats.number_of_LinphoneMessageReceived + 1));
goto end;
}
// Laure starts composing a message
linphone_chat_room_compose(laureCr);
// No new security alert sent because there is a recent one
BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_MultideviceParticipantDetected, initialLaureStats.number_of_MultideviceParticipantDetected + 1, 3000));
// Laure sends the message
const char *laureMessage = "I'm going to the cinema";
_send_message(laureCr, laureMessage);
// No new security alert sent because there is a recent one
BC_ASSERT_TRUE(wait_for_list(coresList, &laure->stat.number_of_MultideviceParticipantDetected, initialLaureStats.number_of_MultideviceParticipantDetected + 1, 3000));
// Laure sends another message
_send_message(laureCr, laureMessage);
// Check that Laure received another security alert because a multidevice participant was detected during encryption
......@@ -4752,6 +4763,86 @@ end:
linphone_core_manager_destroy(laure);
}
static void group_chat_lime_v2_call_security_alert (void) {
// Simulate security alert during ZRTP call
// Check if security event is sent to correct chatrooms
LinphoneCoreManager *marie = linphone_core_manager_create("marie_lime_v2_rc");
LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_lime_v2_rc");
bctbx_list_t *coresManagerList = NULL;
bctbx_list_t *participantsAddresses = NULL;
coresManagerList = bctbx_list_append(coresManagerList, marie);
coresManagerList = bctbx_list_append(coresManagerList, pauline);
int dummy = 0;
bctbx_list_t *coresList = init_core_for_conference(coresManagerList);
start_core_for_conference(coresManagerList);
participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(pauline->lc)));
stats initialMarieStats = marie->stat;
stats initialPaulineStats = pauline->stat;
// Wait for lime users to be created on X3DH server
wait_for_list(coresList, &dummy, 1, 1000);
// Check encryption status for both participants
BC_ASSERT_TRUE(linphone_core_lime_v2_enabled(marie->lc));
BC_ASSERT_TRUE(linphone_core_lime_v2_enabled(pauline->lc));
// Marie creates a new group chat room
const char *initialSubject = "Friends";
LinphoneChatRoom *marieCr = create_chat_room_client_side(coresList, marie, &initialMarieStats, participantsAddresses, initialSubject, -1);
const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr);
// Check that the chat room is correctly created on Pauline's side and that the participants are added
LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, 0);
// Marie sends the message
const char *marieMessage = "Hey ! What's up ?";
_send_message(marieCr, marieMessage);
BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_LinphoneMessageReceived, initialPaulineStats.number_of_LinphoneMessageReceived + 1, 10000));
LinphoneChatMessage *paulineLastMsg = pauline->stat.last_received_chat_message;
if (!BC_ASSERT_PTR_NOT_NULL(paulineLastMsg))
goto end;
// Check that the message was correctly decrypted
BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(paulineLastMsg), marieMessage);
LinphoneAddress *marieAddr = linphone_address_new(linphone_core_get_identity(marie->lc));
BC_ASSERT_TRUE(linphone_address_weak_equal(marieAddr, linphone_chat_message_get_from_address(paulineLastMsg)));
linphone_address_unref(marieAddr);
// Check chatroom security level
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(marieCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(paulineCr), LinphoneChatRoomSecurityLevelEncrypted, int, "%d");
linphone_core_set_media_encryption(marie->lc, LinphoneMediaEncryptionZRTP);
linphone_core_set_media_encryption(pauline->lc, LinphoneMediaEncryptionZRTP);
// ZRTP verification call between Marie and Pauline
bool_t call_ok = FALSE;
BC_ASSERT_TRUE((call_ok = simple_zrtp_call_with_sas_validation(marie, pauline, TRUE, FALSE)));
if (!call_ok) goto end;
// Check chatroom security level
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(marieCr), LinphoneChatRoomSecurityLevelSafe, int, "%d");
BC_ASSERT_EQUAL(linphone_chat_room_get_security_level(paulineCr), LinphoneChatRoomSecurityLevelUnsafe, int, "%d");
// Check chatroom security event
BC_ASSERT_TRUE(wait_for_list(coresList, &pauline->stat.number_of_ManInTheMiddleDetected, initialPaulineStats.number_of_ManInTheMiddleDetected + 1, 3000));
end:
// Clean local LIMEv2 databases
linphone_core_delete_local_lime_v2_db(marie->lc);
linphone_core_delete_local_lime_v2_db(pauline->lc);
// Clean db from chat room
linphone_core_manager_delete_chat_room(marie, marieCr, coresList);
linphone_core_manager_delete_chat_room(pauline, paulineCr, coresList);
bctbx_list_free(coresList);
bctbx_list_free(coresManagerList);
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
}
static void group_chat_lime_v2_send_multiple_successive_encrypted_messages (void) {
LinphoneCoreManager *marie = linphone_core_manager_create("marie_lime_v2_rc");
LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_lime_v2_rc");
......@@ -5578,6 +5669,7 @@ test_t group_chat_tests[] = {
TEST_TWO_TAGS("LIMEv2 chatroom security level downgrade adding participant", group_chat_lime_v2_chatroom_security_level_downgrade_adding_participant, "CreateUserInDb", "LeaksMemory"),
TEST_TWO_TAGS("LIMEv2 chatroom security level downgrade resetting zrtp", group_chat_lime_v2_chatroom_security_level_downgrade_resetting_zrtp, "CreateUserInDb", "LeaksMemory"),
TEST_TWO_TAGS("LIMEv2 chatroom security alert", group_chat_lime_v2_chatroom_security_alert, "CreateUserInDb", "LeaksMemory"),
TEST_TWO_TAGS("LIMEv2 call security alert", group_chat_lime_v2_call_security_alert, "CreateUserInDb", "LeaksMemory"),
TEST_TWO_TAGS("LIMEv2 multiple successive messages", group_chat_lime_v2_send_multiple_successive_encrypted_messages, "CreateUserInDb", "LeaksMemory"),
TEST_ONE_TAG("LIMEv2 encrypted message to disabled LIMEv2", group_chat_lime_v2_send_encrypted_message_to_disabled_lime_v2, "CreateUserInDb"),
TEST_ONE_TAG("LIMEv2 plain message to enabled LIMEv2", group_chat_lime_v2_send_plain_message_to_enabled_lime_v2, "CreateUserInDb"),
......
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