diff --git a/server/channels/app/remote_cluster.go b/server/channels/app/remote_cluster.go index ca469cc0163..020964c0ff0 100644 --- a/server/channels/app/remote_cluster.go +++ b/server/channels/app/remote_cluster.go @@ -202,7 +202,7 @@ func (a *App) CreateRemoteClusterInvite(remoteId, siteURL, token, password strin RemoteId: remoteId, SiteURL: siteURL, Token: token, - Version: 2, + Version: 3, } if err := invite.IsValid(); err != nil { diff --git a/server/platform/services/remotecluster/invitation.go b/server/platform/services/remotecluster/invitation.go index a9735327908..a0b2b414d70 100644 --- a/server/platform/services/remotecluster/invitation.go +++ b/server/platform/services/remotecluster/invitation.go @@ -80,7 +80,7 @@ func makeConfirmFrame(rc *model.RemoteCluster, siteURL string) (*model.RemoteClu SiteURL: siteURL, Token: rc.Token, RefreshedToken: rc.RemoteToken, - Version: 2, + Version: 3, } confirmRaw, err := json.Marshal(confirm) if err != nil { diff --git a/server/public/model/remote_cluster.go b/server/public/model/remote_cluster.go index 78f693ff3f6..53bcb078a79 100644 --- a/server/public/model/remote_cluster.go +++ b/server/public/model/remote_cluster.go @@ -8,6 +8,7 @@ import ( "crypto/cipher" "crypto/md5" "crypto/rand" + "crypto/sha256" "encoding/base32" "encoding/json" "errors" @@ -17,6 +18,7 @@ import ( "regexp" "strings" + "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" ) @@ -401,9 +403,16 @@ func (rci *RemoteClusterInvite) Encrypt(password string) ([]byte, error) { return nil, err } - key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32) - if err != nil { - return nil, err + var key []byte + if rci.Version >= 3 { + // Use PBKDF2 for version 3 and above + key = pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New) + } else { + // Use scrypt for older versions + key, err = scrypt.Key([]byte(password), salt, 32768, 8, 1, 32) + if err != nil { + return nil, err + } } block, err := aes.NewCipher(key[:]) @@ -437,9 +446,28 @@ func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error salt := encrypted[:16] encrypted = encrypted[16:] - key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32) - if err != nil { - return err + // Try PBKDF2 first (for version 3+) + if err := rci.tryDecrypt(encrypted, password, salt, true); err == nil { + return nil + } + + // Fall back to scrypt (for older versions) + return rci.tryDecrypt(encrypted, password, salt, false) +} + +func (rci *RemoteClusterInvite) tryDecrypt(encrypted []byte, password string, salt []byte, usePBKDF2 bool) error { + var key []byte + var err error + + if usePBKDF2 { + // Use PBKDF2 for version 3 and above + key = pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New) + } else { + // Use scrypt for older versions + key, err = scrypt.Key([]byte(password), salt, 32768, 8, 1, 32) + if err != nil { + return err + } } block, err := aes.NewCipher(key[:]) diff --git a/server/public/model/remote_cluster_test.go b/server/public/model/remote_cluster_test.go index d9689e32951..615995a0ec5 100644 --- a/server/public/model/remote_cluster_test.go +++ b/server/public/model/remote_cluster_test.go @@ -158,13 +158,55 @@ func TestRemoteClusterInviteEncryption(t *testing.T) { } } +func TestRemoteClusterInviteBackwardCompatibility(t *testing.T) { + // Test that we can decrypt invites created with the old scrypt method + oldInvite := RemoteClusterInvite{ + RemoteId: NewId(), + SiteURL: "https://example.com:8065", + Token: NewId(), + RefreshedToken: NewId(), + Version: 2, // Old version using scrypt + } + + password := "test password" + + // Encrypt with old method (scrypt) + encrypted, err := oldInvite.Encrypt(password) + require.NoError(t, err) + + // Decrypt should work with backward compatibility + decryptedInvite := RemoteClusterInvite{} + err = decryptedInvite.Decrypt(encrypted, password) + require.NoError(t, err) + assert.Equal(t, oldInvite, decryptedInvite) + + // Test new version (PBKDF2) + newInvite := RemoteClusterInvite{ + RemoteId: NewId(), + SiteURL: "https://example.com:8065", + Token: NewId(), + RefreshedToken: NewId(), + Version: 3, // New version using PBKDF2 + } + + // Encrypt with new method (PBKDF2) + encrypted, err = newInvite.Encrypt(password) + require.NoError(t, err) + + // Decrypt should work + decryptedInvite = RemoteClusterInvite{} + err = decryptedInvite.Decrypt(encrypted, password) + require.NoError(t, err) + assert.Equal(t, newInvite, decryptedInvite) +} + func makeInvite(url string) RemoteClusterInvite { return RemoteClusterInvite{ RemoteId: NewId(), SiteURL: url, Token: NewId(), RefreshedToken: NewId(), - Version: 2, + Version: 3, } }