User:Senya/Proposal: Account renaming and private key reissuing

From diaspora* project wiki

Introduction

Account migration feature among other things requires two essential components: changing the diaspora* ID and reissuing person's private key while saving person's identity. As a special case of the ID change we can consider a change of the ID without changing the server, i.e. renaming the user locally. This task might be considered as a step to account migration, because it requires that other pods in the federation are informed of the fact the ID has changed (and it doesn't really matter if the pod has changed or not), but at the same time it doesn't have the complexity of rebuilding user's account - because it persists on the same pod. This part might be left for the next iteration after account rename is implemented.

Reissuing the person's private key is another self-valuable task. In asymmetric cryptography it is nice to have key reissuing procedure, because the key may leak due to some security accident. But diaspora* doesn't have it.

In this document I propose an implementation plan for these two features.

Optionality

The feature allows users to change their IDs on the pods. In previous discussions it was proposed that the old account name after migration is made unavailable on the pod to register again like it is done with the account deletion. The account names may be considered as a limited resource, so a podmin might want to limit users ability to occupy the names. Moreover, account migration would create additional load on the pods, so owners of the big pods may want not to participate in this load growth. So I propose that the account renaming feature is optional and may be enabled/disabled using the config/diaspora.yml file.


Database changes

A new table "account_migrations" in the DB is introduced and a corresponding model. It is used to track ID changes of the users. It has the following schema:

  create_table "account_migrations", force: :cascade do |t|
    t.string   "old_diaspora_id", null: false
    t.string   "new_diaspora_id", null: false
    t.text     "old_private_key"
  end

"old_private_key" field is not null only for the pod where the user is persisting.


New federation message

A new federation message is introduced in order to inform pods of the account name change. Here is the definition in the "diaspora_federation" DSL:

    class AccountMigration < Entity
      property :old_diaspora_id
      property :new_diaspora_id
    end

Both properties are required.

The federation message is ALWAYS signed with the private key corresponding to the old_diaspora_id.


Scenarios

A user changes his diaspora* ID within the same server

1) The user goes to his account settings and sees a section called "Account name change".

2) The user enters new account name and submits. The UI informs user that the operation creates additional load on diaspora* so he must use the feature only for serious reasons. User confirms.

3) The pod changes the account name in the database for User, Person and Profile objects corresponding to the user.

4) The pod creates an AccountMigration object and stores it in it's database. The old_private_key is filled with the current private key. In case of the account migration from pod to pod this key is different from the present one, but for local move it doesn't make much sense to change it. Anyway, for the future use the old_private_key must be filled for every migration where a private key is available

5) The pod sends the AccountMigration object as an "account_migration" federation message to all the contacts of the user.

A pod receives an AccountMigration message

1) The pod stores the received AccountMigration object in the DB.

2) The pod finds a Person model for the account and updates the account ID in the model.

3) The pod schedules refetch of the account in order to fix some possible differences. (Refetch is modified comparing to how it works now, see #Refetching_user_account).

A pod receives a federation message for the user whose diaspora* ID has changed

For every received message the pod checks whether the recipient is in AccountMigrations table. If the ID is in the table, it does the following:

1) The pod discards the received message.

2) The pod issues the AccountMigration message back to the sender of the message signed with the old_private_key.

Private key update

In the modified version of the save_person_after_webfinger (see #Refetching_user_account) user key is updated when profile is refetched. These is a possible implementation of the key update:

1) A podmin decides to reissue a key for a user and runs a routine from the rails console.

2) The routine creates a new AccountMigration object with old_id == new_id. Old private key is stored to old_private_key field of the migration object.

3) The routine generates a new key pair for the user and stores it to the User and Person objects in the DB.

4) The routine sends out to all the contacts of the user the AccountMigration message.

It's not necessary that the ID change does change the private key or that the ID change does change the private key, but generally, it could happen simultaneously.

Some implementation details

Refetching user account

Account fetching is done using the DiasporaFederation::Discovery::Discovery.new(diaspora_id).fetch_and_save function. When webfinger is done save_person_after_webfinger is called to persist the result.

ATM, if the save_person_after_webfinger is called while having a Person fetched, the Person object in the DB remains unchanged even if the ID, the key or the guid is different from what we have in the database. For that feature to work we need a different version of the function which will accept changes of that fields.

Here are the possible changes in order to achieve that:

diff --git a/config/initializers/diaspora_federation.rb b/config/initializers/diaspora_federation.rb
index 4d63aeb..9a3b73d 100644
--- a/config/initializers/diaspora_federation.rb
+++ b/config/initializers/diaspora_federation.rb
@@ -49,6 +49,10 @@ DiasporaFederation.configure do |config|
         Person.new(diaspora_handle: person.diaspora_id, guid: person.guid,
                    serialized_public_key: person.exported_key, url: person.url)
 
+      if person_entity.persisted?
+        person_entity.update(guid: person.guid, serialized_public_key: person.exported_key, url: person.url)
+      end
+
       profile = person.profile
       profile_entity = person_entity.profile ||= Profile.new
 
@@ -59,6 +63,7 @@ DiasporaFederation.configure do |config|
       profile_entity.image_url_medium = profile.image_url_medium
       profile_entity.image_url_small = profile.image_url_small
       profile_entity.searchable = profile.searchable
+      profile_entity.diaspora_handle = profile.diaspora_id
 
       person_entity.save!
     end


Signatures breakage

Currently we store two types of signatures in the database for the relayable entities: parent_author_signature and author_signature. The parent_author_signature will be discarded by PR6586. The author_signature is currently not required.. But it'll be required for a possibility to fetch relayables (comments and likes) along with posts (in case when we fetch the users posts for the first time after discovery) when such feature is implemented (I failed to find a record on the tracker for that issue). Therefore, at that point it's not an issue to break signatures, but some update/refetch algorithm for signature must be implemented along with the relayables fetch feature.


Further development

The implementation of the proposal will open way of implementing account migration feature. Implementing account migration from pod to pod would require the archive import functionality. The AccountMigration object/message part from that spec can be reused for the account migration feature.

Also the "pod domain change" feature (issue 728) may be implemented using the same means by creating a set of AccountMigration entries in the DB for the pods which decide to change the domain name.