Intermediate commit - too much written to lose, but definitely won't compile yet. There are a few assumptions in the older key reset code which have to be revisited here as well. group_key_reset
authorKrista 'DarthMama' Bennett <krista@pep.foundation>
Fri, 13 Dec 2019 12:36:03 +0100
branchgroup_key_reset
changeset 428411da23e0fe0c
parent 4271 ba250cce2d87
child 4285 aaf986a50d27
Intermediate commit - too much written to lose, but definitely won't compile yet. There are a few assumptions in the older key reset code which have to be revisited here as well.
src/key_reset.c
src/message_api.c
src/message_api.h
src/pEpEngine.c
src/pEpEngine.h
src/pEp_internal.h
     1.1 --- a/src/key_reset.c	Thu Dec 12 11:25:28 2019 +0100
     1.2 +++ b/src/key_reset.c	Fri Dec 13 12:36:03 2019 +0100
     1.3 @@ -226,6 +226,25 @@
     1.4      reset_message->longmsg = longmsg; 
     1.5      reset_message->shortmsg = strdup("Key reset");    
     1.6      
     1.7 +    char* key_data = NULL;
     1.8 +    size_t* key_data_size = 0;
     1.9 +    status = export_key(session, old_fpr, &key_data, &key_data_size);
    1.10 +    if (status || !key_data || !key_data_size)
    1.11 +        return PEP_KEY_NOT_FOUND;
    1.12 +
    1.13 +    bloblist_t* bl = NULL;
    1.14 +    
    1.15 +    // Better add old revoked key 
    1.16 +    status = package_key_attachment(key_data, 
    1.17 +                                    key_data_size,
    1.18 +                                    "file://revoked.key", 
    1.19 +                                    &bl);   
    1.20 +
    1.21 +    if (!bl)
    1.22 +        status = PEP_OUT_OF_MEMORY;
    1.23 +
    1.24 +    key_data = NULL;
    1.25 +        
    1.26      message* output_msg = NULL;
    1.27      
    1.28      status = encrypt_message(session, reset_message, NULL,
    1.29 @@ -637,30 +656,305 @@
    1.30      return status;
    1.31  }
    1.32  
    1.33 -static stringlist_t* collect_key_material(PEP_SESSION session, stringlist_t* fprs) {
    1.34 -    stringlist_t* keydata = NULL;    
    1.35 -    stringlist_t* curr_fpr = fprs;    
    1.36 -    while (curr_fpr) {
    1.37 -        if (curr_fpr->value) {
    1.38 -            char* key_material = NULL;
    1.39 -            size_t datasize = 0;
    1.40 -            PEP_STATUS status = export_key(session, curr_fpr->value, &key_material, &datasize);
    1.41 -            if (status) {
    1.42 -                free_stringlist(keydata);
    1.43 -                return NULL;
    1.44 -            }
    1.45 -            if (datasize > 0 && key_material) {
    1.46 -                if (!(keydata))
    1.47 -                    keydata = new_stringlist(NULL);
    1.48 +PEP_STATUS initiate_group_key_reset(PEP_SESSION session, 
    1.49 +                                    keyreset_command_list** commands,
    1.50 +                                    stringlist_t** new_key_material) {
    1.51 +    
    1.52 +    if (!session || !old_new_fpr_pairs || !new_keys)
    1.53 +        return PEP_ILLEGAL_VALUE;
    1.54 +        
    1.55 +    PEP_STATUS status = get_all_keys_for_user(session, user_id, &keys);
    1.56 +
    1.57 +    if (!status)
    1.58 +        return status;
    1.59 +    
    1.60 +    keyreset_command_list* new_cmd_list = new_keyreset_command_list(NULL);
    1.61 +        
    1.62 +    // TODO: free
    1.63 +    stringlist_t* curr_key;
    1.64 +    
    1.65 +    for (curr_key = keys; curr_key && curr_key->value; curr_key = curr_key->next) {
    1.66 +        char* curr_fpr = curr_key->value;
    1.67 +        status = get_identities_by_main_key_id(session, curr_key->value, &key_idents);
    1.68 +                    
    1.69 +        if (status != PEP_CANNOT_FIND_IDENTITY) {
    1.70 +            if (status == PEP_STATUS_OK) {
    1.71 +                                
    1.72 +                // now have ident list, or should
    1.73 +                identity_list* curr_ident;
    1.74 +                for (curr_ident = key_idents; curr_ident && curr_ident->ident; 
    1.75 +                                  curr_ident = curr_ident->next) {
    1.76 +                
    1.77 +                    pEp_identity* this_identity = curr_ident->ident;
    1.78 +                    
    1.79 +                    // 0. Make sure this is a group key to begin with
    1.80 +                    if (!(curr_ident->flags & PEP_idf_devicegroup))
    1.81 +                        continue;
    1.82 +
    1.83 +                    // 1. Preserve this sad old identity so we can look on it 
    1.84 +                    //    fondly later and tell people who needs to replace what 
    1.85 +                    pEp_identity* replacement_id = identity_dup(curr_ident->ident);
    1.86 +                        
    1.87 +                    if (!replacement_id)
    1.88 +                        return PEP_OUT_OF_MEMORY;
    1.89 +                        
    1.90 +                    // 2. Get this identity a new key, woman! (ROAR!)
    1.91 +                    status = generate_keypair(session, this_identity);
    1.92                      
    1.93 -                stringlist_add(keydata, key_material);
    1.94 -            }
    1.95 -        }
    1.96 -        curr_fpr = curr_fpr->next;        
    1.97 -    }   
    1.98 -    return keydata; 
    1.99 +                    if (!status)
   1.100 +                        return status;
   1.101 +                        
   1.102 +                    char* new_fpr = strdup(this_identity->fpr);
   1.103 +                    if (!new_fpr)
   1.104 +                        return PEP_OUT_OF_MEMORY;
   1.105 +                        
   1.106 +                    // 3. bind the old and new
   1.107 +                    keyreset_command* new_cmd = new_keyreset_command(replacement_id, new_fpr);
   1.108 +                    if (!new_cmd)
   1.109 +                        return PEP_OUT_OF_MEMORY;
   1.110 +                        
   1.111 +                    keyreset_command_list_add(new_cmd_list, new_cmd);    
   1.112 +                    
   1.113 +                    // 4. Get the new key material
   1.114 +                    char* keydata = NULL;
   1.115 +                    size_t keysize = 0;
   1.116 +
   1.117 +                    status = export_key(session, this_identity->fpr, &keydata, &keysize);
   1.118 +                    if (!status)
   1.119 +                        return status;
   1.120 +                        
   1.121 +                    if (keydata)
   1.122 +                        stringlist_add(new_keys, keydata);
   1.123 +                    
   1.124 +                    status = export_secret_key(session, this_identity->fpr, &keydata, &keysize);
   1.125 +                    if (!status)
   1.126 +                        return status;
   1.127 +                        
   1.128 +                    if (keydata)
   1.129 +                        stringlist_add(new_keys, keydata);
   1.130 +                    
   1.131 +                    // on to the next identity for this key
   1.132 +                        
   1.133 +                } // end loop through idents for this key
   1.134 +            } // end if identities found without error 
   1.135 +        } // end if identitiies found for current key
   1.136 +    } // end loop through keys  
   1.137 +    
   1.138 +    // Ok - we have it all. Now, let's package up the message and put 
   1.139 +    // it into the queue.
   1.140 +    message* grp_reset_msg = NULL;
   1.141 +    status = create_group_key_reset_message(session, 
   1.142 +                                            &grp_reset_msg, 
   1.143 +                                            new_cmd_list,
   1.144 +                                            keys)
   1.145 +    
   1.146 +    // Ok, here's the real fun - now we have to revoke for each 
   1.147 +    // identity, set the replacement, and notify.
   1.148 +    
   1.149 +    keyreset_command_list* curr_cmd = new_cmd_list;
   1.150 +    for ( ; curr_cmd && curr_cmd->command; curr_cmd = curr_cmd->next) {
   1.151 +        pEp_identity* ident = curr_cmd->command->ident;
   1.152 +
   1.153 +        // 1. Revoke 
   1.154 +        status = revoke_key(session, ident->fpr, NULL);
   1.155 +
   1.156 +        // 2. record replacement 
   1.157 +        if (status == PEP_STATUS_OK) 
   1.158 +            status = set_revoked(session, fpr_copy, new_key, time(NULL));            
   1.159 +    
   1.160 +        // 3. send revocation for recent partners
   1.161 +        // FIXME: WE NEED AN IDENT FOR THE FUNCTION BELOW
   1.162 +        if (status == PEP_STATUS_OK)
   1.163 +            status = send_key_reset_to_recents(session, fpr_copy, new_key);            
   1.164 +    }
   1.165 +    
   1.166 +    return status;
   1.167  }
   1.168  
   1.169 +PEP_STATUS create_group_key_reset_message(PEP_SESSION session,
   1.170 +                                          message** dst, 
   1.171 +                                          keyreset_command_list* commands,
   1.172 +                                          stringlist_t* new_key_material) {
   1.173 +                                                   
   1.174 +    if (!dst || !commands || !new_key_material)
   1.175 +        return PEP_ILLEGAL_VALUE;
   1.176 +
   1.177 +    if (!commands->command || !commands->command->ident)
   1.178 +        return PEP_ILLEGAL_VALUE;
   1.179 +        
   1.180 +    *dst = NULL;
   1.181 +
   1.182 +    // the keyreset_command_list contains own grouped identities - we 
   1.183 +    // only need to take one, so we arbitrarily take the first.
   1.184 +    
   1.185 +    // Get own identity user has corresponded with
   1.186 +    pEp_identity* own_identity = identity_dup(commands->command->ident);
   1.187 +
   1.188 +    if (!own_identity)
   1.189 +        return PEP_OUT_OF_MEMORY;
   1.190 +        
   1.191 +    message* reset_message = new_message(PEP_dir_outgoing);
   1.192 +    reset_message->from = own_identity;
   1.193 +    reset_message->to = new_identity_list(identity_dup(own_identity)); // ?
   1.194 +    
   1.195 +    reset_message->shortmsg = strdup("p≡p key reset message - please ignore");
   1.196 +    assert(msg->shortmsg);
   1.197 +    if (!msg->shortmsg)
   1.198 +        goto enomem;
   1.199 +
   1.200 +    reset_message->longmsg = strdup("This message is part of p≡p's key reset protocol.\n\n"
   1.201 +                                    "You can safely ignore it. It will be deleted automatically.\n");
   1.202 +
   1.203 +    add_opt_field(reset_message, "pEp-auto-consume", "yes");
   1.204 +    msg->in_reply_to = stringlist_add(reset_message->in_reply_to, "pEp-auto-consume@pEp.foundation");
   1.205 +
   1.206 +    assert(reset_message->longmsg);
   1.207 +    if (!reset_message->longmsg)
   1.208 +        goto enomem;
   1.209 +
   1.210 +    // Add keys
   1.211 +    stringlist_t* sl = *new_key_material;
   1.212 +    char* keydata = calloc(1,1);
   1.213 +    size_t key_data_size = 1;
   1.214 +    
   1.215 +    while (sl && sl->value) {
   1.216 +        char *_key_data = sl->value;
   1.217 +        assert(_key_data);
   1.218 +        if (!_key_data)
   1.219 +            return PEP_ILLEGAL_VALUE;
   1.220 +            
   1.221 +        size_t _size = strlen(_key_data);
   1.222 +        assert(_size);
   1.223 +            
   1.224 +        // We take ownership of the key material and remove the node.
   1.225 +        char *n = realloc(key_data, key_data_size + _size);
   1.226 +        if (!n)
   1.227 +            return PEP_OUT_OF_MEMORY;
   1.228 +    
   1.229 +        key_data = n;
   1.230 +        key_data_size += _size;
   1.231 +        strlcat(key_data, _key_data, key_data_size);
   1.232 +
   1.233 +        stringlist_t* tmp = sl;
   1.234 +        sl = sl->next;
   1.235 +        tmp->next = NULL;
   1.236 +        stringlist_delete(tmp);            
   1.237 +    }    
   1.238 +    
   1.239 +    bloblist_t* bl = NULL;
   1.240 +    status = package_key_attachment(key_data, 
   1.241 +                                    key_data_size,
   1.242 +                                    "file://groupreset.key", 
   1.243 +                                    &bl);   
   1.244 +                                        
   1.245 +    if (!bl)
   1.246 +        status = PEP_OUT_OF_MEMORY;
   1.247 +
   1.248 +    reset_message->attachments = bl;
   1.249 +
   1.250 +    key_data = NULL;
   1.251 +
   1.252 +    // Add identities 
   1.253 +    char* payload = NULL;
   1.254 +    size_t size = 0;
   1.255 +    
   1.256 +    status = key_reset_commands_to_PER(commands, &payload, &size);    
   1.257 +    if (!status)
   1.258 +        return status;
   1.259 +        
   1.260 +    if (commands && (!payload || size = 0))
   1.261 +        return PEP_UNKNOWN_ERROR;
   1.262 +        
   1.263 +    bl = bloblist_add(reset_message->attachments, payload, size,
   1.264 +                      "application/pEp.keyreset", "ignore_this_attachment.pEp");
   1.265 +            
   1.266 +    message* output_msg = NULL;
   1.267 +    
   1.268 +    status = encrypt_message(session, reset_message, NULL,
   1.269 +                             &output_msg, PEP_enc_PGP_MIME,
   1.270 +                             PEP_encrypt_flag_group_key_reset);
   1.271 +
   1.272 +    if (status == PEP_STATUS_OK)
   1.273 +        *dst = output_msg;
   1.274 +        
   1.275 +    free_message(reset_message);
   1.276 +    
   1.277 +    return status;
   1.278 +}
   1.279 +
   1.280 +PEP_STATUS process_group_key_reset(PEP_SESSION session, 
   1.281 +                                   keyreset_command_list* commands) {
   1.282 +    if (!session || !old_idents || !new_keys)
   1.283 +        return PEP_ILLEGAL_VALUE;
   1.284 +
   1.285 +    char* user_id = NULL;
   1.286 +    PEP_STATUS status = get_default_own_userid(session, &user_id);
   1.287 +    if (status != PEP_STATUS_OK || !user_id)
   1.288 +        goto pEp_free;   
   1.289 +    
   1.290 +    // identity_list* curr_ident = old_idents;
   1.291 +    // stringlist_t* curr_fpr = new_keys;
   1.292 +
   1.293 +    keyreset_command_list* curr_cmd = commands;
   1.294 +    
   1.295 +    for ( ; curr_cmd && curr_cmd->command; curr_ident = curr_cmd->next) {
   1.296 +        keyreset_command* command = curr_cmd->command;
   1.297 +        if (!command->ident || !command->new_key)
   1.298 +            return PEP_UNKNOWN_ERROR;
   1.299 +            
   1.300 +        pEp_identity* this_id = command->ident;
   1.301 +        pEp_identity* this_key = command->new_key;
   1.302 +            
   1.303 +        // 0. check that we even have this identity and the new key
   1.304 +        bool ident_exists = NULL;
   1.305 +        status = exists_identity(session, this_id, &ident_exists);
   1.306 +        
   1.307 +        if (status)
   1.308 +            return status;
   1.309 +            
   1.310 +        if (!ident_exists)
   1.311 +            continue;
   1.312 +        
   1.313 +        stringlist_t* keylist = NULL;
   1.314 +        status = find_key(session, this_key, &keylist);
   1.315 +        if (status)
   1.316 +            return status;
   1.317 +        if (!keylist)
   1.318 +            return PEP_KEY_NOT_FOUND;
   1.319 +        
   1.320 +        // 1. replace its main key (what other checks do we want?)
   1.321 +        status = replace_fpr_for_identity(session, user_id, address, this_key);
   1.322 +
   1.323 +        if (!status)
   1.324 +            return status;
   1.325 +            
   1.326 +        // 2. ???
   1.327 +        
   1.328 +        // 3. revoke old key
   1.329 +        status = revoke_key(session, this_id->fpr, NULL);
   1.330 +                                                    
   1.331 +        this_id->comm_type = PEP_ct_mistrusted;
   1.332 +        status = set_trust(session, this_id);
   1.333 +                    
   1.334 +        if (status == PEP_STATUS_OK)
   1.335 +            // cascade that mistrust for anyone using this key
   1.336 +            status = mark_as_compromised(session, this_key);
   1.337 +                
   1.338 +        if (status == PEP_STATUS_OK)
   1.339 +            status = remove_fpr_as_default(session, this_key);
   1.340 +        if (status == PEP_STATUS_OK)
   1.341 +            status = add_mistrusted_key(session, this_key);
   1.342 +
   1.343 +        // add to revocation list 
   1.344 +        if (status == PEP_STATUS_OK) 
   1.345 +            status = set_revoked(session, this_key, new_key, time(NULL));            
   1.346 +        
   1.347 +        // 4. Profit!
   1.348 +    }           
   1.349 +    return commands;
   1.350 +}
   1.351 +
   1.352 +/*
   1.353  PEP_STATUS key_reset_own_and_deliver_revocations(PEP_SESSION session, 
   1.354                                                   identity_list** own_identities, 
   1.355                                                   stringlist_t** revocations, 
   1.356 @@ -690,7 +984,7 @@
   1.357              if (curr_ident->ident && curr_ident->ident->fpr) {
   1.358                  char* key_material = NULL;
   1.359                  size_t datasize = 0;
   1.360 -                status = export_key(session, curr_ident->ident->fpr, &key_material, &datasize);
   1.361 +                status = export_private_keys(session, curr_ident->ident->fpr, &key_material, &datasize);
   1.362                  if (status) {
   1.363                      free_stringlist(keydata);
   1.364                      return status;
   1.365 @@ -708,6 +1002,7 @@
   1.366      free(revoked_fprs);
   1.367      return PEP_STATUS_OK;
   1.368  }
   1.369 +*/
   1.370  
   1.371  Distribution_t *Distribution_from_keyreset_command_list(
   1.372          const keyreset_command_list *command_list,
   1.373 @@ -889,4 +1184,3 @@
   1.374      ASN_STRUCT_FREE(asn_DEF_Distribution, dist);
   1.375      return status;
   1.376  }
   1.377 -
     2.1 --- a/src/message_api.c	Thu Dec 12 11:25:28 2019 +0100
     2.2 +++ b/src/message_api.c	Fri Dec 13 12:36:03 2019 +0100
     2.3 @@ -861,6 +861,8 @@
     2.4              case PEP_message_key_reset:
     2.5                  inner_type_string = "KEY_RESET";
     2.6                  break;
     2.7 +            case PEP_message_group_key_reset:
     2.8 +                inner_type_string = "GROUP_KEY_RESET"    
     2.9              default:
    2.10                  inner_type_string = "INNER";
    2.11          }
    2.12 @@ -1943,7 +1945,13 @@
    2.13          // FIXME - we need to deal with transport types (via flag)
    2.14          message_wrap_type wrap_type = PEP_message_unwrapped;
    2.15          if ((enc_format != PEP_enc_inline) && (!force_v_1) && ((max_comm_type | PEP_ct_confirmed) == PEP_ct_pEp)) {
    2.16 -            wrap_type = ((flags & PEP_encrypt_flag_key_reset_only) ? PEP_message_key_reset : PEP_message_default);
    2.17 +            if (flags & PEP_encrypt_flag_key_reset_only)
    2.18 +                wrap_type = PEP_message_key_reset;
    2.19 +            else if (flags & PEP_encrypt_flag_group_key_reset)
    2.20 +                wrap_type = PEP_message_group_key_reset;    
    2.21 +            else
    2.22 +                wrap_type = PEP_message_default;
    2.23 +                
    2.24              _src = wrap_message_as_attachment(NULL, src, wrap_type, false, max_version_major, max_version_minor);
    2.25              if (!_src)
    2.26                  goto pEp_error;
    2.27 @@ -4902,3 +4910,27 @@
    2.28      free_identity(ident);
    2.29      return status;
    2.30  }
    2.31 +
    2.32 +PEP_STATUS package_key_attachment(char* keydata, 
    2.33 +                                  size_t size, 
    2.34 +                                  const char* filename,
    2.35 +                                  bloblist_t** attachment) 
    2.36 +{
    2.37 +    if (!keydata || !size || !attachment)
    2.38 +        return PEP_ILLEGAL_VALUE;
    2.39 +        
    2.40 +    // Ensure we don't inject NUL chars into MIME stream.
    2.41 +    char* last_char = key_data + size;
    2.42 +    while (*last_char == '\0') {
    2.43 +        last_char--;
    2.44 +        size--;
    2.45 +    }
    2.46 +
    2.47 +    *attachment = new_bloblist(key_data, size,
    2.48 +                               "application/octet-stream", 
    2.49 +                               filename);
    2.50 +    if (!(*attachment))
    2.51 +        return PEP_OUT_OF_MEMORY;
    2.52 +    
    2.53 +    return PEP_STATUS_OK;                                           
    2.54 +}
     3.1 --- a/src/message_api.h	Thu Dec 12 11:25:28 2019 +0100
     3.2 +++ b/src/message_api.h	Fri Dec 13 12:36:03 2019 +0100
     3.3 @@ -43,10 +43,12 @@
     3.4      PEP_encrypt_flag_force_version_1 = 0x10,
     3.5          
     3.6      PEP_encrypt_flag_key_reset_only = 0x20,
     3.7 +    PEP_encrypt_flag_group_key_reset = 0x40,
     3.8      
     3.9      // This flag is used to let internal functions know that an encryption 
    3.10      // call is being used as part of a reencryption operation
    3.11 -    PEP_encrypt_reencrypt = 0x40
    3.12 +
    3.13 +    PEP_encrypt_reencrypt = 0x80
    3.14      
    3.15  } PEP_encrypt_flags; 
    3.16  
    3.17 @@ -56,7 +58,8 @@
    3.18      PEP_message_unwrapped,  // 1.0 or anything we don't wrap    
    3.19      PEP_message_default,    // typical inner/outer message 2.0
    3.20      PEP_message_transport,  // e.g. for onion layers
    3.21 -    PEP_message_key_reset   // for wrapped key reset information
    3.22 +    PEP_message_key_reset,   // for wrapped key reset information
    3.23 +    PEP_message_group_key_reset // for wrapped group key reset info
    3.24  } message_wrap_type;
    3.25  
    3.26  // encrypt_message() - encrypt message in memory
     4.1 --- a/src/pEpEngine.c	Thu Dec 12 11:25:28 2019 +0100
     4.2 +++ b/src/pEpEngine.c	Fri Dec 13 12:36:03 2019 +0100
     4.3 @@ -155,6 +155,12 @@
     4.4      "   set main_key_id = ?1 "
     4.5      "   where main_key_id = ?2 ;";
     4.6      
     4.7 +static const char *sql_replace_fpr_for_identity =
     4.8 +    "update identity"
     4.9 +    "   set main_key_id = ?1 "
    4.10 +    "   where identity.user_id = ?2 "
    4.11 +    "      and identity.address = ?3; ";
    4.12 +    
    4.13  static const char *sql_remove_fpr_as_default =
    4.14      "update person set main_key_id = NULL where main_key_id = ?1 ;"
    4.15      "update identity set main_key_id = NULL where main_key_id = ?1 ;";
    4.16 @@ -1656,6 +1662,11 @@
    4.17              (int)strlen(sql_replace_identities_fpr), 
    4.18              &_session->replace_identities_fpr, NULL);
    4.19      assert(int_result == SQLITE_OK);
    4.20 +
    4.21 +    int_result = sqlite3_prepare_v2(_session->db, sql_replace_fpr_for_identity,
    4.22 +            (int)strlen(sql_replace_fpr_for_identity), 
    4.23 +            &_session->replace_fpr_for_identity, NULL);
    4.24 +    assert(int_result == SQLITE_OK);
    4.25      
    4.26      int_result = sqlite3_prepare_v2(_session->db, sql_remove_fpr_as_default,
    4.27              (int)strlen(sql_remove_fpr_as_default), 
    4.28 @@ -1998,7 +2009,9 @@
    4.29              if (session->add_userid_alias)
    4.30                  sqlite3_finalize(session->add_userid_alias);
    4.31              if (session->replace_identities_fpr)
    4.32 -                sqlite3_finalize(session->replace_identities_fpr);        
    4.33 +                sqlite3_finalize(session->replace_identities_fpr);   
    4.34 +            if (session->replace_fpr_for_identity)
    4.35 +                sqlite3_finalize(session->replace_fpr_for_identity);
    4.36              if (session->remove_fpr_as_default)
    4.37                  sqlite3_finalize(session->remove_fpr_as_default);            
    4.38              if (session->set_person)
    4.39 @@ -3632,10 +3645,38 @@
    4.40      return PEP_STATUS_OK;
    4.41  }
    4.42  
    4.43 +PEP_STATUS replace_fpr_for_identity(PEP_SESSION session, 
    4.44 +                                    const char* user_id,
    4.45 +                                    const char* address, 
    4.46 +                                    const char* new_fpr) 
    4.47 +{
    4.48 +    assert(user_id);
    4.49 +    assert(address);
    4.50 +    assert(new_fpr);
    4.51 +    
    4.52 +    if (!user_id || !address || !new_fpr)
    4.53 +        return PEP_ILLEGAL_VALUE;
    4.54 +            
    4.55 +    sqlite3_reset(session->replace_fpr_for_identity);
    4.56 +    sqlite3_bind_text(session->replace_fpr_for_identity, 1, new_fpr, -1,
    4.57 +                      SQLITE_STATIC);
    4.58 +    sqlite3_bind_text(session->replace_fpr_for_identity, 2, user_id, -1,
    4.59 +                      SQLITE_STATIC);
    4.60 +    sqlite3_bind_text(session->replace_fpr_for_identity, 2, address, -1,
    4.61 +                      SQLITE_STATIC);
    4.62 +
    4.63 +    int result = sqlite3_step(session->replace_fpr_for_identity);
    4.64 +    sqlite3_reset(session->replace_fpr_for_identity);
    4.65 +    
    4.66 +    if (result != SQLITE_DONE)
    4.67 +        return PEP_CANNOT_SET_IDENTITY;
    4.68 +
    4.69 +    return PEP_STATUS_OK;
    4.70 +}
    4.71  
    4.72  PEP_STATUS replace_identities_fpr(PEP_SESSION session, 
    4.73 -                                 const char* old_fpr, 
    4.74 -                                 const char* new_fpr) 
    4.75 +                                  const char* old_fpr, 
    4.76 +                                  const char* new_fpr) 
    4.77  {
    4.78      assert(old_fpr);
    4.79      assert(new_fpr);
     5.1 --- a/src/pEpEngine.h	Thu Dec 12 11:25:28 2019 +0100
     5.2 +++ b/src/pEpEngine.h	Fri Dec 13 12:36:03 2019 +0100
     5.3 @@ -1429,6 +1429,11 @@
     5.4                       
     5.5  PEP_STATUS set_all_userids_to_own(PEP_SESSION session, 
     5.6                                    identity_list* id_list);
     5.7 +                                  
     5.8 +PEP_STATUS replace_fpr_for_identity(PEP_SESSION session, 
     5.9 +                                    const char* user_id,
    5.10 +                                    const char* address, 
    5.11 +                                    const char* new_fpr);                                  
    5.12  
    5.13  #ifdef __cplusplus
    5.14  }
     6.1 --- a/src/pEp_internal.h	Thu Dec 12 11:25:28 2019 +0100
     6.2 +++ b/src/pEp_internal.h	Fri Dec 13 12:36:03 2019 +0100
     6.3 @@ -174,6 +174,7 @@
     6.4      sqlite3_stmt *get_identities_by_userid;
     6.5      sqlite3_stmt *get_identities_by_main_key_id;
     6.6      sqlite3_stmt *replace_identities_fpr;
     6.7 +    sqlite3_stmt *replace_fpr_for_identity;
     6.8      sqlite3_stmt *replace_main_user_fpr;
     6.9      sqlite3_stmt *get_main_user_fpr;
    6.10      sqlite3_stmt *refresh_userid_default_key;
    6.11 @@ -303,6 +304,11 @@
    6.12      bool add_version,
    6.13      bool clobber);
    6.14  
    6.15 +PEP_STATUS package_key_attachment(char* keydata, 
    6.16 +                                  size_t size, 
    6.17 +                                  const char* filename,
    6.18 +                                  bloblist_t** attachment);
    6.19 +                                  
    6.20  #if defined(NDEBUG) || defined(NOLOG)
    6.21  #define DEBUG_LOG(TITLE, ENTITY, DESC)
    6.22  #else