Alan Yeung пре 4 дана
родитељ
комит
72c746804c

+ 114 - 8
aws/internal/handler/s3.go

@@ -11,6 +11,7 @@ import (
 
 	"aws-sts-mock/internal/service"
 	"aws-sts-mock/internal/storage"
+	"aws-sts-mock/pkg/checksum"
 	"aws-sts-mock/pkg/s3"
 	"aws-sts-mock/pkg/sigv4"
 )
@@ -322,7 +323,28 @@ func (h *S3Handler) handlePutObject(w http.ResponseWriter, r *http.Request, buck
 		return
 	}
 
-	err = h.service.PutObject(creds.AccountID, bucketName, objectKey, r.Body)
+	// Extract checksum headers
+	checksumHeaders := extractChecksumHeaders(r)
+	validator := checksum.NewValidator(checksumHeaders)
+
+	// Validate and store the object
+	result, data, err := validator.ValidateStream(r.Body)
+	if err != nil {
+		log.Printf("Failed to read object data %s/%s for user %s: %v", bucketName, objectKey, creds.AccountID, err)
+		h.writeError(w, "InternalError", "Failed to read object data", objectKey, http.StatusInternalServerError)
+		return
+	}
+
+	// Check if validation failed
+	if !result.Valid {
+		log.Printf("Checksum validation failed for %s/%s: %s", bucketName, objectKey, result.ErrorMessage)
+		h.writeError(w, "InvalidDigest", result.ErrorMessage, objectKey, http.StatusBadRequest)
+		return
+	}
+
+	// Store the object
+	reader := strings.NewReader(string(data))
+	err = h.service.PutObject(creds.AccountID, bucketName, objectKey, reader)
 	if err != nil {
 		if err == storage.ErrBucketNotFound {
 			h.writeError(w, "NoSuchBucket", "The specified bucket does not exist", bucketName, http.StatusNotFound)
@@ -333,9 +355,15 @@ func (h *S3Handler) handlePutObject(w http.ResponseWriter, r *http.Request, buck
 		return
 	}
 
-	log.Printf("Created object: %s/%s for user: %s", bucketName, objectKey, creds.AccountID)
+	log.Printf("Created object: %s/%s for user: %s (validated with %s)",
+		bucketName, objectKey, creds.AccountID, result.ValidatedWith)
+
+	// Set response headers including checksums
+	responseHeaders := result.GetResponseHeaders()
+	for key, value := range responseHeaders {
+		w.Header().Set(key, value)
+	}
 
-	w.Header().Set("ETag", fmt.Sprintf("\"%d\"", time.Now().Unix()))
 	w.Header().Set("x-amz-request-id", generateRequestId())
 	w.WriteHeader(http.StatusOK)
 }
@@ -359,16 +387,35 @@ func (h *S3Handler) handleGetObject(w http.ResponseWriter, r *http.Request, buck
 	}
 	defer file.Close()
 
+	// Read file data to compute checksums
+	data, err := io.ReadAll(file)
+	if err != nil {
+		log.Printf("Failed to read object data %s/%s: %v", bucketName, objectKey, err)
+		h.writeError(w, "InternalError", "Failed to read object data", objectKey, http.StatusInternalServerError)
+		return
+	}
+
+	// Compute checksums for the response
+	validator := checksum.NewValidator(map[string]string{})
+	result := validator.ComputeChecksums(data)
+
 	log.Printf("Retrieved object: %s/%s for user: %s", bucketName, objectKey, creds.AccountID)
 
+	// Set standard headers
 	w.Header().Set("Content-Type", getContentType(objectKey))
 	w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size))
 	w.Header().Set("Last-Modified", info.LastModified.UTC().Format(http.TimeFormat))
-	w.Header().Set("ETag", fmt.Sprintf("\"%s\"", info.ETag))
 	w.Header().Set("x-amz-request-id", generateRequestId())
-	w.WriteHeader(http.StatusOK)
 
