package crypto import ( "bytes" "errors" "io" "testing" ) // TestGenerateDEK_NeverReused ist Akzeptanzkriterium 1: DEK wird pro // Objekt neu erzeugt, nie wiederverwendet. func TestGenerateDEK_NeverReused(t *testing.T) { seen := map[string]bool{} for i := 0; i < 50; i++ { dek, err := GenerateDEK() if err != nil { t.Fatalf("generatedek: %v", err) } if len(dek) != DEKSize { t.Fatalf("dek-laenge = %d, want %d", len(dek), DEKSize) } key := string(dek) if seen[key] { t.Fatal("generatedek lieferte denselben schluessel zweimal") } seen[key] = true } } // TestEncryptDecryptStream_RoundTrip ist Pruefung 1: Round-Trip liefert // identischen Klartext. func TestEncryptDecryptStream_RoundTrip(t *testing.T) { dek, err := GenerateDEK() if err != nil { t.Fatalf("generatedek: %v", err) } plaintext := []byte("Rechnung 2026-0001 — vertraulicher Inhalt") ciphertext, err := EncryptStream(dek, bytes.NewReader(plaintext)) if err != nil { t.Fatalf("encryptstream: %v", err) } ciphertextBytes, err := io.ReadAll(ciphertext) if err != nil { t.Fatalf("chiffretext lesen: %v", err) } if bytes.Equal(ciphertextBytes, plaintext) { t.Fatal("chiffretext ist identisch zum klartext - keine verschluesselung stattgefunden") } decrypted, err := DecryptStream(dek, bytes.NewReader(ciphertextBytes)) if err != nil { t.Fatalf("decryptstream: %v", err) } got, err := io.ReadAll(decrypted) if err != nil { t.Fatalf("klartext lesen: %v", err) } if !bytes.Equal(got, plaintext) { t.Fatalf("entschluesselter klartext = %q, want %q", got, plaintext) } } // TestDecryptStream_RejectsTamperedCiphertext ist Pruefung 2: manipulierter // Chiffretext wird bei Decrypt erkannt und abgelehnt (GCM-Auth-Tag). func TestDecryptStream_RejectsTamperedCiphertext(t *testing.T) { dek, err := GenerateDEK() if err != nil { t.Fatalf("generatedek: %v", err) } ciphertext, err := EncryptStream(dek, bytes.NewReader([]byte("geheimer inhalt"))) if err != nil { t.Fatalf("encryptstream: %v", err) } tampered, err := io.ReadAll(ciphertext) if err != nil { t.Fatalf("chiffretext lesen: %v", err) } // Ein Byte in der Mitte des Chiffretexts kippen (nach dem Nonce-Praefix). tampered[len(tampered)-1] ^= 0xFF if _, err := DecryptStream(dek, bytes.NewReader(tampered)); !errors.Is(err, ErrDecryptFailed) { t.Fatalf("manipulierter chiffretext: erwartet ErrDecryptFailed, habe %v", err) } } // TestDecryptStream_WrongKeyRejected prueft den zweiten Fall, in dem // Entschluesselung scheitern muss: falscher Schluessel statt Manipulation. func TestDecryptStream_WrongKeyRejected(t *testing.T) { dek1, _ := GenerateDEK() dek2, _ := GenerateDEK() ciphertext, err := EncryptStream(dek1, bytes.NewReader([]byte("inhalt"))) if err != nil { t.Fatalf("encryptstream: %v", err) } ciphertextBytes, _ := io.ReadAll(ciphertext) if _, err := DecryptStream(dek2, bytes.NewReader(ciphertextBytes)); !errors.Is(err, ErrDecryptFailed) { t.Fatalf("falscher schluessel: erwartet ErrDecryptFailed, habe %v", err) } } // TestWrapUnwrapDEK_RoundTrip prueft das Envelope-Verpacken des DEK selbst. func TestWrapUnwrapDEK_RoundTrip(t *testing.T) { kek := make([]byte, KEKSize) for i := range kek { kek[i] = byte(i) } dek, err := GenerateDEK() if err != nil { t.Fatalf("generatedek: %v", err) } wrapped, err := WrapDEK(kek, dek) if err != nil { t.Fatalf("wrapdek: %v", err) } if bytes.Equal(wrapped, dek) { t.Fatal("wrapdek lieferte den unveraenderten dek") } unwrapped, err := UnwrapDEK(kek, wrapped) if err != nil { t.Fatalf("unwrapdek: %v", err) } if !bytes.Equal(unwrapped, dek) { t.Fatal("entpackter dek stimmt nicht mit original ueberein") } } // TestRewrapDEK_RotationKeepsObjectReadable ist Pruefung 3: KEK-Rotation // getestet, alte Objekte weiterhin lesbar — OHNE dass der Chiffretext des // Objekts selbst jemals angefasst wird (Akzeptanzkriterium 3). func TestRewrapDEK_RotationKeepsObjectReadable(t *testing.T) { oldKEK := bytes.Repeat([]byte{0x01}, KEKSize) newKEK := bytes.Repeat([]byte{0x02}, KEKSize) dek, err := GenerateDEK() if err != nil { t.Fatalf("generatedek: %v", err) } plaintext := []byte("altes objekt, vor der rotation verschluesselt") ciphertext, err := EncryptStream(dek, bytes.NewReader(plaintext)) if err != nil { t.Fatalf("encryptstream: %v", err) } ciphertextBytes, _ := io.ReadAll(ciphertext) wrappedOld, err := WrapDEK(oldKEK, dek) if err != nil { t.Fatalf("wrapdek (alt): %v", err) } // Rotation: NUR der DEK-Wrapper wird neu verpackt, der Chiffretext // (ciphertextBytes) wird nicht angefasst. wrappedNew, err := RewrapDEK(oldKEK, newKEK, wrappedOld) if err != nil { t.Fatalf("rewrapdek: %v", err) } if bytes.Equal(wrappedNew, wrappedOld) { t.Fatal("rewrapdek lieferte denselben wrapper wie vor der rotation") } // Der alte KEK kann den NEUEN Wrapper nicht mehr entpacken (Rotation // war wirksam). if _, err := UnwrapDEK(oldKEK, wrappedNew); !errors.Is(err, ErrDecryptFailed) { t.Fatal("alter kek konnte den nach der rotation neu verpackten dek weiterhin entpacken") } // Mit dem NEUEN KEK und dem neuen Wrapper ist das unveraenderte, alte // Objekt weiterhin vollstaendig lesbar. dekAfterRotation, err := UnwrapDEK(newKEK, wrappedNew) if err != nil { t.Fatalf("unwrapdek (neu) nach rotation: %v", err) } decrypted, err := DecryptStream(dekAfterRotation, bytes.NewReader(ciphertextBytes)) if err != nil { t.Fatalf("decryptstream nach rotation: %v", err) } got, err := io.ReadAll(decrypted) if err != nil { t.Fatalf("klartext lesen: %v", err) } if !bytes.Equal(got, plaintext) { t.Fatalf("altes objekt nach rotation = %q, want %q", got, plaintext) } }