- mail/internal/dedup/hash.go: HashAndBuffer, SHA-256 auf Klartext VOR Verschluesselung (ARC-02), liefert erneut lesbaren Reader zurueck. - mail/internal/dedup/store.go: Store (Postgres, tenant_slug fest im Primaerschluessel gebunden), Register: Duplikat referenziert Original statt redundant zu speichern. - Alle 3 Pflichtpruefungen real bestanden (siehe mail/docs/ARC-03-PRUEFPROTOKOLL.md): Duplikat aus zwei Quellen erkannt, zwei Mandanten mit identischem Inhalt nicht verknuepft, knapp unterschiedliche Nachricht korrekt nicht erkannt. - Kein Umbau: internal/storage, internal/crypto, internal/encstorage unveraendert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
36 lines
1.4 KiB
Go
36 lines
1.4 KiB
Go
// Package dedup implementiert ARC-03: Dublettenerkennung für
|
|
// archivierte E-Mails über einen Inhalts-Hash. Kombiniert bewusst NICHT
|
|
// mit mail/internal/storage oder mail/internal/crypto — dieses Paket
|
|
// kennt beide nicht, der Aufrufer (spätere Ingest-Tickets) ruft es VOR
|
|
// mail/internal/crypto auf.
|
|
package dedup
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// HashAndBuffer berechnet den SHA-256-Inhalts-Hash von plaintext.
|
|
//
|
|
// Bekannter Fehler vermeiden (siehe ARC-03-Ticket): der Hash MUSS auf
|
|
// dem Klartext berechnet werden, BEVOR mail/internal/crypto verschlüsselt
|
|
// — ein Hash auf dem Chiffretext wäre bei jedem Import anders (neuer
|
|
// DEK je Objekt, siehe ARC-02) und Dublettenerkennung würde vollständig
|
|
// versagen. Reihenfolge: Mail/Anhang empfangen -> HashAndBuffer (dieses
|
|
// Paket) -> verschlüsseln (ARC-02) -> ablegen.
|
|
//
|
|
// plaintext wird beim Hashen vollständig verbraucht — HashAndBuffer
|
|
// liefert deshalb einen erneut lesbaren Reader mit demselben Inhalt für
|
|
// den nachfolgenden Verschlüsselungsschritt zurück.
|
|
func HashAndBuffer(plaintext io.Reader) (contentHash string, buffered io.Reader, err error) {
|
|
var buf bytes.Buffer
|
|
hasher := sha256.New()
|
|
if _, err := io.Copy(hasher, io.TeeReader(plaintext, &buf)); err != nil {
|
|
return "", nil, fmt.Errorf("dedup: klartext hashen: %w", err)
|
|
}
|
|
return hex.EncodeToString(hasher.Sum(nil)), &buf, nil
|
|
}
|