-	io.Copy(w, file)
+	// Set checksum headers
+	w.Header().Set("ETag", fmt.Sprintf(`"%s"`, result.MD5))
+	w.Header().Set("x-amz-checksum-crc32", result.CRC32)
+	w.Header().Set("x-amz-checksum-crc32c", result.CRC32C)
+	w.Header().Set("x-amz-checksum-sha1", result.SHA1)
+	w.Header().Set("x-amz-checksum-sha256", result.SHA256)
+
+	w.WriteHeader(http.StatusOK)
+	w.Write(data)
 }
 
 func (h *S3Handler) handleDeleteObject(w http.ResponseWriter, r *http.Request, bucketName, objectKey string) {
@@ -617,17 +664,47 @@ func (h *S3Handler) handleUploadPart(w http.ResponseWriter, r *http.Request, buc
 		return
 	}
 
+	// Extract checksum headers and validate
+	checksumHeaders := extractChecksumHeaders(r)
+	validator := checksum.NewValidator(checksumHeaders)
+
+	// Validate the part data
+	result, data, err := validator.ValidateStream(r.Body)
+	if err != nil {
+		log.Printf("Failed to read part data for upload %s: %v", uploadID, err)
+		h.writeError(w, "InternalError", "Failed to read part data", "", http.StatusInternalServerError)
+		return
+	}
+
+	// Check if validation failed
+	if !result.Valid {
+		log.Printf("Checksum validation failed for part %d of upload %s: %s",
+			partNumber, uploadID, result.ErrorMessage)
+		h.writeError(w, "InvalidDigest", result.ErrorMessage, "", http.StatusBadRequest)
+		return
+	}
+
 	// Upload the part
-	etag, err := h.service.UploadPart(uploadID, partNumber, r.Body)
+	reader := strings.NewReader(string(data))
+	etag, err := h.service.UploadPart(uploadID, partNumber, reader)
 	if err != nil {
 		log.Printf("Failed to upload part %d for upload %s: %v", partNumber, uploadID, err)
 		h.writeError(w, "InternalError", "Failed to upload part", "", http.StatusInternalServerError)
 		return
 	}
 
-	log.Printf("Uploaded part %d for upload %s, user: %s", partNumber, uploadID, creds.AccountID)
+	log.Printf("Uploaded part %d for upload %s, user: %s (validated with %s)",
+		partNumber, uploadID, creds.AccountID, result.ValidatedWith)
 
+	// Set response headers including checksums
 	w.Header().Set("ETag", etag)
+	responseHeaders := result.GetResponseHeaders()
+	for key, value := range responseHeaders {
+		if key != "ETag" { // Don't override the ETag
+			w.Header().Set(key, value)
+		}
+	}
+
 	w.Header().Set("x-amz-request-id", generateRequestId())
 	w.WriteHeader(http.StatusOK)
 }
@@ -895,3 +972,32 @@ func (h *S3Handler) handleListMultipartUploads(w http.ResponseWriter, r *http.Re
 		log.Printf("Error encoding response: %v", err)
 	}
 }
+
+func extractChecksumHeaders(r *http.Request) map[string]string {
+	headers := make(map[string]string)
+
+	// Extract all checksum-related headers
+	if val := r.Header.Get("Content-MD5"); val != "" {
+		headers["Content-MD5"] = val
+	}
+	if val := r.Header.Get("x-amz-sdk-checksum-algorithm"); val != "" {
+		headers["x-amz-sdk-checksum-algorithm"] = val
+	}
+	if val := r.Header.Get("x-amz-checksum-crc32"); val != "" {
+		headers["x-amz-checksum-crc32"] = val
+	}
+	if val := r.Header.Get("x-amz-checksum-crc32c"); val != "" {
+		headers["x-amz-checksum-crc32c"] = val
+	}
+	if val := r.Header.Get("x-amz-checksum-crc64nvme"); val != "" {
+		headers["x-amz-checksum-crc64nvme"] = val
+	}
+	if val := r.Header.Get("x-amz-checksum-sha1"); val != "" {
+		headers["x-amz-checksum-sha1"] = val
+	}
+	if val := r.Header.Get("x-amz-checksum-sha256"); val != "" {
+		headers["x-amz-checksum-sha256"] = val
+	}
+
+	return headers
+}

+ 425 - 0
aws/pkg/checksum/checksum.go

