github.com/GGP1/kure@v0.8.4/auth/auth.go (about)

     1  package auth
     2  
     3  import (
     4  	"bufio"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  
    13  	cmdutil "github.com/GGP1/kure/commands"
    14  	"github.com/GGP1/kure/config"
    15  	"github.com/GGP1/kure/crypt"
    16  	"github.com/GGP1/kure/db/auth"
    17  	authDB "github.com/GGP1/kure/db/auth"
    18  	"github.com/GGP1/kure/terminal"
    19  
    20  	"github.com/awnumar/memguard"
    21  	"github.com/pkg/errors"
    22  	"github.com/spf13/cobra"
    23  	bolt "go.etcd.io/bbolt"
    24  )
    25  
    26  const (
    27  	authKey string = "auth"
    28  	// Key file path configuration key
    29  	keyfilePath string = "keyfile.path"
    30  )
    31  
    32  // Login verifies that the human/machine that is trying to execute
    33  // a command is effectively the owner of the information.
    34  //
    35  // If it's the first record the user is registered.
    36  func Login(db *bolt.DB) cmdutil.RunEFunc {
    37  	return func(cmd *cobra.Command, args []string) error {
    38  		// If auth is not nil it means the user is already logged in (session)
    39  		if auth := config.Get(authKey); auth != nil {
    40  			return nil
    41  		}
    42  
    43  		params, err := authDB.GetParams(db)
    44  		if err != nil {
    45  			return err
    46  		}
    47  		// The auth key will be nil only on the user's first (successful) command
    48  		if params.AuthKey == nil {
    49  			return Register(db, os.Stdin)
    50  		}
    51  
    52  		password, err := terminal.ScanPassword("Enter master password", false)
    53  		if err != nil {
    54  			return err
    55  		}
    56  
    57  		if params.UseKeyfile {
    58  			password, err = combineKeys(os.Stdin, password)
    59  			if err != nil {
    60  				return err
    61  			}
    62  		}
    63  
    64  		setAuthToConfig(password, params)
    65  
    66  		// Try to decrypt the authentication key
    67  		if _, err := crypt.Decrypt(params.AuthKey); err != nil {
    68  			return errors.New("invalid master password")
    69  		}
    70  
    71  		return nil
    72  	}
    73  }
    74  
    75  // Register registers the user when there aren't any records yet.
    76  func Register(db *bolt.DB, r io.Reader) error {
    77  	password, err := terminal.ScanPassword("New master password", true)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	argon2, err := askArgon2Params(r)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	useKeyfile, err := askKeyfile(r)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	if useKeyfile {
    93  		password, err = combineKeys(r, password)
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	params := authDB.Params{
   100  		Argon2: authDB.Argon2{
   101  			Iterations: argon2.Iterations,
   102  			Memory:     argon2.Memory,
   103  			Threads:    argon2.Threads,
   104  		},
   105  		UseKeyfile: useKeyfile,
   106  	}
   107  
   108  	setAuthToConfig(password, params)
   109  	return authDB.Register(db, params)
   110  }
   111  
   112  func askArgon2Params(r io.Reader) (authDB.Argon2, error) {
   113  	fmt.Println("Set argon2 parameters, leave blank to use the default value")
   114  	fmt.Println("For more information visit https://github.com/GGP1/kure/wiki/Authentication")
   115  
   116  	reader := bufio.NewReader(r)
   117  
   118  	iterations, err := scanParameter(reader, "Iterations", 1)
   119  	if err != nil {
   120  		return authDB.Argon2{}, err
   121  	}
   122  
   123  	// memory is measured in kibibytes, 1 kibibyte = 1024 bytes. 1048576 kibibytes -> 1GiB
   124  	memory, err := scanParameter(reader, "Memory", 1<<20)
   125  	if err != nil {
   126  		return authDB.Argon2{}, err
   127  	}
   128  
   129  	threads, err := scanParameter(reader, "Threads", uint32(runtime.NumCPU()))
   130  	if err != nil {
   131  		return authDB.Argon2{}, err
   132  	}
   133  
   134  	return authDB.Argon2{
   135  		Iterations: iterations,
   136  		Memory:     memory,
   137  		Threads:    threads,
   138  	}, nil
   139  }
   140  
   141  // askKeyfile asks the user if he wants to use a key file or not.
   142  func askKeyfile(r io.Reader) (bool, error) {
   143  	if !terminal.Confirm(r, "Would you like to use a key file?") {
   144  		return false, nil
   145  	}
   146  
   147  	if config.GetString(keyfilePath) != "" {
   148  		if !terminal.Confirm(r, "Would you like to use the path specified in the configuration file?") {
   149  			config.Set(keyfilePath, "")
   150  		}
   151  	}
   152  
   153  	return true, nil
   154  }
   155  
   156  func combineKeys(r io.Reader, password *memguard.Enclave) (*memguard.Enclave, error) {
   157  	path := config.GetString(keyfilePath)
   158  	if path == "" {
   159  		path = terminal.Scanln(bufio.NewReader(r), "Enter key file path")
   160  		path = strings.Trim(path, "\"")
   161  		if path == "" || path == "." {
   162  			return nil, errors.New("invalid key file path")
   163  		}
   164  	}
   165  
   166  	key, err := os.ReadFile(path)
   167  	if err != nil {
   168  		return nil, errors.Wrap(err, "reading key file")
   169  	}
   170  	defer memguard.WipeBytes(key)
   171  
   172  	pwdBuf, err := password.Open()
   173  	if err != nil {
   174  		return nil, errors.Wrap(err, "decrypting password")
   175  	}
   176  
   177  	// If the content is not 32 bytes, hash it and use the hash as the key
   178  	if len(key) != 32 {
   179  		keyHash := sha256.Sum256(key)
   180  		key = keyHash[:]
   181  	}
   182  
   183  	key = append(key, pwdBuf.Bytes()...)
   184  	pwdBuf.Destroy()
   185  
   186  	return memguard.NewEnclave(key), nil
   187  }
   188  
   189  func scanParameter(r *bufio.Reader, field string, defaultValue uint32) (uint32, error) {
   190  	valueStr := terminal.Scanln(r, " "+field)
   191  	if valueStr == "" {
   192  		return defaultValue, nil
   193  	}
   194  
   195  	v, err := strconv.Atoi(valueStr)
   196  	if err != nil || v < 1 {
   197  		return 0, errors.Wrapf(err, "invalid %s number", strings.ToLower(field))
   198  	}
   199  
   200  	return uint32(v), nil
   201  }
   202  
   203  // Auth values must be set to the configuration before any encryption/decryption occurs.
   204  func setAuthToConfig(password *memguard.Enclave, params auth.Params) {
   205  	auth := map[string]interface{}{
   206  		"password":   password,
   207  		"iterations": params.Argon2.Iterations,
   208  		"memory":     params.Argon2.Memory,
   209  		"threads":    params.Argon2.Threads,
   210  	}
   211  	config.Set(authKey, auth)
   212  }