// Package moduletrust implementiert Core API-05: asymmetrische JWT-Signatur // mit Schluesselverteilung (JWKS), damit DMS/Mail/Archive/Workflow JWTs // LOKAL verifizieren koennen, ohne pro Aufruf einen synchronen Request an // Core zu stellen — Core darf Fundament sein, ohne zum Flaschenhals zu // werden (siehe Entscheidungsverlauf "Vertrauensstellung Core<->Module" in // nexarch-state.json). IAM-02s HS256-Session-Cookie (Browser-Login) bleibt // unangetastet — dies ist ein zusaetzlicher, getrennter Vertrauensmechanismus // fuer Modul-zu-Modul/Modul-zu-Core-Aufrufe. package moduletrust import ( "crypto/ed25519" "crypto/rand" "encoding/hex" "fmt" "sync" ) type KeyPair struct { KID string Private ed25519.PrivateKey Public ed25519.PublicKey } // KeyManager haelt ALLE noch gueltigen Schluesselpaare — nicht nur das // aktuell signierende. Rotate erzeugt ein neues Paar und behaelt die alten // fuer die Verifikation bereits ausgestellter Tokens (Akzeptanzkriterium 3: // Rotation ohne Ausfallzeit fuer andere Module). type KeyManager struct { mu sync.RWMutex keys []KeyPair // aeltestes zuerst, neuestes zuletzt } func NewKeyManager() (*KeyManager, error) { m := &KeyManager{} if _, err := m.Rotate(); err != nil { return nil, err } return m, nil } // Rotate erzeugt ein neues Ed25519-Schluesselpaar mit eigener KID und macht // es zum aktuellen Signierschluessel. Aeltere Schluessel bleiben in // PublicKeySet() erhalten, damit bereits ausgestellte Tokens weiterhin // verifizierbar sind. func (m *KeyManager) Rotate() (KeyPair, error) { pub, priv, err := ed25519.GenerateKey(nil) if err != nil { return KeyPair{}, fmt.Errorf("schluesselpaar erzeugen: %w", err) } kidBytes := make([]byte, 8) if _, err := rand.Read(kidBytes); err != nil { return KeyPair{}, fmt.Errorf("kid erzeugen: %w", err) } kp := KeyPair{KID: hex.EncodeToString(kidBytes), Private: priv, Public: pub} m.mu.Lock() m.keys = append(m.keys, kp) m.mu.Unlock() return kp, nil } // SigningKey liefert den aktuellen (neuesten) Schluessel zum Signieren neuer Tokens. func (m *KeyManager) SigningKey() (KeyPair, error) { m.mu.RLock() defer m.mu.RUnlock() if len(m.keys) == 0 { return KeyPair{}, fmt.Errorf("moduletrust: kein schluessel vorhanden") } return m.keys[len(m.keys)-1], nil } // PublicKeySet liefert ALLE bekannten oeffentlichen Schluessel (kid -> // public key) — die Grundlage fuer den JWKS-Endpunkt. func (m *KeyManager) PublicKeySet() map[string]ed25519.PublicKey { m.mu.RLock() defer m.mu.RUnlock() out := make(map[string]ed25519.PublicKey, len(m.keys)) for _, k := range m.keys { out[k.KID] = k.Public } return out }