s3api: map truncated SigV4 headers to the error for the missing field

AWS answers an Authorization header missing Credential= with
InvalidArgument and one missing or malformed Signature= with
AuthorizationHeaderMalformed, instead of a generic MissingFields.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris LuandDevin committed 2026-10-06 09:16:29 -07:00
1 parent eb744e71bd
commit e068bd2167
3 files changed
+43 -5

No files matched your search

+16 -4
View File
@@ -151,8 +151,20 @@ func parseSignV4(v4Auth string) (sv signValues, aec s3err.ErrorCode) {
// Strip off the Algorithm prefix.
v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
if len(authFields) != 3 {
return sv, s3err.ErrMissingFields
malformed := len(authFields) != 3
var hasCredential, hasSignature bool
for _, field := range authFields {
field = strings.TrimSpace(field)
malformed = malformed || field == ""
hasCredential = hasCredential || strings.HasPrefix(field, "Credential=")
hasSignature = hasSignature || strings.HasPrefix(field, "Signature=")
}
if malformed {
// AWS distinguishes which required field is absent.
if !hasCredential {
return sv, s3err.ErrInvalidArgument
}
return sv, s3err.ErrAuthorizationHeaderMalformed
}
// Initialize signature version '4' structured header.
@@ -744,13 +756,13 @@ func parseCredentialHeader(credElement string) (ch credentialHeader, aec s3err.E
func parseSignature(signElement string) (string, s3err.ErrorCode) {
signFields := strings.Split(strings.TrimSpace(signElement), "=")
if len(signFields) != 2 {
return "", s3err.ErrMissingFields
return "", s3err.ErrAuthorizationHeaderMalformed
}
if signFields[0] != "Signature" {
return "", s3err.ErrMissingSignTag
}
if signFields[1] == "" {
return "", s3err.ErrMissingFields
return "", s3err.ErrAuthorizationHeaderMalformed
}
signature := signFields[1]
return signature, s3err.ErrNone
@@ -360,3 +360,29 @@ func TestPresignedPutRejectsUnsignedMetadataHeaders(t *testing.T) {
})
}
}
// TestParseSignV4MissingFields exercises how a truncated Authorization header
// maps to the AWS error code for the absent field.
func TestParseSignV4MissingFields(t *testing.T) {
valid := "AWS4-HMAC-SHA256 Credential=AKIA/20260101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=0123456789abcdef"
tests := []struct {
name string
auth string
want s3err.ErrorCode
}{
{"valid", valid, s3err.ErrNone},
{"no signature field", "AWS4-HMAC-SHA256 Credential=AKIA/20260101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date", s3err.ErrAuthorizationHeaderMalformed},
{"empty signature field", "AWS4-HMAC-SHA256 Credential=AKIA/20260101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, ", s3err.ErrAuthorizationHeaderMalformed},
{"no credential field", "AWS4-HMAC-SHA256 SignedHeaders=host;x-amz-date, Signature=0123456789abcdef", s3err.ErrInvalidArgument},
{"leading comma after dropping credential", "AWS4-HMAC-SHA256 , SignedHeaders=host;x-amz-date, Signature=0123456789abcdef", s3err.ErrInvalidArgument},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, got := parseSignV4(tt.auth)
if got != tt.want {
t.Fatalf("parseSignV4() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -486,7 +486,7 @@ var errorCodeResponse = map[ErrorCode]APIError{
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingSignTag: {
Code: "AccessDenied",
Code: "AuthorizationHeaderMalformed",
Description: "Signature header missing Signature field.",
HTTPStatusCode: http.StatusBadRequest,
},