package signature_test import ( "testing" "time" "sneak.berlin/go/pixa/internal/signature" ) // goldenExpiresUnix is the fixed expiration timestamp used by all golden // vectors: 2024-01-01T00:00:00Z. const goldenExpiresUnix int64 = 1704067200 // goldenSigningKey is the fixed signing key used by all golden vectors. const goldenSigningKey = "golden-test-key" type goldenVector struct { name string req signature.Request // wantSignature is the exact base64url (RFC 4648 URL-safe, // padded) HMAC-SHA256 signature for the request with Expires // set to goldenExpiresUnix. wantSignature string // wantSignedPath is the exact path returned by // GenerateSignedURL for the request. The signature and // expiration are returned separately by GenerateSignedURL and // are not embedded in the path. wantSignedPath string } // goldenVectors returns the known-answer vectors. The expected values // were computed once and are hardcoded here. func goldenVectors() []goldenVector { return []goldenVector{ { name: "resized without query", req: signature.Request{ SourceHost: testHost, SourcePath: testPath, SourceQuery: "", Width: 800, Height: 600, Format: testFormatWebP, }, // Signed data: "cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200" wantSignature: "x5PfPp8QSDo0cJT96od-AEgrQyOVLfqifH5sst61_-w=", wantSignedPath: testSignedPath, }, { name: "resized with query string", req: signature.Request{ SourceHost: testHost, SourcePath: testPath, SourceQuery: "token=abc&v=2", Width: 800, Height: 600, Format: testFormatWebP, }, // Signed data: // "cdn.example.com:/photos/cat.jpg:token=abc&v=2:800:600:webp:1704067200" wantSignature: "394_Vf9TdQFkpQ3XKFDQSyxgqKq8N7mApf2S4QaHqyo=", wantSignedPath: "/v1/image/cdn.example.com/photos/cat.jpg" + "%3Ftoken=abc&v=2/800x600.webp", }, { name: "original size without query", req: signature.Request{ SourceHost: testHost, SourcePath: testPath, SourceQuery: "", Width: 0, Height: 0, Format: testFormatPNG, }, // Signed data: "cdn.example.com:/photos/cat.jpg::0:0:png:1704067200" wantSignature: "7Be7oteeQwvnSPU4bchyQ4ZGYGsAGBKpeEtuQ02ox60=", wantSignedPath: "/v1/image/cdn.example.com/photos/cat.jpg/orig.png", }, } } // TestSigner_GoldenVectors pins the exact HMAC-SHA256 signature output and // the exact generated signed URL path for fully-specified requests with a // hardcoded signing key. // // If any of these assertions fail, the signed byte format // ("host:path:query:width:height:format:expiration"), the base64url // encoding, or the signed URL layout has changed. Such a change breaks // every signature already issued to clients, so it must be made // deliberately: update these constants only as part of an intentional, // documented signature format migration. func TestSigner_GoldenVectors(t *testing.T) { t.Parallel() signer := signature.New(goldenSigningKey) for _, tt := range goldenVectors() { t.Run(tt.name, func(t *testing.T) { t.Parallel() signReq := tt.req signReq.Expires = time.Unix(goldenExpiresUnix, 0) gotSignature := signer.Sign(&signReq) if gotSignature != tt.wantSignature { t.Errorf("Sign() = %q, want %q (signed byte format changed?)", gotSignature, tt.wantSignature) } urlReq := tt.req gotPath, _, _ := signer.GenerateSignedURL(&urlReq, time.Hour) if gotPath != tt.wantSignedPath { t.Errorf("GenerateSignedURL() path = %q, want %q (layout changed?)", gotPath, tt.wantSignedPath) } }) } }