MM-64522: Use PBKDF2 as the new key derivation for remote cluster invitation (#33493)

https://mattermost.atlassian.net/browse/MM-64522

```release-note
NONE
```
This commit is contained in:
Agniva De Sarker 2025-07-21 19:08:31 +05:30 committed by GitHub
parent 14c67d4a60
commit bc859d7fb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 9 deletions

View file

@ -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 {

View file

@ -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 {

View file

@ -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[:])

View file

@ -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,
}
}