Add Quality and FitMode fields to signature.Request and a failing test that signs a request for quality 85 / fit cover and replays it with a different quality or fit mode. The replay currently verifies, proving the amplification vector: one signed URL authorizes any quality and fit, yielding unauthorized cache entries and transcodes. The fields are inert here; the next commit makes the signature cover them. model: claude-opus-4-8
69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
package signature_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/pixa/internal/signature"
|
|
)
|
|
|
|
// signedQualityFitRequest returns a request signed for quality 85 and fit
|
|
// mode "cover", the effective defaults the handler applies before
|
|
// verification.
|
|
func signedQualityFitRequest(signer *signature.Signer) *signature.Request {
|
|
req := &signature.Request{
|
|
SourceHost: testHost,
|
|
SourcePath: testPath,
|
|
Width: 800,
|
|
Height: 600,
|
|
Format: testFormatWebP,
|
|
Quality: 85,
|
|
FitMode: "cover",
|
|
Expires: time.Now().Add(1 * time.Hour),
|
|
}
|
|
req.Signature = signer.Sign(req)
|
|
|
|
return req
|
|
}
|
|
|
|
// TestSigner_Verify_QualityAndFitAreSigned proves that quality and fit are
|
|
// covered by the signature: a URL signed for one quality or fit mode must
|
|
// not verify when replayed with a different quality or fit mode. This is the
|
|
// amplification vector from the issue — one signed URL replayed across many
|
|
// quality and fit values yields many unauthorized cache entries and
|
|
// transcodes — so it must be rejected.
|
|
func TestSigner_Verify_QualityAndFitAreSigned(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
signer := signature.New("test-secret-key")
|
|
|
|
cases := []struct {
|
|
name string
|
|
tamper func(r *signature.Request)
|
|
}{
|
|
{
|
|
name: "replayed with different quality",
|
|
tamper: func(r *signature.Request) { r.Quality = 40 },
|
|
},
|
|
{
|
|
name: "replayed with different fit mode",
|
|
tamper: func(r *signature.Request) { r.FitMode = "contain" },
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
req := signedQualityFitRequest(signer)
|
|
tt.tamper(req)
|
|
|
|
err := signer.Verify(req)
|
|
if !errors.Is(err, signature.ErrInvalid) {
|
|
t.Errorf("Verify() = %v, want %v", err, signature.ErrInvalid)
|
|
}
|
|
})
|
|
}
|
|
}
|