package moduletrust import ( "context" "crypto/ed25519" "errors" "time" "github.com/golang-jwt/jwt/v5" ) var ErrInvalidToken = errors.New("moduletrust: ungueltiges token") // KeyFetchFunc holt den aktuellen Schluesselsatz von Core (z.B. per HTTP-GET // auf ServeJWKS + ParseJWKS). Wird vom Verifier nur bei abgelaufener TTL // aufgerufen — NICHT bei jeder Verify()-Anfrage (Akzeptanzkriterium 1). type KeyFetchFunc func(ctx context.Context) (map[string]ed25519.PublicKey, error) // Verifier ist die Modulseite von API-05: verifiziert JWTs LOKAL gegen einen // per StaleCache zwischengespeicherten Schluesselsatz, ohne pro Aufruf einen // synchronen Request an Core zu stellen. type Verifier struct { cache *StaleCache[map[string]ed25519.PublicKey] } func NewVerifier(ttl time.Duration, fetch KeyFetchFunc) *Verifier { return &Verifier{cache: NewStaleCache(ttl, func(ctx context.Context) (map[string]ed25519.PublicKey, error) { return fetch(ctx) })} } // Verify prueft die Signatur LOKAL gegen den (ggf. abgelaufenen, aber // vorhandenen) Schluesselsatz — FAIL-OPEN fuer bereits ausgestellte Tokens // (Akzeptanzkriterium 2): ist Core nicht erreichbar, aber ein alter // Schluesselsatz bekannt, wird damit weiter verifiziert. func (v *Verifier) Verify(ctx context.Context, tokenString string) (*Claims, error) { keys, _, err := v.cache.Get(ctx) if err != nil { return nil, err } claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodEd25519); !ok { return nil, ErrInvalidToken } kid, _ := t.Header["kid"].(string) pub, ok := keys[kid] if !ok { return nil, ErrInvalidToken } return pub, nil }) if err != nil || !token.Valid { return nil, ErrInvalidToken } return claims, nil } // RequireFreshKeys ruft IMMER frisch von Core ab (FAIL-CLOSED) — fuer // sicherheitskritische Aktionen wie einen komplett neuen Login // (Akzeptanzkriterium 2): schlaegt klar fehl, wenn Core nicht erreichbar // ist, statt auf einem veralteten Schluesselsatz zu vertrauen. func (v *Verifier) RequireFreshKeys(ctx context.Context) error { _, err := v.cache.RequireFresh(ctx) return err }