@@ -0,0 +1,425 @@
+package checksum
+
+import (
+	"crypto/md5"
+	"crypto/sha1"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+	"hash"
+	"hash/crc32"
+	"hash/crc64"
+	"io"
+)
+
+// ChecksumAlgorithm represents supported checksum algorithms
+type ChecksumAlgorithm string
+
+const (
+	AlgorithmMD5       ChecksumAlgorithm = "MD5"
+	AlgorithmCRC32     ChecksumAlgorithm = "CRC32"
+	AlgorithmCRC32C    ChecksumAlgorithm = "CRC32C"
+	AlgorithmCRC64NVME ChecksumAlgorithm = "CRC64NVME"
+	AlgorithmSHA1      ChecksumAlgorithm = "SHA1"
+	AlgorithmSHA256    ChecksumAlgorithm = "SHA256"
+)
+
+// ChecksumValidator validates data against provided checksums
+type ChecksumValidator struct {
+	// Algorithm specified in x-amz-sdk-checksum-algorithm header
+	Algorithm ChecksumAlgorithm
+
+	// Expected checksums from headers (base64 encoded)
+	ExpectedMD5       string // Content-MD5 header
+	ExpectedCRC32     string // x-amz-checksum-crc32 header
+	ExpectedCRC32C    string // x-amz-checksum-crc32c header
+	ExpectedCRC64NVME string // x-amz-checksum-crc64nvme header
+	ExpectedSHA1      string // x-amz-checksum-sha1 header
+	ExpectedSHA256    string // x-amz-checksum-sha256 header
+}
+
+// ChecksumResult contains computed checksums and validation results
+type ChecksumResult struct {
+	MD5       string // base64 encoded
+	CRC32     string // base64 encoded
+	CRC32C    string // base64 encoded
+	CRC64NVME string // base64 encoded
+	SHA1      string // base64 encoded
+	SHA256    string // base64 encoded
+
+	// Validation results
+	Valid         bool
+	ValidatedWith ChecksumAlgorithm
+	ErrorMessage  string
+}
+
+// NewValidator creates a new checksum validator from headers
+func NewValidator(headers map[string]string) *ChecksumValidator {
+	validator := &ChecksumValidator{
+		Algorithm:         ChecksumAlgorithm(headers["x-amz-sdk-checksum-algorithm"]),
+		ExpectedMD5:       headers["Content-MD5"],
+		ExpectedCRC32:     headers["x-amz-checksum-crc32"],
+		ExpectedCRC32C:    headers["x-amz-checksum-crc32c"],
+		ExpectedCRC64NVME: headers["x-amz-checksum-crc64nvme"],
+		ExpectedSHA1:      headers["x-amz-checksum-sha1"],
+		ExpectedSHA256:    headers["x-amz-checksum-sha256"],
+	}
+
+	// Normalize algorithm name
+	if validator.Algorithm == "" {
+		// If no algorithm specified but MD5 is present, use MD5
+		if validator.ExpectedMD5 != "" {
+			validator.Algorithm = AlgorithmMD5
+		}
+	}
+
+	return validator
+}
+
+// ValidateData validates data against expected checksums
+func (v *ChecksumValidator) ValidateData(data []byte) *ChecksumResult {
+	result := v.ComputeChecksums(data)
+
+	// Validate based on algorithm or any provided checksum
+	if v.Algorithm != "" {
+		result.ValidatedWith = v.Algorithm
+		result.Valid = v.validateAlgorithm(result, v.Algorithm)
+		if !result.Valid {
+			result.ErrorMessage = fmt.Sprintf("Checksum mismatch for algorithm %s", v.Algorithm)
+		}
+	} else {
+		// Try to validate with any available checksum
+		result.Valid = true // Assume valid if no checksums provided
+
+		if v.ExpectedMD5 != "" {
+			result.ValidatedWith = AlgorithmMD5
+			if !v.validateAlgorithm(result, AlgorithmMD5) {
+				result.Valid = false
+				result.ErrorMessage = "MD5 checksum mismatch"
+				return result
+			}
+		}
+
+		if v.ExpectedCRC32 != "" {
+			result.ValidatedWith = AlgorithmCRC32
+			if !v.validateAlgorithm(result, AlgorithmCRC32) {
+				result.Valid = false
+				result.ErrorMessage = "CRC32 checksum mismatch"
+				return result
+			}
+		}
+
+		if v.ExpectedCRC32C != "" {
+			result.ValidatedWith = AlgorithmCRC32C
+			if !v.validateAlgorithm(result, AlgorithmCRC32C) {
+				result.Valid = false
+				result.ErrorMessage = "CRC32C checksum mismatch"
+				return result
+			}
+		}
+
+		if v.ExpectedCRC64NVME != "" {
+			result.ValidatedWith = AlgorithmCRC64NVME
+			if !v.validateAlgorithm(result, AlgorithmCRC64NVME) {
+				result.Valid = false
+				result.ErrorMessage = "CRC64NVME checksum mismatch"
+				return result
+			}
+		}
+
+		if v.ExpectedSHA1 != "" {
+			result.ValidatedWith = AlgorithmSHA1
+			if !v.validateAlgorithm(result, AlgorithmSHA1) {
+				result.Valid = false
+				result.ErrorMessage = "SHA1 checksum mismatch"
+				return result
+			}
+		}
+
+		if v.ExpectedSHA256 != "" {
+			result.ValidatedWith = AlgorithmSHA256
+			if !v.validateAlgorithm(result, AlgorithmSHA256) {
+				result.Valid = false
+				result.ErrorMessage = "SHA256 checksum mismatch"
+				return result
+			}
+		}
+	}
+
+	return result
+}
+
+// ValidateStream validates a stream of data against expected checksums
+func (v *ChecksumValidator) ValidateStream(reader io.Reader) (*ChecksumResult, []byte, error) {
+	// Read all data
+	data, err := io.ReadAll(reader)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to read data: %w", err)
+	}
+
+	result := v.ValidateData(data)
+	return result, data, nil
+}
+
+// validateAlgorithm validates a specific algorithm
+func (v *ChecksumValidator) validateAlgorithm(result *ChecksumResult, algorithm ChecksumAlgorithm) bool {
+	switch algorithm {
+	case AlgorithmMD5:
+		return v.ExpectedMD5 == "" || v.ExpectedMD5 == result.MD5
+	case AlgorithmCRC32:
+		return v.ExpectedCRC32 == "" || v.ExpectedCRC32 == result.CRC32
+	case AlgorithmCRC32C:
+		return v.ExpectedCRC32C == "" || v.ExpectedCRC32C == result.CRC32C
+	case AlgorithmCRC64NVME:
+		return v.ExpectedCRC64NVME == "" || v.ExpectedCRC64NVME == result.CRC64NVME
+	case AlgorithmSHA1:
+		return v.ExpectedSHA1 == "" || v.ExpectedSHA1 == result.SHA1
+	case AlgorithmSHA256:
+		return v.ExpectedSHA256 == "" || v.ExpectedSHA256 == result.SHA256
+	default:
+		return false
+	}
+}
+
+// ComputeChecksums computes all supported checksums for the data
+func (v *ChecksumValidator) ComputeChecksums(data []byte) *ChecksumResult {
+	result := &ChecksumResult{
+		Valid: true,
+	}
+
+	// Compute all checksums
+	result.MD5 = computeMD5(data)
+	result.CRC32 = computeCRC32(data)
+	result.CRC32C = computeCRC32C(data)
+	result.CRC64NVME = computeCRC64NVME(data)
+	result.SHA1 = computeSHA1(data)
+	result.SHA256 = computeSHA256(data)
+
+	return result
+}
+
+// GetResponseHeaders returns the appropriate checksum headers for the response
+func (r *ChecksumResult) GetResponseHeaders() map[string]string {
+	headers := make(map[string]string)
+
+	// Always include ETag (MD5)
+	headers["ETag"] = fmt.Sprintf(`"%s"`, r.MD5)
+
+	// Include checksum headers based on what was validated
+	switch r.ValidatedWith {
+	case AlgorithmCRC32:
+		headers["x-amz-checksum-crc32"] = r.CRC32
+		headers["x-amz-checksum-type"] = "COMPOSITE" // For multipart uploads
+	case AlgorithmCRC32C:
+		headers["x-amz-checksum-crc32c"] = r.CRC32C
+		headers["x-amz-checksum-type"] = "COMPOSITE"
+	case AlgorithmCRC64NVME:
+		headers["x-amz-checksum-crc64nvme"] = r.CRC64NVME
+		headers["x-amz-checksum-type"] = "COMPOSITE"
+	case AlgorithmSHA1:
+		headers["x-amz-checksum-sha1"] = r.SHA1
+		headers["x-amz-checksum-type"] = "COMPOSITE"
+	case AlgorithmSHA256:
+		headers["x-amz-checksum-sha256"] = r.SHA256
+		headers["x-amz-checksum-type"] = "COMPOSITE"
+	}
+
+	return headers
+}
+
+// Helper functions to compute individual checksums
+
+func computeMD5(data []byte) string {
+	h := md5.New()
+	h.Write(data)
+	return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func computeCRC32(data []byte) string {
+	h := crc32.NewIEEE()
+	h.Write(data)
+	sum := h.Sum32()
+	// Convert to 4-byte array
+	bytes := []byte{
+		byte(sum >> 24),
+		byte(sum >> 16),
+		byte(sum >> 8),
+		byte(sum),
+	}
+	return base64.StdEncoding.EncodeToString(bytes)
+}
+
+func computeCRC32C(data []byte) string {
+	// CRC32C uses Castagnoli polynomial
+	table := crc32.MakeTable(crc32.Castagnoli)
+	h := crc32.New(table)
+	h.Write(data)
+	sum := h.Sum32()
+	// Convert to 4-byte array
+	bytes := []byte{
+		byte(sum >> 24),
+		byte(sum >> 16),
+		byte(sum >> 8),
+		byte(sum),
+	}
+	return base64.StdEncoding.EncodeToString(bytes)
+}
+
+func computeCRC64NVME(data []byte) string {
+	// CRC64 with NVME polynomial
+	// Using ECMA polynomial as approximation (Go doesn't have NVME built-in)
+	table := crc64.MakeTable(crc64.ECMA)
+	h := crc64.New(table)
+	h.Write(data)
+	sum := h.Sum64()
+	// Convert to 8-byte array
+	bytes := []byte{
+		byte(sum >> 56),
+		byte(sum >> 48),
+		byte(sum >> 40),
+		byte(sum >> 32),
+		byte(sum >> 24),
+		byte(sum >> 16),
+		byte(sum >> 8),
+		byte(sum),
+	}
+	return base64.StdEncoding.EncodeToString(bytes)
+}
+
+func computeSHA1(data []byte) string {
+	h := sha1.New()
+	h.Write(data)
+	return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func computeSHA256(data []byte) string {
+	h := sha256.New()
+	h.Write(data)
+	return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+// StreamingValidator allows computing checksums while streaming data
+type StreamingValidator struct {
+	md5Hash       hash.Hash
+	crc32Hash     hash.Hash32
+	crc32cHash    hash.Hash32
+	crc64nvmeHash hash.Hash64
+	sha1Hash      hash.Hash
+	sha256Hash    hash.Hash
+
+	bytesWritten int64
+}
+
+// NewStreamingValidator creates a new streaming validator
+func NewStreamingValidator() *StreamingValidator {
+	return &StreamingValidator{
+		md5Hash:       md5.New(),
+		crc32Hash:     crc32.NewIEEE(),
+		crc32cHash:    crc32.New(crc32.MakeTable(crc32.Castagnoli)),
+		crc64nvmeHash: crc64.New(crc64.MakeTable(crc64.ECMA)),
+		sha1Hash:      sha1.New(),
+		sha256Hash:    sha256.New(),
+	}
+}
+
+// Write implements io.Writer
+func (sv *StreamingValidator) Write(p []byte) (n int, err error) {
+	sv.md5Hash.Write(p)
+	sv.crc32Hash.Write(p)
+	sv.crc32cHash.Write(p)
+	sv.crc64nvmeHash.Write(p)
+	sv.sha1Hash.Write(p)
+	sv.sha256Hash.Write(p)
+	sv.bytesWritten += int64(len(p))
+	return len(p), nil
+}
+
+// GetResult returns the computed checksums
+func (sv *StreamingValidator) GetResult() *ChecksumResult {
+	result := &ChecksumResult{
+		MD5:    base64.StdEncoding.EncodeToString(sv.md5Hash.Sum(nil)),
+		SHA1:   base64.StdEncoding.EncodeToString(sv.sha1Hash.Sum(nil)),
+		SHA256: base64.StdEncoding.EncodeToString(sv.sha256Hash.Sum(nil)),
+	}
+
+	// CRC32
+	sum32 := sv.crc32Hash.Sum32()
+	result.CRC32 = base64.StdEncoding.EncodeToString([]byte{
+		byte(sum32 >> 24), byte(sum32 >> 16), byte(sum32 >> 8), byte(sum32),
+	})
+
+	// CRC32C
+	sum32c := sv.crc32cHash.Sum32()
+	result.CRC32C = base64.StdEncoding.EncodeToString([]byte{
+		byte(sum32c >> 24), byte(sum32c >> 16), byte(sum32c >> 8), byte(sum32c),
+	})
+
+	// CRC64NVME
+	sum64 := sv.crc64nvmeHash.Sum64()
+	result.CRC64NVME = base64.StdEncoding.EncodeToString([]byte{
+		byte(sum64 >> 56), byte(sum64 >> 48), byte(sum64 >> 40), byte(sum64 >> 32),
+		byte(sum64 >> 24), byte(sum64 >> 16), byte(sum64 >> 8), byte(sum64),
+	})
+
+	return result
+}
+
+// BytesWritten returns the total bytes written
+func (sv *StreamingValidator) BytesWritten() int64 {
+	return sv.bytesWritten
+}
+
+// ValidateWithExpected validates the computed checksums against expected values
+func (sv *StreamingValidator) ValidateWithExpected(validator *ChecksumValidator) *ChecksumResult {
+	result := sv.GetResult()
+
+	// Check each expected checksum
+	if validator.ExpectedMD5 != "" && validator.ExpectedMD5 != result.MD5 {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmMD5
+		result.ErrorMessage = "MD5 checksum mismatch"
+		return result
+	}
+
+	if validator.ExpectedCRC32 != "" && validator.ExpectedCRC32 != result.CRC32 {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmCRC32
+		result.ErrorMessage = "CRC32 checksum mismatch"
+		return result
+	}
+
+	if validator.ExpectedCRC32C != "" && validator.ExpectedCRC32C != result.CRC32C {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmCRC32C
+		result.ErrorMessage = "CRC32C checksum mismatch"
+		return result
+	}
+
+	if validator.ExpectedCRC64NVME != "" && validator.ExpectedCRC64NVME != result.CRC64NVME {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmCRC64NVME
+		result.ErrorMessage = "CRC64NVME checksum mismatch"
+		return result
+	}
+
+	if validator.ExpectedSHA1 != "" && validator.ExpectedSHA1 != result.SHA1 {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmSHA1
+		result.ErrorMessage = "SHA1 checksum mismatch"
+		return result
+	}
+
+	if validator.ExpectedSHA256 != "" && validator.ExpectedSHA256 != result.SHA256 {
+		result.Valid = false
+		result.ValidatedWith = AlgorithmSHA256
+		result.ErrorMessage = "SHA256 checksum mismatch"
+		return result
+	}
+
+	result.Valid = true
+	if validator.Algorithm != "" {
+		result.ValidatedWith = validator.Algorithm
+	}
+
+	return result
+}

+ 50 - 74
aws/pkg/sigv4/sigv4.go

@@ -45,8 +45,6 @@ var mockCredentials = map[string]AWSCredentials{
 
 // ValidateSigV4Middleware validates AWS Signature Version 4
 func ValidateSigV4Middleware(next http.HandlerFunc) http.HandlerFunc {
-	testHMAC()
-
 	return func(w http.ResponseWriter, r *http.Request) {
 		// Read the body
 		bodyBytes, err := io.ReadAll(r.Body)
@@ -70,12 +68,10 @@ func ValidateSigV4Middleware(next http.HandlerFunc) http.HandlerFunc {
 			return
 		}
 
-		log.Println(sigV4)
-		log.Println(sigV4.AccessKeyID)
 		// Validate credential
 		creds, ok := mockCredentials[sigV4.AccessKeyID]
 		if !ok {
-			writeAuthError(w, "InvalidClientTokenId", "The security token included in the request is invalid 1")
+			writeAuthError(w, "InvalidClientTokenId", "The security token included in the request is invalid")
 			return
 		}
 
@@ -111,18 +107,20 @@ func ValidateSigV4Middleware(next http.HandlerFunc) http.HandlerFunc {
 		}
 
 		// Compare signatures
-		log.Println(expectedSig + " " + sigV4.Signature)
+		//log.Printf("Expected signature: %s", expectedSig)
+		//log.Printf("Provided signature: %s", sigV4.Signature)
+
 		if expectedSig != sigV4.Signature {
-			//writeAuthError(w, "SignatureDoesNotMatch",
-			//"The request signature we calculated does not match the signature you provided")
-			//return
+			writeAuthError(w, "SignatureDoesNotMatch",
+				"The request signature we calculated does not match the signature you provided")
+			return
 		}
 
 		// Validate session token if present
 		if creds.SessionToken != "" {
 			reqToken := r.Header.Get("X-Amz-Security-Token")
 			if reqToken != creds.SessionToken {
-				writeAuthError(w, "InvalidClientTokenId", "The security token included in the request is invalid 2")
+				writeAuthError(w, "InvalidClientTokenId", "The security token included in the request is invalid")
 				return
 			}
 		}
@@ -190,48 +188,18 @@ func parseSigV4Header(authHeader string) (*sigV4Components, error) {
 	return sig, nil
 }
 
-func testHMAC() {
-	fmt.Println("=== Testing with quotes in secret key ===")
-
-	// The secret key WITH quotes as it appears in environment variable
-	secretKeyWithQuotes := "\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\""
-
-	kDate := hmacSHA256([]byte("AWS4"+secretKeyWithQuotes), "20251016")
-	fmt.Println("kDate with quotes:", hex.EncodeToString(kDate))
-
-	kRegion := hmacSHA256(kDate, "us-west-2")
-	kService := hmacSHA256(kRegion, "sts")
-	kSigning := hmacSHA256(kService, "aws4_request")
-
-	fmt.Println("Signing key with quotes:", hex.EncodeToString(kSigning))
-
-	stringToSign := "AWS4-HMAC-SHA256\n20251016T185909Z\n20251016/us-west-2/sts/aws4_request\nb1eade88f71175b2790108f7015d7ded65f982153aad655da6d50d5976ae14df"
-
-	signature := hmacSHA256(kSigning, stringToSign)
-
-	fmt.Println("Our signature:        ", hex.EncodeToString(signature))
-	fmt.Println("AWS CLI's signature:  ", "c434c79f1567cf160a5cb88d2ad710a5bc524bbb6d481ce51b4b63787f9c21a4")
-	fmt.Println("Match:", hex.EncodeToString(signature) == "c434c79f1567cf160a5cb88d2ad710a5bc524bbb6d481ce51b4b63787f9c21a4")
-}
-
 func calculateSignature(r *http.Request, body []byte, creds AWSCredentials, sigV4 *sigV4Components) (string, error) {
 	amzDate := r.Header.Get("X-Amz-Date")
 	canonicalRequest := buildCanonicalRequest(r, body, sigV4.SignedHeaders)
 	stringToSign := buildStringToSign(canonicalRequest, sigV4, amzDate)
 
-	signingKey := deriveSigningKey(creds.SecretAccessKey, sigV4.Date, sigV4.Region, sigV4.Service)
+	log.Printf("String to sign:\n%s", stringToSign)
 
-	log.Println("=== Final HMAC Input ===")
-	log.Printf("Key (hex): %s\n", hex.EncodeToString(signingKey))
-	log.Printf("Data (bytes): %v\n", []byte(stringToSign))
-	log.Printf("Data (hex): %s\n", hex.EncodeToString([]byte(stringToSign)))
+	signingKey := deriveSigningKey(creds.SecretAccessKey, sigV4.Date, sigV4.Region, sigV4.Service)
 
 	signature := hmacSHA256(signingKey, stringToSign)
 	finalSig := hex.EncodeToString(signature)
 
-	log.Printf("Signature (hex): %s\n", finalSig)
-	log.Println("=== End Final HMAC Input ===")
-
 	return finalSig, nil
 }
 
@@ -239,7 +207,8 @@ func buildCanonicalRequest(r *http.Request, body []byte, signedHeaders []string)
 	// Method
 	method := r.Method
 
-	// Canonical URI
+	// Canonical URI - Extract from RequestURI to get the path before any routing modifications
+	// RequestURI includes the full path, e.g., "/my-test-bucket/4.psd?uploads"
 	canonicalURI := r.URL.Path
 	if canonicalURI == "" {
 		canonicalURI = "/"
@@ -266,6 +235,18 @@ func buildCanonicalRequest(r *http.Request, body []byte, signedHeaders []string)
 		payloadHash,
 	)
 
+	/*
+		log.Printf("=== Canonical Request ===")
+		log.Printf("Method: %s", method)
+		log.Printf("Canonical URI: %s", canonicalURI)
+		log.Printf("Canonical Query String: %s", canonicalQueryString)
+		log.Printf("Canonical Headers:\n%s", canonicalHeaders)
+		log.Printf("Signed Headers: %s", signedHeadersStr)
+		log.Printf("Payload Hash: %s", payloadHash)
+		log.Printf("Full Canonical Request:\n%s", canonicalRequest)
+		log.Printf("========================")
+	*/
+
 	return canonicalRequest
 }
 
@@ -274,13 +255,34 @@ func buildCanonicalQueryString(r *http.Request) string {
 		return ""
 	}
 
-	// Split the raw query string by '&'
-	params := strings.Split(r.URL.RawQuery, "&")
+	// Parse the query parameters properly
+	query := r.URL.Query()
+
+	// Get all keys and sort them
+	keys := make([]string, 0, len(query))
+	for k := range query {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
 
-	// Sort the parameters
-	sort.Strings(params)
+	// Build the canonical query string
+	var params []string
+	for _, key := range keys {
+		values := query[key]
+		// Sort values for this key
+		sort.Strings(values)
+
+		for _, value := range values {
+			if value == "" {
+				// Empty value - add just key with equals sign
+				params = append(params, key+"=")
+			} else {
+				// Non-empty value
+				params = append(params, key+"="+value)
+			}
+		}
+	}
 
-	// Join them back with '&'
 	return strings.Join(params, "&")
 }
 
@@ -293,7 +295,6 @@ func buildCanonicalHeaders(r *http.Request, signedHeaders []string) string {
 		// Special handling for Host header
 		if headerLower == "host" {
 			headers[headerLower] = r.Host
-			log.Printf("Header '%s' = '%s'", headerLower, r.Host)
 			continue
 		}
 
@@ -304,7 +305,6 @@ func buildCanonicalHeaders(r *http.Request, signedHeaders []string) string {
 				trimmedValues[i] = strings.TrimSpace(v)
 			}
 			headers[headerLower] = strings.Join(trimmedValues, ",")
-			log.Printf("Header '%s' = '%s'", headerLower, headers[headerLower])
 		}
 	}
 
@@ -328,12 +328,7 @@ func buildCanonicalHeaders(r *http.Request, signedHeaders []string) string {
 }
 
 func buildStringToSign(canonicalRequest string, sigV4 *sigV4Components, amzDate string) string {
-	log.Println("=== Canonical Request (raw) ===")
-	log.Printf("%q\n", canonicalRequest) // Using %q to show escape characters
-	log.Println("=== End Canonical Request (raw) ===")
-
 	canonicalRequestHash := sha256Hash([]byte(canonicalRequest))
-	log.Println("Canonical Request Hash:", canonicalRequestHash)
 
 	stringToSign := fmt.Sprintf("AWS4-HMAC-SHA256\n%s\n%s\n%s",
 		amzDate,
@@ -341,33 +336,14 @@ func buildStringToSign(canonicalRequest string, sigV4 *sigV4Components, amzDate
 		canonicalRequestHash,
 	)
 
-	log.Println("=== String to Sign ===")
-	log.Println(stringToSign)
-	log.Println("=== End String to Sign ===")
-
 	return stringToSign
 }
 
 func deriveSigningKey(secretKey, date, region, service string) []byte {
-	log.Println("=== Deriving Signing Key ===")
-	log.Println("Secret Key:", secretKey)
-	log.Println("Date:", date)
-	log.Println("Region:", region)
-	log.Println("Service:", service)
-
 	kDate := hmacSHA256([]byte("AWS4"+secretKey), date)
-	log.Println("kDate:", hex.EncodeToString(kDate))
-
 	kRegion := hmacSHA256(kDate, region)
-	log.Println("kRegion:", hex.EncodeToString(kRegion))
-
 	kService := hmacSHA256(kRegion, service)
-	log.Println("kService:", hex.EncodeToString(kService))
-
 	kSigning := hmacSHA256(kService, "aws4_request")
-	log.Println("kSigning:", hex.EncodeToString(kSigning))
-	log.Println("=== End Deriving Signing Key ===")
-
 	return kSigning
 }
 

BIN
aws/output.txt → aws/uploads/123456789012/my-test-bucket/DSC01472 copy.jpg