From 00b6b32cf32c6ee1b23b7762b821c7d936734bc4 Mon Sep 17 00:00:00 2001 From: madipo2611 Date: Wed, 20 Aug 2025 23:29:04 +0300 Subject: [PATCH] =?UTF-8?q?v.0.0.4=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D1=88=D0=B8=D1=84=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto/server_crypto.go | 101 ++++++++++++++++++++++++ crypto/vault.go | 100 +++++++++++++++++++++++ go.mod | 18 ++++- go.sum | 64 +++++++++++++-- migrations/0002_initial_schema.down.sql | 3 + migrations/0002_initial_schema.up.sql | 14 ++++ server.go | 88 ++++++++++++++++++--- 7 files changed, 369 insertions(+), 19 deletions(-) create mode 100644 crypto/server_crypto.go create mode 100644 crypto/vault.go create mode 100644 migrations/0002_initial_schema.down.sql create mode 100644 migrations/0002_initial_schema.up.sql diff --git a/crypto/server_crypto.go b/crypto/server_crypto.go new file mode 100644 index 0000000..ecb477a --- /dev/null +++ b/crypto/server_crypto.go @@ -0,0 +1,101 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "io" +) + +type CryptoService struct { + privateKey *rsa.PrivateKey + Vault *VaultManager +} + +func NewCryptoService() (*CryptoService, error) { + vault, err := NewVaultManager() + if err != nil { + return nil, err + } + + // Загрузка приватного ключа из Vault + keyBytes, err := vault.GetMasterPrivateKey() + if err != nil { + return nil, err + } + + privateKey, err := x509.ParsePKCS1PrivateKey(keyBytes) + if err != nil { + return nil, err + } + + return &CryptoService{ + privateKey: privateKey, + Vault: vault, + }, nil +} + +// Шифрование сообщения +func (cs *CryptoService) EncryptMessage(content string) ([]byte, []byte, []byte, error) { + // Генерация сессионного AES ключа + aesKey := make([]byte, 32) + if _, err := rand.Read(aesKey); err != nil { + return nil, nil, nil, err + } + + // Шифрование сообщения AES-GCM + block, err := aes.NewCipher(aesKey) + if err != nil { + return nil, nil, nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, nil, nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, nil, nil, err + } + + encryptedContent := gcm.Seal(nonce, nonce, []byte(content), nil) + + // Шифрование AES ключа RSA-OAEP + encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &cs.privateKey.PublicKey, aesKey, nil) + if err != nil { + return nil, nil, nil, err + } + + return encryptedContent, encryptedKey, nonce, nil +} + +// Расшифровка сообщения +func (cs *CryptoService) DecryptMessage(encryptedContent, encryptedKey, nonce []byte) (string, error) { + // Расшифровка AES ключа + aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, cs.privateKey, encryptedKey, nil) + if err != nil { + return "", err + } + + // Расшифровка сообщения + block, err := aes.NewCipher(aesKey) + if err != nil { + return "", err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + plaintext, err := gcm.Open(nil, nonce, encryptedContent, nil) + if err != nil { + return "", err + } + + return string(plaintext), nil +} diff --git a/crypto/vault.go b/crypto/vault.go new file mode 100644 index 0000000..4f936be --- /dev/null +++ b/crypto/vault.go @@ -0,0 +1,100 @@ +package crypto + +import ( + "encoding/base64" + "fmt" + "log" + "time" + + "github.com/hashicorp/vault/api" +) + +type VaultManager struct { + client *api.Client +} + +func NewVaultManager() (*VaultManager, error) { + config := api.DefaultConfig() + config.Address = "http://192.168.0.59:8200" // или https в продакшене + + client, err := api.NewClient(config) + if err != nil { + return nil, fmt.Errorf("failed to create Vault client: %v", err) + } + + // Аутентификация через username/password + authData := map[string]interface{}{ + "password": "2T6sDQ3PyG6x+0Z950ojAA+lWQ8HhUqd", + } + + secret, err := client.Logical().Write("auth/userpass/login/tailly-app", authData) + if err != nil { + return nil, fmt.Errorf("failed to authenticate with Vault: %v", err) + } + + client.SetToken(secret.Auth.ClientToken) + + // Настраиваем автоматическое обновление токена + go func() { + for { + time.Sleep(30 * time.Minute) + secret, err := client.Auth().Token().RenewSelf(0) + if err != nil { + log.Printf("Failed to renew Vault token: %v", err) + } else { + client.SetToken(secret.Auth.ClientToken) + } + } + }() + + return &VaultManager{client: client}, nil +} + +func (v *VaultManager) GetMasterPrivateKey() ([]byte, error) { + secret, err := v.client.Logical().Read("kv/data/keys/master") + if err != nil { + return nil, fmt.Errorf("failed to read master key: %v", err) + } + + if secret == nil || secret.Data == nil { + return nil, fmt.Errorf("master key not found") + } + + data := secret.Data["data"].(map[string]interface{}) + keyBase64 := data["private_key"].(string) + + return base64.StdEncoding.DecodeString(keyBase64) +} + +func (v *VaultManager) StoreSessionKey(chatID int, messageID int, encryptedKey []byte) error { + path := fmt.Sprintf("kv/data/keys/chat_%d/message_%d", chatID, messageID) + + _, err := v.client.Logical().Write(path, map[string]interface{}{ + "data": map[string]interface{}{ + "encrypted_key": base64.StdEncoding.EncodeToString(encryptedKey), + "timestamp": time.Now().Unix(), + "chat_id": chatID, + "message_id": messageID, + }, + }) + + return err +} + +func (v *VaultManager) GetSessionKey(chatID int, messageID int) ([]byte, error) { + path := fmt.Sprintf("kv/data/keys/chat_%d/message_%d", chatID, messageID) + + secret, err := v.client.Logical().Read(path) + if err != nil { + return nil, err + } + + if secret == nil || secret.Data == nil { + return nil, fmt.Errorf("session key not found") + } + + data := secret.Data["data"].(map[string]interface{}) + keyBase64 := data["encrypted_key"].(string) + + return base64.StdEncoding.DecodeString(keyBase64) +} diff --git a/go.mod b/go.mod index fdd95f2..94d4569 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,27 @@ module tailly_messages go 1.25rc2 require ( + github.com/hashicorp/vault/api v1.20.0 github.com/jackc/pgx/v4 v4.18.3 github.com/rabbitmq/amqp091-go v1.10.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.7 ) require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -20,11 +32,15 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/puddle v1.3.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 20fa377..f9dcacb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -8,6 +12,11 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -15,6 +24,8 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -24,6 +35,31 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4= +github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -87,21 +123,38 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -111,17 +164,13 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= @@ -170,6 +219,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -193,6 +243,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/migrations/0002_initial_schema.down.sql b/migrations/0002_initial_schema.down.sql new file mode 100644 index 0000000..950ffe8 --- /dev/null +++ b/migrations/0002_initial_schema.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE messages +DROP COLUMN encrypted_key, +DROP COLUMN nonce; \ No newline at end of file diff --git a/migrations/0002_initial_schema.up.sql b/migrations/0002_initial_schema.up.sql new file mode 100644 index 0000000..f3afc9d --- /dev/null +++ b/migrations/0002_initial_schema.up.sql @@ -0,0 +1,14 @@ +ALTER TABLE messages + ADD COLUMN encrypted_key BYTEA, +ADD COLUMN nonce BYTEA; + +-- Обновляем существующие сообщения (если есть) +UPDATE messages SET + encrypted_key = ''::bytea, +nonce = ''::bytea +WHERE encrypted_key IS NULL OR nonce IS NULL; + +-- Делаем поля обязательными +ALTER TABLE messages + ALTER COLUMN encrypted_key SET NOT NULL, +ALTER COLUMN nonce SET NOT NULL; \ No newline at end of file diff --git a/server.go b/server.go index fea0274..d6501cb 100644 --- a/server.go +++ b/server.go @@ -17,6 +17,7 @@ import ( "net" "os" "sync" + crypto2 "tailly_messages/crypto" "tailly_messages/proto" "time" ) @@ -25,14 +26,21 @@ type server struct { proto.UnimplementedMessageServiceServer db *pgxpool.Pool rabbitConn *amqp.Connection + crypto *crypto2.CryptoService mu sync.Mutex logger *log.Logger } func NewServer(db *pgxpool.Pool, rabbitConn *amqp.Connection) *server { + cryptoService, err := crypto2.NewCryptoService() + if err != nil { + log.Fatalf("Failed to initialize crypto service: %v", err) + } + return &server{ db: db, rabbitConn: rabbitConn, + crypto: cryptoService, logger: log.New(os.Stdout, "MESSAGE_SERVICE: ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile), } } @@ -77,7 +85,7 @@ func (s *server) CreateChat(ctx context.Context, req *proto.CreateChatRequest) ( errMsg := fmt.Sprintf("One or both users don't exist: user1=%d (%t), user2=%d (%t)", user1, user1Exists, user2, user2Exists) s.logger.Println(errMsg) - return nil, fmt.Errorf("%w", errors.New(errMsg)) // Оборачиваем ошибку + return nil, fmt.Errorf("%w", errors.New(errMsg)) } var chat proto.Chat @@ -130,9 +138,25 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest) s.logger.Printf("SendMessage execution time: %v", time.Since(start)) }(time.Now()) + // Шифрование сообщения + s.logger.Printf("Encrypting message for chat_id=%d", req.ChatId) + encryptedContent, encryptedKey, nonce, err := s.crypto.EncryptMessage(req.Content) + if err != nil { + s.logger.Printf("Failed to encrypt message: %v", err) + return nil, fmt.Errorf("failed to encrypt message: %v", err) + } + + // Сохранение сессионного ключа в Vault + s.logger.Printf("Storing session key in Vault for chat_id=%d, message", req.ChatId) + err = s.crypto.Vault.StoreSessionKey(int(req.ChatId), 0, encryptedKey) // messageID будет 0 до вставки + if err != nil { + s.logger.Printf("Failed to store session key: %v", err) + return nil, fmt.Errorf("failed to store session key: %v", err) + } + s.logger.Printf("Getting chat info for chat_id=%d", req.ChatId) var user1Id, user2Id int32 - err := s.db.QueryRow(ctx, "SELECT user1_id, user2_id FROM chats WHERE id = $1", req.ChatId).Scan(&user1Id, &user2Id) + err = s.db.QueryRow(ctx, "SELECT user1_id, user2_id FROM chats WHERE id = $1", req.ChatId).Scan(&user1Id, &user2Id) if err != nil { s.logger.Printf("Failed to get chat info: %v", err) return nil, fmt.Errorf("failed to get chat info: %v", err) @@ -146,19 +170,19 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest) } else { errMsg := fmt.Sprintf("sender %d is not a participant of chat %d", req.SenderId, req.ChatId) s.logger.Println(errMsg) - return nil, errors.New(errMsg) // Используем errors.New + return nil, errors.New(errMsg) } - s.logger.Printf("Inserting message into database: chat_id=%d, sender_id=%d, receiver_id=%d", + s.logger.Printf("Inserting encrypted message into database: chat_id=%d, sender_id=%d, receiver_id=%d", req.ChatId, req.SenderId, receiverId) var message proto.Message var createdAt time.Time err = s.db.QueryRow(ctx, ` - INSERT INTO messages (chat_id, sender_id, receiver_id, content) - VALUES ($1, $2, $3, $4) + INSERT INTO messages (chat_id, sender_id, receiver_id, content, encrypted_key, nonce) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, chat_id, sender_id, receiver_id, content, status, created_at - `, req.ChatId, req.SenderId, receiverId, req.Content).Scan( + `, req.ChatId, req.SenderId, receiverId, encryptedContent, encryptedKey, nonce).Scan( &message.Id, &message.ChatId, &message.SenderId, &message.ReceiverId, &message.Content, &message.Status, &createdAt, ) if err != nil { @@ -168,6 +192,14 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest) message.CreatedAt = timestamppb.New(createdAt) + // Обновление сессионного ключа в Vault с правильным messageID + s.logger.Printf("Updating session key in Vault with actual message_id=%d", message.Id) + err = s.crypto.Vault.StoreSessionKey(int(req.ChatId), int(message.Id), encryptedKey) + if err != nil { + s.logger.Printf("Failed to update session key with message ID: %v", err) + // Не прерываем выполнение, так как ключ уже сохранен с chatID + } + s.logger.Printf("Updating chat updated_at for chat_id=%d", req.ChatId) _, err = s.db.Exec(ctx, `UPDATE chats SET updated_at = NOW() WHERE id = $1`, req.ChatId) if err != nil { @@ -175,7 +207,25 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest) return nil, err } - s.logger.Printf("Publishing message to RabbitMQ for user_id=%d", receiverId) + // Расшифровка для отправки через RabbitMQ (получателю нужен расшифрованный текст) + decryptedContent, err := s.crypto.DecryptMessage(encryptedContent, encryptedKey, nonce) + if err != nil { + s.logger.Printf("Failed to decrypt message for RabbitMQ: %v", err) + return nil, fmt.Errorf("failed to decrypt message: %v", err) + } + + // Создаем копию сообщения с расшифрованным содержимым для RabbitMQ + rabbitMsg := proto.Message{ + Id: message.Id, + ChatId: message.ChatId, + SenderId: message.SenderId, + ReceiverId: message.ReceiverId, + Content: decryptedContent, + Status: message.Status, + CreatedAt: message.CreatedAt, + } + + s.logger.Printf("Publishing decrypted message to RabbitMQ for user_id=%d", receiverId) var lastErr error for i := 0; i < 3; i++ { ch, err := s.rabbitConn.Channel() @@ -187,7 +237,7 @@ func (s *server) SendMessage(ctx context.Context, req *proto.SendMessageRequest) } queueName := fmt.Sprintf("user_%d_messages", receiverId) - msgBytes, _ := json.Marshal(message) + msgBytes, _ := json.Marshal(rabbitMsg) s.logger.Printf("Publishing to queue %s: %s", queueName, string(msgBytes)) err = ch.PublishWithContext(ctx, @@ -281,7 +331,7 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages s.logger.Printf("Querying messages for chat_id=%d, limit=%d, offset=%d", req.ChatId, req.Limit, req.Offset) rows, err := s.db.Query(ctx, ` - SELECT id, chat_id, sender_id, receiver_id, content, status, created_at + SELECT id, chat_id, sender_id, receiver_id, content, encrypted_key, nonce, status, created_at FROM messages WHERE chat_id = $1 ORDER BY created_at DESC @@ -297,13 +347,27 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages for rows.Next() { var msg proto.Message var createdAt time.Time + var encryptedKey, nonce []byte + var encryptedContent string + err := rows.Scan( - &msg.Id, &msg.ChatId, &msg.SenderId, &msg.ReceiverId, &msg.Content, &msg.Status, &createdAt, + &msg.Id, &msg.ChatId, &msg.SenderId, &msg.ReceiverId, + &encryptedContent, &encryptedKey, &nonce, &msg.Status, &createdAt, ) if err != nil { s.logger.Printf("Failed to scan message row: %v", err) return nil, err } + + // Расшифровка сообщения + decryptedContent, err := s.crypto.DecryptMessage([]byte(encryptedContent), encryptedKey, nonce) + if err != nil { + s.logger.Printf("Failed to decrypt message %d: %v", msg.Id, err) + msg.Content = "[encrypted - decryption failed]" + } else { + msg.Content = decryptedContent + } + msg.CreatedAt = timestamppb.New(createdAt) messages = append(messages, &msg) } @@ -313,7 +377,7 @@ func (s *server) GetChatMessages(ctx context.Context, req *proto.GetChatMessages return nil, err } - s.logger.Printf("Retrieved %d messages for chat_id=%d", len(messages), req.ChatId) + s.logger.Printf("Retrieved and decrypted %d messages for chat_id=%d", len(messages), req.ChatId) resp := &proto.MessagesResponse{Messages: messages} s.logResponse("GetChatMessages", resp, nil) return resp, nil