102 lines
2.1 KiB
Go
102 lines
2.1 KiB
Go
|
|
/*
|
||
|
|
* 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
|
||
|
|
}
|