diff --git a/cmd/api-response.go b/cmd/api-response.go index c6ad7b07e..43020a5a3 100644 --- a/cmd/api-response.go +++ b/cmd/api-response.go @@ -777,8 +777,7 @@ func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, n type metaCheckFn = func(name string, action policy.Action) (s3Err APIErrorCode) // generates CopyObjectResponse from the committed object information. -func generateCopyObjectResponse(oi ObjectInfo, h http.Header) CopyObjectResponse { - cs, _ := oi.decryptChecksums(0, h) +func generateCopyObjectResponse(oi ObjectInfo, cs map[string]string) CopyObjectResponse { return CopyObjectResponse{ ETag: "\"" + oi.ETag + "\"", LastModified: amztime.ISO8601Format(oi.ModTime.UTC()), diff --git a/cmd/object-copy-checksum_test.go b/cmd/object-copy-checksum_test.go index ad373f171..2f2c56328 100644 --- a/cmd/object-copy-checksum_test.go +++ b/cmd/object-copy-checksum_test.go @@ -387,6 +387,44 @@ func testAPICopyObjectServerSideChecksumEncryption(obj ObjectLayer, instanceType } }) } + + oldKey := bytes.Repeat([]byte{0x31}, 32) + oldKeyMD5 := md5.Sum(oldKey) + newKey := bytes.Repeat([]byte{0x42}, 32) + newKeyMD5 := md5.Sum(newKey) + encryptedSource := "copy-checksum/sse-c-different-key-source.bin" + putCopyChecksumSource(t, apiRouter, credentials, bucketName, encryptedSource, data, map[string]string{ + xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(oldKey), + xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(oldKeyMD5[:]), + }) + + destination := "copy-checksum/sse-c-different-key-destination.bin" + rec := copyChecksumRequest(t, apiRouter, credentials, bucketName, encryptedSource, destination, map[string]string{ + xhttp.AmzChecksumAlgo: hash.ChecksumCRC32.String(), + xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(newKey), + xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(newKeyMD5[:]), + xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: xhttp.AmzEncryptionAES, + xhttp.AmzServerSideEncryptionCopyCustomerKey: base64.StdEncoding.EncodeToString(oldKey), + xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: base64.StdEncoding.EncodeToString(oldKeyMD5[:]), + }) + if rec.Code != http.StatusOK { + t.Fatalf("%s: different-key SSE-C CopyObject failed: %d %s", instanceType, rec.Code, rec.Body.String()) + } + assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data) + if got, want := rec.Header().Get(hash.ChecksumCRC32.Key()), mustChecksum(t, hash.ChecksumCRC32, data); got != want { + t.Fatalf("%s: different-key SSE-C response header checksum %q, want %q", instanceType, got, want) + } + if got := rec.Header().Get(xhttp.AmzChecksumType); got != xhttp.AmzChecksumTypeFullObject { + t.Fatalf("%s: different-key SSE-C response checksum type %q, want %q", instanceType, got, xhttp.AmzChecksumTypeFullObject) + } + newKeyHeaders := http.Header{ + xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}, + xhttp.AmzServerSideEncryptionCustomerKey: []string{base64.StdEncoding.EncodeToString(newKey)}, + xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{base64.StdEncoding.EncodeToString(newKeyMD5[:])}, + } + assertCopyChecksum(t, obj, bucketName, destination, hash.ChecksumCRC32, data, false, newKeyHeaders) }) } diff --git a/cmd/object-copy-metadata_test.go b/cmd/object-copy-metadata_test.go index 43470436d..399f192ee 100644 --- a/cmd/object-copy-metadata_test.go +++ b/cmd/object-copy-metadata_test.go @@ -150,6 +150,7 @@ func testAPICopyObjectSSECKeyRotationKeepsCompressionState(obj ObjectLayer, inst newMD5 := md5.Sum(newKey) putCopyChecksumSource(t, apiRouter, credentials, bucketName, object, data, map[string]string{ + xhttp.AmzChecksumCRC32: mustChecksum(t, hash.ChecksumCRC32, data), xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES, xhttp.AmzServerSideEncryptionCustomerKey: base64.StdEncoding.EncodeToString(oldKey), xhttp.AmzServerSideEncryptionCustomerKeyMD5: base64.StdEncoding.EncodeToString(oldMD5[:]), @@ -172,6 +173,7 @@ func testAPICopyObjectSSECKeyRotationKeepsCompressionState(obj ObjectLayer, inst if rec.Code != http.StatusOK { t.Fatalf("%s: key rotation failed: %d %s", instanceType, rec.Code, rec.Body.String()) } + assertCopyChecksumResponse(t, rec, hash.ChecksumCRC32, data) after, err := obj.GetObjectInfo(t.Context(), bucketName, object, ObjectOptions{}) if err != nil { t.Fatal(err) diff --git a/cmd/object-handlers-common.go b/cmd/object-handlers-common.go index a6febc122..abb4c49f8 100644 --- a/cmd/object-handlers-common.go +++ b/cmd/object-handlers-common.go @@ -353,6 +353,11 @@ func isETagEqual(left, right string) bool { // upon a success Put/Copy/CompleteMultipart/Delete requests // to activate delete only headers set delete as true func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, del bool, h http.Header) { + cs, _ := objInfo.decryptChecksums(0, h) + setPutObjHeadersWithChecksum(w, objInfo, del, cs) +} + +func setPutObjHeadersWithChecksum(w http.ResponseWriter, objInfo ObjectInfo, del bool, cs map[string]string) { // We must not use the http.Header().Set method here because some (broken) // clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive). // Therefore, we have to set the ETag directly as map entry. @@ -374,7 +379,6 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, del bool, h htt lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts()) } } - cs, _ := objInfo.decryptChecksums(0, h) hash.AddChecksumHeader(w, cs) } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index d5e868925..649136227 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -1107,6 +1107,14 @@ func cloneRequestWithoutCopyReplicationHeaders(r *http.Request) *http.Request { return clone } +func copyDestinationSSEHeaders(h http.Header) http.Header { + dst := h.Clone() + dst.Del(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) + dst.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey) + dst.Del(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) + return dst +} + // getRemoteInstanceTransport contains a roundtripper for external (not peers) servers var remoteInstanceTransport atomic.Value @@ -1816,14 +1824,16 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re origETag := objInfo.ETag objInfo.ETag = getDecryptedETag(r.Header, objInfo, false) - response := generateCopyObjectResponse(objInfo, r.Header) + dstHeaders := copyDestinationSSEHeaders(r.Header) + checksums, _ := objInfo.decryptChecksums(0, dstHeaders) + response := generateCopyObjectResponse(objInfo, checksums) encodedSuccessResponse := encodeResponse(response) if dsc := mustReplicate(ctx, dstBucket, dstObject, objInfo.getMustReplicateOptions(replication.ObjectReplicationType, dstOpts)); dsc.ReplicateAny() { scheduleReplication(ctx, objInfo, objectAPI, dsc, replication.ObjectReplicationType) } - setPutObjHeaders(w, objInfo, false, r.Header) + setPutObjHeadersWithChecksum(w, objInfo, false, checksums) // We must not use the http.Header().Set method here because some (broken) // clients expect the x-amz-copy-source-version-id header key to be literally // "x-amz-copy-source-version-id"- not in canonicalized form, preserve it.