mirror of
https://github.com/versity/versitygw.git
synced 2026-07-20 15:02:23 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user