Skip to content

fix: implement session key reuse for move/rename operations#3

Open
mcanevet wants to merge 2 commits intorclone:masterfrom
mcanevet:lmwashere/master
Open

fix: implement session key reuse for move/rename operations#3
mcanevet wants to merge 2 commits intorclone:masterfrom
mcanevet:lmwashere/master

Conversation

@mcanevet
Copy link

Summary

Implement session key reuse for move/rename operations to fix Code 2000 errors.

Changes

  • Add ReEncryptPassphrase function: re-encrypts node passphrase to new parent keyring while reusing the original symmetric session key
  • Add ReEncryptName function: re-encrypts node name for new parent keyring while reusing the original session key and re-signing with current addrKR
  • Add decryptSessionKey helper with fallback chain
  • Update moveLink to use new functions
  • Set ContentHash = nil explicitly for file moves
  • Only include NodePassphraseSignature for anonymous nodes (KeyAuthor == nil)

Why

Code 2000 ("maybe outdated app") errors during move/rename are caused by:

  1. Generating a new session key instead of reusing the original
  2. Sending NodePassphraseSignature for non-anonymous nodes

This fix addresses both issues.

Test Results

  • FsMove: PASS (was failing with Code 2000)
  • FsDirMove: PASS (was failing with Code 2000)
  • All other tests: PASS

Requires: rclone/go-proton-api#4

Josh Miller and others added 2 commits February 25, 2026 08:15
This commit fixes two bugs that prevent file uploads to Proton Drive:

--- Bug 1: handleRevisionConflict ignores ReplaceExistingDraft when
    GetRevisions returns 2501 ---

When a previous upload attempt fails after the file draft is created but
before blocks are committed, a broken draft remains. On the next attempt
the draft is found (2500 "file already exists"), and GetRevisions is called
to decide what to do with it. However, broken drafts return 422/2501 from
the /revisions endpoint, causing handleRevisionConflict to return an error
immediately — even when ReplaceExistingDraft=true.

Fix: if GetRevisions fails AND ReplaceExistingDraft is set AND the link is
in draft state, treat it as a broken draft and delete the link so the caller
can retry from scratch.

--- Bug 2: block uploads missing required Verifier.Token ---

Proton's storage backend now requires a Verifier.Token per block in the
POST /drive/blocks request. Without it, the storage server rejects block
uploads with HTTP 422 / Code=200501 "Operation failed: Please retry".

The token is computed by fetching a VerificationCode for the revision via:
  GET /drive/v2/volumes/{volumeID}/links/{linkID}/revisions/{revisionID}/verification
then XOR-ing it with the leading bytes of each block's ciphertext (algorithm
sourced from the official Proton Drive JS SDK in ProtonDriveApps/sdk).

Also bumps go-proton-api to v1.0.1-0.20260218123427-1a63a293e3a2 which
updates the default API host from mail.proton.me/api to drive-api.proton.me.

Note: the JS SDK performs an additional client-side decryption check before
computing the XOR token (to detect bit-flips / bad hardware). That step is
not implemented here; the server-side manifest signature still provides
end-to-end integrity verification. A future improvement could add it.

This fix was identified and generated with Claude Code (AI assistant) by a
non-programmer user (GitHub: lmwashere). It has not been independently
reviewed by a Go or cryptography expert. Expert review before merging is
strongly recommended.

Reproducer: rclone copy <file> proton: --verbose
Expected:   upload succeeds
Actual:     422 POST fra-storage.proton.me/storage/blocks (Code=200501)
            followed by 422 GET .../revisions (Code=2501) on retry
This fixes Code 2000 errors during move/rename operations.

Changes:
- Add ReEncryptPassphrase function: re-encrypts node passphrase to new
  parent keyring while reusing the original symmetric session key
- Add ReEncryptName function: re-encrypts node name for new parent keyring
  while reusing the original session key and re-signing with current addrKR
- Add decryptSessionKey helper with fallback chain
- Update moveLink to use new functions
- Set ContentHash = nil explicitly for file moves
- Only include NodePassphraseSignature for anonymous nodes (KeyAuthor == nil)

Test results:
- FsMove: PASS (was failing with Code 2000)
- FsDirMove: PASS (was failing with Code 2000)
- All other tests: PASS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant