status-im/status-go

View on GitHub
server/pairing/common.go

Summary

Maintainability
A
0 mins
Test Coverage
F
57%
package pairing

import (
    "fmt"
    "io/fs"
    "io/ioutil"
    "os"
    "path/filepath"
    "reflect"
    "regexp"
    "strings"

    "github.com/status-im/status-go/protocol/requests"

    "gopkg.in/go-playground/validator.v9"

    "github.com/status-im/status-go/account/generator"
    "github.com/status-im/status-go/api"
    "github.com/status-im/status-go/eth-node/keystore"
)

func newValidate() (*validator.Validate, error) {
    var validate = validator.New()
    var keyUIDPattern = regexp.MustCompile(`^0x[0-9a-fA-F]{64}$`)
    if err := validate.RegisterValidation("keyuid", func(fl validator.FieldLevel) bool {
        return keyUIDPattern.MatchString(fl.Field().String())
    }); err != nil {
        return nil, err
    }

    if err := validate.RegisterValidation("keystorepath", func(fl validator.FieldLevel) bool {
        keyUIDField := fl.Parent()
        if keyUIDField.Kind() == reflect.Ptr {
            keyUIDField = keyUIDField.Elem()
        }

        keyUID := keyUIDField.FieldByName("KeyUID").String()
        return strings.HasSuffix(fl.Field().String(), keyUID)
    }); err != nil {
        return nil, err
    }

    return validate, nil
}

func validateKeys(keys map[string][]byte, password string) error {
    for _, key := range keys {
        k, err := keystore.DecryptKey(key, password)
        if err != nil {
            return err
        }

        err = generator.ValidateKeystoreExtendedKey(k)
        if err != nil {
            return err
        }
    }

    return nil
}

func loadKeys(keys map[string][]byte, keyStorePath string) error {
    fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
        if err != nil {
            return err
        }

        if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
            return nil
        }

        rawKeyFile, err := ioutil.ReadFile(path)
        if err != nil {
            return fmt.Errorf("invalid account key file: %v", err)
        }

        keys[dirEntry.Name()] = rawKeyFile

        return nil
    }

    err := filepath.WalkDir(keyStorePath, fileWalker)
    if err != nil {
        return fmt.Errorf("cannot traverse key store folder: %v", err)
    }

    return nil
}

func validate(s interface{}) error {
    v, err := newValidate()
    if err != nil {
        return err
    }

    return v.Struct(s)
}

func validateAndVerifyPassword(s interface{}, senderConfig *SenderConfig) error {
    err := validate(s)
    if err != nil {
        return err
    }

    keys := make(map[string][]byte)
    err = loadKeys(keys, senderConfig.KeystorePath)
    if err != nil {
        return err
    }

    return validateKeys(keys, senderConfig.Password)
}

func validateReceiverConfig(s interface{}, receiverConfig *ReceiverConfig) error {
    err := validate(s)
    if err != nil {
        return err
    }

    return receiverConfig.CreateAccount.Validate(&requests.CreateAccountValidation{
        AllowEmptyDisplayName:        true,
        AllowEmptyCustomizationColor: true,
        AllowEmptyPassword:           true,
    })
}

func emptyDir(dir string) error {
    // Open the directory
    d, err := os.Open(dir)
    if err != nil {
        return err
    }
    defer d.Close()

    // Get all the directory entries
    entries, err := d.Readdir(-1)
    if err != nil {
        return err
    }

    // Remove all the files and directories
    for _, entry := range entries {
        name := entry.Name()
        if name == "." || name == ".." {
            continue
        }
        path := filepath.Join(dir, name)
        if entry.IsDir() {
            err = os.RemoveAll(path)
            if err != nil {
                return err
            }
        } else {
            err = os.Remove(path)
            if err != nil {
                return err
            }
        }
    }
    return nil
}

func validateReceivedKeystoreFiles(expectedKeys []string, keys map[string][]byte, password string) error {
    for _, searchKey := range expectedKeys {
        found := false
        for key := range keys {
            if strings.Contains(key, strings.ToLower(searchKey)) {
                found = true
                break
            }
        }
        if !found {
            return fmt.Errorf("one or more expected keystore files are not found among the sent files")
        }
    }

    return validateKeys(keys, password)
}

func validateKeystoreFilesConfig(backend *api.GethStatusBackend, conf interface{}) error {
    var (
        loggedInKeyUID string
        password       string
        numOfKeypairs  int
        keystorePath   string
    )

    switch c := conf.(type) {
    case *KeystoreFilesSenderServerConfig:
        loggedInKeyUID = c.SenderConfig.LoggedInKeyUID
        password = c.SenderConfig.Password
        numOfKeypairs = len(c.SenderConfig.KeypairsToExport)
        keystorePath = c.SenderConfig.KeystorePath
    case *KeystoreFilesReceiverClientConfig:
        loggedInKeyUID = c.ReceiverConfig.LoggedInKeyUID
        password = c.ReceiverConfig.Password
        numOfKeypairs = len(c.ReceiverConfig.KeypairsToImport)
        keystorePath = c.ReceiverConfig.KeystorePath
    default:
        return fmt.Errorf("unknown config type: %v", reflect.TypeOf(conf))
    }

    accountService := backend.StatusNode().AccountService()
    if accountService == nil {
        return fmt.Errorf("cannot resolve accounts service instance")
    }

    if !accountService.GetMessenger().HasPairedDevices() {
        return fmt.Errorf("there are no known paired devices")
    }

    selectedAccount, err := backend.GetActiveAccount()
    if err != nil {
        return err
    }

    if selectedAccount.KeyUID != loggedInKeyUID {
        return fmt.Errorf("configuration is not meant for the logged in account")
    }

    if selectedAccount.KeycardPairing == "" {
        if !accountService.VerifyPassword(password) {
            return fmt.Errorf("provided password is not correct")
        }
    }

    if numOfKeypairs == 0 {
        return fmt.Errorf("it should be at least a single keypair set a keystore files are transferred for")
    }

    if keystorePath == "" {
        return fmt.Errorf("keyStorePath can not be empty")
    }

    return nil
}