1
0
Fork 0
mirror of https://github.com/deltachat/deltachat-core.git synced 2025-10-05 10:39:27 +02:00
deltachat-core/src/dc_securejoin.c
2018-07-02 16:03:32 +02:00

773 lines
27 KiB
C

/*******************************************************************************
*
* Delta Chat Core
* Copyright (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see http://www.gnu.org/licenses/ .
*
******************************************************************************/
#include <stdarg.h>
#include <unistd.h>
#include "dc_context.h"
#include "dc_key.h"
#include "dc_apeerstate.h"
#include "dc_mimeparser.h"
#include "dc_mimefactory.h"
#include "dc_job.h"
#include "dc_token.h"
#define LOCK { pthread_mutex_lock(&context->m_bobs_qr_critical); locked = 1; }
#define UNLOCK if( locked ) { pthread_mutex_unlock(&context->m_bobs_qr_critical); locked = 0; }
/*******************************************************************************
* Tools: Handle degraded keys and lost verificaton
******************************************************************************/
void dc_handle_degrade_event(dc_context_t* context, dc_apeerstate_t* peerstate)
{
sqlite3_stmt* stmt = NULL;
uint32_t contact_id = 0;
uint32_t contact_chat_id = 0;
if( context == NULL || peerstate == NULL ) {
goto cleanup;
}
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
// with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this)
if( peerstate->m_degrade_event & DC_DE_FINGERPRINT_CHANGED )
{
stmt = dc_sqlite3_prepare(context->m_sql, "SELECT id FROM contacts WHERE addr=?;");
sqlite3_bind_text(stmt, 1, peerstate->m_addr, -1, SQLITE_STATIC);
sqlite3_step(stmt);
contact_id = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
if( contact_id == 0 ) {
goto cleanup;
}
dc_create_or_lookup_nchat_by_contact_id(context, contact_id, DC_CHAT_DEADDROP_BLOCKED, &contact_chat_id, NULL);
char* msg = dc_mprintf("Changed setup for %s", peerstate->m_addr);
dc_add_device_msg(context, contact_chat_id, msg);
free(msg);
context->m_cb(context, DC_EVENT_CHAT_MODIFIED, contact_chat_id, 0);
}
cleanup:
;
}
/*******************************************************************************
* Tools: Misc.
******************************************************************************/
static int encrypted_and_signed(dc_mimeparser_t* mimeparser, const char* expected_fingerprint)
{
if( !mimeparser->m_e2ee_helper->m_encrypted ) {
dc_log_warning(mimeparser->m_context, 0, "Message not encrypted.");
return 0;
}
if( dc_hash_count(mimeparser->m_e2ee_helper->m_signatures)<=0 ) {
dc_log_warning(mimeparser->m_context, 0, "Message not signed.");
return 0;
}
if( expected_fingerprint == NULL ) {
dc_log_warning(mimeparser->m_context, 0, "Fingerprint for comparison missing.");
return 0;
}
if( dc_hash_find_str(mimeparser->m_e2ee_helper->m_signatures, expected_fingerprint) == NULL ) {
dc_log_warning(mimeparser->m_context, 0, "Message does not match expected fingerprint %s.", expected_fingerprint);
return 0;
}
return 1;
}
static char* get_self_fingerprint(dc_context_t* context)
{
char* self_addr = NULL;
dc_key_t* self_key = dc_key_new();
char* fingerprint = NULL;
if( (self_addr = dc_sqlite3_get_config(context->m_sql, "configured_addr", NULL)) == NULL
|| !dc_key_load_self_public(self_key, self_addr, context->m_sql) ) {
goto cleanup;
}
if( (fingerprint=dc_key_get_fingerprint(self_key)) == NULL ) {
goto cleanup;
}
cleanup:
free(self_addr);
dc_key_unref(self_key);
return fingerprint;
}
static uint32_t chat_id_2_contact_id(dc_context_t* context, uint32_t contact_chat_id)
{
uint32_t contact_id = 0;
dc_array_t* contacts = dc_get_chat_contacts(context, contact_chat_id);
if( dc_array_get_cnt(contacts) != 1 ) {
goto cleanup;
}
contact_id = dc_array_get_id(contacts, 0);
cleanup:
dc_array_unref(contacts);
return contact_id;
}
static int fingerprint_equals_sender(dc_context_t* context, const char* fingerprint, uint32_t contact_chat_id)
{
int fingerprint_equal = 0;
dc_array_t* contacts = dc_get_chat_contacts(context, contact_chat_id);
dc_contact_t* contact = dc_contact_new(context);
dc_apeerstate_t* peerstate = dc_apeerstate_new(context);
char* fingerprint_normalized = NULL;
if( dc_array_get_cnt(contacts) != 1 ) {
goto cleanup;
}
if( !dc_contact_load_from_db(contact, context->m_sql, dc_array_get_id(contacts, 0))
|| !dc_apeerstate_load_by_addr(peerstate, context->m_sql, contact->m_addr) ) {
goto cleanup;
}
fingerprint_normalized = dc_normalize_fingerprint(fingerprint);
if( strcasecmp(fingerprint_normalized, peerstate->m_public_key_fingerprint) == 0 ) {
fingerprint_equal = 1;
}
cleanup:
free(fingerprint_normalized);
dc_contact_unref(contact);
dc_array_unref(contacts);
return fingerprint_equal;
}
static int mark_peer_as_verified(dc_context_t* context, const char* fingerprint)
{
int success = 0;
dc_apeerstate_t* peerstate = dc_apeerstate_new(context);
if( !dc_apeerstate_load_by_fingerprint(peerstate, context->m_sql, fingerprint) ) {
goto cleanup;
}
if( !dc_apeerstate_set_verified(peerstate, DC_PS_PUBLIC_KEY, fingerprint, DC_BIDIRECT_VERIFIED) ) {
goto cleanup;
}
// set MUTUAL as an out-of-band-verification is a strong hint that encryption is wanted.
// the state may be corrected by the Autocrypt headers as usual later;
// maybe it is a good idea to add the prefer-encrypt-state to the QR code.
peerstate->m_prefer_encrypt = DC_PE_MUTUAL;
peerstate->m_to_save |= DC_SAVE_ALL;
dc_apeerstate_save_to_db(peerstate, context->m_sql, 0);
success = 1;
cleanup:
dc_apeerstate_unref(peerstate);
return success;
}
static const char* lookup_field(dc_mimeparser_t* mimeparser, const char* key)
{
const char* value = NULL;
struct mailimf_field* field = dc_mimeparser_lookup_field(mimeparser, key);
if( field == NULL || field->fld_type != MAILIMF_FIELD_OPTIONAL_FIELD
|| field->fld_data.fld_optional_field == NULL || (value=field->fld_data.fld_optional_field->fld_value) == NULL ) {
return NULL;
}
return value;
}
static void send_handshake_msg(dc_context_t* context, uint32_t contact_chat_id, const char* step, const char* param2, const char* fingerprint, const char* grpid)
{
dc_msg_t* msg = dc_msg_new();
msg->m_type = DC_MSG_TEXT;
msg->m_text = dc_mprintf("Secure-Join: %s", step);
msg->m_hidden = 1;
dc_param_set_int(msg->m_param, DC_PARAM_CMD, DC_CMD_SECUREJOIN_MESSAGE);
dc_param_set (msg->m_param, DC_PARAM_CMD_ARG, step);
if( param2 ) {
dc_param_set(msg->m_param, DC_PARAM_CMD_ARG2, param2); // depening on step, this goes either to Secure-Join-Invitenumber or Secure-Join-Auth in mrmimefactory.c
}
if( fingerprint ) {
dc_param_set(msg->m_param, DC_PARAM_CMD_ARG3, fingerprint);
}
if( grpid ) {
dc_param_set(msg->m_param, DC_PARAM_CMD_ARG4, grpid);
}
if( strcmp(step, "vg-request")==0 || strcmp(step, "vc-request")==0 ) {
dc_param_set_int(msg->m_param, DC_PARAM_FORCE_PLAINTEXT, DC_FP_ADD_AUTOCRYPT_HEADER); // the request message MUST NOT be encrypted - it may be that the key has changed and the message cannot be decrypted otherwise
}
else {
dc_param_set_int(msg->m_param, DC_PARAM_GUARANTEE_E2EE, 1); /* all but the first message MUST be encrypted */
}
dc_send_msg_object(context, contact_chat_id, msg);
dc_msg_unref(msg);
}
static void could_not_establish_secure_connection(dc_context_t* context, uint32_t contact_chat_id, const char* details)
{
uint32_t contact_id = chat_id_2_contact_id(context, contact_chat_id);
dc_contact_t* contact = dc_get_contact(context, contact_id);
char* msg = dc_mprintf("Could not establish secure connection to %s.", contact? contact->m_addr : "?");
dc_add_device_msg(context, contact_chat_id, msg);
dc_log_error(context, 0, "%s (%s)", msg, details); // additionaly raise an error; this typically results in a toast (inviter side) or a dialog (joiner side)
free(msg);
dc_contact_unref(contact);
}
static void secure_connection_established(dc_context_t* context, uint32_t contact_chat_id)
{
uint32_t contact_id = chat_id_2_contact_id(context, contact_chat_id);
dc_contact_t* contact = dc_get_contact(context, contact_id);
char* msg = dc_mprintf("Secure connection to %s established.", contact? contact->m_addr : "?");
dc_add_device_msg(context, contact_chat_id, msg);
// in addition to DC_EVENT_MSGS_CHANGED (sent by dc_add_device_msg()), also send DC_EVENT_CHAT_MODIFIED to update all views
context->m_cb(context, DC_EVENT_CHAT_MODIFIED, contact_chat_id, 0);
free(msg);
dc_contact_unref(contact);
}
static void end_bobs_joining(dc_context_t* context, int status)
{
context->m_bobs_status = status;
dc_stop_ongoing_process(context);
}
/*******************************************************************************
* Secure-join main flow
******************************************************************************/
/**
* Get QR code text that will offer an secure-join verification.
* The QR code is compatible to the OPENPGP4FPR format so that a basic
* fingerprint comparison also works eg. with K-9 or OpenKeychain.
*
* The scanning Delta Chat device will pass the scanned content to
* dc_check_qr() then; if this function returns
* DC_QR_ASK_VERIFYCONTACT or DC_QR_ASK_VERIFYGROUP an out-of-band-verification
* can be joined using dc_join_securejoin()
*
* @memberof dc_context_t
* @param context The context object.
* @param group_chat_id If set to the ID of a chat, the "Joining a verified group" protocol is offered in the QR code.
* If set to 0, the "Setup Verified Contact" protocol is offered in the QR code.
* @return Text that should go to the qr code.
*/
char* dc_get_securejoin_qr(dc_context_t* context, uint32_t group_chat_id)
{
/* =========================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
========================================================= */
char* qr = NULL;
char* self_addr = NULL;
char* self_addr_urlencoded = NULL;
char* self_name = NULL;
char* self_name_urlencoded = NULL;
char* fingerprint = NULL;
char* invitenumber = NULL;
char* auth = NULL;
dc_chat_t* chat = NULL;
char* group_name = NULL;
char* group_name_urlencoded= NULL;
if( context == NULL || context->m_magic!=DC_CONTEXT_MAGIC ) {
goto cleanup;
}
dc_ensure_secret_key_exists(context);
// invitenumber will be used to allow starting the handshake, auth will be used to verify the fingerprint
invitenumber = dc_token_lookup__(context, DC_TOKEN_INVITENUMBER, group_chat_id);
if( invitenumber == NULL ) {
invitenumber = dc_create_id();
dc_token_save__(context, DC_TOKEN_INVITENUMBER, group_chat_id, invitenumber);
}
auth = dc_token_lookup__(context, DC_TOKEN_AUTH, group_chat_id);
if( auth == NULL ) {
auth = dc_create_id();
dc_token_save__(context, DC_TOKEN_AUTH, group_chat_id, auth);
}
if( (self_addr = dc_sqlite3_get_config(context->m_sql, "configured_addr", NULL)) == NULL ) {
dc_log_error(context, 0, "Not configured, cannot generate QR code.");
goto cleanup;
}
self_name = dc_sqlite3_get_config(context->m_sql, "displayname", "");
if( (fingerprint=get_self_fingerprint(context)) == NULL ) {
goto cleanup;
}
self_addr_urlencoded = dc_urlencode(self_addr);
self_name_urlencoded = dc_urlencode(self_name);
if( group_chat_id )
{
// parameters used: a=g=x=i=s=
chat = dc_get_chat(context, group_chat_id);
if( chat == NULL || chat->m_type != DC_CHAT_TYPE_VERIFIED_GROUP ) {
dc_log_error(context, 0, "Secure join is only available for verified groups.");
goto cleanup;
}
group_name = dc_chat_get_name(chat);
group_name_urlencoded = dc_urlencode(group_name);
qr = dc_mprintf(DC_OPENPGP4FPR_SCHEME "%s#a=%s&g=%s&x=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, group_name_urlencoded, chat->m_grpid, invitenumber, auth);
}
else
{
// parameters used: a=n=i=s=
qr = dc_mprintf(DC_OPENPGP4FPR_SCHEME "%s#a=%s&n=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, self_name_urlencoded, invitenumber, auth);
}
cleanup:
free(self_addr_urlencoded);
free(self_addr);
free(self_name);
free(self_name_urlencoded);
free(fingerprint);
free(invitenumber);
free(auth);
dc_chat_unref(chat);
free(group_name);
free(group_name_urlencoded);
return qr? qr : dc_strdup(NULL);
}
/**
* Join an out-of-band-verification initiated on another device with dc_get_securejoin_qr().
* This function is typically called when dc_check_qr() returns
* lot.m_state=DC_QR_ASK_VERIFYCONTACT or lot.m_state=DC_QR_ASK_VERIFYGROUP.
*
* This function takes some time and sends and receives several messages.
* You should call it in a separate thread; if you want to abort it, you should
* call dc_stop_ongoing_process().
*
* @memberof dc_context_t
* @param context The context object
* @param qr The text of the scanned QR code. Typically, the same string as given
* to dc_check_qr().
* @return 0=Out-of-band verification failed or aborted, 1=Out-of-band
* verification successfull, the UI may redirect to the corresponding chat
* where a new system message with the state was added.
*
* TODO: check if we should say to the caller, which activity to show after
* vc-request:
* - for a qr-scan while group-creation, returning to the chatlist might be better
* - for a qr-scan to add a contact (even without handshake), opening the created normal-chat is better
* (for vg-request always the new group is shown, this is perfect)
*/
uint32_t dc_join_securejoin(dc_context_t* context, const char* qr)
{
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
int ret_chat_id = 0;
int ongoing_allocated = 0;
#define CHECK_EXIT if( context->m_shall_stop_ongoing ) { goto cleanup; }
uint32_t contact_chat_id = 0;
dc_lot_t* qr_scan = NULL;
int join_vg = 0;
int locked = 0;
dc_log_info(context, 0, "Requesting secure-join ...");
dc_ensure_secret_key_exists(context);
if( (ongoing_allocated=dc_alloc_ongoing(context)) == 0 ) {
goto cleanup;
}
if( ((qr_scan=dc_check_qr(context, qr))==NULL)
|| (qr_scan->m_state!=DC_QR_ASK_VERIFYCONTACT && qr_scan->m_state!=DC_QR_ASK_VERIFYGROUP) ) {
dc_log_error(context, 0, "Unknown QR code.");
goto cleanup;
}
if( (contact_chat_id=dc_create_chat_by_contact_id(context, qr_scan->m_id)) == 0 ) {
dc_log_error(context, 0, "Unknown contact.");
goto cleanup;
}
CHECK_EXIT
if( context->m_cb(context, DC_EVENT_IS_OFFLINE, 0, 0)!=0 ) {
dc_log_error(context, DC_ERROR_NO_NETWORK, NULL);
goto cleanup;
}
CHECK_EXIT
join_vg = (qr_scan->m_state==DC_QR_ASK_VERIFYGROUP);
context->m_bobs_status = 0;
LOCK
context->m_bobs_qr_scan = qr_scan;
UNLOCK
if( fingerprint_equals_sender(context, qr_scan->m_fingerprint, contact_chat_id) ) {
// the scanned fingerprint matches Alice's key, we can proceed to step 4b) directly and save two mails
dc_log_info(context, 0, "Taking protocol shortcut.");
context->m_bob_expects = DC_VC_CONTACT_CONFIRM;
context->m_cb(context, DC_EVENT_SECUREJOIN_JOINER_PROGRESS, chat_id_2_contact_id(context, contact_chat_id), 4);
char* own_fingerprint = get_self_fingerprint(context);
send_handshake_msg(context, contact_chat_id, join_vg? "vg-request-with-auth" : "vc-request-with-auth",
qr_scan->m_auth, own_fingerprint, join_vg? qr_scan->m_text2 : NULL); // Bob -> Alice
free(own_fingerprint);
}
else {
context->m_bob_expects = DC_VC_AUTH_REQUIRED;
send_handshake_msg(context, contact_chat_id, join_vg? "vg-request" : "vc-request",
qr_scan->m_invitenumber, NULL, NULL); // Bob -> Alice
}
while( 1 ) {
CHECK_EXIT
usleep(300*1000); // 0.3 seconds
}
cleanup:
context->m_bob_expects = 0;
if( context->m_bobs_status == DC_BOB_SUCCESS ) {
if( join_vg ) {
ret_chat_id = dc_get_chat_id_by_grpid(context, qr_scan->m_text2, NULL, NULL);
}
else {
ret_chat_id = contact_chat_id;
}
}
LOCK
context->m_bobs_qr_scan = NULL;
UNLOCK
dc_lot_unref(qr_scan);
if( ongoing_allocated ) { dc_free_ongoing(context); }
return ret_chat_id;
}
int dc_handle_securejoin_handshake(dc_context_t* context, dc_mimeparser_t* mimeparser, uint32_t contact_id)
{
int locked = 0;
const char* step = NULL;
int join_vg = 0;
char* scanned_fingerprint_of_alice = NULL;
char* auth = NULL;
char* own_fingerprint = NULL;
uint32_t contact_chat_id = 0;
int contact_chat_id_blocked = 0;
char* grpid = NULL;
int ret = 0;
if( context == NULL || mimeparser == NULL || contact_id <= DC_CONTACT_ID_LAST_SPECIAL ) {
goto cleanup;
}
if( (step=lookup_field(mimeparser, "Secure-Join")) == NULL ) {
goto cleanup;
}
dc_log_info(context, 0, ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message '%s' received", step);
join_vg = (strncmp(step, "vg-", 3)==0);
dc_create_or_lookup_nchat_by_contact_id(context, contact_id, DC_CHAT_NOT_BLOCKED, &contact_chat_id, &contact_chat_id_blocked);
if( contact_chat_id_blocked ) {
dc_unblock_chat(context, contact_chat_id);
}
ret = DC_IS_HANDSHAKE_STOP_NORMAL_PROCESSING;
if( strcmp(step, "vg-request")==0 || strcmp(step, "vc-request")==0 )
{
/* =========================================================
==== Alice - the inviter side ====
==== Step 3 in "Setup verified contact" protocol ====
========================================================= */
// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
const char* invitenumber = NULL;
if( (invitenumber=lookup_field(mimeparser, "Secure-Join-Invitenumber")) == NULL ) {
dc_log_warning(context, 0, "Secure-join denied (invitenumber missing)."); // do not raise an error, this might just be spam or come from an old request
goto cleanup;
}
if( dc_token_exists__(context, DC_TOKEN_INVITENUMBER, invitenumber) == 0 ) {
dc_log_warning(context, 0, "Secure-join denied (bad invitenumber)."); // do not raise an error, this might just be spam or come from an old request
goto cleanup;
}
dc_log_info(context, 0, "Secure-join requested.");
context->m_cb(context, DC_EVENT_SECUREJOIN_INVITER_PROGRESS, contact_id, 3);
send_handshake_msg(context, contact_chat_id, join_vg? "vg-auth-required" : "vc-auth-required",
NULL, NULL, NULL); // Alice -> Bob
}
else if( strcmp(step, "vg-auth-required")==0 || strcmp(step, "vc-auth-required")==0 )
{
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 4 in "Setup verified contact" protocol =====
========================================================== */
// verify that Alice's Autocrypt key and fingerprint matches the QR-code
LOCK
if ( context->m_bobs_qr_scan==NULL
|| context->m_bob_expects!=DC_VC_AUTH_REQUIRED
|| (join_vg && context->m_bobs_qr_scan->m_state!=DC_QR_ASK_VERIFYGROUP) ) {
dc_log_warning(context, 0, "auth-required message out of sync.");
goto cleanup; // no error, just aborted somehow or a mail from another handshake
}
scanned_fingerprint_of_alice = dc_strdup(context->m_bobs_qr_scan->m_fingerprint);
auth = dc_strdup(context->m_bobs_qr_scan->m_auth);
if( join_vg ) {
grpid = dc_strdup(context->m_bobs_qr_scan->m_text2);
}
UNLOCK
if( !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) ) {
could_not_establish_secure_connection(context, contact_chat_id, mimeparser->m_e2ee_helper->m_encrypted? "No valid signature." : "Not encrypted.");
end_bobs_joining(context, DC_BOB_ERROR);
goto cleanup;
}
if( !fingerprint_equals_sender(context, scanned_fingerprint_of_alice, contact_chat_id) ) {
// MitM?
could_not_establish_secure_connection(context, contact_chat_id, "Fingerprint mismatch on joiner-side.");
end_bobs_joining(context, DC_BOB_ERROR);
goto cleanup;
}
dc_log_info(context, 0, "Fingerprint verified.");
own_fingerprint = get_self_fingerprint(context);
context->m_cb(context, DC_EVENT_SECUREJOIN_JOINER_PROGRESS, contact_id, 4);
context->m_bob_expects = DC_VC_CONTACT_CONFIRM;
send_handshake_msg(context, contact_chat_id, join_vg? "vg-request-with-auth" : "vc-request-with-auth",
auth, own_fingerprint, grpid); // Bob -> Alice
}
else if( strcmp(step, "vg-request-with-auth")==0 || strcmp(step, "vc-request-with-auth")==0 )
{
/* ============================================================
==== Alice - the inviter side ====
==== Steps 5+6 in "Setup verified contact" protocol ====
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
const char* fingerprint = NULL;
if( (fingerprint=lookup_field(mimeparser, "Secure-Join-Fingerprint")) == NULL ) {
could_not_establish_secure_connection(context, contact_chat_id, "Fingerprint not provided.");
goto cleanup;
}
if( !encrypted_and_signed(mimeparser, fingerprint) ) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth not encrypted.");
goto cleanup;
}
if( !fingerprint_equals_sender(context, fingerprint, contact_chat_id) ) {
// MitM?
could_not_establish_secure_connection(context, contact_chat_id, "Fingerprint mismatch on inviter-side.");
goto cleanup;
}
dc_log_info(context, 0, "Fingerprint verified.");
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
const char* auth = NULL;
if( (auth=lookup_field(mimeparser, "Secure-Join-Auth")) == NULL ) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth not provided.");
goto cleanup;
}
if( dc_token_exists__(context, DC_TOKEN_AUTH, auth) == 0 ) {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
goto cleanup;
}
if( !mark_peer_as_verified(context, fingerprint) ) {
could_not_establish_secure_connection(context, contact_chat_id, "Fingerprint mismatch on inviter-side."); // should not happen, we've compared the fingerprint some lines above
goto cleanup;
}
dc_scaleup_contact_origin(context, contact_id, DC_ORIGIN_SECUREJOIN_INVITED);
dc_log_info(context, 0, "Auth verified.");
secure_connection_established(context, contact_chat_id);
context->m_cb(context, DC_EVENT_CONTACTS_CHANGED, contact_id/*selected contact*/, 0);
context->m_cb(context, DC_EVENT_SECUREJOIN_INVITER_PROGRESS, contact_id, 6);
if( join_vg ) {
// the vg-member-added message is special: this is a normal Chat-Group-Member-Added message with an additional Secure-Join header
grpid = dc_strdup(lookup_field(mimeparser, "Secure-Join-Group"));
int is_verified = 0;
uint32_t verified_chat_id = dc_get_chat_id_by_grpid(context, grpid, NULL, &is_verified);
if( verified_chat_id == 0 || !is_verified ) {
dc_log_error(context, 0, "Verified chat not found.");
goto cleanup;
}
dc_add_contact_to_chat_ex(context, verified_chat_id, contact_id, DC_FROM_HANDSHAKE); // Alice -> Bob and all members
}
else {
send_handshake_msg(context, contact_chat_id, "vc-contact-confirm",
NULL, NULL, NULL); // Alice -> Bob
}
}
else if( strcmp(step, "vg-member-added")==0 || strcmp(step, "vc-contact-confirm")==0 )
{
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 7 in "Setup verified contact" protocol =====
========================================================== */
if( join_vg ) {
// vg-member-added is just part of a Chat-Group-Member-Added which should be kept in any way, eg. for multi-client
ret = DC_IS_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
}
if (context->m_bob_expects!=DC_VC_CONTACT_CONFIRM) {
if( join_vg ) {
dc_log_info(context, 0, "vg-member-added received as broadcast.");
}
else {
dc_log_warning(context, 0, "Unexpected secure-join mail order.");
}
goto cleanup;
}
LOCK
if (context->m_bobs_qr_scan==NULL || (join_vg && context->m_bobs_qr_scan->m_state!=DC_QR_ASK_VERIFYGROUP)) {
dc_log_warning(context, 0, "Message out of sync or belongs to a different handshake.");
goto cleanup;
}
scanned_fingerprint_of_alice = dc_strdup(context->m_bobs_qr_scan->m_fingerprint);
UNLOCK
if( !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) ) {
could_not_establish_secure_connection(context, contact_chat_id, "Contact confirm message not encrypted.");
end_bobs_joining(context, DC_BOB_ERROR);
goto cleanup;
}
// TODO: for the broadcasted vg-member-added, make sure, the message is ours (eg. by comparing Chat-Group-Member-Added against SELF)
if( !mark_peer_as_verified(context, scanned_fingerprint_of_alice) ) {
could_not_establish_secure_connection(context, contact_chat_id, "Fingerprint mismatch on joiner-side."); // MitM? - key has changed since vc-auth-required message
goto cleanup;
}
dc_scaleup_contact_origin(context, contact_id, DC_ORIGIN_SECUREJOIN_JOINED);
secure_connection_established(context, contact_chat_id);
context->m_cb(context, DC_EVENT_CONTACTS_CHANGED, 0/*no select event*/, 0);
context->m_bob_expects = 0;
end_bobs_joining(context, DC_BOB_SUCCESS);
}
// delete the message, as SMTP and IMAP is done in separate threads it should be okay to delete the message just now.
// for errors, we do not the corresponding message at all, it may come eg. from another device or may be useful to find out what was going wrong.
if( ret == DC_IS_HANDSHAKE_STOP_NORMAL_PROCESSING ) {
struct mailimf_field* field;
if( (field=dc_mimeparser_lookup_field(mimeparser, "Message-ID"))!=NULL && field->fld_type==MAILIMF_FIELD_MESSAGE_ID ) {
struct mailimf_message_id* fld_message_id = field->fld_data.fld_message_id;
if( fld_message_id && fld_message_id->mid_value ) {
dc_job_add(context, DC_JOB_DELETE_MSG_ON_IMAP, dc_rfc724_mid_exists__(context, fld_message_id->mid_value, NULL, NULL), NULL, 0);
}
}
}
cleanup:
UNLOCK
free(scanned_fingerprint_of_alice);
free(auth);
free(own_fingerprint);
free(grpid);
return ret;
}