Repository: minio/minio (language: go)

Issue:
object attributes Checksum attribute is empty for SHA1 / SHA256 multi-part uploads
## Expected Behavior

If I upload a multi-part object using --checksum-algorithm CRC32, CRC32C, SHA1, or SHA256 I expect to see a  top-level checksum that is calculated as a hash-of-hashes of the individual parts checksums.

If I then get the object attributes at a later point in time I expect to be able to get that same hash-of-hashes via the "Checksum" attribute.

## Current Behavior

If I upload a multi-part object using aws-cli s3api I can see it producing a checksum (top level hash-of-hashes checksum) for all four algorithms CRC32, CRC32C, SHA1 and SHA256.  This is the same behavior as with AWS S3.

If I use get-object-attributes on the object afterwards I can only retrieve a top-level Checksum attribute for CRC32 and CRC32C uploads, for SHA1 and SHA256 the Checksum attribute is empty.  This behavior is different than what I see on S3, where all four algorithms behave the same way, producing the hash-of-hashes in the response body of complete-multipart-upload request, and producing the expected Checksum attribute on a subsequent get-object-attributes request.

An example using CRC32 works as expected:
```
$ ./testsuite/bin/test.multipart.sh --profile s3root_test --bucket test --key hello_world.txt --source ./hello_world.txt --algorithm CRC32C
aws --profile s3root_test s3api create-multipart-upload --bucket test --key hello_world.txt --checksum-algorithm CRC32C
{
    "ChecksumAlgorithm": "CRC32C",
    "Bucket": "test",
    "Key": "hello_world.txt",
    "UploadId": "MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5LmZkNWQ3ZGI3LTQwZjMtNDc0MS1iMDdmLTEzM2FhYjI1MTNlOXgxNzIzMDYxOTA5NDYyNzM2ODc1"
}
aws --profile s3root_test s3api upload-part --bucket test --key hello_world.txt --part-number 1 --upload-id MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5LmZkNWQ3ZGI3LTQwZjMtNDc0MS1iMDdmLTEzM2FhYjI1MTNlOXgxNzIzMDYxOTA5NDYyNzM2ODc1 --body ./hello_world.txt --checksum-algorithm CRC32C
{
    "ETag": "\"bea8252ff4e80f41719ea13cdf007273\"",
    "ChecksumCRC32C": "0s3l1A=="
}
aws --profile s3root_test s3api complete-multipart-upload --bucket test --key hello_world.txt --multipart-upload Parts=[{ETag="bea8252ff4e80f41719ea13cdf007273",ChecksumCRC32C=0s3l1A==,PartNumber=1}] --upload-id MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5LmZkNWQ3ZGI3LTQwZjMtNDc0MS1iMDdmLTEzM2FhYjI1MTNlOXgxNzIzMDYxOTA5NDYyNzM2ODc1
{
    "VersionId": "b20d4c61-aebe-4c9e-afb5-2b97d37a5f66",
    "Location": "http://test.elm.stanford.edu:9000/test/hello_world.txt",
    "Bucket": "test",
    "Key": "hello_world.txt",
    "ETag": "\"a9a83095955b3d3efacc7f833a103cb2-1\"",
    "ChecksumCRC32C": "mtDgeA==-1"
}
aws --profile s3root_test s3api get-object-attributes --bucket test --key hello_world.txt --object-attributes ETag Checksum ObjectParts StorageClass ObjectSize
{
    "LastModified": "2024-08-07T20:18:31+00:00",
    "VersionId": "b20d4c61-aebe-4c9e-afb5-2b97d37a5f66",
    "ETag": "a9a83095955b3d3efacc7f833a103cb2-1",
    "Checksum": {
        "ChecksumCRC32C": "mtDgeA=="
    },
    "ObjectParts": {
        "TotalPartsCount": 1,
        "PartNumberMarker": 0,
        "NextPartNumberMarker": 1,
        "MaxParts": 10000,
        "IsTruncated": false,
        "Parts": [
            {
                "PartNumber": 1,
                "Size": 14,
                "ChecksumCRC32C": "0s3l1A=="
            }
        ]
    },
    "StorageClass": "STANDARD",
    "ObjectSize": 14
}
```

