diff --git a/weed/s3api/chunked_reader_v4_test.go b/weed/s3api/chunked_reader_v4_test.go index b75a9aa86..48650c36b 100644 --- a/weed/s3api/chunked_reader_v4_test.go +++ b/weed/s3api/chunked_reader_v4_test.go @@ -234,13 +234,10 @@ func TestSignedStreamingUpload(t *testing.T) { assert.Equal(t, chunk1Data+chunk2Data, string(data)) } -// TestSignedStreamingUploadWithTrailer tests streaming uploads with signed chunks and trailers -// This tests the STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER content-sha256 header value -// which is used by AWS SDK v2 when checksum validation is enabled -func TestSignedStreamingUploadWithTrailer(t *testing.T) { - iam := setupIam() - - // Create a simple streaming upload with 1 chunk and a trailer +// createTrailerStreamingRequest creates a streaming upload request with trailer for testing. +// If useValidTrailerSignature is true, uses a correctly calculated trailer signature; +// otherwise uses an intentionally wrong signature for negative testing. +func createTrailerStreamingRequest(t *testing.T, useValidTrailerSignature bool) (*http.Request, string) { chunk1Data := "hello world\n" // Use current time for signatures @@ -292,17 +289,23 @@ func TestSignedStreamingUploadWithTrailer(t *testing.T) { checksum := crcWriter.Sum(nil) base64EncodedChecksum := base64.StdEncoding.EncodeToString(checksum) - // Calculate trailer signature - // The canonical trailer content uses \n for signing (per AWS SigV4 spec) - trailerCanonical := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\n" - trailerHash := getSHA256Hash([]byte(trailerCanonical)) - trailerStringToSign := "AWS4-HMAC-SHA256-TRAILER\n" + amzDate + "\n" + scope + "\n" + - finalSignature + "\n" + trailerHash - trailerSignature := getSignature(signingKey, trailerStringToSign) - // The on-wire trailer format uses \r\n (HTTP/aws-chunked convention) trailerOnWire := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\r\n" + // Calculate or use wrong trailer signature + var trailerSignature string + if useValidTrailerSignature { + // The canonical trailer content uses \n for signing (per AWS SigV4 spec) + trailerCanonical := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\n" + trailerHash := getSHA256Hash([]byte(trailerCanonical)) + trailerStringToSign := "AWS4-HMAC-SHA256-TRAILER\n" + amzDate + "\n" + scope + "\n" + + finalSignature + "\n" + trailerHash + trailerSignature = getSignature(signingKey, trailerStringToSign) + } else { + // Intentionally wrong signature for negative testing + trailerSignature = "0000000000000000000000000000000000000000000000000000000000000000" + } + // Build the chunked payload with trailer and trailer signature payload := fmt.Sprintf("c;chunk-signature=%s\r\n%s\r\n", chunk1Signature, chunk1Data) + fmt.Sprintf("0;chunk-signature=%s\r\n", finalSignature) + @@ -326,6 +329,16 @@ func TestSignedStreamingUploadWithTrailer(t *testing.T) { defaultAccessKeyId, scope, signedHeaders, seedSignature) req.Header.Set("Authorization", authHeader) + return req, chunk1Data +} + +// TestSignedStreamingUploadWithTrailer tests streaming uploads with signed chunks and trailers +// This tests the STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER content-sha256 header value +// which is used by AWS SDK v2 when checksum validation is enabled +func TestSignedStreamingUploadWithTrailer(t *testing.T) { + iam := setupIam() + req, expectedData := createTrailerStreamingRequest(t, true) + // Test the chunked reader reader, errCode := iam.newChunkedReader(req) assert.Equal(t, s3err.ErrNone, errCode) @@ -334,94 +347,14 @@ func TestSignedStreamingUploadWithTrailer(t *testing.T) { // Read and verify the payload data, err := io.ReadAll(reader) assert.NoError(t, err) - assert.Equal(t, chunk1Data, string(data)) + assert.Equal(t, expectedData, string(data)) } // TestSignedStreamingUploadWithTrailerInvalidSignature tests that invalid trailer signatures are rejected // This is a negative test case to ensure trailer signature validation is working func TestSignedStreamingUploadWithTrailerInvalidSignature(t *testing.T) { iam := setupIam() - - // Create a simple streaming upload with 1 chunk and a trailer - chunk1Data := "hello world\n" - - // Use current time for signatures - now := time.Now().UTC() - amzDate := now.Format(iso8601Format) - dateStamp := now.Format(yyyymmdd) - - // Calculate seed signature - scope := dateStamp + "/" + defaultRegion + "/s3/aws4_request" - - // Build canonical request for seed signature - hashedPayload := "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER" - canonicalHeaders := "content-encoding:aws-chunked\n" + - "host:s3.amazonaws.com\n" + - "x-amz-content-sha256:" + hashedPayload + "\n" + - "x-amz-date:" + amzDate + "\n" + - "x-amz-decoded-content-length:12\n" + - "x-amz-trailer:x-amz-checksum-crc32\n" - signedHeaders := "content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-trailer" - - canonicalRequest := "PUT\n" + - "/test-bucket/test-object\n" + - "\n" + - canonicalHeaders + "\n" + - signedHeaders + "\n" + - hashedPayload - - canonicalRequestHash := getSHA256Hash([]byte(canonicalRequest)) - stringToSign := "AWS4-HMAC-SHA256\n" + amzDate + "\n" + scope + "\n" + canonicalRequestHash - - signingKey := getSigningKey(defaultSecretAccessKey, dateStamp, defaultRegion, "s3") - seedSignature := getSignature(signingKey, stringToSign) - - // Calculate chunk signatures - chunk1Hash := getSHA256Hash([]byte(chunk1Data)) - chunk1StringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" + - seedSignature + "\n" + emptySHA256 + "\n" + chunk1Hash - chunk1Signature := getSignature(signingKey, chunk1StringToSign) - - // Final chunk (0 bytes) - finalStringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" + - chunk1Signature + "\n" + emptySHA256 + "\n" + emptySHA256 - finalSignature := getSignature(signingKey, finalStringToSign) - - // Calculate CRC32 checksum for trailer - crcWriter := crc32.NewIEEE() - _, crcErr := crcWriter.Write([]byte(chunk1Data)) - assert.NoError(t, crcErr) - checksum := crcWriter.Sum(nil) - base64EncodedChecksum := base64.StdEncoding.EncodeToString(checksum) - - // The on-wire trailer format uses \r\n (HTTP/aws-chunked convention) - trailerOnWire := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\r\n" - - // Use an INTENTIONALLY WRONG trailer signature - wrongTrailerSignature := "0000000000000000000000000000000000000000000000000000000000000000" - - // Build the chunked payload with trailer and WRONG trailer signature - payload := fmt.Sprintf("c;chunk-signature=%s\r\n%s\r\n", chunk1Signature, chunk1Data) + - fmt.Sprintf("0;chunk-signature=%s\r\n", finalSignature) + - trailerOnWire + - "x-amz-trailer-signature:" + wrongTrailerSignature + "\r\n" + - "\r\n" - - // Create the request - req, err := http.NewRequest("PUT", "http://s3.amazonaws.com/test-bucket/test-object", - bytes.NewReader([]byte(payload))) - assert.NoError(t, err) - - req.Header.Set("Host", "s3.amazonaws.com") - req.Header.Set("x-amz-date", amzDate) - req.Header.Set("x-amz-content-sha256", hashedPayload) - req.Header.Set("Content-Encoding", "aws-chunked") - req.Header.Set("x-amz-decoded-content-length", "12") - req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32") - - authHeader := fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", - defaultAccessKeyId, scope, signedHeaders, seedSignature) - req.Header.Set("Authorization", authHeader) + req, expectedData := createTrailerStreamingRequest(t, false) // Test the chunked reader - it should be created successfully reader, errCode := iam.newChunkedReader(req) @@ -438,7 +371,7 @@ func TestSignedStreamingUploadWithTrailerInvalidSignature(t *testing.T) { assert.Contains(t, err.Error(), "signature", "Error should indicate signature mismatch") } else { // If no error, content should still be correct (trailer sig validation not yet implemented) - assert.Equal(t, chunk1Data, string(data)) + assert.Equal(t, expectedData, string(data)) } }