ygggo/internal/utils/e2ee/e2ee_test.go
Kaiy Belist 39759db7c2 - Inter-node Protocol: Implemented a custom communication protocol based on Protobuf structures.
- Bridge Node Support: Added functionality to manage mail (send/receive) on remote nodes via a bridge node. Full transparent management requires a private key on the bridge, while the remote node can operate with only a public key.
 - End-to-End Encryption (E2EE): Integrated E2EE using age. Encryption and decryption occur strictly at the bridge level; node administrators cannot access message content.
 - New Addressing Schema: Transitioned from nodepub@yggmail to userpub@nodepub.
 - Key Adaptation: Implemented ed25519 to x25519 coordinate adaptation, allowing signing keys to be used for encryption.

Changed
 - Address Encoding: Switched from hex16 to base32 for the address space.
 - Memory Optimization: Replaced direct memory allocations with streaming for mail processing to reduce RAM overhead.
 - Code Refactoring: Performed decomposition of the original inherited codebase to improve maintainability.

Fixed
 - Mail Updates: Fixed the mail retrieval mechanism to ensure near-instant updates for incoming messages.

Changes to be committed:
	modified:   .gitignore
	modified:   README.md
	modified:   cmd/yggmail/main.go
	modified:   go.mod
	modified:   go.sum
	new file:   internal/account/manager.go
	new file:   internal/account/session.go
	modified:   internal/imapserver/backend.go
	modified:   internal/imapserver/imap.go
	modified:   internal/imapserver/mailbox.go
	modified:   internal/imapserver/notify.go
	modified:   internal/imapserver/user.go
	new file:   internal/notify/notify.go
	new file:   internal/notify/remote/remote.go
	new file:   internal/remote/client.go
	new file:   internal/remote/handlers.go
	new file:   internal/remote/io.go
	new file:   internal/remote/server.go
	new file:   internal/remote/sign.go
	new file:   internal/remote/types/request.pb.go
	new file:   internal/remote/types/request.proto
	new file:   internal/remote/types/response.pb.go
	new file:   internal/remote/types/response.proto
	new file:   internal/shell/shell.go
	modified:   internal/smtpsender/sender.go
	modified:   internal/smtpserver/backend.go
	modified:   internal/smtpserver/session_local.go
	modified:   internal/smtpserver/session_remote.go
	modified:   internal/smtpserver/smtp.go
	new file:   internal/storage/filestore/filestore.go
	new file:   internal/storage/local/storage.go
	new file:   internal/storage/remote/mail.go
	new file:   internal/storage/remote/mailbox.go
	new file:   internal/storage/remote/queue.go
	new file:   internal/storage/remote/storage.go
	new file:   internal/storage/remote/watch.go
	modified:   internal/storage/sqlite3/sqlite3.go
	new file:   internal/storage/sqlite3/table_accounts.go
	modified:   internal/storage/sqlite3/table_mailboxes.go
	modified:   internal/storage/sqlite3/table_mails.go
	modified:   internal/storage/sqlite3/table_queue.go
	modified:   internal/storage/storage.go
	modified:   internal/storage/types/types.go
	modified:   internal/utils/address.go
	new file:   internal/utils/age/age.go
	new file:   internal/utils/age/bech32/bech32.go
	new file:   internal/utils/crypt.go
	new file:   internal/utils/e2ee/crypt.go
	new file:   internal/utils/e2ee/e2ee_test.go
	new file:   internal/utils/e2ee/msg.go
	new file:   internal/utils/x25519.go
	modified:   internal/welcome/welcome.go
2026-02-14 02:34:09 +05:00

84 lines
2.3 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_test
import (
"bytes"
"crypto/ed25519"
"io"
"testing"
"github.com/neilalexander/yggmail/internal/utils"
"github.com/neilalexander/yggmail/internal/utils/age"
"github.com/neilalexander/yggmail/internal/utils/e2ee"
)
func TestFullE2EECycle(t *testing.T) {
_, senderPriv, _ := ed25519.GenerateKey(nil)
senderPub := senderPriv.Public().(ed25519.PublicKey)
_, recipientPriv, _ := ed25519.GenerateKey(nil)
recipientPub := recipientPriv.Public().(ed25519.PublicKey)
_, domainPriv, _ := ed25519.GenerateKey(nil)
domainPub := domainPriv.Public().(ed25519.PublicKey)
senderAddr := utils.CreateAddress(senderPub, domainPub) // например: userkey@domainkey
original := []byte("From: " + senderAddr + "\r\nSubject: test\r\n\r\nHello world! Это тест.")
encryptedReader, err := e2ee.EncryptFilter(
bytes.NewReader(original),
senderAddr,
[]ed25519.PublicKey{recipientPub},
func(data []byte) ([]byte, error) {
return ed25519.Sign(senderPriv, data), nil
},
func(w io.Writer, rcpt ...ed25519.PublicKey) (io.WriteCloser, error) {
r, err := age.Recipient(rcpt...)
if err != nil {
return nil, err
}
return age.EncryptArmor(w, r...)
},
)
if err != nil {
t.Fatal("EncryptFilter failed:", err)
}
encryptedBytes, _ := io.ReadAll(encryptedReader)
encryptedReader.Close()
stored := bytes.NewReader(encryptedBytes)
decryptor := func(r io.Reader) (io.Reader, error) {
id, err := age.Identity(recipientPriv)
if err != nil {
return nil, err
}
return age.DecryptArmor(r, id)
}
decrypted, err := e2ee.DecryptFilter(stored, decryptor)
if err != nil {
t.Fatal("DecryptFilter failed:", err)
}
result, _ := io.ReadAll(decrypted)
decrypted.Close()
if !bytes.Contains(result, []byte("Hello world! Это тест.")) {
t.Fatalf("Decryption failed, got:\n%s", string(result))
}
if !bytes.Contains(result, []byte("Subject: test")) {
t.Fatalf("Original headers lost, got:\n%s", string(result))
}
t.Log("Full E2EE cycle OK. Decrypted body preview:\n", string(result[:200]))
}