package crypto import ( "context" "encoding/base64" "encoding/json" "fmt" "net/http" "net/url" ) // KEKProvider liefert den aktuellen, entschluesselten Tenant-KEK // (Akzeptanzkriterium 2: KEK kommt ausschliesslich von Core API-10). // Schmale Schnittstelle, damit Tests einen Fake statt eines echten // HTTP-Aufrufs einsetzen koennen. type KEKProvider interface { TenantKEK(ctx context.Context, tenantSlug string) ([]byte, error) } // tenantKEKResponse entspricht Core internal/kek.tenantKEKResponse // (JSON-Vertrag: tenant_kek_base64) — dieselbe Struktur, hier gespiegelt, // da DMS Cores internal/-Pakete nicht importieren kann. type tenantKEKResponse struct { TenantKEKBase64 string `json:"tenant_kek_base64"` } // HTTPKEKProvider bezieht den Tenant-KEK ueber Cores KEK-Handler // (internal/kek.Handler.TenantKEKHandler, API-10), authentifiziert ueber // dasselbe Service-Credential-Verfahren wie jeder andere Modul-Core-Aufruf // (API-02) — identisches Muster wie internal/storage.HTTPUsageReporter aus // FDN-03. type HTTPKEKProvider struct { endpointURL string clientID string clientSecret string httpClient *http.Client } func NewHTTPKEKProvider(endpointURL, clientID, clientSecret string, httpClient *http.Client) *HTTPKEKProvider { if httpClient == nil { httpClient = http.DefaultClient } return &HTTPKEKProvider{endpointURL: endpointURL, clientID: clientID, clientSecret: clientSecret, httpClient: httpClient} } func (p *HTTPKEKProvider) TenantKEK(ctx context.Context, tenantSlug string) ([]byte, error) { u, err := url.Parse(p.endpointURL) if err != nil { return nil, fmt.Errorf("crypto: kek-endpunkt-url ungueltig: %w", err) } q := u.Query() q.Set("tenant", tenantSlug) u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, fmt.Errorf("crypto: kek-anfrage aufbauen: %w", err) } req.Header.Set("X-Nexarch-Client-Id", p.clientID) req.Header.Set("X-Nexarch-Client-Secret", p.clientSecret) resp, err := p.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("crypto: kek-anfrage senden: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("crypto: kek-bezug von core abgelehnt: status %d", resp.StatusCode) } var body tenantKEKResponse if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, fmt.Errorf("crypto: kek-antwort dekodieren: %w", err) } kek, err := base64.StdEncoding.DecodeString(body.TenantKEKBase64) if err != nil { return nil, fmt.Errorf("crypto: kek base64-dekodieren: %w", err) } return kek, nil }