An example using SHA1 reveals an empty Checksum attribute in the get-object-attributes response:
```
$ ./testsuite/bin/test.multipart.sh --profile s3root_test --bucket test --key hello_world.txt --source ./hello_world.txt --algorithm SHA1
aws --profile s3root_test s3api create-multipart-upload --bucket test --key hello_world.txt --checksum-algorithm SHA1
{
    "ChecksumAlgorithm": "SHA1",
    "Bucket": "test",
    "Key": "hello_world.txt",
    "UploadId": "MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5Ljc1ZmYzYzU2LWQ3YmQtNGU5Yi04YzE2LTNiNjJjNWRkMTIzY3gxNzIzMDYyNjQ5MzI5NjI3MTY3"
}
aws --profile s3root_test s3api upload-part --bucket test --key hello_world.txt --part-number 1 --upload-id MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5Ljc1ZmYzYzU2LWQ3YmQtNGU5Yi04YzE2LTNiNjJjNWRkMTIzY3gxNzIzMDYyNjQ5MzI5NjI3MTY3 --body ./hello_world.txt --checksum-algorithm SHA1
{
    "ETag": "\"bea8252ff4e80f41719ea13cdf007273\"",
    "ChecksumSHA1": "YP3pwjELDUytTauNEmsEOH77ook="
}
aws --profile s3root_test s3api complete-multipart-upload --bucket test --key hello_world.txt --multipart-upload Parts=[{ETag="bea8252ff4e80f41719ea13cdf007273",ChecksumSHA1=YP3pwjELDUytTauNEmsEOH77ook=,PartNumber=1}] --upload-id MmRmYWZkY2UtZDkyNS00ZjFiLTg5YzgtNDkzZDFhN2Y0MGE5Ljc1ZmYzYzU2LWQ3YmQtNGU5Yi04YzE2LTNiNjJjNWRkMTIzY3gxNzIzMDYyNjQ5MzI5NjI3MTY3
{
    "VersionId": "2d219002-7c1f-4924-9671-830e8dc71b83",
    "Location": "http://test.elm.stanford.edu:9000/test/hello_world.txt",
    "Bucket": "test",
    "Key": "hello_world.txt",
    "ETag": "\"a9a83095955b3d3efacc7f833a103cb2-1\"",
    "ChecksumSHA1": "On8NBgbepwbfKOmCUftizAIGBuA=-1"
}
aws --profile s3root_test s3api get-object-attributes --bucket test --key hello_world.txt --object-attributes ETag Checksum ObjectParts StorageClass ObjectSize
{
    "LastModified": "2024-08-07T20:30:50+00:00",
    "VersionId": "2d219002-7c1f-4924-9671-830e8dc71b83",
    "ETag": "a9a83095955b3d3efacc7f833a103cb2-1",
    "Checksum": {},
    "ObjectParts": {
        "TotalPartsCount": 1,
        "PartNumberMarker": 0,
        "NextPartNumberMarker": 1,
        "MaxParts": 10000,
        "IsTruncated": false,
        "Parts": [
            {
                "PartNumber": 1,
                "Size": 14,
                "ChecksumSHA1": "YP3pwjELDUytTauNEmsEOH77ook="
            }
        ]
    },
    "StorageClass": "STANDARD",
    "ObjectSize": 14
}
```

If I run the SHA1 test against AWS S3 I get back a Checksum attribute.
```
$ ./test.multipart.sh --profile srcc-jrobinso --bucket test-jrobinso --key hello_world.txt --source hello_world.txt --algorithm SHA1
aws --profile srcc-jrobinso s3api create-multipart-upload --bucket test-jrobinso --key hello_world.txt --checksum-algorithm SHA1
{
    "ServerSideEncryption": "AES256",
    "ChecksumAlgorithm": "SHA1",
    "Bucket": "test-jrobinso",
    "Key": "hello_world.txt",
    "UploadId": "qG64yr_N.VNs_SL_jHCY4Uj7j7DyUupiDzrR5DlARRIGTiUKfDLebTokJpLGKICMkYFFPdvHTWDpKRN9iV.z6Kw3FNfeFarFx.JdlL1Y0jli0arLrjKDNGakBdu2X59q"
}
aws --profile srcc-jrobinso s3api upload-part --bucket test-jrobinso --key hello_world.txt --part-number 1 --upload-id qG64yr_N.VNs_SL_jHCY4Uj7j7DyUupiDzrR5DlARRIGTiUKfDLebTokJpLGKICMkYFFPdvHTWDpKRN9iV.z6Kw3FNfeFarFx.JdlL1Y0jli0arLrjKDNGakBdu2X59q --body hello_world.txt --checksum-algorithm SHA1
{
    "ServerSideEncryption": "AES256",
    "ETag": "\"bea8252ff4e80f41719ea13cdf007273\"",
    "ChecksumSHA1": "YP3pwjELDUytTauNEmsEOH77ook="
}
aws --profile srcc-jrobinso s3api complete-multipart-upload --bucket test-jrobinso --key hello_world.txt --multipart-upload Parts=[{ETag="bea8252ff4e80f41719ea13cdf007273",ChecksumSHA1=YP3pwjELDUytTauNEmsEOH77ook=,PartNumber=1}] --upload-id qG64yr_N.VNs_SL_jHCY4Uj7j7DyUupiDzrR5DlARRIGTiUKfDLebTokJpLGKICMkYFFPdvHTWDpKRN9iV.z6Kw3FNfeFarFx.JdlL1Y0jli0arLrjKDNGakBdu2X59q
{
    "ServerSideEncryption": "AES256",
    "Location": "https://test-jrobinso.s3.us-west-2.amazonaws.com/hello_world.txt",
    "Bucket": "test-jrobinso",
    "Key": "hello_world.txt",
    "ETag": "\"a9a83095955b3d3efacc7f833a103cb2-1\"",
    "ChecksumSHA1": "On8NBgbepwbfKOmCUftizAIGBuA=-1"
}
aws --profile srcc-jrobinso s3api get-object-attributes --bucket test-jrobinso --key hello_world.txt --object-attributes ETag Checksum ObjectParts StorageClass ObjectSize
{
    "LastModified": "2024-08-07T20:35:41+00:00",
    "ETag": "a9a83095955b3d3efacc7f833a103cb2-1",
    "Checksum": {
        "ChecksumSHA1": "On8NBgbepwbfKOmCUftizAIGBuA="
    },
    "ObjectParts": {
        "TotalPartsCount": 1,
        "PartNumberMarker": 0,
        "NextPartNumberMarker": 1,
        "MaxParts": 1000,
        "IsTruncated": false,
        "Parts": [
            {
                "PartNumber": 1,
                "Size": 14,
                "ChecksumSHA1": "YP3pwjELDUytTauNEmsEOH77ook="
            }
        ]
    },
    "StorageClass": "STANDARD",
    "ObjectSize": 14
}
```

