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
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>
107 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|