68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
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
|
|
}
|