From e921d08b5a8a79bd0937ace0d08f035467ba902e Mon Sep 17 00:00:00 2001 From: Kleonikos Kyriakis Date: Tue, 19 Dec 2023 15:38:00 +0200 Subject: [PATCH] Introduce own e2ee encryption using RSA key-pairs and symmetric AES key (envelope enc) - add public key repo and cache - hard-code public keys temporarily - increase room pool size to 10 - fix broken multi-chunk messages transmission --- config/config.go | 1 + config/flag_keys.go | 1 + config/flags.go | 1 + go.sum | 11 -- internal/app/app.go | 9 +- .../{matrix_compressor.go => compress.go} | 6 +- .../{matrix_decompressor.go => decompress.go} | 0 internal/matrix/encryption.go | 85 +++++++++++++++ internal/matrix/matrix_messenger.go | 102 +++++++++++++++--- internal/matrix/room_handler.go | 12 +-- internal/matrix/types.go | 8 +- internal/messaging/messenger.go | 8 +- utils/aes/aes.go | 102 ++++++++++++++++++ utils/rsa/rsa.go | 101 +++++++++++++++++ 14 files changed, 402 insertions(+), 45 deletions(-) rename internal/matrix/{matrix_compressor.go => compress.go} (93%) rename internal/matrix/{matrix_decompressor.go => decompress.go} (100%) create mode 100644 internal/matrix/encryption.go create mode 100644 utils/aes/aes.go create mode 100644 utils/rsa/rsa.go diff --git a/config/config.go b/config/config.go index f34656de..5c41f99a 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ type AppConfig struct { DeveloperMode bool `mapstructure:"developer_mode"` SupportedRequestTypes SupportedRequestTypesFlag `mapstructure:"supported_request_types"` BotMode uint `mapstructure:"bot_mode"` // 0 both, 1 request, 2 response + PrivateRSAFileKey string `mapstructure:"private_rsa_file_key"` } type MatrixConfig struct { Key string `mapstructure:"matrix_key"` // TODO @evlekht I'd suggest to add some parsed config, so we'll see on config read if some fields are invalid diff --git a/config/flag_keys.go b/config/flag_keys.go index e8dd9cea..cd871dbd 100644 --- a/config/flag_keys.go +++ b/config/flag_keys.go @@ -16,4 +16,5 @@ const ( MessengerTimeoutKey = "messenger_timeout" SupportedRequestTypesKey = "supported_request_types" BotModeKey = "bot_mode" + PrivateRSAKey = "private_rsa_key" ) diff --git a/config/flags.go b/config/flags.go index 2e82cd68..102e7131 100644 --- a/config/flags.go +++ b/config/flags.go @@ -6,6 +6,7 @@ func readAppConfig(cfg AppConfig, fs *flag.FlagSet) { fs.BoolVar(&cfg.DeveloperMode, DeveloperMode, false, "Sets developer mode") fs.Var(&cfg.SupportedRequestTypes, SupportedRequestTypesKey, "The list of supported request types") fs.UintVar(&cfg.BotMode, BotModeKey, 0, "The bot mode") + fs.StringVar(&cfg.PrivateRSAFileKey, PrivateRSAKey, "", "The private RSA key file") flag.Parse() } diff --git a/go.sum b/go.sum index fefda482..9f0dc933 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,6 @@ -buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231211091155-5467620e05ed.2 h1:Yy0x91aZhzQOikR33x5eEIFEWS1TZzuzRc+LP8NuCgQ= -buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231211091155-5467620e05ed.2/go.mod h1:xDIPwKMomacOmFbzRICgdUP/gpjEoetNVYVTVr29H0k= -buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231214132539-21b35d953f3d.2 h1:ykl0rTU4nNvPtJRm2lqOCRpgOd93RSt3ev8hNkbozDE= -buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231214132539-21b35d953f3d.2/go.mod h1:tKtDR8xG+DIFkSv8PiW1YM64GxJ/44n3UfZAN+5jfJ8= buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231220001345-2dbff1450b98.2 h1:E1OG6V0s//gFBHK/aAniN4Cb2l/QFYsKdTT11Ymgh6g= buf.build/gen/go/chain4travel/camino-messenger-protocol/grpc/go v1.3.0-20231220001345-2dbff1450b98.2/go.mod h1:6OlE1AqRT7EzKZ6ukFLo6Qmf7iv4I59YUlYkiJFxly8= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.28.1-20231211091155-5467620e05ed.4/go.mod h1:2viX8eSuMFjoDrr8x3FYytCp81PVYkdgfB68aIcGW6c= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.28.1-20231214132539-21b35d953f3d.4/go.mod h1:2viX8eSuMFjoDrr8x3FYytCp81PVYkdgfB68aIcGW6c= buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.28.1-20231220001345-2dbff1450b98.4/go.mod h1:2viX8eSuMFjoDrr8x3FYytCp81PVYkdgfB68aIcGW6c= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231211091155-5467620e05ed.2 h1:8HbCQyMVfu/+Spx4yOPwWThwJpr0JELRxJgt8Kdoso4= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231211091155-5467620e05ed.2/go.mod h1:h8QtMQVd5+WnHrXJrqA/eCt8mGw9efCAmxoHzeORKdw= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231214132539-21b35d953f3d.2 h1:HTcdQrjEKtCEizgMVc1kmNtsGSQQ04WTh7fUNEuqCFE= -buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231214132539-21b35d953f3d.2/go.mod h1:h8QtMQVd5+WnHrXJrqA/eCt8mGw9efCAmxoHzeORKdw= buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231220001345-2dbff1450b98.2 h1:Wne/F/pUbrMAIQ874Akd5nXxoXM2tjzM14PdimMB3X8= buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go v1.31.0-20231220001345-2dbff1450b98.2/go.mod h1:h8QtMQVd5+WnHrXJrqA/eCt8mGw9efCAmxoHzeORKdw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -668,7 +658,6 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= diff --git a/internal/app/app.go b/internal/app/app.go index 71465ef8..15682642 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,6 +2,7 @@ package app import ( "context" + rsa_util "github.com/chain4travel/camino-messenger-bot/utils/rsa" "github.com/chain4travel/camino-messenger-bot/config" "github.com/chain4travel/camino-messenger-bot/internal/matrix" @@ -60,8 +61,12 @@ func (a *App) Run(ctx context.Context) error { a.logger.Error("Invalid bot mode") return nil } - - messenger := matrix.NewMessenger(&a.cfg.MatrixConfig, a.logger) + privateRSAKey, err := rsa_util.ParseRSAPrivateKeyFromFile(a.cfg.PrivateRSAFileKey) + if err != nil { + a.logger.Error("Error while parsing private RSA key") + return nil + } + messenger := matrix.NewMessenger(&a.cfg.MatrixConfig, a.logger, privateRSAKey) userIDUpdated := make(chan string) // Channel to pass the userID g.Go(func() error { a.logger.Infof("Starting message receiver with botmode %d ...", a.cfg.BotMode) diff --git a/internal/matrix/matrix_compressor.go b/internal/matrix/compress.go similarity index 93% rename from internal/matrix/matrix_compressor.go rename to internal/matrix/compress.go index bc50865a..3154174e 100644 --- a/internal/matrix/matrix_compressor.go +++ b/internal/matrix/compress.go @@ -7,7 +7,6 @@ package matrix import ( "fmt" - "github.com/chain4travel/camino-messenger-bot/internal/compression" "github.com/chain4travel/camino-messenger-bot/internal/messaging" "github.com/chain4travel/camino-messenger-bot/internal/metadata" @@ -21,6 +20,9 @@ func compressAndSplitCaminoMatrixMsg(msg messaging.Message) ([]CaminoMatrixMessa bytes []byte err error ) + if err != nil { + return nil, err + } switch msg.Type.Category() { case messaging.Request, messaging.Response: @@ -53,7 +55,7 @@ func compressAndSplitCaminoMatrixMsg(msg messaging.Message) ([]CaminoMatrixMessa for i, chunk := range splitCompressedContent[1:] { messages = append(messages, CaminoMatrixMessage{ MessageEventContent: event.MessageEventContent{MsgType: event.MessageType(msg.Type)}, - Metadata: metadata.Metadata{RequestID: msg.Metadata.RequestID, NumberOfChunks: uint(len(splitCompressedContent)), ChunkIndex: uint(i + 1)}, + Metadata: metadata.Metadata{RequestID: msg.Metadata.RequestID, Recipient: msg.Metadata.Recipient, NumberOfChunks: uint(len(splitCompressedContent)), ChunkIndex: uint(i + 1)}, CompressedContent: chunk, }) } diff --git a/internal/matrix/matrix_decompressor.go b/internal/matrix/decompress.go similarity index 100% rename from internal/matrix/matrix_decompressor.go rename to internal/matrix/decompress.go diff --git a/internal/matrix/encryption.go b/internal/matrix/encryption.go new file mode 100644 index 00000000..649e8103 --- /dev/null +++ b/internal/matrix/encryption.go @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022-2023, Chain4Travel AG. All rights reserved. + * See the file LICENSE for licensing terms. + */ + +package matrix + +import ( + "crypto/rsa" + "encoding/base64" + "fmt" + aes_util "github.com/chain4travel/camino-messenger-bot/utils/aes" + rsa_util "github.com/chain4travel/camino-messenger-bot/utils/rsa" + "sync" +) + +type EncryptionKeyRepository struct { + pubKeyCache map[string]*rsa.PublicKey + symmetricKeyCache map[string][]byte + mu sync.Mutex +} + +func NewEncryptionKeyRepository() *EncryptionKeyRepository { + return &EncryptionKeyRepository{pubKeyCache: make(map[string]*rsa.PublicKey), symmetricKeyCache: make(map[string][]byte)} +} +func (p *EncryptionKeyRepository) getPublicKeyForRecipient(recipient string) (*rsa.PublicKey, error) { + pkey := p.fetchKeyFromCache(recipient) + if pkey != nil { + return pkey, nil + } + var encodedPubKey string + //TODO for now it's all keys are hardcoded. Later we need to get the keys from the key server + switch recipient { + case "@t-kopernikus1tyewqsap6v8r8wghg7qn7dyfzg2prtcrw04ke3:matrix.camino.network": + encodedPubKey = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJpZ0tDQVlFQWpwR2R4NlloMkJnR3V2QUdQQTZiMnAzd1dBWnZYalQvWTBQR1ViRlR0U1lVY1hydEc1eEIKcy9DYVo4NGtmTEpHRnhEV3d3d3E4bzRoNHBzOGd0aHJkaG5QMUFIOEtGRFFCTzJNbDY5ZmFZYWd4ajdtVUxnSQpqTWIzUEorVjQzZUQrRktHZis2R0E5aHpXd09RZDhvVWdZVnhSQ2xMU0tMYi82WXFqaU81LzFxK3plTWowZWF2CmJQY2VsK2V6UVlpQmE3UzNJcHFGUFdhL0N0TTd3Qi90UWI2MnAzWFRkU0pnenR1SlJpTU5MeFI2NFU3WWlHcGsKR0VYSjFyd2lPZFhPMjJaRyt4UkxmOEl3ZFF2dEUxR1VnL1llTEtSOWd5blI5WTNiZzA5UWErRkpYQ1FrTjVHUApJY2E0S2R4UGpjQ0xHTklGVlVSTnNkTjFrZnJzcXpLTXNQOVgwQkFUMHNWYTk3WTd5RnAxUTFKTmU4Uy96T1FWCm9XOHJpSVFvWGRqSDNES2Q3cERQekN2TEpQRm50dzF5YWRUZ1pLbGs5Y21tT0dDbXh5SUZwMW5mTXk1R1FDM20KS1AxZ2NIV3J5UmFBcG4reG9BSFdIcHErcVNicmpka0h2MEt1MDRaMTRYcWhaK2Ezc3FtM3oreWpNYTF2OExDUApvK2I3OFI4OGpqVDFBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCg==" + case "@t-kopernikus15ss2mvy86h0hcwdhfukdx7y3cvuwdqxm6a0aqm:matrix.camino.network": + encodedPubKey = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJpZ0tDQVlFQXI5a1RrWHkyNWlIaTNhai9ib2VER3VFTmNJZ1dqVmlYRHVmUFJUQ1FSV0I0TEt1eCtaWXoKaElWckdPb2l6eDNoR29NTnMwOUlvODFzb2wyd3crQ0tENUtTYTVHVHNJelh6ZytGcEErTmsrcDFOOGlsWFVINQp4d2NFTlRBclVCK2Y0SmU4Vkl0dEc5ZVhHQW9aQ1RYc2FTRWNmVG1Lc24vVUdsSHVQdGs5WHVpTlNTb3k0ZTMvCnpGcGFjZngyaVR6TVJOMjc1Ky9aZjllZ3RtSnVXS0JKcnNOcC9iQ245Q2ErcURheDNMTmJpdG55TUF2eG5rUmgKTWZrQ1lHTHZmSkFoRVVlSVJUUkRLT0xCSEtRVFJpQ1F4SHlXSVVEQkswbkZtbkt5Ti80RUI1RWkzTkg4RkpFKwpaK1NobmExdmlkdWV0R2NtMjhKRFRweXhGRStyZXZQWWs3aXVJZGF3VEZtTUlabkRrTnpRRkxlSStHaXFPN2JNCkRlT0NSa2FBRDhnSkEzT29OeXBmUlRuaEMvVHFvMWk1VjZ1RlV5RU9LT3dvMHk4cEFCSmNTRzBoUVRxQUh3blAKZkZFLzI2REtsMzQzZ1oxV3lBa29QcUUyVk1ESklSVFVUcHhBR09IMk9qZDRnWjBJWk1QTks0RDYyMWk4V2NrZApNTlI5ZEZRQW1mOS9BZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCg==" + default: + return nil, fmt.Errorf("no public key found for recipient: %s", recipient) + } + pubKeyBytes, err := base64.StdEncoding.DecodeString(encodedPubKey) + if err != nil { + return nil, err + } + pKey, err := rsa_util.ParseRSAPublicKey(pubKeyBytes) + if err != nil { + return nil, err + } + p.cachePublicKey(recipient, pKey) + return pKey, nil +} + +func (p *EncryptionKeyRepository) cachePublicKey(recipient string, key *rsa.PublicKey) { + p.mu.Lock() + defer p.mu.Unlock() + p.pubKeyCache[recipient] = key +} + +func (p *EncryptionKeyRepository) fetchKeyFromCache(recipient string) *rsa.PublicKey { + p.mu.Lock() + defer p.mu.Unlock() + return p.pubKeyCache[recipient] +} + +func (p *EncryptionKeyRepository) getSymmetricKeyForRecipient(recipient string) []byte { + key := p.fetchSymmetricKeyFromCache(recipient) + if key != nil { + return key + } + key = aes_util.GenerateAESKey() + p.cacheSymmetricKey(recipient, key) + return key +} + +func (p *EncryptionKeyRepository) cacheSymmetricKey(recipient string, key []byte) { + p.mu.Lock() + defer p.mu.Unlock() + p.symmetricKeyCache[recipient] = key +} + +func (p *EncryptionKeyRepository) fetchSymmetricKeyFromCache(recipient string) []byte { + p.mu.Lock() + defer p.mu.Unlock() + return p.symmetricKeyCache[recipient] +} diff --git a/internal/matrix/matrix_messenger.go b/internal/matrix/matrix_messenger.go index 148f156e..a9602e46 100644 --- a/internal/matrix/matrix_messenger.go +++ b/internal/matrix/matrix_messenger.go @@ -2,8 +2,11 @@ package matrix import ( "context" + "crypto/rsa" "errors" "fmt" + aes_util "github.com/chain4travel/camino-messenger-bot/utils/aes" + rsa_util "github.com/chain4travel/camino-messenger-bot/utils/rsa" "reflect" "sync" "time" @@ -34,27 +37,70 @@ type client struct { cryptoHelper *cryptohelper.CryptoHelper } type messenger struct { - msgChannel chan messaging.Message - cfg *config.MatrixConfig - logger *zap.SugaredLogger - client client - roomHandler RoomHandler - msgAssembler MessageAssembler - mu sync.Mutex + msgChannel chan messaging.Message + cfg *config.MatrixConfig + logger *zap.SugaredLogger + client client + roomHandler RoomHandler + msgAssembler MessageAssembler + mu sync.Mutex + privateRSAKey *rsa.PrivateKey + encryptionKeyRepository EncryptionKeyRepository } -func NewMessenger(cfg *config.MatrixConfig, logger *zap.SugaredLogger) *messenger { +func (m *messenger) Encrypt(msg *CaminoMatrixMessage) error { + pubKey, err := m.encryptionKeyRepository.getPublicKeyForRecipient(msg.Metadata.Recipient) + if err != nil { + return err + } + + symmetricKey := m.encryptionKeyRepository.getSymmetricKeyForRecipient(msg.Metadata.Recipient) + // encrypt symmetric key with recipient's public key + msg.EncryptedSymmetricKey, err = rsa_util.EncryptWithPublicKey(symmetricKey, pubKey) + if err != nil { + return err + } + // encrypt message with symmetric key + encryptedCompressedContent, err := aes_util.Encrypt(msg.CompressedContent, symmetricKey) + if err != nil { + return err + } + msg.CompressedContent = nil + msg.EncryptedCompressedContent = encryptedCompressedContent + return nil +} + +func (m *messenger) Decrypt(msg *CaminoMatrixMessage) error { + // decrypt symmetric key with private key + symmetricKey, err := rsa_util.DecryptWithPrivateKey(msg.EncryptedSymmetricKey, m.privateRSAKey) + if err != nil { + return err + } + + m.encryptionKeyRepository.cacheSymmetricKey(msg.Metadata.Sender, symmetricKey) + // decrypt message with symmetric key + decryptedCompressedContent, err := aes_util.Decrypt(msg.EncryptedCompressedContent, symmetricKey) + if err != nil { + return err + } + msg.CompressedContent = decryptedCompressedContent + return nil +} + +func NewMessenger(cfg *config.MatrixConfig, logger *zap.SugaredLogger, privateRSAKey *rsa.PrivateKey) *messenger { c, err := mautrix.NewClient(cfg.Host, "", "") if err != nil { panic(err) } return &messenger{ - msgChannel: make(chan messaging.Message), - cfg: cfg, - logger: logger, - client: client{Client: c}, - roomHandler: NewRoomHandler(c, logger), - msgAssembler: NewMessageAssembler(logger), + msgChannel: make(chan messaging.Message), + cfg: cfg, + logger: logger, + client: client{Client: c}, + roomHandler: NewRoomHandler(c, logger), + msgAssembler: NewMessageAssembler(logger), + privateRSAKey: privateRSAKey, + encryptionKeyRepository: *NewEncryptionKeyRepository(), } } func (m *messenger) Checkpoint() string { @@ -68,9 +114,24 @@ func (m *messenger) StartReceiver(botMode uint) (string, error) { processCamMsg := func(source mautrix.EventSource, evt *event.Event) { msg := evt.Content.Parsed.(*CaminoMatrixMessage) - go func() { t := time.Now() + if msg.EncryptedSymmetricKey == nil { // if no symmetric key is provided, it should have been exchanged and cached already + key := m.encryptionKeyRepository.fetchSymmetricKeyFromCache(msg.Metadata.Sender) + if key == nil { + m.logger.Errorf("no symmetric key found for sender: %s [request-id:%s]", msg.Metadata.Sender, msg.Metadata.RequestID) + return + } else { + msg.EncryptedSymmetricKey = key + } + } + err := m.Decrypt(msg) + if err != nil { + m.logger.Errorf("failed to decrypt message: %v", err) + return + } + fmt.Printf("%d|decrypted-message|%s|%d\n", t.UnixMilli(), evt.ID.String(), time.Since(t).Milliseconds()) + t = time.Now() completeMsg, err, completed := m.msgAssembler.AssembleMessage(*msg) if err != nil { m.logger.Errorf("failed to assemble message: %v", err) @@ -82,6 +143,7 @@ func (m *messenger) StartReceiver(botMode uint) (string, error) { completeMsg.Metadata.StampOn(fmt.Sprintf("matrix-sent-%s", completeMsg.MsgType), evt.Timestamp) completeMsg.Metadata.StampOn(fmt.Sprintf("%s-%s-%s", m.Checkpoint(), "received", completeMsg.MsgType), t.UnixMilli()) + t = time.Now() m.mu.Lock() m.msgChannel <- messaging.Message{ Metadata: completeMsg.Metadata, @@ -107,7 +169,7 @@ func (m *messenger) StartReceiver(botMode uint) (string, error) { if evt.GetStateKey() == m.client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite && !m.roomHandler.HasAlreadyJoined(id.UserID(evt.Sender.String()), evt.RoomID) { _, err := m.client.JoinRoomByID(evt.RoomID) if err == nil { - m.roomHandler.CacheRoom(id.UserID(evt.Sender.String()), evt.RoomID) // add room to cache + m.roomHandler.CacheRoom(id.UserID(evt.Sender.String()), evt.RoomID) // add room to pubKeyCache m.logger.Info("Joined room after invite", zap.String("room_id", evt.RoomID.String()), zap.String("inviter", evt.Sender.String())) @@ -200,7 +262,13 @@ func (m *messenger) SendAsync(_ context.Context, msg messaging.Message) error { func (m *messenger) sendMessageEvents(roomID id.RoomID, eventType event.Type, messages []CaminoMatrixMessage) error { //TODO add retry logic? for _, msg := range messages { - _, err := m.client.SendMessageEvent(roomID, eventType, msg, mautrix.ReqSendEvent{TransactionID: msg.Metadata.RequestID}) + t := time.Now() + err := m.Encrypt(&msg) + if err != nil { + return err + } + fmt.Printf("%d|encrypted-message|%d\n", t.UnixMilli(), time.Since(t).Milliseconds()) + _, err = m.client.SendMessageEvent(roomID, eventType, msg) if err != nil { return err } diff --git a/internal/matrix/room_handler.go b/internal/matrix/room_handler.go index 1645323c..d6a852b3 100644 --- a/internal/matrix/room_handler.go +++ b/internal/matrix/room_handler.go @@ -8,7 +8,7 @@ import ( "sync" ) -const RoomPoolSize = 10 +const RoomPoolSize = 50 type RoomHandler interface { Init() @@ -34,12 +34,12 @@ func (r *roomHandler) Init() { return } - // cache all encrypted rooms + // pubKeyCache all encrypted rooms for _, roomID := range rooms.JoinedRooms { - r.logger.Debugf("Caching room %v | encrypted: %v", roomID, r.client.StateStore.IsEncrypted(roomID)) - if !r.client.StateStore.IsEncrypted(roomID) { + if r.client.StateStore.IsEncrypted(roomID) { continue } + r.logger.Debugf("Caching room %v | encrypted: %v", roomID, r.client.StateStore.IsEncrypted(roomID)) members, err := r.client.JoinedMembers(roomID) if err != nil { r.logger.Debugf("failed to fetch members for room %v", roomID) @@ -82,7 +82,7 @@ func (r *roomHandler) GetOrCreateRoomForRecipient(recipient id.UserID) (id.RoomI if err != nil { return "", err } else { - err = r.enableEncryptionForRoom(roomID) + //err = r.enableEncryptionForRoom(roomID) return roomID, err } } @@ -130,7 +130,7 @@ func (r *roomHandler) getEncryptedRoomForRecipient(recipient id.UserID) (id.Room createdRooms := 0 for _, roomID := range rooms.JoinedRooms { - if !r.client.StateStore.IsEncrypted(roomID) { + if r.client.StateStore.IsEncrypted(roomID) { continue } members, err := r.client.JoinedMembers(roomID) diff --git a/internal/matrix/types.go b/internal/matrix/types.go index ad434fe4..8ce69e20 100644 --- a/internal/matrix/types.go +++ b/internal/matrix/types.go @@ -10,9 +10,11 @@ import ( // CaminoMatrixMessage is a matrix-specific message format used for communication between the messenger and the service type CaminoMatrixMessage struct { event.MessageEventContent - Content messaging.MessageContent `json:"content"` - CompressedContent []byte `json:"compressed_content"` - Metadata metadata.Metadata `json:"metadata"` + Content messaging.MessageContent `json:"content"` + CompressedContent []byte `json:"compressed_content"` + EncryptedCompressedContent []byte `json:"encrypted_compressed_content"` + EncryptedSymmetricKey []byte `json:"encrypted_symmetric_key"` + Metadata metadata.Metadata `json:"metadata"` } type ByChunkIndex []CaminoMatrixMessage diff --git a/internal/messaging/messenger.go b/internal/messaging/messenger.go index d785cfb8..bb0241a8 100644 --- a/internal/messaging/messenger.go +++ b/internal/messaging/messenger.go @@ -12,8 +12,8 @@ type APIMessageResponse struct { } type Messenger interface { metadata.Checkpoint - StartReceiver(botMode uint) (string, error) // start receiving messages. Returns the user id - StopReceiver() error // stop receiving messages - SendAsync(ctx context.Context, m Message) error // asynchronous call (fire and forget) - Inbound() chan Message // channel where incoming messages are written + StartReceiver(botMode uint) (string, error) // start receiving messages. Returns the user id + StopReceiver() error // stop receiving messages + SendAsync(context.Context, Message) error // asynchronous call (fire and forget) + Inbound() chan Message // channel where incoming messages are written } diff --git a/utils/aes/aes.go b/utils/aes/aes.go new file mode 100644 index 00000000..10332120 --- /dev/null +++ b/utils/aes/aes.go @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022-2023, Chain4Travel AG. All rights reserved. + * See the file LICENSE for licensing terms. + */ + +package aes_util + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" +) + +func main() { + // Generate a random AES key + key := GenerateAESKey() + + // The data you want to encrypt + data := []byte("This is a large byte array that needs to be encrypted.") + + // Encrypt the data + encryptedData, err := Encrypt(data, key) + if err != nil { + fmt.Println("Encryption error:", err) + return + } + + fmt.Println("Encrypted data:", encryptedData) + + // Decrypt the data + decryptedData, err := Decrypt(encryptedData, key) + if err != nil { + fmt.Println("Decryption error:", err) + return + } + + fmt.Println("Decrypted data:", string(decryptedData)) +} + +// GenerateAESKey generates a random AES key. +func GenerateAESKey() []byte { + key := make([]byte, 32) // AES-256 key size + _, err := rand.Read(key) + if err != nil { + panic("Error generating AES key: " + err.Error()) + } + return key +} + +// Encrypt encrypts data using AES. +func Encrypt(data, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Create a new AES cipher block mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // Generate a random nonce + nonce := make([]byte, gcm.NonceSize()) + if _, err = rand.Read(nonce); err != nil { + return nil, err + } + + // Encrypt the data + ciphertext := gcm.Seal(nil, nonce, data, nil) + return append(nonce, ciphertext...), nil +} + +// Decrypt decrypts data using AES. +func Decrypt(ciphertext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Create a new AES cipher block mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // Extract the nonce from the ciphertext + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, fmt.Errorf("Ciphertext is too short") + } + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + // Decrypt the data + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} diff --git a/utils/rsa/rsa.go b/utils/rsa/rsa.go new file mode 100644 index 00000000..6d210a23 --- /dev/null +++ b/utils/rsa/rsa.go @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022-2023, Chain4Travel AG. All rights reserved. + * See the file LICENSE for licensing terms. + */ + +package rsa_util + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "os" +) + +func main() { + // Replace these file paths with your own private and public key file paths + privateKeyFilePath := "private_key2.pem" + publicKeyFilePath := "public_key2.pem" + + // Read private key + _, err := ParseRSAPrivateKeyFromFile(privateKeyFilePath) + if err != nil { + fmt.Printf("Error reading private key file: %v\n", err) + return + } + + publicKeyBytes, err := os.ReadFile(publicKeyFilePath) + if err != nil { + fmt.Printf("Error reading public key file: %v\n", err) + return + } + + // Use the keys as needed + //fmt.Println("Private Key:", privateKey) + + b := make([]byte, base64.StdEncoding.EncodedLen(len(publicKeyBytes))) + base64.StdEncoding.Encode(b, publicKeyBytes) + fmt.Println("Public Key:", string(b)) + dec := make([]byte, base64.StdEncoding.DecodedLen(len(b))) + base64.StdEncoding.Decode(dec, b) + fmt.Println("Public Key:", string(dec)) + + p, err := ParseRSAPublicKey(dec) + if err != nil { + fmt.Printf("Error parsing public key: %v\n", err) + return + } + fmt.Println("Public Key:", p) +} + +func ParseRSAPrivateKeyFromFile(keyFilePath string) (*rsa.PrivateKey, error) { + privateKeyBytes, err := os.ReadFile(keyFilePath) + if err != nil { + fmt.Printf("Error reading private key file: %v\n", err) + return nil, err + } + return ParseRSAPrivateKey(privateKeyBytes) +} +func ParseRSAPrivateKey(keyBytes []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(keyBytes) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return nil, fmt.Errorf("failed to decode PEM block containing private key") + } + + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %v", err) + } + + return privateKey, nil +} + +func ParseRSAPublicKey(keyBytes []byte) (*rsa.PublicKey, error) { + block, _ := pem.Decode(keyBytes) + if block == nil || block.Type != "RSA PUBLIC KEY" { + return nil, fmt.Errorf("failed to decode PEM block containing public key") + } + + publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse public key: %v", err) + } + + return publicKey, nil +} + +// EncryptWithPublicKey encrypts data with public key +func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) ([]byte, error) { + hash := sha512.New() + return rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil) +} + +// DecryptWithPrivateKey decrypts data with private key +func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error) { + hash := sha512.New() + return rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil) +}