87 lines
3.1 KiB
Go
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")
|
|
}
|
|
}
|
|
}
|