/* * Copyright (c) 2026 Kaiy Ragur * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package e2ee import ( "crypto/ed25519" "fmt" "io" "net/mail" "time" "github.com/emersion/go-message" "github.com/neilalexander/yggmail/internal/utils" ) func EncryptFilter( origMsg io.ReadSeeker, fromAddr string, rcptPk []ed25519.PublicKey, mySign Signer, myEnc Encryptor, ) (io.ReadCloser, error) { packReader, err := EncryptPack(origMsg, rcptPk, mySign, myEnc) if err != nil { return nil, fmt.Errorf("failed to sign and encrypt: %w", err) } hdr := new(message.Header) hdr.Set("From", fromAddr) hdr.Set("Date", time.Now().Format(time.RFC1123Z)) hdr.Set("X-Yggmail-Encryption", "v1") pr, pw := io.Pipe() go func() { tw, err := message.CreateWriter(pw, *hdr) if err != nil { pw.CloseWithError(err) return } _, err = io.Copy(tw, packReader) tw.Close() pw.CloseWithError(err) }() return pr, nil } func DecryptFilter(r io.ReadSeeker, decryptor Decryptor) (io.ReadCloser, error) { r.Seek(0, io.SeekStart) msg, err := mail.ReadMessage(r) if err != nil { r.Seek(0, io.SeekStart) return io.NopCloser(r), nil } if msg.Header.Get("X-Yggmail-Encryption") != "v1" { r.Seek(0, io.SeekStart) return io.NopCloser(r), nil } fromStr := msg.Header.Get("From") fPk := extractPublicKey(fromStr) if fPk == nil { r.Seek(0, io.SeekStart) return io.NopCloser(r), nil } innerVerify := func(data []byte, sig []byte) error { if !ed25519.Verify(fPk, data, sig) { return fmt.Errorf("inner signature invalid") } return nil } decrypted, err := DecryptPack(msg.Body, decryptor, innerVerify) if err != nil { r.Seek(0, io.SeekStart) return nil, fmt.Errorf("decrypt enc packet error: %w", err) } return decrypted, nil } func extractPublicKey(addr string) ed25519.PublicKey { a, err := mail.ParseAddress(addr) if err != nil { return nil } addr = a.Address _, aPk, err := utils.ParseAddress(addr) if err != nil { return nil } return aPk }