# WIMSE ECT — Go Reference Implementation Go reference implementation of [Execution Context Tokens (ECTs)](../../draft-nennemann-wimse-execution-context-01.txt) for WIMSE. Implements ECT creation (ES256), verification (Section 7), DAG validation (Section 6), and an in-memory audit ledger (Section 9). ## Layout ``` go-lang/ ├── go.mod, go.sum ├── ect/ # library │ ├── types.go # Payload, Audience, constants │ ├── audience.go # aud marshal/unmarshal │ ├── create.go # Create(), GenerateKey() │ ├── verify.go # Parse(), Verify(), VerifyOptions │ ├── dag.go # ValidateDAG(), ECTStore │ ├── ledger.go # Ledger, MemoryLedger │ ├── config.go # Config, LoadConfigFromEnv() │ ├── jti_cache.go # JTICache for replay protection │ ├── errors.go # Sentinel errors (ErrExpired, ErrReplay, etc.) │ ├── validate.go # ValidateExt, ValidUUID, ValidateHashFormat │ └── *_test.go ├── testdata/ │ └── valid_root_ect_payload.json └── cmd/demo/ # two-agent workflow demo └── main.go ``` ## Usage ### Library ```go import "github.com/nennemann/ect-refimpl/go-lang/ect" // Production: load config from environment cfg := ect.LoadConfigFromEnv() key, _ := ect.GenerateKey() payload := &ect.Payload{ Iss: "spiffe://example.com/agent/a", Aud: ect.Audience{"spiffe://example.com/agent/b"}, Iat: time.Now().Unix(), Exp: time.Now().Add(10*time.Minute).Unix(), Jti: "550e8400-e29b-41d4-a716-446655440000", ExecAct: "review_spec", Pred: []string{}, Ext: map[string]interface{}{ "pol": "policy_v1", "pol_decision": "approved", }, } compact, err := ect.Create(payload, key, cfg.CreateOptions("agent-a-key")) // Verify with optional replay cache store := ect.NewMemoryLedger() var jtiCache ect.JTICache if cfg.JTIReplaySize > 0 { jtiCache = ect.NewJTICache(cfg.JTIReplaySize, cfg.JTIReplayTTL) } opts := cfg.VerifyOptions() opts.VerifierID = "spiffe://example.com/agent/b" opts.ResolveKey = resolver opts.Store = store if jtiCache != nil { opts.JTISeen = jtiCache.Seen } parsed, err := ect.Verify(compact, opts) if err != nil { ... } if jtiCache != nil { jtiCache.Add(parsed.Payload.Jti) } store.Append(compact, parsed.Payload) ``` ### Demo ```bash cd refimpl/go-lang && go run ./cmd/demo ``` ### Tests ```bash cd refimpl/go-lang && go test ./ect/... -cover ``` Unit tests are in `ect/*_test.go`. Coverage target: **~90%** (run `go test ./ect/... -coverprofile=cover.out && go tool cover -func=cover.out`). Remaining uncovered lines are mostly Parse/Verify error paths that require custom JWS or multi-sig tokens. ## draft-01 claim changes | -00 (previous) | -01 (current) | Notes | |----------------|---------------|-------| | `par` | `pred` | Predecessor task IDs | | `pol`, `pol_decision` | removed (use `ect_ext`) | Policy claims moved to extension object | | `sub` | not defined | Standard JWT claim, not part of ECT spec | | `typ: wimse-exec+jwt` | `typ: exec+jwt` (preferred) | Both accepted for backward compat | | `MaxParLength` | `MaxPredLength` | Renamed to match `pred` claim | ## Production configuration (environment) | Variable | Default | Description | |----------|---------|-------------| | `ECT_IAT_MAX_AGE_MINUTES` | 15 | Max age of `iat` (minutes). | | `ECT_IAT_MAX_FUTURE_SEC` | 30 | Max `iat` in future / clock skew (seconds). | | `ECT_DEFAULT_EXPIRY_MIN` | 10 | Default token expiry when `exp` is zero (minutes). | | `ECT_JTI_REPLAY_CACHE_SIZE` | 0 | Max JTIs to remember for replay check; 0 = disabled. | | `ECT_JTI_REPLAY_TTL_MIN` | 60 | TTL for JTI cache entries (minutes). | ### Replay cache (multi-instance) `JTICache` is in-memory only. For multiple verifier instances (e.g. behind a load balancer), use a shared store (Redis, database) so every instance sees the same "seen" JTIs. Implement `JTISeen` as a function that checks (and optionally records) the JTI in that store (e.g. with TTL). Pass it in `VerifyOptions.JTISeen`. See refimpl/README for an overview. ## Dependencies - [go-jose/v4](https://github.com/go-jose/go-jose/v4) for JWS (ES256) and JWK handling. ## License Same as the Internet-Draft (IETF Trust). Code under Revised BSD per BCP 78/79.