181 lines
5.1 KiB
Go
181 lines
5.1 KiB
Go
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
|
|
}
|