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 }