From 7fc43d964b3d7f4a8ee738696db2f64c62257990 Mon Sep 17 00:00:00 2001 From: chrislu Date: Sun, 16 Nov 2025 18:10:42 -0800 Subject: [PATCH] offset --- weed/s3api/s3api_object_handlers.go | 44 +++++++++++++++++-------- weed/s3api/s3api_object_handlers_put.go | 13 +++++++- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 0ce251f28..8546b9375 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -1224,12 +1224,21 @@ func (s3a *S3ApiServer) decryptSSECChunkView(ctx context.Context, fileChunk *fil // Decrypt with CTR IV offset adjustment // CTR mode: IV for block N = base_IV + (N / 16) - // For multipart objects, each part is encrypted independently with offset=0 - // So we need to adjust IV based on position WITHIN THE PART, not absolute file position - offsetWithinPart := chunkView.ViewOffset - ssecMetadata.PartOffset - glog.V(3).Infof("decryptSSECChunkView: chunk=%s, fileChunk.Offset=%d, chunkView.OffsetInChunk=%d, chunkView.ViewOffset=%d, chunkView.ViewSize=%d, metadata.PartOffset=%d, offsetWithinPart=%d", - chunkView.FileId, fileChunk.Offset, chunkView.OffsetInChunk, chunkView.ViewOffset, chunkView.ViewSize, ssecMetadata.PartOffset, offsetWithinPart) - adjustedIV := adjustCTRIV(chunkIV, offsetWithinPart) + // PartOffset in metadata distinguishes single-part vs multipart: + // - Single-part: PartOffset = 0 for all chunks (one encrypted stream starting at 0) + // - Multipart: PartOffset = chunk position within part during upload + var ivOffset int64 + if ssecMetadata.PartOffset == 0 { + // Single-part upload: use absolute file position + ivOffset = chunkView.ViewOffset + } else { + // Multipart upload: use position within the part's encrypted stream + // After assembly, chunk.Offset is in final file, so we calculate position within part + ivOffset = chunkView.ViewOffset - fileChunk.Offset + } + glog.V(3).Infof("decryptSSECChunkView: chunk=%s, fileChunk.Offset=%d, chunkView.ViewOffset=%d, chunkView.ViewSize=%d, metadata.PartOffset=%d, ivOffset=%d", + chunkView.FileId, fileChunk.Offset, chunkView.ViewOffset, chunkView.ViewSize, ssecMetadata.PartOffset, ivOffset) + adjustedIV := adjustCTRIV(chunkIV, ivOffset) return CreateSSECDecryptedReader(encryptedReader, customerKey, adjustedIV) } @@ -1257,21 +1266,28 @@ func (s3a *S3ApiServer) decryptSSEKMSChunkView(ctx context.Context, fileChunk *f } // Decrypt with CTR IV offset adjustment - // For multipart objects, each part is encrypted independently with offset=0 - // So we need to adjust IV based on position WITHIN THE PART, not absolute file position - // sseKMSKey.ChunkOffset contains the part offset (where this part starts in the file) - offsetWithinPart := chunkView.ViewOffset - sseKMSKey.ChunkOffset - adjustedIV := adjustCTRIV(sseKMSKey.IV, offsetWithinPart) + // ChunkOffset in KMS metadata distinguishes single-part vs multipart (same logic as SSE-C) + // - Single-part: ChunkOffset = 0 for all chunks (one encrypted stream starting at 0) + // - Multipart: ChunkOffset = chunk position within part during upload + var ivOffset int64 + if sseKMSKey.ChunkOffset == 0 { + // Single-part upload: use absolute file position + ivOffset = chunkView.ViewOffset + } else { + // Multipart upload: use position within the part's encrypted stream + ivOffset = chunkView.ViewOffset - fileChunk.Offset + } + adjustedIV := adjustCTRIV(sseKMSKey.IV, ivOffset) adjustedKey := &SSEKMSKey{ KeyID: sseKMSKey.KeyID, EncryptedDataKey: sseKMSKey.EncryptedDataKey, EncryptionContext: sseKMSKey.EncryptionContext, BucketKeyEnabled: sseKMSKey.BucketKeyEnabled, IV: adjustedIV, - ChunkOffset: offsetWithinPart, + ChunkOffset: ivOffset, } - glog.V(3).Infof("decryptSSEKMSChunkView: chunk=%s, ViewOffset=%d, PartOffset=%d, offsetWithinPart=%d", - chunkView.FileId, chunkView.ViewOffset, sseKMSKey.ChunkOffset, offsetWithinPart) + glog.V(3).Infof("decryptSSEKMSChunkView: chunk=%s, fileChunk.Offset=%d, ViewOffset=%d, metadata.ChunkOffset=%d, ivOffset=%d", + chunkView.FileId, fileChunk.Offset, chunkView.ViewOffset, sseKMSKey.ChunkOffset, ivOffset) return CreateSSEKMSDecryptedReader(encryptedReader, adjustedKey) } diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index 28b5ab687..1e34d53d6 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -390,6 +390,17 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, uploadUrl string, dataReader for _, chunk := range chunkResult.FileChunks { chunk.SseType = filer_pb.SSEType_SSE_C if len(sseIV) > 0 { + // For single-part uploads (partNumber == 0), all chunks belong to one encrypted stream starting at 0 + // For multipart uploads (partNumber > 0), each part is encrypted independently starting at 0 + var partOffset int64 + if partNumber == 0 { + // Single-part: use 0 since the entire object is one encrypted stream starting at position 0 + partOffset = 0 + } else { + // Multipart: use chunk.Offset which represents position within this part's encrypted stream + partOffset = chunk.Offset + } + ssecMetadataStruct := struct { Algorithm string `json:"algorithm"` IV string `json:"iv"` @@ -399,7 +410,7 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, uploadUrl string, dataReader Algorithm: "AES256", IV: base64.StdEncoding.EncodeToString(sseIV), KeyMD5: customerKey.KeyMD5, - PartOffset: chunk.Offset, // Use actual chunk offset + PartOffset: partOffset, } if ssecMetadata, serErr := json.Marshal(ssecMetadataStruct); serErr == nil { chunk.SseMetadata = ssecMetadata