Files
patrickandClaude Sonnet 5 54cba799ce
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
FDN-22: verschluesselung der textkopien
OCR-Text, Klartext-Extrakt und E-Rechnungs-Suchtext liegen nur noch
verschluesselt (AES-256-GCM, DEK je Revision mit Mandanten-KEK verpackt,
Bindung an Revision und Seite) in der Datenbank. Migration 0031, Werkzeug
textcrypt-migrate fuer Bestandsdaten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 00:26:23 +02:00

107 lines
3.4 KiB
Go

package textcrypt
import (
"bytes"
"context"
"errors"
"strings"
"testing"
dmscrypto "gitea.perlbach24.de/scripte/nexarch/dms/internal/crypto"
)
type staticKEK struct {
kek []byte
err error
}
func (s staticKEK) TenantKEK(context.Context, string) ([]byte, error) { return s.kek, s.err }
func vaultWith(b byte) Vault {
return Vault{Crypto: dmscrypto.NewService(staticKEK{kek: bytes.Repeat([]byte{b}, dmscrypto.KEKSize)}), TenantSlug: "acme"}
}
func TestSealOpenRoundtripAndUniqueNonce(t *testing.T) {
dek, _ := dmscrypto.GenerateDEK()
aad := AADPage("rev-1", 3)
a, err := Seal(dek, aad, "Zebrafinken bauen Lehmziegel")
if err != nil {
t.Fatal(err)
}
b, _ := Seal(dek, aad, "Zebrafinken bauen Lehmziegel")
if bytes.Equal(a, b) {
t.Fatal("zwei verschluesselungen desselben textes sind identisch (nonce wiederverwendet)")
}
if bytes.Contains(a, []byte("Zebrafinken")) {
t.Fatal("klartext im chiffretext")
}
got, err := Open(dek, aad, a)
if err != nil || got != "Zebrafinken bauen Lehmziegel" {
t.Fatalf("open: %q %v", got, err)
}
}
func TestOpenRejectsWrongKeyAADTamperAndVersion(t *testing.T) {
dek, _ := dmscrypto.GenerateDEK()
other, _ := dmscrypto.GenerateDEK()
aad := AADIndex("rev-1")
sealed, _ := Seal(dek, aad, "geheim")
cases := map[string]func() error{
"falscher schluessel": func() error { _, err := Open(other, aad, sealed); return err },
"andere revision": func() error { _, err := Open(dek, AADIndex("rev-2"), sealed); return err },
"andere seite": func() error { _, err := Open(dek, AADPage("rev-1", 2), sealed); return err },
"andere textart": func() error { _, err := Open(dek, AADInvoice("rev-1"), sealed); return err },
"manipuliert": func() error {
m := append([]byte(nil), sealed...)
m[len(m)-1] ^= 0xFF
_, err := Open(dek, aad, m)
return err
},
"unbekannte version": func() error {
m := append([]byte(nil), sealed...)
m[0] = 0x7F
_, err := Open(dek, aad, m)
return err
},
"zu kurz": func() error { _, err := Open(dek, aad, []byte{versionV1, 1, 2}); return err },
}
for name, f := range cases {
if err := f(); !errors.Is(err, ErrDecryptText) {
t.Errorf("%s: erwartet ErrDecryptText, erhalten %v", name, err)
}
}
}
func TestVaultFailsClosedWithoutCryptoOrWithWrongKEK(t *testing.T) {
ctx := context.Background()
if _, _, err := (Vault{}).NewKey(ctx); !errors.Is(err, ErrNoCrypto) {
t.Fatalf("ohne dienst: %v", err)
}
if _, err := (Vault{Crypto: vaultWith(1).Crypto}).Key(ctx, []byte{1}); !errors.Is(err, ErrNoCrypto) {
t.Fatalf("ohne mandant: %v", err)
}
a := vaultWith(0x11)
dek, wrapped, err := a.NewKey(ctx)
if err != nil {
t.Fatal(err)
}
if again, err := a.Key(ctx, wrapped); err != nil || !bytes.Equal(again, dek) {
t.Fatalf("schluessel nicht wiederherstellbar: %v", err)
}
if _, err := vaultWith(0x22).Key(ctx, wrapped); !errors.Is(err, ErrDecryptText) {
t.Fatalf("fremder kek: %v", err)
}
broken := Vault{Crypto: dmscrypto.NewService(staticKEK{err: errors.New("kek-dienst nicht erreichbar")}), TenantSlug: "acme"}
if _, err := broken.Key(ctx, wrapped); err == nil || !strings.Contains(err.Error(), "nicht erreichbar") {
t.Fatalf("kek-ausfall muss fehlschlagen: %v", err)
}
if _, _, err := broken.NewKey(ctx); err == nil {
t.Fatal("kek-ausfall beim anlegen muss fehlschlagen")
}
if _, err := a.Key(ctx, nil); !errors.Is(err, ErrDecryptText) {
t.Fatalf("leerer verpackter schluessel: %v", err)
}
}