84 lines
2.6 KiB
Go
84 lines
2.6 KiB
Go
// 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
|
|
}
|