package ect import ( "crypto/ecdsa" "encoding/json" "os" "testing" "time" ) func TestCreateRoundtrip(t *testing.T) { key, err := GenerateKey() if err != nil { t.Fatal(err) } now := time.Now() payload := &Payload{ Iss: "spiffe://example.com/agent/a", Aud: []string{"spiffe://example.com/agent/b"}, Iat: now.Unix(), Exp: now.Add(10 * time.Minute).Unix(), Jti: "e4f5a6b7-c8d9-0123-ef01-234567890abc", Tid: "550e8400-e29b-41d4-a716-446655440001", ExecAct: "review_spec", Par: []string{}, Pol: "spec_review_policy_v2", PolDecision: PolDecisionApproved, } compact, err := Create(payload, key, CreateOptions{KeyID: "agent-a-key-1"}) if err != nil { t.Fatal(err) } if compact == "" { t.Fatal("expected non-empty compact JWS") } // Verify with same key resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid != "agent-a-key-1" { return nil, nil } return &key.PublicKey, nil } opts := VerifyOptions{ VerifierID: "spiffe://example.com/agent/b", ResolveKey: resolver, Now: now, IATMaxAge: 15 * time.Minute, IATMaxFuture: 30 * time.Second, } parsed, err := Verify(compact, opts) if err != nil { t.Fatal(err) } if parsed.Payload.Tid != payload.Tid || parsed.Payload.ExecAct != payload.ExecAct { t.Errorf("payload mismatch: got tid=%q exec_act=%q", parsed.Payload.Tid, parsed.Payload.ExecAct) } } func TestCreateWithTestVector(t *testing.T) { data, err := os.ReadFile("testdata/valid_root_ect_payload.json") if err != nil { t.Skipf("test vector not found: %v", err) return } var p Payload if err := json.Unmarshal(data, &p); err != nil { t.Fatal(err) } key, err := GenerateKey() if err != nil { t.Fatal(err) } // Override timestamps for verification now := time.Now() p.Iat = now.Unix() p.Exp = now.Add(10 * time.Minute).Unix() compact, err := Create(&p, key, CreateOptions{KeyID: "test-kid"}) if err != nil { t.Fatal(err) } resolver := func(kid string) (*ecdsa.PublicKey, error) { if kid != "test-kid" { return nil, nil } return &key.PublicKey, nil } _, err = Verify(compact, VerifyOptions{ VerifierID: p.Aud[0], ResolveKey: resolver, Now: now, IATMaxAge: 15 * time.Minute, IATMaxFuture: 30 * time.Second, }) if err != nil { t.Fatal(err) } }