From 27a9a37dc7c0b30ddf973e17cc3ac886a3142ca9 Mon Sep 17 00:00:00 2001 From: Josh Miller Date: Wed, 25 Feb 2026 08:14:25 -0500 Subject: [PATCH] drive: add block upload verifier token and revision verification endpoint Proton's storage backend now requires a Verifier.Token per block when requesting block upload URLs (POST /drive/blocks). Without it, the storage server rejects uploads with HTTP 422 / Code=200501 "Operation failed: Please retry". This commit adds: - RevisionVerification type and BlockUploadVerifier type in block_types.go - Verifier field (omitempty) on BlockUploadInfo - GetRevisionVerification() method calling the v2 API endpoint: GET /drive/v2/volumes/{volumeID}/links/{linkID}/revisions/{revisionID}/verification The VerificationCode returned by that endpoint is XOR'd with the leading bytes of each block's ciphertext in Proton-API-Bridge to produce the per-block token (matching the algorithm in the official Proton Drive JS SDK). Note: the JS SDK also decrypts each block as a client-side integrity check before computing the XOR. That step is not implemented here; the server-side manifest signature still provides end-to-end integrity verification. This fix was identified and generated with Claude Code (AI assistant) by a non-programmer user. It has not been independently reviewed by a Go or cryptography expert. Expert review before merging is strongly recommended. Fixes uploads failing with: 422 POST fra-storage.proton.me/storage/blocks: Operation failed: Please retry (Code=200501, Status=422) --- block_types.go | 13 +++++++++++++ link_file.go | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/block_types.go b/block_types.go index ad41611..f634e52 100644 --- a/block_types.go +++ b/block_types.go @@ -28,6 +28,19 @@ type BlockUploadInfo struct { Size int64 EncSignature string Hash string + Verifier BlockUploadVerifier `json:",omitempty"` +} + +// BlockUploadVerifier holds the per-block verification token required by the +// Proton storage backend to authenticate that the block was correctly encrypted. +type BlockUploadVerifier struct { + Token string `json:",omitempty"` +} + +// RevisionVerification is the response from the block upload verification endpoint. +type RevisionVerification struct { + VerificationCode string // Base64-encoded verification code XOR'd with each block + ContentKeyPacket string // Encrypted content session key (for client-side integrity check) } type BlockUploadLink struct { diff --git a/link_file.go b/link_file.go index 173d9b8..d05dd70 100644 --- a/link_file.go +++ b/link_file.go @@ -8,6 +8,29 @@ import ( "github.com/go-resty/resty/v2" ) +// GetRevisionVerification fetches the verification code for a draft revision from the +// v2 volume-based API. The VerificationCode is XOR'd with each block's encrypted bytes +// to produce a per-block Verifier.Token that the storage backend requires. +func (c *Client) GetRevisionVerification(ctx context.Context, volumeID, linkID, revisionID string) (RevisionVerification, error) { + var res struct { + VerificationCode string + ContentKeyPacket string + } + + if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) { + return r.SetResult(&res).Get( + "/drive/v2/volumes/" + volumeID + "/links/" + linkID + "/revisions/" + revisionID + "/verification", + ) + }); err != nil { + return RevisionVerification{}, err + } + + return RevisionVerification{ + VerificationCode: res.VerificationCode, + ContentKeyPacket: res.ContentKeyPacket, + }, nil +} + func (c *Client) ListRevisions(ctx context.Context, shareID, linkID string) ([]RevisionMetadata, error) { var res struct { Revisions []RevisionMetadata