package handler import ( "bytes" "context" "fmt" "io" "net/http" "strconv" "strings" "testing" "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) func TestSigV4AChunkedReader(t *testing.T) { t.Run("with trailers", func(t *testing.T) { accessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6" secretKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e" chunk1 := "Testing with the {sdk-java}" body := "1b;chunk-signature=3045022100956ca03d2166100b455b532de542892f73925fbcea2f6498674a39a61bb4860902202977c1d47aea548d434540f89640ce97e605d18353cbbd75a619874f02e3dd22**\r\n" + chunk1 + "\r\n0;chunk-signature=304502210097dcc1721675469910ef8712fc2af0678eb90c12216dd6228c6b621fb6f805a0022047d27d21ae2af8a8172f2ef83c81ce9d4746aa88fc9ee0ca783eaa5e71aaef6c**\r\n" + "x-amz-checksum-crc32:Np6zMg==\r\n" + "x-amz-trailer-signature:304502200ecacd9aa2c432af5a2327c22a2ff9b32f44ab8559de00309219aef105eaaac102210092cbc0e78c4bcd56490a73da8ceed1934be80f3affeffb14d8c743fc292dda4f**\r\n\r\n" reqBody := bytes.NewBufferString(body) req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody) require.NoError(t, err) req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32") signature := "3045022100ddbc6ab11785d7f23d299de7db97379116f543377a44e38170a4e43b38b0d62b02201d8dca13c67f04f45491345152db4b704768eb8bb89b5215fd59bb4a4d9d7b61" signingTime, err := time.Parse("20060102T150405Z", "20250203T144621Z") require.NoError(t, err) key, err := keys.NewPrivateKey() require.NoError(t, err) accessBox, err := newTestAccessBox(key) require.NoError(t, err) accessBox.Gate.SecretKey = secretKey ctx := middleware.SetBox(req.Context(), &middleware.Box{ AccessBox: accessBox, AuthHeaders: &middleware.AuthHeader{ AccessKeyID: accessKeyID, SignatureV4: signature, }, ClientTime: signingTime, }) req = req.WithContext(ctx) r, err := newSignV4aChunkedReader(req) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, chunk1, string(data)) }) t.Run("without trailers", func(t *testing.T) { accessKeyID := "2XEbqH4M3ym7a3E3esxfZ2gRLnMwDXrCN4y1SkQg5fHa09sThVmVL3EE6xeKsyMzaqu5jPi41YCaVbnwbwCTF3bx1" secretKey := "00637f53f842573aaa06c2164c598973cd986880987111416cf71f1619def537" chunk1 := "Testing with the {sdk-java}" reqBody := bytes.NewBufferString("1b;chunk-signature=3045022100b63692a1b20759bdabd342011823427a8952df75c93174d98ad043abca8052e002201695228a91ba986171b8d0ad20856d3d94ca3614d0a90a50a531ba8e52447b9b**\r\n") _, err := reqBody.WriteString(chunk1) require.NoError(t, err) _, err = reqBody.WriteString("\r\n0;chunk-signature=30440220455885a2d4e9f705256ca6b0a5a22f7f784780ccbd1c0a371e5db3059c91745b022073259dd44746cbd63261d628a04d25be5a32a974c077c5c2d83c8157fb323b9f****\r\n\r\n") require.NoError(t, err) req, err := http.NewRequest("PUT", "http://localhost:8084/test/tmp", reqBody) require.NoError(t, err) signature := "30440220574244c5ff5deba388c4e3b0541a42113179b6839b3e6b4212d255a118fa9089022056f7b9b72c93f67dbcd25fe9ca67950b5913fc00bb7a62bc276c21e828c0b6c7" signingTime, err := time.Parse("20060102T150405Z", "20240904T133253Z") require.NoError(t, err) key, err := keys.NewPrivateKey() require.NoError(t, err) accessBox, err := newTestAccessBox(key) require.NoError(t, err) accessBox.Gate.SecretKey = secretKey ctx := middleware.SetBox(req.Context(), &middleware.Box{ AccessBox: accessBox, AuthHeaders: &middleware.AuthHeader{ AccessKeyID: accessKeyID, SignatureV4: signature, }, ClientTime: signingTime, }) req = req.WithContext(ctx) r, err := newSignV4aChunkedReader(req) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, chunk1, string(data)) }) } func TestSigV4ChunkedReader(t *testing.T) { accessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6" secretKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e" signature := "b740b3b2a08c541c3fc4bd155a448e25408b509a29af98a86356b894930b93e8" signingTime, err := time.Parse("20060102T150405Z", "20250203T134442Z") require.NoError(t, err) key, err := keys.NewPrivateKey() require.NoError(t, err) accessBox, err := newTestAccessBox(key) require.NoError(t, err) accessBox.Gate.SecretKey = secretKey setBoxFn := func(ctx context.Context) context.Context { return middleware.SetBox(ctx, &middleware.Box{ AccessBox: accessBox, AuthHeaders: &middleware.AuthHeader{ AccessKeyID: accessKeyID, SignatureV4: signature, Region: "us-east-1", }, ClientTime: signingTime, }) } chunk1 := "Testing with the {sdk-java}" t.Run("with trailers", func(t *testing.T) { body := "1b;chunk-signature=a6a9be5fff05db0b542aedb2203d892b4162250885d06b1422b173ee0ea92ba5\r\n" + chunk1 + "\r\n0;chunk-signature=31afd083a57c416c46afaf101649d7f0c6c0627cfa60c0f93d1f7ea84396ee42\r\n" + "x-amz-checksum-crc32:Np6zMg==\r\n" + "x-amz-trailer-signature:40ec0046ac730fa27a1451d00d849056c49553ee753f5d158306d05671a42125\r\n\r\n" reqBody := bytes.NewBufferString(body) req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody) require.NoError(t, err) req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32") req = req.WithContext(setBoxFn(req.Context())) r, err := newSignV4ChunkedReader(req) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, chunk1, string(data)) }) t.Run("without trailers", func(t *testing.T) { body := "1b;chunk-signature=a6a9be5fff05db0b542aedb2203d892b4162250885d06b1422b173ee0ea92ba5\r\n" + chunk1 + "\r\n0;chunk-signature=31afd083a57c416c46afaf101649d7f0c6c0627cfa60c0f93d1f7ea84396ee42\r\n\r\n" reqBody := bytes.NewBufferString(body) req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody) require.NoError(t, err) req = req.WithContext(setBoxFn(req.Context())) r, err := newSignV4ChunkedReader(req) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, chunk1, string(data)) }) } func TestUnsignedChunkReader(t *testing.T) { chunk1 := "chunk1" chunk2 := "chunk2" t.Run("with trailer", func(t *testing.T) { chunks := []string{chunk1, chunk2} trailer := map[string]string{"x-amz-checksum-crc64nvme": "q1EYl4rI0TU="} body, expected := getChunkedBody(t, chunks, trailer) r, err := newUnsignedChunkedReader(body) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, expected, string(data)) require.EqualValues(t, trailer, r.TrailerHeaders()) }) t.Run("without trailer", func(t *testing.T) { chunks := []string{chunk1, chunk2} body, expected := getChunkedBody(t, chunks, nil) r, err := newUnsignedChunkedReader(body) require.NoError(t, err) data, err := io.ReadAll(r) require.NoError(t, err) require.Equal(t, expected, string(data)) }) } func getChunkedBody(t *testing.T, chunks []string, trailers map[string]string) (*bytes.Buffer, string) { res := bytes.NewBufferString("") for i, chunk := range chunks { meta := strconv.FormatInt(int64(len(chunk)), 16) + "\r\n" if i != 0 { meta = "\r\n" + meta } _, err := res.WriteString(meta) require.NoError(t, err) _, err = res.WriteString(chunk) require.NoError(t, err) } _, err := res.WriteString("\r\n0\r\n") require.NoError(t, err) for k, v := range trailers { _, err := res.WriteString(fmt.Sprintf("%s:%s\n", k, v)) require.NoError(t, err) } _, err = res.WriteString("\r\n") require.NoError(t, err) return res, strings.Join(chunks, "") }