package ucp import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestSigningConfig_FromPEM(t *testing.T) { // PEM from the generated key pair (openssl ecparam -genkey -name prime256v1) pemData := `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIDTssEctnrEqym80fx2jEVolTW+tWrHyo8fmbi8hMFWmoAoGCCqGSM49 AwEHoUQDQgAEpnPksDSSMcNgUYT4++Z6XuS0V2p6TNMSjWBsgXOu7mo5Esoux+Cm y+C84ty5VUAdEpoDyNfWMeaAlHKGo+FjiQ== -----END EC PRIVATE KEY-----` cfg, err := NewSigningConfigFromPEM([]byte(pemData), "k6n-ecdsa-2026") if err != nil { t.Fatalf("NewSigningConfigFromPEM failed: %v", err) } if cfg.KeyID != "k6n-ecdsa-2026" { t.Fatalf("expected key ID 'k6n-ecdsa-2026', got %q", cfg.KeyID) } if cfg.PrivateKey == nil { t.Fatal("expected non-nil private key") } if cfg.PrivateKey.Curve.Params().Name != "P-256" { t.Fatalf("expected P-256 curve, got %s", cfg.PrivateKey.Curve.Params().Name) } } func TestSigningConfig_InvalidPEM(t *testing.T) { _, err := NewSigningConfigFromPEM([]byte("not a pem"), "test") if err == nil { t.Fatal("expected error for invalid PEM") } } func TestWithSigning_AddsHeaders(t *testing.T) { // Load the test key. cfg := mustLoadTestKey(t) // A simple handler that returns a JSON response. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) }) wrapped := WithSigning(handler, cfg) req := httptest.NewRequest("GET", "/test", nil) rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, req) // Check the response body. if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } // Check that signature headers are present. sigInput := rec.Header().Get("Signature-Input") sig := rec.Header().Get("Signature") ts := rec.Header().Get("x-ucp-timestamp") digest := rec.Header().Get("Content-Digest") if sigInput == "" { t.Fatal("expected Signature-Input header") } if sig == "" { t.Fatal("expected Signature header") } if ts == "" { t.Fatal("expected x-ucp-timestamp header") } if digest == "" { t.Fatal("expected Content-Digest header") } // Verify the signature input format. if !strings.HasPrefix(sigInput, `sig1=(@status "content-type" "x-ucp-timestamp" "content-digest");`) { t.Fatalf("unexpected Signature-Input format: %q", sigInput) } if !strings.Contains(sigInput, `keyid="k6n-ecdsa-2026"`) { t.Fatalf("Signature-Input missing keyid: %q", sigInput) } if !strings.Contains(sigInput, `alg="ecdsa-p256"`) { t.Fatalf("Signature-Input missing alg: %q", sigInput) } // Verify the signature format. if !strings.HasPrefix(sig, "sig1=:") || !strings.HasSuffix(sig, ":") { t.Fatalf("unexpected Signature format: %q", sig) } } func TestWithSigning_NilConfig(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) wrapped := WithSigning(handler, nil) req := httptest.NewRequest("GET", "/test", nil) rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } if rec.Header().Get("Signature-Input") != "" { t.Fatal("expected no Signature-Input when signing is nil") } } func TestWithSigning_DifferentStatuses(t *testing.T) { cfg := mustLoadTestKey(t) for _, status := range []int{200, 201, 400, 404, 500} { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) w.Write([]byte(`{}`)) }) wrapped := WithSigning(handler, cfg) req := httptest.NewRequest("GET", "/test", nil) rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, req) if rec.Code != status { t.Fatalf("expected status %d, got %d", status, rec.Code) } if rec.Header().Get("Signature-Input") == "" { t.Fatalf("expected Signature-Input for status %d", status) } } } func TestWithSigning_WithUCPCartHandler(t *testing.T) { cfg := mustLoadTestKey(t) applier := newTestApplier() handler := CartHandler(applier) wrapped := WithSigning(handler, cfg) // Create a cart through the wrapped handler. req := httptest.NewRequest("POST", "/", nil) rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("expected 201, got %d", rec.Code) } // Verify signed. if rec.Header().Get("Signature-Input") == "" { t.Fatal("expected Signature-Input on UCP cart response") } if rec.Header().Get("x-ucp-timestamp") == "" { t.Fatal("expected x-ucp-timestamp on UCP cart response") } if rec.Header().Get("Content-Digest") == "" { t.Fatal("expected Content-Digest on UCP cart response") } } // mustLoadTestKey loads the test PEM for signing tests. func mustLoadTestKey(t testing.TB) *SigningConfig { t.Helper() pemData := `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIDTssEctnrEqym80fx2jEVolTW+tWrHyo8fmbi8hMFWmoAoGCCqGSM49 AwEHoUQDQgAEpnPksDSSMcNgUYT4++Z6XuS0V2p6TNMSjWBsgXOu7mo5Esoux+Cm y+C84ty5VUAdEpoDyNfWMeaAlHKGo+FjiQ== -----END EC PRIVATE KEY-----` cfg, err := NewSigningConfigFromPEM([]byte(pemData), "k6n-ecdsa-2026") if err != nil { t.Fatalf("mustLoadTestKey: %v", err) } return cfg }