Files
Christian Nennemann bbf557e54b Restructure refimpl into go-lang and python subdirectories
Move Go reference implementation to refimpl/go-lang/ and add new
Python reference implementation in refimpl/python/. Update build.sh
with renamed draft and simplified tool paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:11:55 +01:00
..

WIMSE ECT — Go Reference Implementation

Go reference implementation of Execution Context Tokens (ECTs) 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

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",
    Par: []string{},
    Pol: "policy_v1",
    PolDecision: ect.PolDecisionApproved,
}
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

cd refimpl/go-lang && go run ./cmd/demo

Tests

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.

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

License

Same as the Internet-Draft (IETF Trust). Code under Revised BSD per BCP 78/79.