Envelope-Encryption: DEK (32 Byte) frisch je Objekt, AES-256-GCM fuer Objektinhalt, DEK selbst mit Tenant-KEK verpackt (WrapDEK/UnwrapDEK). KEK kommt ausschliesslich ueber HTTPKEKProvider von Core API-10 (internal/kek.Handler.TenantKEKHandler-Vertrag, Service-Credential- authentifiziert), wird nie persistiert - jeder Seal/Open-Aufruf bezieht ihn frisch. RewrapDEK fuer KEK-Rotation ohne Neuverschluesselung der Objekte (nur der DEK-Wrapper wird neu verpackt, Chiffretext bleibt unveraendert). Auf 192.168.1.131 verifiziert: Round-Trip Encrypt/Decrypt, manipulierter Chiffretext UND falscher Schluessel werden beide ueber GCM-Auth-Tag abgelehnt, KEK-Rotation real getestet (RewrapDEK, altes Objekt danach mit neuem KEK weiterhin lesbar, alter KEK funktioniert nicht mehr). Befund dokumentiert: Cores internal/kek.Handler (Gegenstelle fuer den KEK-Bezug) ist noch in keinem cmd/*/main.go verdrahtet, dieselbe Fehlerklasse wie FDN-03/QA-05/AUD-06. HTTPKEKProvider daher gegen den dokumentierten Vertrag getestet, nicht gegen eine laufende Core-Instanz. Siehe dms/docs/FDN-09-PRUEFPROTOKOLL.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package crypto
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Envelope ist das Ergebnis einer Seal-Operation: der Chiffretext-Stream
|
|
// plus der mit dem Tenant-KEK verpackte DEK, der zusammen mit dem Objekt
|
|
// persistiert werden muss (z.B. in file_revisions, FDN-02 — die konkrete
|
|
// Spalte ist nicht Bestandteil dieser Kachel, siehe "Nicht Bestandteil").
|
|
type Envelope struct {
|
|
Ciphertext io.Reader
|
|
WrappedDEK []byte
|
|
}
|
|
|
|
// Service verbindet KEKProvider mit den Envelope-Operationen — die
|
|
// Storage-Abstraktion (FDN-03) ruft ausschliesslich Service auf, nie die
|
|
// Einzelfunktionen aus envelope.go direkt.
|
|
type Service struct {
|
|
kek KEKProvider
|
|
}
|
|
|
|
func NewService(kek KEKProvider) *Service {
|
|
return &Service{kek: kek}
|
|
}
|
|
|
|
// Seal erzeugt einen neuen DEK (Akzeptanzkriterium 1), verschluesselt
|
|
// plaintext damit und verpackt den DEK mit dem aktuellen Tenant-KEK
|
|
// (Akzeptanzkriterium 2 — der KEK wird bei JEDEM Aufruf frisch von Core
|
|
// bezogen, nie zwischengespeichert).
|
|
func (s *Service) Seal(ctx context.Context, tenantSlug string, plaintext io.Reader) (*Envelope, error) {
|
|
dek, err := GenerateDEK()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ciphertext, err := EncryptStream(dek, plaintext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
kek, err := s.kek.TenantKEK(ctx, tenantSlug)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("crypto: tenant-kek beziehen: %w", err)
|
|
}
|
|
wrappedDEK, err := WrapDEK(kek, dek)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Envelope{Ciphertext: ciphertext, WrappedDEK: wrappedDEK}, nil
|
|
}
|
|
|
|
// Open entpackt den DEK mit dem aktuellen Tenant-KEK und entschluesselt
|
|
// ciphertext damit.
|
|
func (s *Service) Open(ctx context.Context, tenantSlug string, wrappedDEK []byte, ciphertext io.Reader) (io.Reader, error) {
|
|
kek, err := s.kek.TenantKEK(ctx, tenantSlug)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("crypto: tenant-kek beziehen: %w", err)
|
|
}
|
|
dek, err := UnwrapDEK(kek, wrappedDEK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return DecryptStream(dek, ciphertext)
|
|
}
|