From 98807ed3f6f6eafaae75611dd1a38aa903eb7dcd Mon Sep 17 00:00:00 2001 From: chrislu Date: Sat, 15 Nov 2025 12:55:23 -0800 Subject: [PATCH] s3: Fix SSE-C multipart IV base64 decoding bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Critical Bug Found**: SSE-C multipart uploads were failing because: Root Cause: - entry.Extended[SeaweedFSSSEIV] stores base64-encoded IV (24 bytes for 16-byte IV) - SerializeSSECMetadata expects raw IV bytes (16 bytes) - During multipart completion, we were passing base64 IV directly → serialization error Error Message: "Failed to serialize SSE-C metadata for chunk in part X: invalid IV length: expected 16 bytes, got 24" Fix: - Base64-decode IV before passing to SerializeSSECMetadata - Added error handling for decode failures Impact: - SSE-C multipart uploads will now correctly serialize chunk metadata - Chunks will have proper SSE metadata for decryption during GET This fixes the SSE-C subtest of TestSSEMultipartUploadIntegration. SSE-KMS still has a separate issue (error code 23) being investigated. --- weed/s3api/filer_multipart.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/weed/s3api/filer_multipart.go b/weed/s3api/filer_multipart.go index b145ed616..fe2de1f45 100644 --- a/weed/s3api/filer_multipart.go +++ b/weed/s3api/filer_multipart.go @@ -269,19 +269,25 @@ func (s3a *S3ApiServer) completeMultipartUpload(r *http.Request, input *s3.Compl } } else if chunk.SseType == filer_pb.SSEType_SSE_C { // For SSE-C chunks, create per-chunk metadata using the part's IV - if ivData, exists := entry.Extended[s3_constants.SeaweedFSSSEIV]; exists { - // Get keyMD5 from entry metadata if available - var keyMD5 string - if keyMD5Data, keyExists := entry.Extended[s3_constants.AmzServerSideEncryptionCustomerKeyMD5]; keyExists { - keyMD5 = string(keyMD5Data) - } - - // Create SSE-C metadata with the part's IV and this chunk's within-part offset - if ssecMetadata, serErr := SerializeSSECMetadata(ivData, keyMD5, withinPartOffset); serErr == nil { - sseKmsMetadata = ssecMetadata // Reuse the same field for unified handling - glog.V(4).Infof("Created SSE-C metadata for chunk in part %d: withinPartOffset=%d", partNumber, withinPartOffset) + if ivDataBase64, exists := entry.Extended[s3_constants.SeaweedFSSSEIV]; exists { + // Decode base64-encoded IV (stored IV is base64, but SerializeSSECMetadata expects raw bytes) + ivData, decodeErr := base64.StdEncoding.DecodeString(string(ivDataBase64)) + if decodeErr != nil { + glog.Errorf("Failed to decode SSE-C IV for chunk in part %d: %v", partNumber, decodeErr) } else { - glog.Errorf("Failed to serialize SSE-C metadata for chunk in part %d: %v", partNumber, serErr) + // Get keyMD5 from entry metadata if available + var keyMD5 string + if keyMD5Data, keyExists := entry.Extended[s3_constants.AmzServerSideEncryptionCustomerKeyMD5]; keyExists { + keyMD5 = string(keyMD5Data) + } + + // Create SSE-C metadata with the part's IV and this chunk's within-part offset + if ssecMetadata, serErr := SerializeSSECMetadata(ivData, keyMD5, withinPartOffset); serErr == nil { + sseKmsMetadata = ssecMetadata // Reuse the same field for unified handling + glog.V(4).Infof("Created SSE-C metadata for chunk in part %d: withinPartOffset=%d", partNumber, withinPartOffset) + } else { + glog.Errorf("Failed to serialize SSE-C metadata for chunk in part %d: %v", partNumber, serErr) + } } } else { glog.Errorf("SSE-C chunk in part %d missing IV in entry metadata", partNumber)