A metric buttload of code that I finally need to stop shelving and unshelving - new key reset, with bugs. krista-local-7-Jan-2020
authorKrista 'DarthMama' Bennett <krista@pep.foundation>
Tue, 07 Jan 2020 22:35:39 +0100
branchkrista-local-7-Jan-2020
changeset 43121d1672a6dfb9
parent 4296 9b0161ee85d0
child 4314 0ecfe74a1b7f
A metric buttload of code that I finally need to stop shelving and unshelving - new key reset, with bugs.
src/key_reset.c
src/key_reset.h
src/message.h
src/message_api.c
src/pEpEngine.c
src/pEpEngine.h
src/pEp_internal.h
test/src/KeyResetMessageTest.cc
     1.1 --- a/src/key_reset.c	Tue Jan 07 12:21:43 2020 +0100
     1.2 +++ b/src/key_reset.c	Tue Jan 07 22:35:39 2020 +0100
     1.3 @@ -7,7 +7,10 @@
     1.4  #include "key_reset.h"
     1.5  #include "distribution_codec.h"
     1.6  #include "map_asn1.h"
     1.7 +#include "keymanagement.h"
     1.8 +#include "baseprotocol.h"
     1.9  #include "../asn.1/Distribution.h"
    1.10 +#include "Sync_impl.h" // this seems... bad
    1.11  
    1.12  #include <string.h>
    1.13  #include <stdlib.h>
    1.14 @@ -17,8 +20,90 @@
    1.15  #define KEY_RESET_MAJOR_VERSION 1L
    1.16  #define KEY_RESET_MINOR_VERSION 0L
    1.17  
    1.18 +static PEP_STATUS _generate_keyreset_command_message(PEP_SESSION session,
    1.19 +                                                     const pEp_identity* from_ident,
    1.20 +                                                     const pEp_identity* to_ident,
    1.21 +                                                     const char* old_fpr,
    1.22 +                                                     const char* new_fpr,
    1.23 +                                                     bool is_private,
    1.24 +                                                     message** dst) {
    1.25 +                                                                                                                  
    1.26 +    if (!session || !from_ident || !old_fpr || !new_fpr || !dst)
    1.27 +        return PEP_ILLEGAL_VALUE;
    1.28 +
    1.29 +    // safe cast
    1.30 +    if (!is_me(session, (pEp_identity*)from_ident))
    1.31 +        return PEP_ILLEGAL_VALUE;
    1.32 +
    1.33 +    PEP_STATUS status = PEP_STATUS_OK;
    1.34 +        
    1.35 +    *dst = NULL;
    1.36 +    
    1.37 +    stringlist_t* keydata = NULL;
    1.38 +    
    1.39 +    // Ok, generate payload here...
    1.40 +    pEp_identity* outgoing_ident = identity_dup(from_ident);
    1.41 +    if (!outgoing_ident)
    1.42 +        return PEP_OUT_OF_MEMORY;
    1.43 +    free(outgoing_ident->fpr);
    1.44 +    outgoing_ident->fpr = strdup(old_fpr);
    1.45 +    if (!outgoing_ident->fpr)
    1.46 +        return PEP_OUT_OF_MEMORY;
    1.47 +        
    1.48 +    // Need payload now
    1.49 +    keyreset_command* kr_command = new_keyreset_command(outgoing_ident, new_fpr);
    1.50 +    keyreset_command_list* kr_list = new_keyreset_command_list(kr_command);
    1.51 +    char* payload = NULL;
    1.52 +    size_t size = 0;
    1.53 +    status = key_reset_commands_to_PER(kr_list, &payload, &size);
    1.54 +    message* msg = NULL;
    1.55 +    status = base_prepare_message(session, outgoing_ident, to_ident,
    1.56 +                                  BASE_KEYRESET, payload, size, NULL,
    1.57 +                                  &msg);
    1.58 +    if (status) {
    1.59 +        free(msg);
    1.60 +        return status;
    1.61 +    }    
    1.62 +    if (!msg)
    1.63 +        return PEP_OUT_OF_MEMORY;
    1.64 +    if (!msg->attachments)
    1.65 +        return PEP_UNKNOWN_ERROR;
    1.66 +    
    1.67 +    char* key_material = NULL;
    1.68 +    size_t datasize = 0;
    1.69 +    status = export_key(session, new_fpr, &key_material, &datasize);
    1.70 +
    1.71 +    if (datasize > 0 && key_material) { 
    1.72 +        if (status) {
    1.73 +            free_stringlist(keydata);
    1.74 +            return status;
    1.75 +        }
    1.76 +        stringlist_add(keydata, key_material);
    1.77 +                        
    1.78 +        key_material = NULL;
    1.79 +        datasize = 0;    
    1.80 +        if (is_private) {
    1.81 +            status = export_secret_key(session, new_fpr, &key_material, &datasize);    
    1.82 +            if (status) {
    1.83 +                free_stringlist(keydata);
    1.84 +                return status;
    1.85 +            }
    1.86 +            if (datasize > 0 && key_material)
    1.87 +                stringlist_add(keydata, key_material);
    1.88 +        }    
    1.89 +    }    
    1.90 +    // Attach to message
    1.91 +    stringlist_t* curr_key = keydata;
    1.92 +    for ( ; curr_key && curr_key->value; curr_key = curr_key->next) {
    1.93 +        bloblist_add(msg->attachments, curr_key->value, size, "application/pgp-keys",
    1.94 +                                                              "file://pEpkey.asc");
    1.95 +    }
    1.96 +    return status;
    1.97 +}
    1.98 +
    1.99  PEP_STATUS has_key_reset_been_sent(
   1.100          PEP_SESSION session, 
   1.101 +        const char* from_addr,
   1.102          const char* user_id, 
   1.103          const char* revoked_fpr,
   1.104          bool* contacted)
   1.105 @@ -29,7 +114,7 @@
   1.106      assert(revoked_fpr);
   1.107      assert(!EMPTYSTR(user_id));
   1.108  
   1.109 -    if (!session || !contacted || EMPTYSTR(revoked_fpr) || EMPTYSTR(user_id))
   1.110 +    if (!session || !contacted || EMPTYSTR(from_addr) || EMPTYSTR(revoked_fpr) || EMPTYSTR(user_id))
   1.111          return PEP_ILLEGAL_VALUE;
   1.112      
   1.113      *contacted = false;
   1.114 @@ -46,7 +131,9 @@
   1.115      sqlite3_reset(session->was_id_for_revoke_contacted);
   1.116      sqlite3_bind_text(session->was_id_for_revoke_contacted, 1, revoked_fpr, -1,
   1.117              SQLITE_STATIC);
   1.118 -    sqlite3_bind_text(session->was_id_for_revoke_contacted, 2, user_id, -1,
   1.119 +    sqlite3_bind_text(session->was_id_for_revoke_contacted, 2, from_addr, -1,
   1.120 +            SQLITE_STATIC);        
   1.121 +    sqlite3_bind_text(session->was_id_for_revoke_contacted, 3, user_id, -1,
   1.122              SQLITE_STATIC);        
   1.123      int result = sqlite3_step(session->was_id_for_revoke_contacted);
   1.124      switch (result) {
   1.125 @@ -64,26 +151,26 @@
   1.126      return PEP_STATUS_OK;
   1.127  }
   1.128  
   1.129 -//static const char *sql_set_revoke_contact_as_notified =
   1.130 -//    "insert or replace into revocation_contact_list(fpr, contact_id) values (?1, ?2) ;";
   1.131 -
   1.132  PEP_STATUS set_reset_contact_notified(
   1.133          PEP_SESSION session,
   1.134 +        const char* own_address,
   1.135          const char* revoke_fpr,
   1.136          const char* contact_id
   1.137      )
   1.138  {
   1.139      PEP_STATUS status = PEP_STATUS_OK;
   1.140      
   1.141 -    assert(session && !EMPTYSTR(revoke_fpr) && !EMPTYSTR(contact_id));
   1.142 +    assert(session && !EMPTYSTR(own_address) && !EMPTYSTR(revoke_fpr) && !EMPTYSTR(contact_id));
   1.143      
   1.144 -    if (!session || EMPTYSTR(revoke_fpr) || EMPTYSTR(contact_id))
   1.145 +    if (!session || EMPTYSTR(own_address) || EMPTYSTR(revoke_fpr) || EMPTYSTR(contact_id))
   1.146          return PEP_ILLEGAL_VALUE;
   1.147      
   1.148      sqlite3_reset(session->set_revoke_contact_as_notified);
   1.149      sqlite3_bind_text(session->set_revoke_contact_as_notified, 1, revoke_fpr, -1, 
   1.150              SQLITE_STATIC);
   1.151 -    sqlite3_bind_text(session->set_revoke_contact_as_notified, 2, contact_id, -1,
   1.152 +    sqlite3_bind_text(session->set_revoke_contact_as_notified, 2, own_address, -1, 
   1.153 +            SQLITE_STATIC);            
   1.154 +    sqlite3_bind_text(session->set_revoke_contact_as_notified, 3, contact_id, -1,
   1.155              SQLITE_STATIC);
   1.156  
   1.157      int result;
   1.158 @@ -102,149 +189,266 @@
   1.159      return status;    
   1.160  }
   1.161  
   1.162 -
   1.163 +// FIXME: fpr ownership
   1.164  PEP_STATUS receive_key_reset(PEP_SESSION session,
   1.165                               message* reset_msg) {
   1.166  
   1.167 -    if (!session || !reset_msg)
   1.168 +    if (!session || !reset_msg || !reset_msg->_sender_fpr)
   1.169          return PEP_ILLEGAL_VALUE;
   1.170  
   1.171 +    PEP_STATUS status = PEP_STATUS_OK;
   1.172 +
   1.173 +    stringlist_t* keylist = NULL;
   1.174 +    
   1.175 +    char* sender_fpr = reset_msg->_sender_fpr;
   1.176 +
   1.177 +    bool revoked = false;
   1.178 +
   1.179 +    status = key_revoked(session, sender_fpr, &revoked); 
   1.180 +    
   1.181 +    if (status != PEP_STATUS_OK)
   1.182 +        return status;
   1.183 +
   1.184 +    // Bail if revoked
   1.185 +    if (revoked) {
   1.186 +        return PEP_ILLEGAL_VALUE; // could be an attack            
   1.187 +    }
   1.188 +    // Otherwise, bail
   1.189 +    else {
   1.190 +        bool mistrusted = false;
   1.191 +        status = is_mistrusted_key(session, sender_fpr, &mistrusted);
   1.192 +        
   1.193 +        if (status != PEP_STATUS_OK)
   1.194 +            return status;
   1.195 +        
   1.196 +        if (mistrusted)
   1.197 +            return PEP_ILLEGAL_VALUE;
   1.198 +    }
   1.199 +
   1.200 +    
   1.201 +    // Parse reset message
   1.202 +    
   1.203      pEp_identity* sender_id = reset_msg->from;
   1.204 -                
   1.205 +                            
   1.206      if (!sender_id)
   1.207          return PEP_MALFORMED_KEY_RESET_MSG;
   1.208 +
   1.209 +    if (is_me(session, sender_id)) {
   1.210 +        // first off, we need to make sure we're up-to-date
   1.211 +        status = myself(session, sender_id);        
   1.212 +    }
   1.213 +    else {    
   1.214 +        status = update_identity(session, sender_id);
   1.215 +        if (!sender_id->user_id)
   1.216 +            return PEP_UNKNOWN_ERROR;
   1.217 +    }
   1.218 +    
   1.219 +    bool sender_own_key = false;
   1.220 +    
   1.221 +    if (is_me(session, sender_id)) {
   1.222 +        // Do own-reset-checks
   1.223 +        status = is_own_key(session, sender_fpr, &sender_own_key);
   1.224          
   1.225 -    PEP_STATUS status = update_identity(session, sender_id);
   1.226 -    if (!sender_id->user_id)
   1.227 -        return PEP_UNKNOWN_ERROR;
   1.228 +        if (status != PEP_STATUS_OK)
   1.229 +            return status;
   1.230          
   1.231 -    if (is_me(session, sender_id))
   1.232 -        return PEP_ILLEGAL_VALUE;    
   1.233 +        // Should we mistrust the sender_fpr here??
   1.234 +        if (!sender_own_key) 
   1.235 +            return PEP_ILLEGAL_VALUE; // actually, this is an attack                
   1.236          
   1.237 -    if (!reset_msg->longmsg || strncmp(reset_msg->longmsg, "OLD: ", 5) != 0) 
   1.238 -        return PEP_MALFORMED_KEY_RESET_MSG;
   1.239 -
   1.240 +        // Make sure it's a TRUSTED own key
   1.241 +        char* keyholder = sender_id->fpr;
   1.242 +        
   1.243 +        sender_id->fpr = sender_fpr;                     
   1.244 +        status = get_trust(session, sender_id);
   1.245 +        sender_id->fpr = keyholder;
   1.246 +            
   1.247 +        if (sender_id->comm_type < PEP_ct_pEp)
   1.248 +            return PEP_ILLEGAL_VALUE;
   1.249 +    }
   1.250 +        
   1.251      status = PEP_STATUS_OK;
   1.252      char* old_fpr = NULL;
   1.253      char* new_fpr = NULL;
   1.254      
   1.255 -    stringlist_t* keylist = NULL;
   1.256 -    pEp_identity* temp_ident = identity_dup(sender_id);
   1.257 -    if (!temp_ident) {
   1.258 -        status = PEP_OUT_OF_MEMORY;
   1.259 -        goto pEp_free;
   1.260 -    }        
   1.261 +    size_t size = 0;
   1.262 +    const char* payload = NULL;
   1.263 +
   1.264 +    status = base_extract_message(session,
   1.265 +                                  reset_msg,
   1.266 +                                  BASE_KEYRESET,
   1.267 +                                  &size,
   1.268 +                                  &payload,
   1.269 +                                  NULL);
   1.270 +                                  
   1.271 +    if (status != PEP_STATUS_OK)
   1.272 +        return status;
   1.273 +        
   1.274 +    if (!payload || size == 0)
   1.275 +        return PEP_MALFORMED_KEY_RESET_MSG;
   1.276 +        
   1.277 +    keyreset_command_list* resets = NULL; 
   1.278 +    
   1.279 +    status = PER_to_key_reset_commands(payload, size, &resets);
   1.280 +
   1.281 +    if (status != PEP_STATUS_OK)
   1.282 +        return status;
   1.283 +        
   1.284 +    if (!resets)
   1.285 +        return PEP_MALFORMED_KEY_RESET_MSG;
   1.286 +
   1.287 +    keyreset_command_list* curr_cl = resets;
   1.288 +    
   1.289 +    for ( ; curr_cl && curr_cl->command; curr_cl = curr_cl->next) {    
   1.290 +        keyreset_command* curr_cmd = curr_cl->command;
   1.291 +        if (!curr_cmd || !curr_cmd->ident || !curr_cmd->ident->fpr ||
   1.292 +            !curr_cmd->ident->address) {
   1.293 +            return PEP_MALFORMED_KEY_RESET_MSG;        
   1.294 +        }
   1.295 +        // Make sure that this key is at least one we associate 
   1.296 +        // with the sender. FIXME: check key election interaction
   1.297 +        // N.B. If we ever allow ourselves to send resets to ourselves
   1.298 +        // for not-own stuff, this will have to be revised
   1.299 +        pEp_identity* curr_ident = curr_cmd->ident;
   1.300 +        
   1.301 +        old_fpr = curr_ident->fpr;
   1.302 +        new_fpr = curr_cmd->new_key;
   1.303 +        
   1.304 +        status = find_keys(session, new_fpr, &keylist);
   1.305 +        if (status != PEP_STATUS_OK)
   1.306 +            goto pEp_free;
   1.307 +        if (!keylist) {
   1.308 +            status = PEP_MALFORMED_KEY_RESET_MSG;
   1.309 +            goto pEp_free;
   1.310 +        }
   1.311 +        
   1.312 +        // We need to update the identity to get the user_id
   1.313 +        curr_ident->fpr = NULL; // ensure old_fpr is preserved
   1.314 +        status = update_identity(session, curr_ident);
   1.315 +        
   1.316 +        // temp fpr set for function call
   1.317 +        curr_ident->fpr = (char*)sender_fpr;
   1.318 +        status = get_trust(session, curr_ident);
   1.319 +        
   1.320 +        PEP_comm_type ct_result = curr_ident->comm_type;
   1.321 +        
   1.322 +        // Reset fpr on ident struct in case we bail to fulfill ownership contract
   1.323 +        curr_ident->fpr = old_fpr;    
   1.324 +
   1.325 +        if (status != PEP_STATUS_OK)
   1.326 +            return status;
   1.327 +
   1.328 +        // Basically, see if fpr is even in the database
   1.329 +        // for this user - we'll get PEP_ct_unknown if it isn't
   1.330 +        if (ct_result < PEP_ct_strong_but_unconfirmed)
   1.331 +            return PEP_KEY_NOT_RESET;
   1.332              
   1.333 -    char* rest = NULL;
   1.334 -    char* p = strtok_r(reset_msg->longmsg, "\r\n", &rest);
   1.335 -    if (!EMPTYSTR(p + 5))
   1.336 -        old_fpr = strdup(p + 5);
   1.337 -    else {
   1.338 -        status = PEP_MALFORMED_KEY_RESET_MSG;
   1.339 -        goto pEp_free;
   1.340 -    }
   1.341 +        // Now check the fpr we're trying to change (old_fpr), which we reset a few lines above -
   1.342 +        // again, if it doesn't belong to the user, we won't use it.
   1.343 +        status = get_trust(session, curr_ident);
   1.344 +
   1.345 +        if (status != PEP_STATUS_OK)
   1.346 +            return status;            
   1.347 +
   1.348 +        if (curr_ident->comm_type < PEP_ct_strong_but_unconfirmed)
   1.349 +            return PEP_KEY_NOT_RESET;
   1.350 +            
   1.351 +        // Hooray! We apparently now are dealing with keys 
   1.352 +        // belonging to the user from a message signed by 
   1.353 +        // the user.
   1.354 +        if (!sender_own_key) {
   1.355 +            status = key_reset(session, old_fpr, curr_ident);
   1.356 +            if (status != PEP_STATUS_OK)
   1.357 +                return status;
   1.358 +                                
   1.359 +            // Make new key the default    
   1.360 +            curr_ident->fpr = new_fpr;
   1.361      
   1.362 -    bool own_key = false;
   1.363 -    status = is_own_key(session, old_fpr, &own_key);
   1.364 -    
   1.365 -    if (own_key) {
   1.366 -        // Nope, no one can make us our own default. If we want to do that,
   1.367 -        // that's keysync, NOT key reset.
   1.368 -        status = PEP_ILLEGAL_VALUE;
   1.369 -        goto pEp_free;
   1.370 -    }
   1.371 +            // This only sets as the default, does NOT TRUST IN ANY WAY
   1.372 +            curr_ident->comm_type = curr_ident->comm_type & (~PEP_ct_confirmed);
   1.373 +            status = set_identity(session, curr_ident);  
   1.374 +            if (status != PEP_STATUS_OK)
   1.375 +                goto pEp_free; 
   1.376 +        }    
   1.377 +        else {
   1.378 +            // set new key as the default for this identity
   1.379 +            // N.B. If for some reason this is only a pubkey,
   1.380 +            // then so be it - but we need to double-check to 
   1.381 +            // ensure that in this case, we end up with a private one,
   1.382 +            // so talk to vb about this.
   1.383 +            // Make new key the default    
   1.384 +            curr_ident->fpr = new_fpr;
   1.385 +            status = set_own_key(session, curr_ident, new_fpr);
   1.386              
   1.387 -    p = strtok_r(NULL, "\r\n", &rest); 
   1.388 -    if (strncmp(p, "NEW: ", 5) != 0  || EMPTYSTR(p + 5)) {
   1.389 -        status = PEP_MALFORMED_KEY_RESET_MSG;
   1.390 -        goto pEp_free;
   1.391 +            if (status != PEP_STATUS_OK)
   1.392 +                return status;
   1.393 +                
   1.394 +            // key reset on old key
   1.395 +            status = key_reset(session, old_fpr, curr_ident);
   1.396 +            if (status != PEP_STATUS_OK)
   1.397 +                return status;            
   1.398 +        }
   1.399 +        old_fpr = NULL;
   1.400 +        new_fpr = NULL;    
   1.401      }
   1.402  
   1.403 -    new_fpr = strdup(p + 5);
   1.404 -        
   1.405 -    // Reset the original key
   1.406 -    status = key_reset(session, old_fpr, temp_ident, NULL, NULL);
   1.407 -    if (status != PEP_STATUS_OK)
   1.408 -        goto pEp_free;
   1.409 -        
   1.410 -    status = find_keys(session, new_fpr, &keylist);
   1.411 -    if (status != PEP_STATUS_OK)
   1.412 -        goto pEp_free;
   1.413 -        
   1.414 -    if (!keylist) {
   1.415 -        status = PEP_KEY_NOT_FOUND;
   1.416 -        goto pEp_free;
   1.417 -    }
   1.418 -
   1.419 -    // alright, we've checked as best we can. Let's set that baby.
   1.420 -    sender_id->fpr = new_fpr;
   1.421 -    
   1.422 -    // This only sets as the default, does NOT TRUST IN ANY WAY
   1.423 -    sender_id->comm_type = sender_id->comm_type & (~PEP_ct_confirmed);
   1.424 -    status = set_identity(session, sender_id);
   1.425 -    
   1.426 -    sender_id->fpr = NULL; // ownership for free
   1.427  pEp_free:    
   1.428      free_stringlist(keylist);    
   1.429      free(old_fpr);
   1.430      free(new_fpr);
   1.431 -    free_identity(temp_ident);
   1.432      return status;
   1.433  }
   1.434  
   1.435  PEP_STATUS create_standalone_key_reset_message(PEP_SESSION session,
   1.436                                                 message** dst, 
   1.437 +                                               pEp_identity* own_identity,
   1.438                                                 pEp_identity* recip,
   1.439                                                 const char* old_fpr,
   1.440                                                 const char* new_fpr) {
   1.441                                                     
   1.442 -    if (!dst || !recip->user_id || !recip->address)
   1.443 +    if (!dst || !own_identity || EMPTYSTR(own_identity->address) 
   1.444 +             || !recip || EMPTYSTR(recip->user_id) 
   1.445 +             || EMPTYSTR(recip->address))
   1.446          return PEP_ILLEGAL_VALUE;
   1.447  
   1.448 -    if (!old_fpr || !new_fpr)
   1.449 +    if (EMPTYSTR(old_fpr) || EMPTYSTR(new_fpr))
   1.450          return PEP_ILLEGAL_VALUE;
   1.451          
   1.452      *dst = NULL;
   1.453 -    // Get own identity user has corresponded with
   1.454 -    pEp_identity* own_identity = NULL;
   1.455      
   1.456 -    PEP_STATUS status = get_own_ident_for_contact_id(session,
   1.457 -                                                     recip,
   1.458 -                                                     &own_identity);                                                       
   1.459 +    message* reset_msg = NULL;
   1.460 +    
   1.461 +    PEP_STATUS status = _generate_keyreset_command_message(session, own_identity,
   1.462 +                                                           recip,
   1.463 +                                                           old_fpr, new_fpr, false,
   1.464 +                                                           &reset_msg);
   1.465 +                            
   1.466      if (status != PEP_STATUS_OK)
   1.467 -        return status;
   1.468 -        
   1.469 -    message* reset_message = new_message(PEP_dir_outgoing);
   1.470 -    reset_message->from = own_identity;
   1.471 -    reset_message->to = new_identity_list(identity_dup(recip)); // ?
   1.472 +        goto pEp_free;
   1.473      
   1.474 -    const char* oldtag = "OLD: ";
   1.475 -    const char* newtag = "\nNEW: ";
   1.476 -    const size_t taglens = 11;
   1.477 -    size_t full_len = taglens + strlen(old_fpr) + strlen(new_fpr) + 2; // \n and \0
   1.478 -    char* longmsg = calloc(full_len, 1);
   1.479 -    strlcpy(longmsg, oldtag, full_len);
   1.480 -    strlcat(longmsg, old_fpr, full_len);
   1.481 -    strlcat(longmsg, newtag, full_len);
   1.482 -    strlcat(longmsg, new_fpr, full_len);
   1.483 -    strlcat(longmsg, "\n", full_len);
   1.484 -    reset_message->longmsg = longmsg; 
   1.485 -    reset_message->shortmsg = strdup("Key reset");    
   1.486 +    if (!reset_msg)
   1.487 +        return PEP_ILLEGAL_VALUE;
   1.488 +                                                                         
   1.489 +    if (!reset_msg->attachments)
   1.490 +        return PEP_UNKNOWN_ERROR;
   1.491      
   1.492      message* output_msg = NULL;
   1.493      
   1.494 -    status = encrypt_message(session, reset_message, NULL,
   1.495 +    status = encrypt_message(session, reset_msg, NULL,
   1.496                               &output_msg, PEP_enc_PGP_MIME,
   1.497                               PEP_encrypt_flag_key_reset_only);
   1.498  
   1.499      if (status == PEP_STATUS_OK)
   1.500          *dst = output_msg;
   1.501          
   1.502 -    free_message(reset_message);
   1.503 +pEp_free:
   1.504 +        
   1.505 +    free_message(reset_msg);    
   1.506      return status;
   1.507  }
   1.508  
   1.509  PEP_STATUS send_key_reset_to_recents(PEP_SESSION session,
   1.510 +                                     pEp_identity* from_ident,
   1.511                                       const char* old_fpr, 
   1.512                                       const char* new_fpr) {
   1.513      assert(old_fpr);
   1.514 @@ -287,18 +491,27 @@
   1.515              
   1.516          // Check if they've already been told - this shouldn't be the case, but...
   1.517          bool contacted = false;
   1.518 -        status = has_key_reset_been_sent(session, user_id, old_fpr, &contacted);
   1.519 +        status = has_key_reset_been_sent(session, from_ident->address, user_id, old_fpr, &contacted);
   1.520          if (status != PEP_STATUS_OK)
   1.521              goto pEp_free;
   1.522      
   1.523          if (contacted)
   1.524              continue;
   1.525              
   1.526 +        // Make sure they've ever *contacted* this address    
   1.527 +        bool in_contact_w_this_address = false;
   1.528 +        status = has_partner_contacted_address(session, from_ident->address, curr_id->user_id, 
   1.529 +                                               &in_contact_w_this_address);
   1.530 +        
   1.531 +        if (!in_contact_w_this_address)
   1.532 +            continue;
   1.533 +            
   1.534          // if not, make em a message    
   1.535          reset_msg = NULL;
   1.536          
   1.537          status = create_standalone_key_reset_message(session,
   1.538                                                       &reset_msg,
   1.539 +                                                     from_ident,
   1.540                                                       curr_id,
   1.541                                                       old_fpr,
   1.542                                                       new_fpr);
   1.543 @@ -322,7 +535,7 @@
   1.544          }
   1.545              
   1.546          // Put into notified DB
   1.547 -        status = set_reset_contact_notified(session, old_fpr, user_id);
   1.548 +        status = set_reset_contact_notified(session, from_ident->address, old_fpr, user_id);
   1.549          if (status != PEP_STATUS_OK)
   1.550              goto pEp_free;            
   1.551      }
   1.552 @@ -341,7 +554,7 @@
   1.553      if (!session || !ident || (ident && (EMPTYSTR(ident->user_id) || EMPTYSTR(ident->address))))
   1.554          return PEP_ILLEGAL_VALUE;
   1.555      
   1.556 -    return key_reset(session, fpr, ident, NULL, NULL);    
   1.557 +    return key_reset(session, fpr, ident);    
   1.558  }
   1.559  
   1.560  DYNAMIC_API PEP_STATUS key_reset_user(
   1.561 @@ -360,13 +573,121 @@
   1.562      if (is_me(session, input_ident) && EMPTYSTR(fpr))
   1.563          return PEP_ILLEGAL_VALUE;
   1.564          
   1.565 -    PEP_STATUS status = key_reset(session, fpr, input_ident, NULL, NULL);
   1.566 +    PEP_STATUS status = key_reset(session, fpr, input_ident);
   1.567      free_identity(input_ident);
   1.568      return status;
   1.569  }
   1.570  
   1.571  DYNAMIC_API PEP_STATUS key_reset_all_own_keys(PEP_SESSION session) {
   1.572 -    return key_reset(session, NULL, NULL, NULL, NULL);
   1.573 +    return key_reset(session, NULL, NULL);
   1.574 +}
   1.575 +
   1.576 +
   1.577 +static PEP_STATUS _key_reset_device_group_for_shared_key(PEP_SESSION session, 
   1.578 +                                                         identity_list* key_idents, 
   1.579 +                                                         const char* old_key) {
   1.580 +    assert(session);
   1.581 +    assert(key_idents);
   1.582 +    assert(old_key);
   1.583 +    
   1.584 +    if (!session || !key_idents || EMPTYSTR(old_key))
   1.585 +        return PEP_ILLEGAL_VALUE;
   1.586 +        
   1.587 +    messageToSend_t send_cb = session->messageToSend;
   1.588 +    if (!send_cb)
   1.589 +        return PEP_SYNC_NO_MESSAGE_SEND_CALLBACK;
   1.590 +        
   1.591 +    PEP_STATUS status = PEP_STATUS_OK;
   1.592 +        
   1.593 +    // each of these has the same key and needs a new one.
   1.594 +    identity_list* curr_ident;
   1.595 +    for (curr_ident = key_idents; curr_ident && curr_ident->ident; curr_ident = curr_ident->next) {
   1.596 +        if (curr_ident->ident->flags & PEP_idf_devicegroup) {
   1.597 +            pEp_identity* ident = curr_ident->ident;
   1.598 +            free(ident->fpr);
   1.599 +            ident->fpr = NULL;
   1.600 +            status = generate_keypair(session, ident);
   1.601 +            if (status != PEP_STATUS_OK)
   1.602 +                return status;
   1.603 +                
   1.604 +        }
   1.605 +        else {
   1.606 +            status = key_reset(session, old_key, curr_ident->ident); 
   1.607 +        }        
   1.608 +    }
   1.609 +        
   1.610 +    // Ok, everyone's got a new keypair. Hoorah! 
   1.611 +    // generate, sign, and push messages into queue
   1.612 +    for (curr_ident = key_idents; curr_ident && curr_ident->ident; curr_ident = curr_ident->next) {
   1.613 +        if (curr_ident->ident->flags & PEP_idf_devicegroup) {
   1.614 +            message* outmsg = NULL;
   1.615 +            
   1.616 +            // FIXME - do we have to? Maybe check IN the funct
   1.617 +            pEp_identity* to_me = identity_dup(curr_ident->ident);
   1.618 +            if (!to_me)
   1.619 +                return PEP_OUT_OF_MEMORY;
   1.620 +                
   1.621 +            status = _generate_keyreset_command_message(session, curr_ident->ident, to_me,
   1.622 +                                                        old_key, curr_ident->ident->fpr,
   1.623 +                                                        true, &outmsg);
   1.624 +            
   1.625 +            message* enc_msg = NULL;
   1.626 +            
   1.627 +            // encrypt this baby and get out
   1.628 +            // extra keys???
   1.629 +            status = encrypt_message(session, outmsg, NULL, &enc_msg, PEP_enc_PGP_MIME, 0);
   1.630 +            
   1.631 +            if (status != PEP_STATUS_OK) {
   1.632 +                goto pEp_free;
   1.633 +            }
   1.634 +            
   1.635 +            // insert into queue
   1.636 +            status = send_cb(enc_msg);
   1.637 +
   1.638 +            if (status != PEP_STATUS_OK) {
   1.639 +                free(enc_msg);
   1.640 +                goto pEp_free;            
   1.641 +            }                         
   1.642 +            // Does there need to be some kind of signal here for sync?                                               
   1.643 +        }    
   1.644 +    }
   1.645 +    
   1.646 +    // Ok, we've signed everything we need to with the old key,
   1.647 +    // Revoke that baby.
   1.648 +    status = revoke_key(session, old_key, NULL);
   1.649 +
   1.650 +    if (!status)
   1.651 +        goto pEp_free;
   1.652 +        
   1.653 +    for (curr_ident = key_idents; curr_ident && curr_ident->ident; curr_ident = curr_ident->next) {
   1.654 +        if (curr_ident->ident->flags & PEP_idf_devicegroup) {
   1.655 +            pEp_identity* ident = curr_ident->ident;
   1.656 +            
   1.657 +            // N.B. This sort of sucks because we overwrite this every time.
   1.658 +            // But this case is infrequent and we don't rely on the binding.
   1.659 +            if (status == PEP_STATUS_OK) 
   1.660 +                status = set_revoked(session, old_key, curr_ident->ident->fpr, time(NULL));            
   1.661 +
   1.662 +            if (!status)
   1.663 +                goto pEp_free;
   1.664 +                
   1.665 +            pEp_identity* tmp_ident = identity_dup(ident);
   1.666 +            if (!tmp_ident) {
   1.667 +                status = PEP_OUT_OF_MEMORY;
   1.668 +                goto pEp_free;
   1.669 +            }    
   1.670 +            free(tmp_ident->fpr);    
   1.671 +            
   1.672 +            // for all active communication partners:
   1.673 +            //      active_send revocation            
   1.674 +            tmp_ident->fpr = strdup(old_key); // freed in free_identity
   1.675 +            if (status == PEP_STATUS_OK)
   1.676 +                status = send_key_reset_to_recents(session, tmp_ident, old_key, ident->fpr);        
   1.677 +            free_identity(tmp_ident);
   1.678 +        }    
   1.679 +    }    
   1.680 +pEp_free:
   1.681 +    return status;
   1.682  }
   1.683  
   1.684  // Notes to integrate into header:
   1.685 @@ -374,9 +695,7 @@
   1.686  PEP_STATUS key_reset(
   1.687          PEP_SESSION session,
   1.688          const char* key_id,
   1.689 -        pEp_identity* ident,
   1.690 -        identity_list** own_identities,
   1.691 -        stringlist_t** own_revoked_fprs
   1.692 +        pEp_identity* ident
   1.693      )
   1.694  {
   1.695      if (!session || (ident && EMPTYSTR(ident->user_id)))
   1.696 @@ -426,7 +745,7 @@
   1.697              
   1.698              for (curr_key = keys; curr_key && curr_key->value; curr_key = curr_key->next) {
   1.699                  // FIXME: Is the ident really necessary?
   1.700 -                status = key_reset(session, curr_key->value, tmp_ident, own_identities, own_revoked_fprs);
   1.701 +                status = key_reset(session, curr_key->value, tmp_ident);
   1.702                  if (status != PEP_STATUS_OK)
   1.703                      break;
   1.704              }
   1.705 @@ -500,20 +819,21 @@
   1.706                      if (status == PEP_STATUS_OK) {
   1.707                          // now have ident list, or should
   1.708                          identity_list* curr_ident;
   1.709 -                        
   1.710 -                        for (curr_ident = key_idents; curr_ident && curr_ident->ident; 
   1.711 -                                                        curr_ident = curr_ident->next) {
   1.712 -                            
   1.713 -                            pEp_identity* this_identity = curr_ident->ident;
   1.714 -                            // Do the full reset on this identity        
   1.715 -                            status = key_reset(session, fpr_copy, this_identity, own_identities, own_revoked_fprs);
   1.716 -                            
   1.717 -                            // Ident list gets freed below, do not free here!
   1.718  
   1.719 -                            if (status != PEP_STATUS_OK)
   1.720 -                                break;
   1.721 -                            
   1.722 +                        bool is_grouped = false;
   1.723 +                        if (!deviceGrouped(session, &is_grouped)) {
   1.724 +                            for (curr_ident = key_idents; curr_ident && curr_ident->ident; 
   1.725 +                                                            curr_ident = curr_ident->next) {
   1.726 +                                
   1.727 +                                pEp_identity* this_identity = curr_ident->ident;
   1.728 +                                // Do the full reset on this identity        
   1.729 +                                status = key_reset(session, fpr_copy, this_identity);
   1.730 +                                
   1.731 +                                // Ident list gets freed below, do not free here!
   1.732 +                            }
   1.733                          }
   1.734 +                        else {
   1.735 +                            status = _key_reset_device_group_for_shared_key(session, key_idents, fpr_copy);                        }    
   1.736                      }
   1.737                      // Ok, we've either now reset for each own identity with this key, or 
   1.738                      // we got an error and want to bail anyway.
   1.739 @@ -521,8 +841,14 @@
   1.740                  }    
   1.741              }
   1.742              
   1.743 +            if (EMPTYSTR(tmp_ident->address)) {
   1.744 +                return PEP_UNKNOWN_ERROR; // FIXME - what case IS this?
   1.745 +            }
   1.746 +            
   1.747 +            // Ok, first we generate a new key.
   1.748 +            
   1.749              // Base case for is_own_private starts here
   1.750 -            
   1.751 +            // tmp ident is an actual identity now (not just a skeleton?)
   1.752              status = revoke_key(session, fpr_copy, NULL);
   1.753              
   1.754              // If we have a full identity, we have some cleanup and generation tasks here
   1.755 @@ -532,23 +858,9 @@
   1.756                      tmp_ident->fpr = NULL;
   1.757                      status = myself(session, tmp_ident);
   1.758                  }
   1.759 -                if (status == PEP_STATUS_OK && tmp_ident->fpr && strcmp(fpr_copy, tmp_ident->fpr) != 0) {
   1.760 +                if (status == PEP_STATUS_OK && tmp_ident->fpr && strcmp(fpr_copy, tmp_ident->fpr) != 0)
   1.761                      new_key = strdup(tmp_ident->fpr);
   1.762 -//                    status = set_own_key(session, tmp_ident, new_key);
   1.763 -                }
   1.764 -
   1.765 -                if (own_revoked_fprs) {
   1.766 -                    // We can dedup this later
   1.767 -                    if (!(*own_revoked_fprs))
   1.768 -                        *own_revoked_fprs = new_stringlist(NULL);
   1.769 -                    
   1.770 -                    char* revkey = strdup(fpr_copy);
   1.771 -                    if (!revkey) {
   1.772 -                        status = PEP_OUT_OF_MEMORY;
   1.773 -                        goto pEp_free;
   1.774 -                    }
   1.775 -                    stringlist_add(*own_revoked_fprs, revkey);                
   1.776 -                }
   1.777 +                // Error handling?    
   1.778                  
   1.779                  // mistrust fpr from trust
   1.780                  tmp_ident->fpr = fpr_copy;
   1.781 @@ -562,18 +874,6 @@
   1.782                      // Update fpr for outgoing
   1.783                      status = myself(session, tmp_ident);
   1.784                  }
   1.785 -                
   1.786 -                if (status == PEP_STATUS_OK && own_identities) {
   1.787 -                    if (!(*own_identities))
   1.788 -                        *own_identities = new_identity_list(NULL);
   1.789 -                    
   1.790 -                    pEp_identity* new_ident = identity_dup(tmp_ident);
   1.791 -                    if (!new_ident) {
   1.792 -                        status = PEP_OUT_OF_MEMORY;
   1.793 -                        goto pEp_free;
   1.794 -                    }
   1.795 -                    identity_list_add(*own_identities, new_ident);            
   1.796 -                }    
   1.797              }    
   1.798              
   1.799              if (status == PEP_STATUS_OK)
   1.800 @@ -594,8 +894,11 @@
   1.801                      status = set_revoked(session, fpr_copy, new_key, time(NULL));            
   1.802                  // for all active communication partners:
   1.803                  //      active_send revocation
   1.804 +                
   1.805 +                tmp_ident->fpr = fpr_copy;
   1.806                  if (status == PEP_STATUS_OK)
   1.807 -                    status = send_key_reset_to_recents(session, fpr_copy, new_key);        
   1.808 +                    status = send_key_reset_to_recents(session, tmp_ident, fpr_copy, new_key);        
   1.809 +                tmp_ident->fpr = NULL;    
   1.810              }        
   1.811          } // end is_own_private
   1.812          else {
   1.813 @@ -642,6 +945,7 @@
   1.814      return status;
   1.815  }
   1.816  
   1.817 +/*
   1.818  static stringlist_t* collect_key_material(PEP_SESSION session, stringlist_t* fprs) {
   1.819      stringlist_t* keydata = NULL;    
   1.820      stringlist_t* curr_fpr = fprs;    
   1.821 @@ -702,6 +1006,17 @@
   1.822                  }
   1.823                  if (datasize > 0 && key_material)
   1.824                      stringlist_add(keydata, key_material);
   1.825 +                    
   1.826 +                key_material = NULL;
   1.827 +                datasize = 0;    
   1.828 +                status = export_private_keys(session, curr_ident->ident->fpr, &key_material, &datasize);    
   1.829 +                if (status) {
   1.830 +                    free_stringlist(keydata);
   1.831 +                    return status;
   1.832 +                }
   1.833 +                if (datasize > 0 && key_material)
   1.834 +                    stringlist_add(keydata, key_material);
   1.835 +
   1.836              }
   1.837              curr_ident = curr_ident->next;
   1.838          }
   1.839 @@ -713,7 +1028,7 @@
   1.840      free(revoked_fprs);
   1.841      return PEP_STATUS_OK;
   1.842  }
   1.843 -
   1.844 +*/
   1.845  Distribution_t *Distribution_from_keyreset_command_list(
   1.846          const keyreset_command_list *command_list,
   1.847          Distribution_t *dist
   1.848 @@ -778,6 +1093,7 @@
   1.849      return NULL;
   1.850  }
   1.851  
   1.852 +
   1.853  PEP_STATUS key_reset_commands_to_PER(const keyreset_command_list *command_list, char **cmds, size_t *size)
   1.854  {
   1.855      PEP_STATUS status = PEP_STATUS_OK;
   1.856 @@ -908,4 +1224,3 @@
   1.857      ASN_STRUCT_FREE(asn_DEF_Distribution, dist);
   1.858      return status;
   1.859  }
   1.860 -
     2.1 --- a/src/key_reset.h	Tue Jan 07 12:21:43 2020 +0100
     2.2 +++ b/src/key_reset.h	Tue Jan 07 22:35:39 2020 +0100
     2.3 @@ -114,11 +114,6 @@
     2.4  //                              if NULL and fpr is non-NULL, we'll reset the key for all
     2.5  //                              associated identities. If both ident and fpr are NULL, see 
     2.6  //                              the fpr arg documentation.
     2.7 -//      own_identities (out)    IF this is resetting own identities, passing in a pointer here 
     2.8 -//                              will cause key_reset to return a list of own_identities featuring 
     2.9 -//                              the new keys
    2.10 -//      own_revoked_fprs (out)  IF this is resetting own identities, passing in a pointer here 
    2.11 -//                              will cause key_reset to send a list of the revoked fprs
    2.12  //
    2.13  //      Note: ident->fpr is always ignored
    2.14  //
    2.15 @@ -128,26 +123,26 @@
    2.16  PEP_STATUS key_reset(
    2.17          PEP_SESSION session,
    2.18          const char* fpr,
    2.19 -        pEp_identity* ident,
    2.20 -        identity_list** own_identities,
    2.21 -        stringlist_t** own_revoked_fprs
    2.22 +        pEp_identity* ident
    2.23      );
    2.24  
    2.25 -
    2.26 +/*
    2.27  PEP_STATUS key_reset_own_and_deliver_revocations(PEP_SESSION session, 
    2.28                                                   identity_list** own_identities, 
    2.29                                                   stringlist_t** revocations, 
    2.30                                                   stringlist_t** keys);
    2.31 -
    2.32 +*/
    2.33  
    2.34  PEP_STATUS has_key_reset_been_sent(
    2.35          PEP_SESSION session, 
    2.36 +        const char* from_addr,
    2.37          const char* user_id, 
    2.38          const char* revoked_fpr,
    2.39          bool* contacted);
    2.40  
    2.41  PEP_STATUS set_reset_contact_notified(
    2.42          PEP_SESSION session,
    2.43 +        const char* own_address,
    2.44          const char* revoke_fpr,
    2.45          const char* contact_id
    2.46      );
    2.47 @@ -157,11 +152,14 @@
    2.48  
    2.49  PEP_STATUS create_standalone_key_reset_message(PEP_SESSION session,
    2.50                                                 message** dst, 
    2.51 +                                               pEp_identity* own_identity,
    2.52                                                 pEp_identity* recip,
    2.53                                                 const char* old_fpr,
    2.54                                                 const char* new_fpr);
    2.55 +
    2.56                                                 
    2.57  PEP_STATUS send_key_reset_to_recents(PEP_SESSION session,
    2.58 +                                     pEp_identity* from_ident,
    2.59                                       const char* old_fpr, 
    2.60                                       const char* new_fpr);
    2.61   
     3.1 --- a/src/message.h	Tue Jan 07 12:21:43 2020 +0100
     3.2 +++ b/src/message.h	Tue Jan 07 22:35:39 2020 +0100
     3.3 @@ -80,7 +80,6 @@
     3.4      struct _message_ref_list *next;
     3.5  } message_ref_list;
     3.6  
     3.7 -
     3.8  // new_message() - allocate new message
     3.9  //
    3.10  //  parameters:
     4.1 --- a/src/message_api.c	Tue Jan 07 12:21:43 2020 +0100
     4.2 +++ b/src/message_api.c	Tue Jan 07 22:35:39 2020 +0100
     4.3 @@ -3395,6 +3395,65 @@
     4.4          && keylist->value[0];
     4.5  }
     4.6  
     4.7 +// practically speaking, only useful to get user_id/address intersection
     4.8 +// we presume no dups in the first list if you're looking for
     4.9 +// a unique result.
    4.10 +static PEP_STATUS ident_list_intersect(identity_list* list_a, 
    4.11 +                                       identity_list* list_b,
    4.12 +                                       identity_list** intersection) {
    4.13 +
    4.14 +    if (!intersection)
    4.15 +        return PEP_ILLEGAL_VALUE;
    4.16 +                                           
    4.17 +    *intersection = NULL;                                       
    4.18 +    if (!list_a || !list_b || !list_a->ident || !list_b->ident)
    4.19 +        return PEP_STATUS_OK;
    4.20 +                
    4.21 +        
    4.22 +    *intersection = NULL;
    4.23 +    
    4.24 +    identity_list* isect = NULL;
    4.25 +    
    4.26 +    identity_list* curr_a = list_a;
    4.27 +    for ( ; curr_a && curr_a->ident; curr_a = curr_a->next) {
    4.28 +        pEp_identity* id_a = curr_a->ident;
    4.29 +        if (EMPTYSTR(id_a->user_id) || EMPTYSTR(id_a->address))
    4.30 +            continue;
    4.31 +
    4.32 +        identity_list* curr_b = list_b;
    4.33 +        for ( ; curr_b && curr_b->ident; curr_b = curr_b->next) {
    4.34 +            pEp_identity* id_b = curr_b->ident;
    4.35 +            if (EMPTYSTR(id_b->user_id) || EMPTYSTR(id_b->address))
    4.36 +                continue;
    4.37 +                
    4.38 +            if (strcmp(id_a->user_id, id_b->user_id) == 0 &&
    4.39 +                strcmp(id_a->address, id_b->address) == 0) {
    4.40 +                pEp_identity* result_id = identity_dup(id_b);
    4.41 +                if (!id_b) 
    4.42 +                    goto enomem;
    4.43 +                    
    4.44 +                if (!isect) {
    4.45 +                    isect = new_identity_list(result_id);
    4.46 +                    if (!isect) 
    4.47 +                        goto enomem;
    4.48 +                }    
    4.49 +                else {
    4.50 +                    if (!identity_list_add(isect, result_id))
    4.51 +                        goto enomem;
    4.52 +                }   
    4.53 +                break;  
    4.54 +            }
    4.55 +        }
    4.56 +    }
    4.57 +    *intersection = isect;
    4.58 +    return PEP_STATUS_OK;
    4.59 +
    4.60 +enomem:
    4.61 +    free_identity_list(isect);
    4.62 +    return PEP_OUT_OF_MEMORY;
    4.63 +    
    4.64 +}
    4.65 +
    4.66  static PEP_STATUS _decrypt_message(
    4.67          PEP_SESSION session,
    4.68          message *src,
    4.69 @@ -3924,42 +3983,73 @@
    4.70                      continue; // Again, shouldn't occur
    4.71  
    4.72                  if (curr_pair->key && curr_pair->value) {
    4.73 -                    status = create_standalone_key_reset_message(session,
    4.74 -                        &reset_msg,
    4.75 -                        msg->from,
    4.76 -                        curr_pair->key,
    4.77 -                        curr_pair->value);
    4.78 -
    4.79 -                    // If we can't find the identity, this is someone we've never mailed, so we just
    4.80 -                    // go on letting them use the wrong key until we mail them ourselves. (Spammers, etc)
    4.81 -                    if (status != PEP_CANNOT_FIND_IDENTITY) {
    4.82 +                    /* Figure out which address(es) this came to so we know who to reply from */                    
    4.83 +
    4.84 +                    identity_list* my_rev_ids = NULL;
    4.85 +                    
    4.86 +                    /* check by replacement ID for identities which used this key? */
    4.87 +                    status = get_identities_by_main_key_id(session, curr_pair->value,
    4.88 +                                                           &my_rev_ids);
    4.89 +                                                                                                                      
    4.90 +                    if (status == PEP_STATUS_OK && my_rev_ids) {
    4.91 +                        // get identities in this list the message was to/cc'd to (not for bcc)
    4.92 +                        identity_list* used_ids_for_key = NULL;
    4.93 +                        status = ident_list_intersect(my_rev_ids, msg->to, &used_ids_for_key);
    4.94 +                        if (status != PEP_STATUS_OK)
    4.95 +                            goto pEp_error; // out of memory
    4.96 +
    4.97 +                        identity_list* used_cc_ids = NULL;    
    4.98 +                        status = ident_list_intersect(my_rev_ids, msg->to, &used_cc_ids);
    4.99                          if (status != PEP_STATUS_OK)
   4.100                              goto pEp_error;
   4.101  
   4.102 -                        if (!reset_msg) {
   4.103 -                            status = PEP_OUT_OF_MEMORY;
   4.104 -                            goto pEp_error;
   4.105 -                        }
   4.106 -                        // insert into queue
   4.107 -                        if (session->messageToSend)
   4.108 -                            status = session->messageToSend(reset_msg);
   4.109 -                        else
   4.110 -                            status = PEP_SYNC_NO_MESSAGE_SEND_CALLBACK;
   4.111 -
   4.112 -
   4.113 -                        if (status == PEP_STATUS_OK) {
   4.114 -                            // Put into notified DB
   4.115 -                            status = set_reset_contact_notified(session, curr_pair->key, msg->from->user_id);
   4.116 -                            if (status != PEP_STATUS_OK) // It's ok to barf because it's a DB problem??
   4.117 -                                goto pEp_error;
   4.118 -                        }
   4.119 -                        else {
   4.120 -                            // According to Volker, this would only be a fatal error, so...
   4.121 -                            free_message(reset_msg); // ??
   4.122 -                            reset_msg = NULL; // ??
   4.123 -                            goto pEp_error;
   4.124 -                        }
   4.125 -                    }
   4.126 +                        used_ids_for_key = identity_list_join(used_ids_for_key, used_cc_ids);
   4.127 +                        
   4.128 +                        identity_list* curr_recip = used_ids_for_key;
   4.129 +                        
   4.130 +                        for ( ; curr_recip && curr_recip->ident; curr_recip = curr_recip->next) {
   4.131 +                            if (!is_me(session, curr_recip->ident))
   4.132 +                                continue;
   4.133 +                        
   4.134 +                            status = create_standalone_key_reset_message(session,
   4.135 +                                &reset_msg,
   4.136 +                                curr_recip->ident,
   4.137 +                                msg->from,
   4.138 +                                curr_pair->key,
   4.139 +                                curr_pair->value);
   4.140 +
   4.141 +                            // If we can't find the identity, this is someone we've never mailed, so we just
   4.142 +                            // go on letting them use the wrong key until we mail them ourselves. (Spammers, etc)
   4.143 +                            if (status != PEP_CANNOT_FIND_IDENTITY) {
   4.144 +                                if (status != PEP_STATUS_OK)
   4.145 +                                    goto pEp_error;
   4.146 +
   4.147 +                                if (!reset_msg) {
   4.148 +                                    status = PEP_OUT_OF_MEMORY;
   4.149 +                                    goto pEp_error;
   4.150 +                                }
   4.151 +                                // insert into queue
   4.152 +                                if (session->messageToSend)
   4.153 +                                    status = session->messageToSend(reset_msg);
   4.154 +                                else
   4.155 +                                    status = PEP_SYNC_NO_MESSAGE_SEND_CALLBACK;
   4.156 +
   4.157 +
   4.158 +                                if (status == PEP_STATUS_OK) {
   4.159 +                                    // Put into notified DB
   4.160 +                                    status = set_reset_contact_notified(session, curr_recip->ident->address, curr_pair->key, msg->from->user_id);
   4.161 +                                    if (status != PEP_STATUS_OK) // It's ok to barf because it's a DB problem??
   4.162 +                                        goto pEp_error;
   4.163 +                                }
   4.164 +                                else {
   4.165 +                                    // According to Volker, this would only be a fatal error, so...
   4.166 +                                    free_message(reset_msg); // ??
   4.167 +                                    reset_msg = NULL; // ??
   4.168 +                                    goto pEp_error;
   4.169 +                                }
   4.170 +                            }
   4.171 +                        }    
   4.172 +                    } // else we couldn't find an ident for replacement key    
   4.173                  }
   4.174              }
   4.175          }
     5.1 --- a/src/pEpEngine.c	Tue Jan 07 12:21:43 2020 +0100
     5.2 +++ b/src/pEpEngine.c	Tue Jan 07 22:35:39 2020 +0100
     5.3 @@ -503,19 +503,22 @@
     5.4      "select own_address from social_graph where own_userid = ?1 and contact_userid = ?2 ;";
     5.5  
     5.6  static const char *sql_set_revoke_contact_as_notified =
     5.7 -    "insert or replace into revocation_contact_list(fpr, contact_id) values (?1, ?2) ;";
     5.8 +    "insert or replace into revocation_contact_list(fpr, own_address, contact_id) values (?1, ?2, ?3) ;";
     5.9      
    5.10  static const char *sql_get_contacted_ids_from_revoke_fpr =
    5.11      "select * from revocation_contact_list where fpr = ?1 ;";
    5.12  
    5.13  static const char *sql_was_id_for_revoke_contacted = 
    5.14 -    "select count(*) from revocation_contact_list where fpr = ?1 and contact_id = ?2 ;";
    5.15 +    "select count(*) from revocation_contact_list where fpr = ?1 and own_address = ?2 and contact_id = ?3 ;";
    5.16 +
    5.17 +static const char *sql_has_id_contacted_address =
    5.18 +    "select count(*) from social_graph where own_address = ?1 and contact_userid = ?2 ;";
    5.19  
    5.20  // We only need user_id and address, since in the main usage, we'll call update_identity
    5.21  // on this anyway when sending out messages.
    5.22  static const char *sql_get_last_contacted =
    5.23      "select user_id, address from identity where datetime('now') < datetime(timestamp, '+14 days') ; ";
    5.24 -    
    5.25 +        
    5.26  static int user_version(void *_version, int count, char **text, char **name)
    5.27  {
    5.28      assert(_version);
    5.29 @@ -806,6 +809,112 @@
    5.30    fprintf(stderr, "(%d) %s\n", iErrCode, zMsg);
    5.31  }
    5.32  
    5.33 +static PEP_STATUS upgrade_revoc_contact_to_13(PEP_SESSION session) {
    5.34 +    // I HATE SQLITE.
    5.35 +    PEP_STATUS status = PEP_STATUS_OK;
    5.36 +    int int_result = 0;
    5.37 +
    5.38 +    // Ok, first we ADD the column so we can USE it.
    5.39 +    // We will end up propagating the "error" this first time 
    5.40 +    // (one-to-one revoke-replace relationships), but since key reset
    5.41 +    // hasn't been used in production, this is not a customer-facing 
    5.42 +    // issue.
    5.43 +    int_result = sqlite3_exec(
    5.44 +        session->db,
    5.45 +        "alter table revocation_contact_list\n"
    5.46 +        "   add column own_address text\n",
    5.47 +        NULL,
    5.48 +        NULL,
    5.49 +        NULL
    5.50 +    );
    5.51 +    assert(int_result == SQLITE_OK);
    5.52 +
    5.53 +    sqlite3_stmt* update_revoked_w_addr_stmt = NULL;
    5.54 +    const char* sql_query = "update revocation_contact_list set own_address = ?1 where fpr = ?2;";
    5.55 +    sqlite3_prepare_v2(session->db, sql_query, -1, &update_revoked_w_addr_stmt, NULL);
    5.56 +                
    5.57 +    // the best we can do here is search per address, since these
    5.58 +    // are no longer associated with an identity. For now, if we find 
    5.59 +    // something we can't add an address to, we'll delete the record.
    5.60 +    // this should not, in the current environment, ever happen, but 
    5.61 +    // since we need to make the address part of the primary key, it's 
    5.62 +    // the right thing to do. sqlite does support null fields in a primary 
    5.63 +    // key for a weird version compatibility reason, but that doesn't 
    5.64 +    // mean we should use it, and we should be *safe*, not relying 
    5.65 +    // on an implementation-specific quirk which might be sanely removed 
    5.66 +    // in a future sqlite version.
    5.67 +    stringpair_t* revoked_key_to_own_address = NULL;
    5.68 +    
    5.69 +    identity_list* id_list = NULL;
    5.70 +    status = own_identities_retrieve(session, &id_list);
    5.71 +
    5.72 +    if (!status || !id_list)
    5.73 +        return PEP_STATUS_OK; // it's empty AFAIK (FIXME)
    5.74 +    
    5.75 +    identity_list* curr_own = id_list;
    5.76 +    
    5.77 +    // Ok, go through and find any keys associated with this address  
    5.78 +    for ( ; curr_own && curr_own->ident; curr_own = curr_own->next) {
    5.79 +        if (EMPTYSTR(curr_own->ident->address)) // shouldn't happen
    5.80 +            continue;
    5.81 +        stringlist_t* keylist = NULL;
    5.82 +        status = find_keys(session, curr_own->ident->address, &keylist);
    5.83 +        stringlist_t* curr_key = keylist;
    5.84 +        for ( ; curr_key && curr_key->value; curr_key = curr_key->next) {
    5.85 +            if (EMPTYSTR(curr_key->value))
    5.86 +                continue;
    5.87 +                
    5.88 +            // We just do this lazily - if this isn't a revoked key, it 
    5.89 +            // won't do anything.
    5.90 +            sqlite3_bind_text(update_revoked_w_addr_stmt, 1, curr_own->ident->address, -1,
    5.91 +                              SQLITE_STATIC);
    5.92 +            sqlite3_bind_text(update_revoked_w_addr_stmt, 2, curr_key->value, -1,
    5.93 +                              SQLITE_STATIC);
    5.94 +                              
    5.95 +            int_result = sqlite3_step(update_revoked_w_addr_stmt);
    5.96 +            assert(int_result == SQLITE_DONE);                  
    5.97 +            sqlite3_reset(update_revoked_w_addr_stmt);                      
    5.98 +        }
    5.99 +    }  
   5.100 +    sqlite3_finalize(update_revoked_w_addr_stmt);
   5.101 +                
   5.102 +    int_result = sqlite3_exec(
   5.103 +        session->db,
   5.104 +        "delete from revocation_contact_list where own_address is NULL;\n"        
   5.105 +        "PRAGMA foreign_keys=off;\n"
   5.106 +        "BEGIN TRANSACTION;\n"
   5.107 +        "create table if not exists _revocation_contact_list_new (\n"
   5.108 +        "   fpr text not null references pgp_keypair (fpr)\n"
   5.109 +        "       on delete cascade,\n"
   5.110 +        "   own_address text,\n"
   5.111 +        "   contact_id text not null references person (id)\n"
   5.112 +        "       on delete cascade on update cascade,\n"
   5.113 +        "   timestamp integer default (datetime('now')),\n"
   5.114 +        "   PRIMARY KEY(fpr, own_address, contact_id)\n"
   5.115 +        ");\n"
   5.116 +        "INSERT INTO _revocation_contact_list_new (fpr, "
   5.117 +        "                                          own_address, "
   5.118 +        "                                          contact_id) "
   5.119 +        "   SELECT revocation_contact_list.fpr, "
   5.120 +        "          revocation_contact_list.own_address, "
   5.121 +        "          revocation_contact_list.contact_id "
   5.122 +        "   FROM revocation_contact_list "
   5.123 +        "   WHERE 1;\n"
   5.124 +        "DROP TABLE revocation_contact_list;\n"
   5.125 +        "ALTER TABLE _revocation_contact_list_new RENAME TO revocation_contact_list;\n"
   5.126 +        "COMMIT;\n"
   5.127 +        "\n"
   5.128 +        "PRAGMA foreign_keys=on;\n"
   5.129 +        ,
   5.130 +        NULL,
   5.131 +        NULL,
   5.132 +        NULL
   5.133 +    );
   5.134 +    assert(int_result == SQLITE_OK);    
   5.135 +    
   5.136 +    return status;
   5.137 +}
   5.138 +
   5.139  #ifdef USE_GPG
   5.140  PEP_STATUS pgp_import_ultimately_trusted_keypairs(PEP_SESSION session);
   5.141  #endif // USE_GPG
   5.142 @@ -934,7 +1043,7 @@
   5.143      sqlite3_busy_timeout(_session->system_db, 1000);
   5.144  
   5.145  // increment this when patching DDL
   5.146 -#define _DDL_USER_VERSION "12"
   5.147 +#define _DDL_USER_VERSION "13"
   5.148  
   5.149      if (in_first) {
   5.150  
   5.151 @@ -1052,10 +1161,11 @@
   5.152                  "create table if not exists revocation_contact_list (\n"
   5.153                  "   fpr text not null references pgp_keypair (fpr)\n"
   5.154                  "       on delete cascade,\n"
   5.155 +                "   own_address text,\n"
   5.156                  "   contact_id text not null references person (id)\n"
   5.157                  "       on delete cascade on update cascade,\n"
   5.158                  "   timestamp integer default (datetime('now')),\n"
   5.159 -                "   PRIMARY KEY(fpr, contact_id)\n"
   5.160 +                "   PRIMARY KEY(fpr, own_address, contact_id)\n"
   5.161                  ");\n"
   5.162                  ,
   5.163              NULL,
   5.164 @@ -1103,7 +1213,10 @@
   5.165          // Sometimes the user_version wasn't set correctly. 
   5.166          if (version == 1) {
   5.167              bool version_changed = true;
   5.168 -            if (table_contains_column(_session, "identity", "pEp_version_major")) {
   5.169 +            if (table_contains_column(_session, "revocation_contact_list", "own_address")) {
   5.170 +                version = 13;
   5.171 +            }
   5.172 +            else if (table_contains_column(_session, "identity", "pEp_version_major")) {
   5.173                  version = 12;
   5.174              }
   5.175              else if (db_contains_table(_session, "social_graph") > 0) {
   5.176 @@ -1555,6 +1668,12 @@
   5.177                  if (status != PEP_STATUS_OK)
   5.178                      return status;                      
   5.179              }
   5.180 +            if (version < 13) {
   5.181 +                status = upgrade_revoc_contact_to_13(_session);
   5.182 +                assert(status == PEP_STATUS_OK);
   5.183 +                if (status != PEP_STATUS_OK)
   5.184 +                    return status;
   5.185 +            }        
   5.186          }        
   5.187          else { 
   5.188              // Version from DB was 0, it means this is initial setup.
   5.189 @@ -1715,6 +1834,12 @@
   5.190      assert(int_result == SQLITE_OK);
   5.191  
   5.192      int_result = sqlite3_prepare_v2(_session->db, 
   5.193 +            sql_has_id_contacted_address,
   5.194 +            (int)strlen(sql_has_id_contacted_address), 
   5.195 +            &_session->has_id_contacted_address, NULL);
   5.196 +    assert(int_result == SQLITE_OK);
   5.197 +
   5.198 +    int_result = sqlite3_prepare_v2(_session->db, 
   5.199              sql_get_last_contacted,
   5.200              (int)strlen(sql_get_last_contacted), 
   5.201              &_session->get_last_contacted, NULL);
   5.202 @@ -2022,7 +2147,9 @@
   5.203              if (session->get_contacted_ids_from_revoke_fpr)
   5.204                  sqlite3_finalize(session->get_contacted_ids_from_revoke_fpr);  
   5.205              if (session->was_id_for_revoke_contacted)
   5.206 -                sqlite3_finalize(session->was_id_for_revoke_contacted);   
   5.207 +                sqlite3_finalize(session->was_id_for_revoke_contacted);  
   5.208 +            if (session->has_id_contacted_address)
   5.209 +                sqlite3_finalize(session->has_id_contacted_address);
   5.210              if (session->get_last_contacted)
   5.211                  sqlite3_finalize(session->get_last_contacted);                                       
   5.212              if (session->set_pgp_keypair)
   5.213 @@ -3567,6 +3694,43 @@
   5.214      return PEP_STATUS_OK;
   5.215  }
   5.216  
   5.217 +// FIXME: should be more like is there a communications relationship,
   5.218 +// since this could be either way
   5.219 +PEP_STATUS has_partner_contacted_address(PEP_SESSION session, const char* partner_id,
   5.220 +                                         const char* own_address, bool* was_contacted) {            
   5.221 +    
   5.222 +    assert(session);
   5.223 +    assert(!EMPTYSTR(partner_id));
   5.224 +    assert(!EMPTYSTR(own_address));    
   5.225 +    assert(was_contacted);
   5.226 +    
   5.227 +    if (!session || !was_contacted || EMPTYSTR(partner_id) || EMPTYSTR(own_address))
   5.228 +        return PEP_ILLEGAL_VALUE;
   5.229 +    
   5.230 +    *was_contacted = false;
   5.231 +
   5.232 +    PEP_STATUS status = PEP_STATUS_OK;
   5.233 +    
   5.234 +    sqlite3_reset(session->has_id_contacted_address);
   5.235 +    sqlite3_bind_text(session->has_id_contacted_address, 1, partner_id, -1,
   5.236 +            SQLITE_STATIC);
   5.237 +    int result = sqlite3_step(session->has_id_contacted_address);
   5.238 +    switch (result) {
   5.239 +        case SQLITE_ROW: {
   5.240 +            // yeah yeah, I know, we could be lazy here, but it looks bad.
   5.241 +            *was_contacted = (sqlite3_column_int(session->has_id_contacted_address, 0) != 0);
   5.242 +            status = PEP_STATUS_OK;
   5.243 +            break;
   5.244 +        }
   5.245 +        default:
   5.246 +            status = PEP_UNKNOWN_DB_ERROR;
   5.247 +    }
   5.248 +    sqlite3_reset(session->has_id_contacted_address);
   5.249 +            
   5.250 +    return status;
   5.251 +}
   5.252 +
   5.253 +// FIXME: problematic - can be multiple and this now matters
   5.254  PEP_STATUS get_own_ident_for_contact_id(PEP_SESSION session,
   5.255                                            const pEp_identity* contact,
   5.256                                            pEp_identity** own_ident) {
     6.1 --- a/src/pEpEngine.h	Tue Jan 07 12:21:43 2020 +0100
     6.2 +++ b/src/pEpEngine.h	Tue Jan 07 22:35:39 2020 +0100
     6.3 @@ -1430,6 +1430,8 @@
     6.4  PEP_STATUS set_all_userids_to_own(PEP_SESSION session, 
     6.5                                    identity_list* id_list);
     6.6  
     6.7 +PEP_STATUS has_partner_contacted_address(PEP_SESSION session, const char* partner_id,
     6.8 +                                         const char* own_address, bool* was_contacted);
     6.9  #ifdef __cplusplus
    6.10  }
    6.11  #endif
     7.1 --- a/src/pEp_internal.h	Tue Jan 07 12:21:43 2020 +0100
     7.2 +++ b/src/pEp_internal.h	Tue Jan 07 22:35:39 2020 +0100
     7.3 @@ -191,6 +191,7 @@
     7.4      sqlite3_stmt *set_revoke_contact_as_notified;
     7.5      sqlite3_stmt *get_contacted_ids_from_revoke_fpr;
     7.6      sqlite3_stmt *was_id_for_revoke_contacted;
     7.7 +    sqlite3_stmt *has_id_contacted_address;
     7.8      sqlite3_stmt *get_last_contacted;
     7.9      // sqlite3_stmt *set_device_group;
    7.10      // sqlite3_stmt *get_device_group;
     8.1 --- a/test/src/KeyResetMessageTest.cc	Tue Jan 07 12:21:43 2020 +0100
     8.2 +++ b/test/src/KeyResetMessageTest.cc	Tue Jan 07 22:35:39 2020 +0100
     8.3 @@ -307,7 +307,7 @@
     8.4      );
     8.5      ASSERT_EQ(int_result , SQLITE_OK);
     8.6  
     8.7 -    status = key_reset(session, alice_fpr, from_ident, NULL, NULL);
     8.8 +    status = key_reset(session, alice_fpr, from_ident);
     8.9      ASSERT_EQ(status , PEP_STATUS_OK);
    8.10      ASSERT_GT(m_queue.size(), 0);
    8.11      status = myself(session, from_ident);
    8.12 @@ -460,7 +460,7 @@
    8.13      ASSERT_STRCASEEQ(from_ident->fpr, alice_fpr);
    8.14      ASSERT_TRUE(from_ident->me);
    8.15  
    8.16 -    status = key_reset(session, alice_fpr, from_ident, NULL, NULL);
    8.17 +    status = key_reset(session, alice_fpr, from_ident);
    8.18      ASSERT_EQ(status , PEP_STATUS_OK);
    8.19      m_queue.clear();
    8.20  
    8.21 @@ -524,7 +524,7 @@
    8.22      // FIXME: longer term we need to fix the test, but the key attached to the message below has expired, so for now, we give her a new key
    8.23      slurp_and_import_key(session, "test_keys/pub/pep-test-gabrielle-0xE203586C_pub.asc");
    8.24  
    8.25 -    status = key_reset(session, alice_fpr, from_ident, NULL, NULL);
    8.26 +    status = key_reset(session, alice_fpr, from_ident);
    8.27      ASSERT_EQ(status , PEP_STATUS_OK);
    8.28      ASSERT_EQ(m_queue.size() , 0);
    8.29      m_queue.clear();
    8.30 @@ -570,10 +570,10 @@
    8.31      ASSERT_STRCASEEQ(from_ident->fpr, alice_fpr);
    8.32      ASSERT_TRUE(from_ident->me);
    8.33  
    8.34 -    status = key_reset(session, NULL, NULL, NULL, NULL);
    8.35 +    status = key_reset(session, NULL, NULL);
    8.36      ASSERT_EQ(status , PEP_STATUS_OK);
    8.37  
    8.38 -    status = key_reset(session, NULL, NULL, NULL, NULL);
    8.39 +    status = key_reset(session, NULL, NULL);
    8.40      ASSERT_EQ(status , PEP_STATUS_OK);
    8.41  
    8.42      status = myself(session, from_ident);
    8.43 @@ -1308,7 +1308,7 @@
    8.44      ASSERT_EQ(rating, PEP_rating_reliable);
    8.45      
    8.46  }
    8.47 -
    8.48 +/*
    8.49  TEST_F(KeyResetMessageTest, check_reset_own_with_revocations) {
    8.50      pEp_identity* id1 = new_identity("krista-not-real@darthmama.org", NULL, PEP_OWN_USERID, "Krista at Home");    
    8.51      PEP_STATUS status = myself(session, id1);
    8.52 @@ -1437,7 +1437,7 @@
    8.53          ASSERT_EQ(status, PEP_STATUS_OK);
    8.54      }
    8.55  }
    8.56 -
    8.57 +*/
    8.58  TEST_F(KeyResetMessageTest, codec_test) {
    8.59      // create input values
    8.60