mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-05 02:29:28 +02:00
935 lines
33 KiB
C
935 lines
33 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 "dc_context.h"
|
|
#include "dc_pgp.h"
|
|
#include "dc_aheader.h"
|
|
#include "dc_keyring.h"
|
|
#include "dc_mimeparser.h"
|
|
#include "dc_apeerstate.h"
|
|
|
|
|
|
/*******************************************************************************
|
|
* Tools
|
|
******************************************************************************/
|
|
|
|
|
|
static struct mailmime* new_data_part(void* data, size_t data_bytes, char* default_content_type, int default_encoding)
|
|
{
|
|
//char basename_buf[PATH_MAX];
|
|
struct mailmime_mechanism * encoding;
|
|
struct mailmime_content * content;
|
|
struct mailmime * mime;
|
|
//int r;
|
|
//char * dup_filename;
|
|
struct mailmime_fields * mime_fields;
|
|
int encoding_type;
|
|
char * content_type_str;
|
|
int do_encoding;
|
|
|
|
/*if (filename != NULL) {
|
|
strncpy(basename_buf, filename, PATH_MAX);
|
|
libetpan_basename(basename_buf);
|
|
}*/
|
|
|
|
encoding = NULL;
|
|
|
|
/* default content-type */
|
|
if (default_content_type == NULL)
|
|
content_type_str = "application/octet-stream";
|
|
else
|
|
content_type_str = default_content_type;
|
|
|
|
content = mailmime_content_new_with_str(content_type_str);
|
|
if (content == NULL) {
|
|
goto free_content;
|
|
}
|
|
|
|
do_encoding = 1;
|
|
if (content->ct_type->tp_type == MAILMIME_TYPE_COMPOSITE_TYPE) {
|
|
struct mailmime_composite_type * composite;
|
|
|
|
composite = content->ct_type->tp_data.tp_composite_type;
|
|
|
|
switch (composite->ct_type) {
|
|
case MAILMIME_COMPOSITE_TYPE_MESSAGE:
|
|
if (strcasecmp(content->ct_subtype, "rfc822") == 0)
|
|
do_encoding = 0;
|
|
break;
|
|
|
|
case MAILMIME_COMPOSITE_TYPE_MULTIPART:
|
|
do_encoding = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (do_encoding) {
|
|
if (default_encoding == -1)
|
|
encoding_type = MAILMIME_MECHANISM_BASE64;
|
|
else
|
|
encoding_type = default_encoding;
|
|
|
|
/* default Content-Transfer-Encoding */
|
|
encoding = mailmime_mechanism_new(encoding_type, NULL);
|
|
if (encoding == NULL) {
|
|
goto free_content;
|
|
}
|
|
}
|
|
|
|
mime_fields = mailmime_fields_new_with_data(encoding,
|
|
NULL, NULL, NULL, NULL);
|
|
if (mime_fields == NULL) {
|
|
goto free_content;
|
|
}
|
|
|
|
mime = mailmime_new_empty(content, mime_fields);
|
|
if (mime == NULL) {
|
|
goto free_mime_fields;
|
|
}
|
|
|
|
/*if ((filename != NULL) && (mime->mm_type == MAILMIME_SINGLE)) {
|
|
// duplicates the file so that the file can be deleted when
|
|
// the MIME part is done
|
|
dup_filename = dup_file(privacy, filename);
|
|
if (dup_filename == NULL) {
|
|
goto free_mime;
|
|
}
|
|
|
|
r = mailmime_set_body_file(mime, dup_filename);
|
|
if (r != MAILIMF_NO_ERROR) {
|
|
free(dup_filename);
|
|
goto free_mime;
|
|
}
|
|
}*/
|
|
if( data!=NULL && data_bytes>0 && mime->mm_type == MAILMIME_SINGLE ) {
|
|
mailmime_set_body_text(mime, data, data_bytes);
|
|
}
|
|
|
|
return mime;
|
|
|
|
// free_mime:
|
|
//mailmime_free(mime);
|
|
goto err;
|
|
free_mime_fields:
|
|
mailmime_fields_free(mime_fields);
|
|
mailmime_content_free(content);
|
|
goto err;
|
|
free_content:
|
|
if (encoding != NULL)
|
|
mailmime_mechanism_free(encoding);
|
|
if (content != NULL)
|
|
mailmime_content_free(content);
|
|
err:
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a MIME structure contains a multipart/report part.
|
|
*
|
|
* As reports are often unencrypted, we do not reset the Autocrypt header in
|
|
* this case.
|
|
*
|
|
* However, Delta Chat itself has no problem with encrypted multipart/report
|
|
* parts and MUAs should be encouraged to encrpyt multipart/reports as well so
|
|
* that we could use the normal Autocrypt processing.
|
|
*
|
|
* @private
|
|
*
|
|
* @param mime The mime struture to check
|
|
*
|
|
* @return 1=multipart/report found in MIME, 0=no multipart/report found
|
|
*/
|
|
static int contains_report(struct mailmime* mime)
|
|
{
|
|
if( mime->mm_type == MAILMIME_MULTIPLE )
|
|
{
|
|
if( mime->mm_content_type->ct_type->tp_type==MAILMIME_TYPE_COMPOSITE_TYPE
|
|
&& mime->mm_content_type->ct_type->tp_data.tp_composite_type->ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART
|
|
&& strcmp(mime->mm_content_type->ct_subtype, "report")==0 ) {
|
|
return 1;
|
|
}
|
|
|
|
clistiter* cur;
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
if( contains_report((struct mailmime*)clist_content(cur)) ) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else if( mime->mm_type == MAILMIME_MESSAGE )
|
|
{
|
|
if( contains_report(mime->mm_data.mm_message.mm_msg_mime) ) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Generate Keypairs
|
|
******************************************************************************/
|
|
|
|
|
|
static int load_or_generate_self_public_key__(dc_context_t* context, dc_key_t* public_key, const char* self_addr,
|
|
struct mailmime* random_data_mime /*for an extra-seed of the random generator. For speed reasons, only give _available_ pointers here, do not create any data - in very most cases, the key is not generated!*/)
|
|
{
|
|
static int s_in_key_creation = 0; /* avoid double creation (we unlock the database during creation) */
|
|
int key_created = 0;
|
|
int success = 0, key_creation_here = 0;
|
|
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC || public_key == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !dc_key_load_self_public(public_key, self_addr, context->sql) )
|
|
{
|
|
/* create the keypair - this may take a moment, however, as this is in a thread, this is no big deal */
|
|
if( s_in_key_creation ) { goto cleanup; }
|
|
key_creation_here = 1;
|
|
s_in_key_creation = 1;
|
|
|
|
/* seed the random generator */
|
|
{
|
|
uintptr_t seed[4];
|
|
seed[0] = (uintptr_t)time(NULL); /* time */
|
|
seed[1] = (uintptr_t)seed; /* stack */
|
|
seed[2] = (uintptr_t)public_key; /* heap */
|
|
seed[3] = (uintptr_t)pthread_self(); /* thread ID */
|
|
dc_pgp_rand_seed(context, seed, sizeof(seed));
|
|
|
|
if( random_data_mime ) {
|
|
MMAPString* random_data_mmap = NULL;
|
|
int col = 0;
|
|
if( (random_data_mmap=mmap_string_new(""))==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
mailmime_write_mem(random_data_mmap, &col, random_data_mime);
|
|
dc_pgp_rand_seed(context, random_data_mmap->str, random_data_mmap->len);
|
|
mmap_string_free(random_data_mmap);
|
|
}
|
|
}
|
|
|
|
{
|
|
dc_key_t* private_key = dc_key_new();
|
|
|
|
dc_log_info(context, 0, "Generating keypair ...");
|
|
|
|
/* The public key must contain the following:
|
|
- a signing-capable primary key Kp
|
|
- a user id
|
|
- a self signature
|
|
- an encryption-capable subkey Ke
|
|
- a binding signature over Ke by Kp
|
|
(see https://autocrypt.readthedocs.io/en/latest/level0.html#type-p-openpgp-based-key-data )*/
|
|
key_created = dc_pgp_create_keypair(context, self_addr, public_key, private_key);
|
|
|
|
if( !key_created ) {
|
|
dc_log_warning(context, 0, "Cannot create keypair.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !dc_pgp_is_valid_key(context, public_key)
|
|
|| !dc_pgp_is_valid_key(context, private_key) ) {
|
|
dc_log_warning(context, 0, "Generated keys are not valid.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !dc_key_save_self_keypair(public_key, private_key, self_addr, 1/*set default*/, context->sql) ) {
|
|
dc_log_warning(context, 0, "Cannot save keypair.");
|
|
goto cleanup;
|
|
}
|
|
|
|
dc_log_info(context, 0, "Keypair generated.");
|
|
|
|
dc_key_unref(private_key);
|
|
}
|
|
}
|
|
|
|
success = 1;
|
|
|
|
cleanup:
|
|
if( key_creation_here ) { s_in_key_creation = 0; }
|
|
return success;
|
|
}
|
|
|
|
|
|
int dc_ensure_secret_key_exists(dc_context_t* context)
|
|
{
|
|
/* normally, the key is generated as soon as the first mail is send
|
|
(this is to gain some extra-random-seed by the message content and the timespan between program start and message sending) */
|
|
int success = 0;
|
|
dc_key_t* public_key = dc_key_new();
|
|
char* self_addr = NULL;
|
|
|
|
if( context==NULL || context->magic != DC_CONTEXT_MAGIC || public_key==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( (self_addr=dc_sqlite3_get_config(context->sql, "configured_addr", NULL))==NULL ) {
|
|
dc_log_warning(context, 0, "Cannot ensure secret key if context is not configured.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !load_or_generate_self_public_key__(context, public_key, self_addr, NULL/*no random text data for seeding available*/) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
success = 1;
|
|
|
|
cleanup:
|
|
dc_key_unref(public_key);
|
|
free(self_addr);
|
|
return success;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Encrypt
|
|
******************************************************************************/
|
|
|
|
|
|
void dc_e2ee_encrypt(dc_context_t* context, const clist* recipients_addr,
|
|
int force_unencrypted,
|
|
int e2ee_guaranteed, /*set if e2ee was possible on sending time; we should not degrade to transport*/
|
|
int min_verified,
|
|
struct mailmime* in_out_message, dc_e2ee_helper_t* helper)
|
|
{
|
|
int col = 0, do_encrypt = 0;
|
|
dc_aheader_t* autocryptheader = dc_aheader_new();
|
|
struct mailimf_fields* imffields_unprotected = NULL; /*just a pointer into mailmime structure, must not be freed*/
|
|
dc_keyring_t* keyring = dc_keyring_new();
|
|
dc_key_t* sign_key = dc_key_new();
|
|
MMAPString* plain = mmap_string_new("");
|
|
char* ctext = NULL;
|
|
size_t ctext_bytes = 0;
|
|
dc_array_t* peerstates = dc_array_new(NULL, 10);
|
|
|
|
if( helper ) { memset(helper, 0, sizeof(dc_e2ee_helper_t)); }
|
|
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC || recipients_addr == NULL || in_out_message == NULL
|
|
|| in_out_message->mm_parent /* libEtPan's pgp_encrypt_mime() takes the parent as the new root. We just expect the root as being given to this function. */
|
|
|| autocryptheader == NULL || keyring==NULL || sign_key==NULL || plain == NULL || helper == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* init autocrypt header from db */
|
|
autocryptheader->prefer_encrypt = DC_PE_NOPREFERENCE;
|
|
if( context->e2ee_enabled ) {
|
|
autocryptheader->prefer_encrypt = DC_PE_MUTUAL;
|
|
}
|
|
|
|
autocryptheader->addr = dc_sqlite3_get_config(context->sql, "configured_addr", NULL);
|
|
if( autocryptheader->addr == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !load_or_generate_self_public_key__(context, autocryptheader->public_key, autocryptheader->addr, in_out_message/*only for random-seed*/) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* load peerstate information etc. */
|
|
if( autocryptheader->prefer_encrypt==DC_PE_MUTUAL || e2ee_guaranteed )
|
|
{
|
|
do_encrypt = 1;
|
|
clistiter* iter1;
|
|
for( iter1 = clist_begin(recipients_addr); iter1!=NULL ; iter1=clist_next(iter1) ) {
|
|
const char* recipient_addr = clist_content(iter1);
|
|
dc_apeerstate_t* peerstate = dc_apeerstate_new(context);
|
|
dc_key_t* key_to_use = NULL;
|
|
if( strcasecmp(recipient_addr, autocryptheader->addr) == 0 )
|
|
{
|
|
; // encrypt to SELF, this key is added below
|
|
}
|
|
else if( dc_apeerstate_load_by_addr(peerstate, context->sql, recipient_addr)
|
|
&& (key_to_use=dc_apeerstate_peek_key(peerstate, min_verified)) != NULL
|
|
&& (peerstate->prefer_encrypt==DC_PE_MUTUAL || e2ee_guaranteed) )
|
|
{
|
|
dc_keyring_add(keyring, key_to_use); /* we always add all recipients (even on IMAP upload) as otherwise forwarding may fail */
|
|
dc_array_add_ptr(peerstates, peerstate);
|
|
}
|
|
else
|
|
{
|
|
dc_apeerstate_unref(peerstate);
|
|
do_encrypt = 0;
|
|
break; /* if we cannot encrypt to a single recipient, we cannot encrypt the message at all */
|
|
}
|
|
}
|
|
}
|
|
|
|
if( do_encrypt ) {
|
|
dc_keyring_add(keyring, autocryptheader->public_key); /* we always add ourself as otherwise forwarded messages are not readable */
|
|
if( !dc_key_load_self_private(sign_key, autocryptheader->addr, context->sql) ) {
|
|
do_encrypt = 0;
|
|
}
|
|
}
|
|
|
|
if( force_unencrypted ) {
|
|
do_encrypt = 0;
|
|
}
|
|
|
|
if( (imffields_unprotected=mailmime_find_mailimf_fields(in_out_message))==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* encrypt message, if possible */
|
|
if( do_encrypt )
|
|
{
|
|
/* prepare part to encrypt */
|
|
mailprivacy_prepare_mime(in_out_message); /* encode quoted printable all text parts */
|
|
|
|
struct mailmime* part_to_encrypt = in_out_message->mm_data.mm_message.mm_msg_mime;
|
|
part_to_encrypt->mm_parent = NULL;
|
|
struct mailimf_fields* imffields_encrypted = mailimf_fields_new_empty();
|
|
struct mailmime* message_to_encrypt = mailmime_new(MAILMIME_MESSAGE, NULL, 0, mailmime_fields_new_empty(), /* mailmime_new_message_data() calls mailmime_fields_new_with_version() which would add the unwanted MIME-Version:-header */
|
|
mailmime_get_content_message(), NULL, NULL, NULL, NULL, imffields_encrypted, part_to_encrypt);
|
|
|
|
/* gossip keys */
|
|
int iCnt = dc_array_get_cnt(peerstates);
|
|
if( iCnt > 1 ) {
|
|
for( int i = 0; i < iCnt; i++ ) {
|
|
char* p = dc_apeerstate_render_gossip_header((dc_apeerstate_t*)dc_array_get_ptr(peerstates, i), min_verified);
|
|
if( p ) {
|
|
mailimf_fields_add(imffields_encrypted, mailimf_field_new_custom(strdup("Autocrypt-Gossip"), p/*takes ownership*/));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* memoryhole headers */
|
|
clistiter* cur = clist_begin(imffields_unprotected->fld_list);
|
|
while( cur!=NULL ) {
|
|
int move_to_encrypted = 0;
|
|
|
|
struct mailimf_field* field = (struct mailimf_field*)clist_content(cur);
|
|
if( field ) {
|
|
if( field->fld_type == MAILIMF_FIELD_SUBJECT ) {
|
|
move_to_encrypted = 1;
|
|
}
|
|
else if( field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD ) {
|
|
struct mailimf_optional_field* opt_field = field->fld_data.fld_optional_field;
|
|
if( opt_field && opt_field->fld_name ) {
|
|
if( strncmp(opt_field->fld_name, "Secure-Join", 11)==0
|
|
|| (strncmp(opt_field->fld_name, "Chat-", 5)==0 && strcmp(opt_field->fld_name, "Chat-Version")!=0)/*Chat-Version may be used for filtering and is not added to the encrypted part, however, this is subject to change*/ ) {
|
|
move_to_encrypted = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( move_to_encrypted ) {
|
|
mailimf_fields_add(imffields_encrypted, field);
|
|
cur = clist_delete(imffields_unprotected->fld_list, cur);
|
|
}
|
|
else {
|
|
cur = clist_next(cur);
|
|
}
|
|
}
|
|
|
|
char* e = dc_stock_str(context, DC_STR_ENCRYPTEDMSG); char* subject_str = dc_mprintf(DC_CHAT_PREFIX " %s", e); free(e);
|
|
struct mailimf_subject* subject = mailimf_subject_new(dc_encode_header_words(subject_str));
|
|
mailimf_fields_add(imffields_unprotected, mailimf_field_new(MAILIMF_FIELD_SUBJECT, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, subject, NULL, NULL, NULL));
|
|
free(subject_str);
|
|
|
|
clist_append(part_to_encrypt->mm_content_type->ct_parameters, mailmime_param_new_with_data("protected-headers", "v1"));
|
|
|
|
/* convert part to encrypt to plain text */
|
|
mailmime_write_mem(plain, &col, message_to_encrypt);
|
|
if( plain->str == NULL || plain->len<=0 ) {
|
|
goto cleanup;
|
|
}
|
|
//char* t1=dc_null_terminate(plain->str,plain->len);printf("PLAIN:\n%s\n",t1);free(t1); // DEBUG OUTPUT
|
|
|
|
if( !dc_pgp_pk_encrypt(context, plain->str, plain->len, keyring, sign_key, 1/*use_armor*/, (void**)&ctext, &ctext_bytes) ) {
|
|
goto cleanup;
|
|
}
|
|
helper->cdata_to_free = ctext;
|
|
//char* t2=dc_null_terminate(ctext,ctext_bytes);printf("ENCRYPTED:\n%s\n",t2);free(t2); // DEBUG OUTPUT
|
|
|
|
/* create MIME-structure that will contain the encrypted text */
|
|
struct mailmime* encrypted_part = new_data_part(NULL, 0, "multipart/encrypted", -1);
|
|
|
|
struct mailmime_content* content = encrypted_part->mm_content_type;
|
|
clist_append(content->ct_parameters, mailmime_param_new_with_data("protocol", "application/pgp-encrypted"));
|
|
|
|
static char version_content[] = "Version: 1\r\n";
|
|
struct mailmime* version_mime = new_data_part(version_content, strlen(version_content), "application/pgp-encrypted", MAILMIME_MECHANISM_7BIT);
|
|
mailmime_smart_add_part(encrypted_part, version_mime);
|
|
|
|
struct mailmime* ctext_part = new_data_part(ctext, ctext_bytes, "application/octet-stream", MAILMIME_MECHANISM_7BIT);
|
|
mailmime_smart_add_part(encrypted_part, ctext_part);
|
|
|
|
/* replace the original MIME-structure by the encrypted MIME-structure */
|
|
in_out_message->mm_data.mm_message.mm_msg_mime = encrypted_part;
|
|
encrypted_part->mm_parent = in_out_message;
|
|
mailmime_free(message_to_encrypt);
|
|
//MMAPString* t3=mmap_string_new("");mailmime_write_mem(t3,&col,in_out_message);char* t4=dc_null_terminate(t3->str,t3->len); printf("ENCRYPTED+MIME_ENCODED:\n%s\n",t4);free(t4);mmap_string_free(t3); // DEBUG OUTPUT
|
|
|
|
helper->encryption_successfull = 1;
|
|
}
|
|
|
|
char* p = dc_aheader_render(autocryptheader);
|
|
if( p == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
mailimf_fields_add(imffields_unprotected, mailimf_field_new_custom(strdup("Autocrypt"), p/*takes ownership of pointer*/));
|
|
|
|
cleanup:
|
|
dc_aheader_unref(autocryptheader);
|
|
dc_keyring_unref(keyring);
|
|
dc_key_unref(sign_key);
|
|
if( plain ) { mmap_string_free(plain); }
|
|
|
|
for( int i=dc_array_get_cnt(peerstates)-1; i>=0; i-- ) { dc_apeerstate_unref((dc_apeerstate_t*)dc_array_get_ptr(peerstates, i)); }
|
|
dc_array_unref(peerstates);
|
|
}
|
|
|
|
|
|
void dc_e2ee_thanks(dc_e2ee_helper_t* helper)
|
|
{
|
|
if( helper == NULL ) {
|
|
return;
|
|
}
|
|
|
|
free(helper->cdata_to_free);
|
|
helper->cdata_to_free = NULL;
|
|
|
|
if( helper->gossipped_addr )
|
|
{
|
|
dc_hash_clear(helper->gossipped_addr);
|
|
free(helper->gossipped_addr);
|
|
helper->gossipped_addr = NULL;
|
|
}
|
|
|
|
if( helper->signatures )
|
|
{
|
|
dc_hash_clear(helper->signatures);
|
|
free(helper->signatures);
|
|
helper->signatures = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Decrypt
|
|
******************************************************************************/
|
|
|
|
|
|
static int has_decrypted_pgp_armor(const char* str__, int str_bytes)
|
|
{
|
|
const unsigned char *str_end = (const unsigned char*)str__+str_bytes, *p=(const unsigned char*)str__;
|
|
while( p < str_end ) {
|
|
if( *p > ' ' ) {
|
|
break;
|
|
}
|
|
p++;
|
|
str_bytes--;
|
|
}
|
|
if( str_bytes>27 && strncmp((const char*)p, "-----BEGIN PGP MESSAGE-----", 27)==0 ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int decrypt_part(dc_context_t* context,
|
|
struct mailmime* mime,
|
|
const dc_keyring_t* private_keyring,
|
|
const dc_keyring_t* public_keyring_for_validate, /*may be NULL*/
|
|
dc_hash_t* ret_valid_signatures,
|
|
struct mailmime** ret_decrypted_mime)
|
|
{
|
|
struct mailmime_data* mime_data;
|
|
int mime_transfer_encoding = MAILMIME_MECHANISM_BINARY;
|
|
char* transfer_decoding_buffer = NULL; /* mmap_string_unref()'d if set */
|
|
const char* decoded_data = NULL; /* must not be free()'d */
|
|
size_t decoded_data_bytes = 0;
|
|
void* plain_buf = NULL;
|
|
size_t plain_bytes = 0;
|
|
int sth_decrypted = 0;
|
|
|
|
*ret_decrypted_mime = NULL;
|
|
|
|
/* get data pointer from `mime` */
|
|
mime_data = mime->mm_data.mm_single;
|
|
if( mime_data->dt_type != MAILMIME_DATA_TEXT /* MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing */
|
|
|| mime_data->dt_data.dt_text.dt_data == NULL
|
|
|| mime_data->dt_data.dt_text.dt_length <= 0 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* check headers in `mime` */
|
|
if( mime->mm_mime_fields != NULL ) {
|
|
clistiter* cur;
|
|
for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
|
|
struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
|
|
if( field ) {
|
|
if( field->fld_type == MAILMIME_FIELD_TRANSFER_ENCODING && field->fld_data.fld_encoding ) {
|
|
mime_transfer_encoding = field->fld_data.fld_encoding->enc_type;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* regard `Content-Transfer-Encoding:` */
|
|
if( mime_transfer_encoding == MAILMIME_MECHANISM_7BIT
|
|
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT
|
|
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY )
|
|
{
|
|
decoded_data = mime_data->dt_data.dt_text.dt_data;
|
|
decoded_data_bytes = mime_data->dt_data.dt_text.dt_length;
|
|
if( decoded_data == NULL || decoded_data_bytes <= 0 ) {
|
|
goto cleanup; /* no error - but no data */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int r;
|
|
size_t current_index = 0;
|
|
r = mailmime_part_parse(mime_data->dt_data.dt_text.dt_data, mime_data->dt_data.dt_text.dt_length,
|
|
¤t_index, mime_transfer_encoding,
|
|
&transfer_decoding_buffer, &decoded_data_bytes);
|
|
if( r != MAILIMF_NO_ERROR || transfer_decoding_buffer == NULL || decoded_data_bytes <= 0 ) {
|
|
goto cleanup;
|
|
}
|
|
decoded_data = transfer_decoding_buffer;
|
|
}
|
|
|
|
/* encrypted, decoded data in decoded_data now ... */
|
|
if( !has_decrypted_pgp_armor(decoded_data, decoded_data_bytes) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
dc_hash_t* add_signatures = dc_hash_count(ret_valid_signatures)<=0?
|
|
ret_valid_signatures : NULL; /*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
|
|
|
|
if( !dc_pgp_pk_decrypt(context, decoded_data, decoded_data_bytes, private_keyring, public_keyring_for_validate, 1, &plain_buf, &plain_bytes, add_signatures)
|
|
|| plain_buf==NULL || plain_bytes<=0 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//{char* t1=dc_null_terminate(plain_buf,plain_bytes);printf("\n**********\n%s\n**********\n",t1);free(t1);}
|
|
|
|
{
|
|
size_t index = 0;
|
|
struct mailmime* decrypted_mime = NULL;
|
|
if( mailmime_parse(plain_buf, plain_bytes, &index, &decrypted_mime)!=MAIL_NO_ERROR
|
|
|| decrypted_mime == NULL ) {
|
|
if(decrypted_mime) {mailmime_free(decrypted_mime);}
|
|
goto cleanup;
|
|
}
|
|
|
|
//mailmime_print(decrypted_mime);
|
|
|
|
*ret_decrypted_mime = decrypted_mime;
|
|
sth_decrypted = 1;
|
|
}
|
|
|
|
//mailmime_substitute(mime, new_mime);
|
|
//s. mailprivacy_gnupg.c::pgp_decrypt()
|
|
|
|
cleanup:
|
|
if( transfer_decoding_buffer ) {
|
|
mmap_string_unref(transfer_decoding_buffer);
|
|
}
|
|
return sth_decrypted;
|
|
}
|
|
|
|
|
|
static int decrypt_recursive(dc_context_t* context,
|
|
struct mailmime* mime,
|
|
const dc_keyring_t* private_keyring,
|
|
const dc_keyring_t* public_keyring_for_validate,
|
|
dc_hash_t* ret_valid_signatures,
|
|
struct mailimf_fields** ret_gossip_headers,
|
|
int* ret_has_unencrypted_parts )
|
|
{
|
|
struct mailmime_content* ct;
|
|
clistiter* cur;
|
|
|
|
if( context == NULL || mime == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
if( mime->mm_type == MAILMIME_MULTIPLE )
|
|
{
|
|
ct = mime->mm_content_type;
|
|
if( ct && ct->ct_subtype && strcmp(ct->ct_subtype, "encrypted")==0 ) {
|
|
/* decrypt "multipart/encrypted" -- child parts are eg. "application/pgp-encrypted" (uninteresting, version only),
|
|
"application/octet-stream" (the interesting data part) and optional, unencrypted help files */
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
struct mailmime* decrypted_mime = NULL;
|
|
if( decrypt_part(context, (struct mailmime*)clist_content(cur), private_keyring, public_keyring_for_validate, ret_valid_signatures, &decrypted_mime) )
|
|
{
|
|
/* remember the header containing potentially Autocrypt-Gossip */
|
|
if( *ret_gossip_headers == NULL /* use the outermost decrypted part */
|
|
&& dc_hash_count(ret_valid_signatures) > 0 /* do not trust the gossipped keys when the message cannot be validated eg. due to a bad signature */ )
|
|
{
|
|
size_t dummy = 0;
|
|
struct mailimf_fields* test = NULL;
|
|
if( mailimf_envelope_and_optional_fields_parse(decrypted_mime->mm_mime_start, decrypted_mime->mm_length, &dummy, &test)==MAILIMF_NO_ERROR
|
|
&& test ) {
|
|
*ret_gossip_headers = test;
|
|
}
|
|
}
|
|
|
|
/* replace encrypted mime structure by decrypted one */
|
|
mailmime_substitute(mime, decrypted_mime);
|
|
mailmime_free(mime);
|
|
return 1; /* sth. decrypted, start over from root searching for encrypted parts */
|
|
}
|
|
}
|
|
*ret_has_unencrypted_parts = 1; // there is a part that could not be decrypted
|
|
}
|
|
else {
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
if( decrypt_recursive(context, (struct mailmime*)clist_content(cur), private_keyring, public_keyring_for_validate, ret_valid_signatures, ret_gossip_headers, ret_has_unencrypted_parts) ) {
|
|
return 1; /* sth. decrypted, start over from root searching for encrypted parts */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( mime->mm_type == MAILMIME_MESSAGE )
|
|
{
|
|
if( decrypt_recursive(context, mime->mm_data.mm_message.mm_msg_mime, private_keyring, public_keyring_for_validate, ret_valid_signatures, ret_gossip_headers, ret_has_unencrypted_parts) ) {
|
|
return 1; /* sth. decrypted, start over from root searching for encrypted parts */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ret_has_unencrypted_parts = 1; // there is a part that was not encrypted at all. in combination with otherwise encrypted mails, this is a problem.
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static dc_hash_t* update_gossip_peerstates(dc_context_t* context, time_t message_time, struct mailimf_fields* imffields, const struct mailimf_fields* gossip_headers)
|
|
{
|
|
clistiter* cur1;
|
|
dc_hash_t* recipients = NULL;
|
|
dc_hash_t* gossipped_addr = NULL;
|
|
|
|
for( cur1 = clist_begin(gossip_headers->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
|
|
{
|
|
struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
|
|
if( field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD )
|
|
{
|
|
const struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
|
|
if( optional_field && optional_field->fld_name && strcasecmp(optional_field->fld_name, "Autocrypt-Gossip")==0 )
|
|
{
|
|
dc_aheader_t* gossip_header = dc_aheader_new();
|
|
if( dc_aheader_set_from_string(gossip_header, optional_field->fld_value)
|
|
&& dc_pgp_is_valid_key(context, gossip_header->public_key) )
|
|
{
|
|
/* found an Autocrypt-Gossip entry, create recipents list and check if addr matches */
|
|
if( recipients == NULL ) {
|
|
recipients = mailimf_get_recipients(imffields);
|
|
}
|
|
|
|
if( dc_hash_find(recipients, gossip_header->addr, strlen(gossip_header->addr)) )
|
|
{
|
|
/* valid recipient: update peerstate */
|
|
dc_apeerstate_t* peerstate = dc_apeerstate_new(context);
|
|
if( !dc_apeerstate_load_by_addr(peerstate, context->sql, gossip_header->addr) ) {
|
|
dc_apeerstate_init_from_gossip(peerstate, gossip_header, message_time);
|
|
dc_apeerstate_save_to_db(peerstate, context->sql, 1/*create*/);
|
|
}
|
|
else {
|
|
dc_apeerstate_apply_gossip(peerstate, gossip_header, message_time);
|
|
dc_apeerstate_save_to_db(peerstate, context->sql, 0/*do not create*/);
|
|
}
|
|
|
|
if( peerstate->degrade_event ) {
|
|
dc_handle_degrade_event(context, peerstate);
|
|
}
|
|
|
|
dc_apeerstate_unref(peerstate);
|
|
|
|
// collect all gossipped addresses; we need them later to mark them as being
|
|
// verified when used in a verified group by a verified sender
|
|
if( gossipped_addr == NULL ) {
|
|
gossipped_addr = malloc(sizeof(dc_hash_t));
|
|
dc_hash_init(gossipped_addr, DC_HASH_STRING, 1/*copy key*/);
|
|
}
|
|
dc_hash_insert(gossipped_addr, gossip_header->addr, strlen(gossip_header->addr), (void*)1);
|
|
}
|
|
else
|
|
{
|
|
dc_log_info(context, 0, "Ignoring gossipped \"%s\" as the address is not in To/Cc list.", gossip_header->addr);
|
|
}
|
|
}
|
|
dc_aheader_unref(gossip_header);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( recipients ) {
|
|
dc_hash_clear(recipients);
|
|
free(recipients);
|
|
}
|
|
|
|
return gossipped_addr;
|
|
}
|
|
|
|
|
|
void dc_e2ee_decrypt(dc_context_t* context, struct mailmime* in_out_message,
|
|
dc_e2ee_helper_t* helper)
|
|
{
|
|
/* return values: 0=nothing to decrypt/cannot decrypt, 1=sth. decrypted
|
|
(to detect parts that could not be decrypted, simply look for left "multipart/encrypted" MIME types */
|
|
struct mailimf_fields* imffields = mailmime_find_mailimf_fields(in_out_message); /*just a pointer into mailmime structure, must not be freed*/
|
|
dc_aheader_t* autocryptheader = NULL;
|
|
time_t message_time = 0;
|
|
dc_apeerstate_t* peerstate = dc_apeerstate_new(context);
|
|
char* from = NULL, *self_addr = NULL;
|
|
dc_keyring_t* private_keyring = dc_keyring_new();
|
|
dc_keyring_t* public_keyring_for_validate = dc_keyring_new();
|
|
struct mailimf_fields* gossip_headers = NULL;
|
|
|
|
if( helper ) { memset(helper, 0, sizeof(dc_e2ee_helper_t)); }
|
|
|
|
if( context==NULL || context->magic != DC_CONTEXT_MAGIC || in_out_message==NULL
|
|
|| helper == NULL || imffields==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Autocrypt preparations:
|
|
- Set message_time and from (both may be unset)
|
|
- Get the autocrypt header, if any.
|
|
- Do not abort on errors - we should try at last the decyption below */
|
|
if( imffields )
|
|
{
|
|
struct mailimf_field* field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM);
|
|
if( field && field->fld_data.fld_from ) {
|
|
from = mailimf_find_first_addr(field->fld_data.fld_from->frm_mb_list);
|
|
}
|
|
|
|
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE);
|
|
if( field && field->fld_data.fld_orig_date ) {
|
|
struct mailimf_orig_date* orig_date = field->fld_data.fld_orig_date;
|
|
if( orig_date ) {
|
|
message_time = dc_timestamp_from_date(orig_date->dt_date_time); /* is not yet checked against bad times! */
|
|
if( message_time != DC_INVALID_TIMESTAMP && message_time > time(NULL) ) {
|
|
message_time = time(NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
autocryptheader = dc_aheader_new_from_imffields(from, imffields);
|
|
if( autocryptheader ) {
|
|
if( !dc_pgp_is_valid_key(context, autocryptheader->public_key) ) {
|
|
dc_aheader_unref(autocryptheader);
|
|
autocryptheader = NULL;
|
|
}
|
|
}
|
|
|
|
/* modify the peerstate (eg. if there is a peer but not autocrypt header, stop encryption) */
|
|
|
|
/* apply Autocrypt:-header */
|
|
if( message_time > 0
|
|
&& from )
|
|
{
|
|
if( dc_apeerstate_load_by_addr(peerstate, context->sql, from) ) {
|
|
if( autocryptheader ) {
|
|
dc_apeerstate_apply_header(peerstate, autocryptheader, message_time);
|
|
dc_apeerstate_save_to_db(peerstate, context->sql, 0/*no not create*/);
|
|
}
|
|
else {
|
|
if( message_time > peerstate->last_seen_autocrypt
|
|
&& !contains_report(in_out_message) /*reports are ususally not encrpyted; do not degrade decryption then*/ ){
|
|
dc_apeerstate_degrade_encryption(peerstate, message_time);
|
|
dc_apeerstate_save_to_db(peerstate, context->sql, 0/*no not create*/);
|
|
}
|
|
}
|
|
}
|
|
else if( autocryptheader ) {
|
|
dc_apeerstate_init_from_header(peerstate, autocryptheader, message_time);
|
|
dc_apeerstate_save_to_db(peerstate, context->sql, 1/*create*/);
|
|
}
|
|
}
|
|
|
|
/* load private key for decryption */
|
|
if( (self_addr=dc_sqlite3_get_config(context->sql, "configured_addr", NULL))==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !dc_keyring_load_self_private_for_decrypting(private_keyring, self_addr, context->sql) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* if not yet done, load peer with public key for verification (should be last as the peer may be modified above) */
|
|
if( peerstate->last_seen == 0 ) {
|
|
dc_apeerstate_load_by_addr(peerstate, context->sql, from);
|
|
}
|
|
|
|
if( peerstate->degrade_event ) {
|
|
dc_handle_degrade_event(context, peerstate);
|
|
}
|
|
|
|
// offer both, gossip and public, for signature validation.
|
|
// the caller may check the signature fingerprints as needed later.
|
|
dc_keyring_add(public_keyring_for_validate, peerstate->gossip_key);
|
|
dc_keyring_add(public_keyring_for_validate, peerstate->public_key);
|
|
|
|
/* finally, decrypt. If sth. was decrypted, decrypt_recursive() returns "true" and we start over to decrypt maybe just added parts. */
|
|
helper->signatures = malloc(sizeof(dc_hash_t));
|
|
dc_hash_init(helper->signatures, DC_HASH_STRING, 1/*copy key*/);
|
|
|
|
int iterations = 0;
|
|
while( iterations < 10 ) {
|
|
int has_unencrypted_parts = 0;
|
|
if( !decrypt_recursive(context, in_out_message, private_keyring,
|
|
public_keyring_for_validate,
|
|
helper->signatures, &gossip_headers, &has_unencrypted_parts) ) {
|
|
break;
|
|
}
|
|
|
|
// if we're here, sth. was encrypted. if we're on top-level, and there are no
|
|
// additional unencrypted parts in the message the encryption was fine
|
|
// (signature is handled separately and returned as `signatures`)
|
|
if( iterations == 0
|
|
&& !has_unencrypted_parts ) {
|
|
helper->encrypted = 1;
|
|
}
|
|
|
|
iterations++;
|
|
}
|
|
|
|
/* check for Autocrypt-Gossip */
|
|
if( gossip_headers ) {
|
|
helper->gossipped_addr = update_gossip_peerstates(context, message_time, imffields, gossip_headers);
|
|
}
|
|
|
|
//mailmime_print(in_out_message);
|
|
|
|
cleanup:
|
|
if( gossip_headers ) { mailimf_fields_free(gossip_headers); }
|
|
dc_aheader_unref(autocryptheader);
|
|
dc_apeerstate_unref(peerstate);
|
|
dc_keyring_unref(private_keyring);
|
|
dc_keyring_unref(public_keyring_for_validate);
|
|
free(from);
|
|
free(self_addr);
|
|
}
|
|
|