Files

87 lines
3.1 KiB
Go

package backup
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestSyncSignatureAndConditions(t *testing.T) {
c := S3Config{Endpoint: "https://storage.example.test", Region: "us-east-1", Bucket: "test-bucket", PathStyle: true, AccessKeyID: "access", SecretAccessKey: "secret", SessionToken: "session"}
for _, method := range []string{"GET", "PUT"} {
for _, tag := range []string{"", "\"object-etag\""} {
req, err := objectRequest(context.Background(), c, method, "folder with space/配置.json", []byte("payload"), tag, time.Date(2026, 9, 16, 12, 30, 0, 0, time.UTC))
if err != nil {
t.Fatal(err)
}
condition := ""
header := ""
if method == "PUT" {
if tag == "" {
condition = "if-none-match"
header = "*"
} else {
condition = "if-match"
header = tag
}
if req.Header.Get(condition) != header {
t.Fatal("incorrect write condition")
}
}
signed := "host;"
canonicalHeaders := "host:storage.example.test\n"
if condition != "" {
signed += condition + ";"
canonicalHeaders += condition + ":" + header + "\n"
}
payload := sha256.Sum256([]byte("payload"))
payloadHash := hex.EncodeToString(payload[:])
signed += "x-amz-content-sha256;x-amz-date;x-amz-security-token"
canonicalHeaders += "x-amz-content-sha256:" + payloadHash + "\nx-amz-date:20260916T123000Z\nx-amz-security-token:session\n"
canonical := method + "\n/test-bucket/folder%20with%20space/%E9%85%8D%E7%BD%AE.json\n\n" + canonicalHeaders + "\n" + signed + "\n" + payloadHash
digest := sha256.Sum256([]byte(canonical))
scope := "20260916/us-east-1/s3/aws4_request"
message := "AWS4-HMAC-SHA256\n20260916T123000Z\n" + scope + "\n" + hex.EncodeToString(digest[:])
key := []byte("AWS4secret")
for _, part := range []string{"20260916", "us-east-1", "s3", "aws4_request", message} {
h := hmac.New(sha256.New, key)
h.Write([]byte(part))
key = h.Sum(nil)
}
want := "AWS4-HMAC-SHA256 Credential=access/" + scope + ", SignedHeaders=" + signed + ", Signature=" + hex.EncodeToString(key)
if req.Header.Get("Authorization") != want {
t.Fatalf("wrong signature for %s", method)
}
}
}
}
func TestSyncRejectsMissingETagRedirectAndProviderErrors(t *testing.T) {
for _, code := range []int{200, 302, 403, 404, 412} {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "http://127.0.0.1:1/leak")
w.WriteHeader(code)
w.Write([]byte("provider-secret"))
}))
c := S3Config{Endpoint: server.URL, Region: "us-east-1", Bucket: "test-bucket", PathStyle: true, AccessKeyID: "access", SecretAccessKey: "secret"}
_, _, err := Exchange(context.Background(), c, "GET", "sync.json", nil, "")
server.Close()
if err == nil || strings.Contains(err.Error(), "provider-secret") {
t.Fatalf("unsafe handling of status %d", code)
}
if code == 404 && !errors.Is(err, ErrNotFound) {
t.Fatal("missing object not distinguished")
}
if code == 412 && !errors.Is(err, ErrObjectExists) {
t.Fatal("write race not distinguished")
}
}
}