package signer import ( "net/http" "net/url" "strings" "testing" ) // Test vectors — kept in sync with README.md ("How signing works") so the Python server can cross-check. // // key = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f (32 bytes) // timestamp = 1789189804 (2026-09-12T05:10:04Z) // POST /ingest/v1/batch body = {"probe_id":"ca-qc-01","agent_version":"0.1.0","measurements":[]} // GET /ingest/v1/config body = "" const ( vecKey = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" vecTS = int64(1789189804) vecBody = `{"probe_id":"ca-qc-01","agent_version":"0.1.0","measurements":[]}` // sha256 of vecBody / of the empty string. vecBodyHash = "9fd6962cedcf4a7aedb13c38f7f63137fdbbbf3703b447ebd761234981847c65" vecEmptyHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // Expected signatures — computed independently with Python (hmac + hashlib) before this package existed. vecPostSig = "111075ee8d4c20723598c8f75d5ad89433f0385ff965f123302d2b9411280723" vecGetSig = "3798599e7f1bed2dab170d2aacf5f5288ec7cc591a64bd37285d3ea3c3450a8b" ) func TestBodyHash(t *testing.T) { if got := BodyHash([]byte(vecBody)); got != vecBodyHash { t.Fatalf("body hash = %s, want %s", got, vecBodyHash) } if got := BodyHash(nil); got != vecEmptyHash { t.Fatalf("empty hash = %s, want %s", got, vecEmptyHash) } } func TestCanonicalForm(t *testing.T) { c := Canonical("GET", "/ingest/v1/config", vecTS, nil) want := "GET\n/ingest/v1/config\n1789189804\n" + vecEmptyHash if c != want { t.Fatalf("canonical mismatch:\n got %q\nwant %q", c, want) } } func TestVectors(t *testing.T) { s, err := New("ca-qc-01", vecKey) if err != nil { t.Fatal(err) } if got := s.Sign("POST", "/ingest/v1/batch", vecTS, []byte(vecBody)); got != vecPostSig { t.Errorf("POST signature = %s, want %s", got, vecPostSig) } if got := s.Sign("GET", "/ingest/v1/config", vecTS, nil); got != vecGetSig { t.Errorf("GET signature = %s, want %s", got, vecGetSig) } } func TestApplyIgnoresQueryString(t *testing.T) { s, _ := New("ca-qc-01", vecKey) u, _ := url.Parse("https://www.internetpressure.io/ingest/v1/config?x=1") req, _ := http.NewRequest("GET", u.String(), nil) s.Apply(req, vecTS, nil) if req.Header.Get(HeaderProbe) != "ca-qc-01" || req.Header.Get(HeaderTimestamp) != "1789189804" { t.Fatalf("bad headers: %v", req.Header) } if req.Header.Get(HeaderSignature) != vecGetSig { t.Fatalf("signature with query string should equal the path-only signature") } if !s.Verify("GET", "/ingest/v1/config", vecTS, nil, req.Header.Get(HeaderSignature)) { t.Fatal("verify failed") } if s.Verify("GET", "/ingest/v1/config", vecTS+1, nil, req.Header.Get(HeaderSignature)) { t.Fatal("verify should fail with a different timestamp") } } func TestNewRejectsBadKeys(t *testing.T) { if _, err := New("p", "zz"); err == nil { t.Fatal("non-hex key accepted") } if _, err := New("p", "0011"); err == nil { t.Fatal("short key accepted") } if _, err := New("", vecKey); err == nil { t.Fatal("empty probe id accepted") } if _, err := New("p", strings.Repeat("ab", 32)); err != nil { t.Fatal(err) } }