Merge pull request #2184 from versity/sis/empty-content-md5

fix: reject empty Content-MD5 on PUT operations
This commit is contained in:
Ben McClelland
2026-06-16 21:06:16 -07:00
committed by GitHub
4 changed files with 60 additions and 43 deletions
+8 -2
View File
@@ -33,13 +33,19 @@ import (
// the x-amz-checksum-* headers are explicitly processed by the backend.
func VerifyChecksums(streamBody bool, requireBody bool, requireChecksum bool) fiber.Handler {
return func(ctx fiber.Ctx) error {
md5sum := ctx.Get("Content-Md5")
md5Header := ctx.Request().Header.Peek("Content-Md5")
hasMD5Header := md5Header != nil
md5sum := string(md5Header)
if hasMD5Header && md5sum == "" {
return s3err.GetInvalidDigestErr(md5sum)
}
if streamBody {
// for large data actions(PutObject, UploadPart)
// only stack the md5 reader,as x-amz-checksum-*
// calculation is explicitly handled in back-end
if md5sum == "" {
if !hasMD5Header {
return nil
}
+50
View File
@@ -17,6 +17,8 @@ package integration
import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"fmt"
"net/http"
"strings"
@@ -642,6 +644,54 @@ func PutObject_invalid_website_redirect_location(s *S3Conf) error {
})
}
func PutObject_md5(s *S3Conf) error {
testName := "PutObject_md5"
return actionHandler(s, testName, func(_ *s3.Client, bucket string) error {
body := []byte("dummy data")
sum := md5.Sum(body)
contentMd5 := base64.StdEncoding.EncodeToString(sum[:])
for i, test := range []struct {
name string
md5 string
err s3err.S3Error
}{
{"empty_md5", "", s3err.GetInvalidDigestErr("")},
{"invalid_md5", "invalid_md5", s3err.GetInvalidDigestErr("invalid_md5")},
// valid base64, but invalid md5
{"invalid_md5_length", "aGVsbCBzLGRham5mamFuc2Y=", s3err.GetInvalidDigestErr("aGVsbCBzLGRham5mamFuc2Y=")},
// valid md5, but incorrect
{"incorrect_md5", "XrY7u+Ae7tCTyyK7j1rNww==", s3err.GetBadDigestErr(contentMd5, base64ToHexString("XrY7u+Ae7tCTyyK7j1rNww=="))},
{"success", contentMd5, nil},
} {
req, err := createSignedReq(http.MethodPut, s.endpoint, fmt.Sprintf("%s/obj-%d", bucket, i), s.awsID, s.awsSecret, "s3", s.awsRegion, "", body, time.Now(), map[string]string{
"Content-Md5": test.md5,
})
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
if test.err == nil {
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("test %q failed: expected response status code to be %v, instead got %v", test.name, http.StatusOK, resp.StatusCode)
}
continue
}
if err := checkHTTPResponseApiErr(resp, test.err); err != nil {
return fmt.Errorf("test %q failed: %w", test.name, err)
}
}
return nil
})
}
func PutObject_checksum_algorithm_and_header_mismatch(s *S3Conf) error {
testName := "PutObject_checksum_algorithm_and_header_mismatch"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+2 -2
View File
@@ -36,7 +36,6 @@ func TestAuthentication(ts *TestState) {
ts.Run(Authentication_date_mismatch)
ts.Run(Authentication_incorrect_payload_hash)
ts.Run(Authentication_invalid_sha256_payload_hash)
ts.Run(Authentication_md5)
ts.Run(Authentication_unsigned_required_header)
ts.Run(Authentication_unsigned_non_required_header)
ts.Run(Authentication_signature_error_incorrect_secret_key)
@@ -179,6 +178,7 @@ func TestPutObject(ts *TestState) {
ts.Run(PutObject_invalid_retain_until_date)
ts.Run(PutObject_conditional_writes)
ts.Run(PutObject_should_combine_metadata)
ts.Run(PutObject_md5)
//TODO: remove the condition after implementing checksums in azure
if !ts.conf.azureTests {
ts.Run(PutObject_checksum_algorithm_and_header_mismatch)
@@ -1362,7 +1362,6 @@ func GetIntTests() IntTests {
"Authentication_date_mismatch": Authentication_date_mismatch,
"Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash,
"Authentication_invalid_sha256_payload_hash": Authentication_invalid_sha256_payload_hash,
"Authentication_md5": Authentication_md5,
"Authentication_unsigned_required_header": Authentication_unsigned_required_header,
"Authentication_unsigned_non_required_header": Authentication_unsigned_non_required_header,
"Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key,
@@ -1402,6 +1401,7 @@ func GetIntTests() IntTests {
"PutObject_invalid_retain_until_date": PutObject_invalid_retain_until_date,
"PutObject_conditional_writes": PutObject_conditional_writes,
"PutObject_should_combine_metadata": PutObject_should_combine_metadata,
"PutObject_md5": PutObject_md5,
"PutObject_long_metadata": PutObject_long_metadata,
"PutObject_with_metadata": PutObject_with_metadata,
"PutObject_invalid_website_redirect_location": PutObject_invalid_website_redirect_location,
-39
View File
@@ -15,8 +15,6 @@
package integration
import (
"crypto/md5"
"encoding/base64"
"encoding/xml"
"fmt"
"io"
@@ -500,43 +498,6 @@ func Authentication_incorrect_payload_hash(s *S3Conf) error {
})
}
func Authentication_md5(s *S3Conf) error {
testName := "Authentication_md5"
return actionHandler(s, testName, func(_ *s3.Client, bucket string) error {
sum := md5.Sum(nil)
emptyMd5 := base64.StdEncoding.EncodeToString(sum[:])
for i, test := range []struct {
md5 string
err s3err.S3Error
}{
{"invalid_md5", s3err.GetInvalidDigestErr("invalid_md5")},
// valid base64, but invalid md5
{"aGVsbCBzLGRham5mamFuc2Y=", s3err.GetInvalidDigestErr("aGVsbCBzLGRham5mamFuc2Y=")},
// valid md5, but incorrect
{"XrY7u+Ae7tCTyyK7j1rNww==", s3err.GetBadDigestErr(emptyMd5, base64ToHexString("XrY7u+Ae7tCTyyK7j1rNww=="))},
} {
req, err := createSignedReq(http.MethodPut, s.endpoint, fmt.Sprintf("%s/obj", bucket), s.awsID, s.awsSecret, "s3", s.awsRegion, "", nil, time.Now(), map[string]string{
"Content-Md5": test.md5,
})
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
if err := checkHTTPResponseApiErr(resp, test.err); err != nil {
return fmt.Errorf("test %v failed: %w", i+1, err)
}
}
return nil
})
}
func Authentication_unsigned_required_header(s *S3Conf) error {
testName := "Authentication_unsigned_required_header"
return actionHandler(s, testName, func(_ *s3.Client, bucket string) error {