## Possible Solution

It's unclear to me if this is a client usage problem where MinIO is expecting some additional metadata to be set that AWS S3 is not requiring, if MinIO is failing to store the Checksum for SHA1 / SHA256, is failing to calculate the hash-of-hashes dynamically during get-object-attributes, or if the Checksum is being stored but is simply not being returned.

## Steps to Reproduce (for bugs)

- use s3api create-multipart-upload / upload-part / complete-multipart-upload to upload a single part file using CRC32
- use s3api get-object-attributes to get the Checksum attribute
- use s3api create-multipart-upload / upload-part / complete-multipart-upload to upload a single part file using SHA1
- use s3api get-object-attributes to get the Checksum attribute

For the CRC32 the Checksum attribute is filled in, for SHA1 it is not.

An example script that automates this sequence is available in:

https://github.com/jimrobinson/test-multipart/blob/main/test.multipart.sh

It requires bash, aws-cli, jq, and tee.  Its usage is given in the examples under the Current Behavior section.

## Context

Being able to validate the expected hash-of-hashes of the individual parts is helpful when building tools to confirm that the uploaded data is the same as what we expect.  While the hash-of-hashes *is* being made available once in the response for the complete-multipart-upload call, it'd be nice to be able to refer back to that value later via get-object-attributes requests.

## Regression

Unknown

## Your Environment
* minio version RELEASE.2024-07-31T05-46-26Z (commit-id=a9dc061d847277e30c5c6918d6de6f0606f9d285)
* Linux elm-test-minio-n0 6.9.12-200.fc40.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Jul 27 15:56:15 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
* aws-cli/2.17.13 Python/3.11.9 Linux/6.9.12-200.fc40.x86_64 exe/x86_64.rocky.9


Discussion / hints:

Fix patch:
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index 16b32bf3eda66..4ff2dcc0a50c3 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -762,21 +762,14 @@ func (api objectAPIHandlers) getObjectAttributesHandler(ctx context.Context, obj
 	if _, ok := opts.ObjectAttributes[xhttp.Checksum]; ok {
 		chkSums := objInfo.decryptChecksums(0, r.Header)
 		// AWS does not appear to append part number on this API call.
-		switch {
-		case chkSums["CRC32"] != "":
-			OA.Checksum = new(objectAttributesChecksum)
-			OA.Checksum.ChecksumCRC32 = strings.Split(chkSums["CRC32"], "-")[0]
-		case chkSums["CRC32C"] != "":
-			OA.Checksum = new(objectAttributesChecksum)
-			OA.Checksum.ChecksumCRC32C = strings.Split(chkSums["CRC32C"], "-")[0]
-		case chkSums["SHA256"] != "":
-			OA.Checksum = new(objectAttributesChecksum)
-			OA.Checksum.ChecksumSHA1 = strings.Split(chkSums["SHA1"], "-")[0]
-		case chkSums["SHA1"] != "":
-			OA.Checksum = new(objectAttributesChecksum)
-			OA.Checksum.ChecksumSHA256 = strings.Split(chkSums["SHA256"], "-")[0]
+		if len(chkSums) > 0 {
+			OA.Checksum = &objectAttributesChecksum{
+				ChecksumCRC32:  strings.Split(chkSums["CRC32"], "-")[0],
+				ChecksumCRC32C: strings.Split(chkSums["CRC32C"], "-")[0],
+				ChecksumSHA1:   strings.Split(chkSums["SHA1"], "-")[0],
+				ChecksumSHA256: strings.Split(chkSums["SHA256"], "-")[0],
+			}
 		}
-
 	}
 
 	if _, ok := opts.ObjectAttributes[xhttp.ETag]; ok {


Test patch: