Merge pull request #1413 from versity/sis/invalid-x-amz-content-sha256

fix: adds validation for x-amz-content-sha256 header
This commit is contained in:
Ben McClelland
2025-07-25 10:10:42 -07:00
committed by GitHub
6 changed files with 104 additions and 2 deletions
+3
View File
@@ -115,6 +115,9 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string,
}
hashPayload := ctx.Get("X-Amz-Content-Sha256")
if !utils.IsValidSh256PayloadHeader(hashPayload) {
return s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)
}
if utils.IsBigDataAction(ctx) {
// for streaming PUT actions, authorization is deferred
// until end of stream due to need to get length and
+23
View File
@@ -15,6 +15,7 @@
package utils
import (
"encoding/hex"
"fmt"
"io"
"net/http"
@@ -105,6 +106,28 @@ func IsSpecialPayload(str string) bool {
return specialValues[payloadType(str)]
}
// IsValidSh256PayloadHeader checks if the provided x-amz-content-sha256
// paylod header is valid special paylod type or a valid sh256 hash
func IsValidSh256PayloadHeader(value string) bool {
// empty header is valid
if value == "" {
return true
}
// special values are valid
if specialValues[payloadType(value)] {
return true
}
// check to be a valid sha256
if len(value) != 64 {
return false
}
// decode the string as hex
_, err := hex.DecodeString(value)
return err == nil
}
// Checks if the provided string is unsigned payload trailer type
func IsUnsignedStreamingPayload(str string) bool {
return payloadType(str) == payloadTypeStreamingUnsignedTrailer
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2024 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package utils
import "testing"
func TestIsValidSh256PayloadHeader(t *testing.T) {
tests := []struct {
name string
hash string
want bool
}{
{"empty header", "", true},
{"special payload type 1", "UNSIGNED-PAYLOAD", true},
{"special payload type 2", "STREAMING-UNSIGNED-PAYLOAD-TRAILER", true},
{"special payload type 3", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD", true},
{"special payload type 4", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER", true},
{"special payload type 5", "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD", true},
{"special payload type 6", "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER", true},
{"invalid hext", "invalid_hex", false},
{"valid hex, but not sha256", "d41d8cd98f00b204e9800998ecf8427e", false},
{"valid sh256", "9c56cc51b374bb0f2f8d55af2b34d2a6f8f7f42dd4bbcccbbf8e3279b6e1e6d4", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsValidSh256PayloadHeader(tt.hash); got != tt.want {
t.Errorf("IsValidSh256PayloadHeader() = %v, want %v", got, tt.want)
}
})
}
}
+6
View File
@@ -125,6 +125,7 @@ const (
ErrSignatureTerminationStr
ErrSignatureIncorrService
ErrContentSHA256Mismatch
ErrInvalidSHA256Paylod
ErrMissingContentLength
ErrInvalidAccessKeyID
ErrRequestNotReadyYet
@@ -530,6 +531,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidSHA256Paylod: {
Code: "InvalidArgument",
Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingContentLength: {
Code: "MissingContentLength",
Description: "You must provide the Content-Length HTTP header.",
+2
View File
@@ -31,6 +31,7 @@ func TestAuthentication(s *S3Conf) {
Authentication_invalid_date_header(s)
Authentication_date_mismatch(s)
Authentication_incorrect_payload_hash(s)
Authentication_invalid_sha256_payload_hash(s)
Authentication_incorrect_md5(s)
Authentication_signature_error_incorrect_secret_key(s)
}
@@ -904,6 +905,7 @@ func GetIntTests() IntTests {
"Authentication_invalid_date_header": Authentication_invalid_date_header,
"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_incorrect_md5": Authentication_incorrect_md5,
"Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key,
"PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm,
+27 -2
View File
@@ -462,16 +462,41 @@ func Authentication_date_mismatch(s *S3Conf) error {
})
}
func Authentication_invalid_sha256_payload_hash(s *S3Conf) error {
testName := "Authentication_invalid_sha256_payload_hash"
return authHandler(s, &authConfig{
testName: testName,
method: http.MethodPut,
body: nil,
service: "s3",
date: time.Now(),
path: "bucket/object",
}, func(req *http.Request) error {
req.Header.Set("X-Amz-Content-Sha256", "invalid_sha256")
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)); err != nil {
return err
}
return nil
})
}
func Authentication_incorrect_payload_hash(s *S3Conf) error {
testName := "Authentication_incorrect_payload_hash"
return authHandler(s, &authConfig{
testName: testName,
method: http.MethodGet,
method: http.MethodPut,
body: nil,
service: "s3",
date: time.Now(),
path: "bucket/object?tagging",
}, func(req *http.Request) error {
req.Header.Set("X-Amz-Content-Sha256", "7sa6df576dsa5f675sad67f")
req.Header.Set("X-Amz-Content-Sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b854")
resp, err := s.httpClient.Do(req)
if err != nil {