80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
package moduletrust
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type Claims struct {
|
|
Subject string `json:"sub"`
|
|
TenantSlug string `json:"tenant"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
// Issue signiert ein Token mit dem aktuellen Signierschluessel und traegt
|
|
// dessen KID im JWT-Header ein — der Verifier auf Modulseite waehlt darueber
|
|
// den passenden oeffentlichen Schluessel aus PublicKeySet() aus.
|
|
func (m *KeyManager) Issue(subject, tenantSlug string, ttl time.Duration) (string, error) {
|
|
key, err := m.SigningKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
now := time.Now()
|
|
claims := Claims{
|
|
Subject: subject,
|
|
TenantSlug: tenantSlug,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
|
|
},
|
|
}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
|
|
token.Header["kid"] = key.KID
|
|
return token.SignedString(key.Private)
|
|
}
|
|
|
|
type jwksResponse struct {
|
|
Keys []jwksKey `json:"keys"`
|
|
}
|
|
|
|
type jwksKey struct {
|
|
Kid string `json:"kid"`
|
|
PublicKey string `json:"public_key"` // base64 (raw Ed25519, 32 Byte)
|
|
}
|
|
|
|
// ServeJWKS liefert alle bekannten oeffentlichen Schluessel als JSON —
|
|
// Module fragen dies periodisch ab (nicht pro Request), siehe Verifier.
|
|
func (m *KeyManager) ServeJWKS(w http.ResponseWriter, r *http.Request) {
|
|
set := m.PublicKeySet()
|
|
resp := jwksResponse{Keys: make([]jwksKey, 0, len(set))}
|
|
for kid, pub := range set {
|
|
resp.Keys = append(resp.Keys, jwksKey{Kid: kid, PublicKey: base64.StdEncoding.EncodeToString(pub)})
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(resp)
|
|
}
|
|
|
|
// ParseJWKS dekodiert die JSON-Antwort von ServeJWKS zurueck in kid->PublicKey
|
|
// — Hilfsfunktion fuer Module, die JWKS per HTTP abrufen.
|
|
func ParseJWKS(data []byte) (map[string][]byte, error) {
|
|
var resp jwksResponse
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return nil, fmt.Errorf("jwks parsen: %w", err)
|
|
}
|
|
out := make(map[string][]byte, len(resp.Keys))
|
|
for _, k := range resp.Keys {
|
|
raw, err := base64.StdEncoding.DecodeString(k.PublicKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oeffentlichen schluessel %q dekodieren: %w", k.Kid, err)
|
|
}
|
|
out[k.Kid] = raw
|
|
}
|
|
return out, nil
|
|
}
|