-
Notifications
You must be signed in to change notification settings - Fork 14
Sync protocol (legacy)
Note: This sync approach was used in PfP 2.x. Starting with PfP 3.0, syncing the database file is supposed to happen via an external sync client.
The sync provider can be any platform with the following capabilities:
- Providing authentication tokens with limited access. As PfP has to store authentication tokens unencrypted, these should only allow access to PfP data and not any of the other files.
- Allowing to upload and download a single file.
- Some kind of file versioning ensuring that only the specified version is overwritten on upload. If file has been modified by another client in the meantime, upload should fail.
Trusting the sync provider is not required. The data will be encrypted, so that it will be worthless without knowledge of the master password.
Sync uploads a file named passwords.json
to the sync provider. Its contents are largely identical to a backup file. Starting with PfP 2.2 there is additional data here however:
Key | Type | Value |
---|---|---|
revision | number | Positive integer indicating the revision of the data, each change should increase it. |
signature | base64 | HMAC signature of the file's data. |
Also, the data
key contains sync-secret
in addition to the keys stored in a backup file.
Each update action consists of three steps:
- Download remote data from sync provider if any.
- Merge with local data.
- Upload local data to sync provider.
If no remote data exists, merge step is skipped. Otherwise data is merged using local modification flags (sync:
-prefixed storage keys).
- If a key exists both locally and remotely, set local value to match remote unless a local modification was recorded since last sync.
- If a key exists only remotely, add it locally unless a local modification was recorded since last sync.
- If a key exists only locally, remove it locally unless a local modification was recorded since last sync.
In the upload step, an error could occur due to the file being modified by another client since the download step. In this scenario, sync should abort and retry. No modifications should be kept, neither to local nor to the remote data.
When sync is enabled, all keys should be marked as modified. This ensures that an existing remote file without any data won't result in all local data being removed.
An additional special scenario will typically occur on initial sync: the salt
value stored in the remote data is different from the local salt
value, which makes the encryption incompatible due to different encryption keys being used. In this case, the client has to replace its own salt
and hmac-secret
values with the ones from the remote data. It then needs to rewrite its storage, so that the new encryption key is used and key names are generated with the new HMAC secret.
The purpose of signature and revision fields is to prevent data tampering by the sync provider. While the sync provider cannot decrypt the data, in older versions they could remove all the entries and thus trick the clients into removing all local data. The sync provider could also replace the sync file by an older version and trick the clients into undoing all recent modifications. The former issue is addressed by the signature, the latter by the revision.
The clients have a local HMAC secret used to sign sync data. Locally it is stored both in encrypted (as sync-secret
key) and unencrypted (as SyncData.secret
) form. This duplication is necessary because sync (other than initial sync) has to work without knowing the master password. The remote data contains only the encrypted version.
When a sync is performed, the following scenarios are possible:
- There is no local sync secret yet. In that case it is being taken over (decrypted) from the remote data if it exists there. A new random secret is generated if legacy (pre-2.2) data is encountered or no remote data at all.
- The local and remote sync secrets are identical (encrypted versions are compared without decrypting), so sync can proceed.
- The local and remote sync secrets differ, indicating that remote data was removed and an unrelated client synced to the same storage. In this case the client produces an error which can be solved by disabling sync and connecting again.
The signature field contains a base64-encoded HMAC-SHA256 signature of the revision field and all contents stored under data
. To generate the data to be signed, an array is created having the data revision as its first entry, followed by key/value tuples for all data
entries (ordered by key). This array is serialized as JSON without whitespace and signed. Clients will reject any remote data where the signature doesn't match the sync secret used.
Revision number is increased every time a client uploads changes to remote data. At the same time, the client remembers under SyncData.revision the highest revision number it observed. Clients will reject any remote data where the revision number is lower then the one stored under SyncData.revision.