diff --git a/protocol/common/crypto.go b/protocol/common/crypto.go index ef7093cde..86c770277 100644 --- a/protocol/common/crypto.go +++ b/protocol/common/crypto.go @@ -4,8 +4,10 @@ import ( "crypto/aes" "crypto/cipher" "crypto/ecdsa" + "crypto/rand" "errors" "io" + "math/big" "golang.org/x/crypto/sha3" @@ -21,7 +23,13 @@ const ( defaultECHDMACLength = 16 ) -var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length") +var ( + ErrInvalidCiphertextLength = errors.New("invalid cyphertext length") + + letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + numberRunes = []rune("0123456789") + alphanumericRunes = append(numberRunes, letterRunes...) +) func HashPublicKey(pk *ecdsa.PublicKey) []byte { return Shake256(crypto.CompressPubkey(pk)) @@ -100,3 +108,26 @@ func MakeECDHSharedKey(yourPrivateKey *ecdsa.PrivateKey, theirPubKey *ecdsa.Publ defaultECHDMACLength, ) } + +func randomString(choice []rune, n int) (string, error) { + max := big.NewInt(int64(len(choice))) + rr := rand.Reader + + b := make([]rune, n) + for i := range b { + pos, err := rand.Int(rr, max) + if err != nil { + return "", err + } + b[i] = choice[pos.Int64()] + } + return string(b), nil +} + +func RandomAlphabeticalString(n int) (string, error) { + return randomString(letterRunes, n) +} + +func RandomAlphanumericString(n int) (string, error) { + return randomString(alphanumericRunes, n) +} diff --git a/protocol/common/crypto_test.go b/protocol/common/crypto_test.go new file mode 100644 index 000000000..afbc23e0d --- /dev/null +++ b/protocol/common/crypto_test.go @@ -0,0 +1,47 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var tcs = []int{4, 16, 64, 256, 1024} + +func runeInSlice(a rune, list []rune) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +func validString(s string, runes []rune) bool { + for _, r := range s { + if !runeInSlice(r, runes) { + return false + } + } + return true +} + +func TestRandomAlphabeticalString(t *testing.T) { + for _, n := range tcs { + s, err := RandomAlphabeticalString(n) + require.NoError(t, err) + require.Len(t, s, n) + + require.True(t, validString(s, letterRunes)) + } +} + +func TestRandomAlphanumericString(t *testing.T) { + for _, n := range tcs { + s, err := RandomAlphanumericString(n) + require.NoError(t, err) + require.Len(t, s, n) + + require.True(t, validString(s, alphanumericRunes)) + } +} diff --git a/protocol/messenger.go b/protocol/messenger.go index 0bba380e7..5a8d0dd0c 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -733,6 +733,11 @@ func (m *Messenger) Start() (*MessengerResponse, error) { return nil, err } + err = m.setInstallationHostname() + if err != nil { + return nil, err + } + return response, nil } diff --git a/protocol/messenger_saved_address.go b/protocol/messenger_saved_address.go index 1008624ff..4e7e92e1c 100644 --- a/protocol/messenger_saved_address.go +++ b/protocol/messenger_saved_address.go @@ -2,13 +2,16 @@ package protocol import ( "context" + "fmt" "time" "github.com/golang/protobuf/proto" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/status-im/status-go/protocol/common" + "github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/wallet" ) @@ -32,6 +35,45 @@ func (m *Messenger) garbageCollectRemovedSavedAddresses() error { return m.savedAddressesManager.DeleteSoftRemovedSavedAddresses(uint64(time.Now().AddDate(0, 0, -30).Unix())) } +func (m *Messenger) setInstallationHostname() error { + randomDeviceIDLen := 5 + + ourInstallation, ok := m.allInstallations.Load(m.installationID) + if !ok { + m.logger.Error("Messenger's installationID is not set or not loadable") + return nil + } + + var imd *multidevice.InstallationMetadata + if ourInstallation.InstallationMetadata == nil { + imd = new(multidevice.InstallationMetadata) + } else { + imd = ourInstallation.InstallationMetadata + } + + // If the name is already set, don't do anything + // TODO check the full working mechanics of this + if len(imd.Name) > randomDeviceIDLen { + return nil + } + + if len(imd.Name) == 0 { + n, err := common.RandomAlphabeticalString(randomDeviceIDLen) + if err != nil { + return err + } + + imd.Name = n + } + + hn, err := server.GetDeviceName() + if err != nil { + return err + } + imd.Name = fmt.Sprintf("%s %s", hn, imd.Name) + return m.setInstallationMetadata(m.installationID, imd) +} + func (m *Messenger) dispatchSyncSavedAddress(ctx context.Context, syncMessage protobuf.SyncSavedAddress) error { if !m.hasPairedDevices() { return nil diff --git a/server/device.go b/server/device.go new file mode 100644 index 000000000..0d0a26601 --- /dev/null +++ b/server/device.go @@ -0,0 +1,34 @@ +package server + +import ( + "os" + "strings" +) + +var ( + local = ".local" +) + +func RemoveSuffix(input, suffix string) string { + il := len(input) + sl := len(suffix) + if il > sl { + if input[il-sl:] == suffix { + return input[:il-sl] + } + } + return input +} + +func parseHostname(hostname string) string { + hostname = RemoveSuffix(hostname, local) + return strings.ReplaceAll(hostname, "-", " ") +} + +func GetDeviceName() (string, error) { + name, err := os.Hostname() + if err != nil { + return "", err + } + return parseHostname(name), nil +} diff --git a/server/device_test.go b/server/device_test.go new file mode 100644 index 000000000..d41c4cdbb --- /dev/null +++ b/server/device_test.go @@ -0,0 +1,45 @@ +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var ( + testCaseInput = []string{ + "some-computer-name.local", + "Hello.local", + "I'm an input.locally", + "some-plain-input", + "smol", + } +) + +func TestRemoveSuffix(t *testing.T) { + tce := []string{ + "some-computer-name", + "Hello", + "I'm an input.locally", + "some-plain-input", + "smol", + } + + for i, tci := range testCaseInput { + require.Equal(t, tce[i], RemoveSuffix(tci, local)) + } +} + +func TestParseHostname(t *testing.T) { + tce := []string{ + "some computer name", + "Hello", + "I'm an input.locally", + "some plain input", + "smol", + } + + for i, tci := range testCaseInput { + require.Equal(t, tce[i], parseHostname(tci)) + } +}