github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/config/crypt.go (about) 1 package config 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "crypto/rand" 8 "crypto/sha256" 9 "encoding/base64" 10 "errors" 11 "fmt" 12 "io" 13 "os" 14 "os/exec" 15 "strings" 16 17 "golang.org/x/crypto/nacl/secretbox" 18 19 "github.com/rclone/rclone/fs" 20 "github.com/rclone/rclone/fs/config/obscure" 21 ) 22 23 var ( 24 // Key to use for password en/decryption. 25 // When nil, no encryption will be used for saving. 26 configKey []byte 27 28 // PasswordPromptOutput is output of prompt for password 29 PasswordPromptOutput = os.Stderr 30 31 // PassConfigKeyForDaemonization if set to true, the configKey 32 // is obscured with obscure.Obscure and saved to a temp file 33 // when it is calculated from the password. The path of that 34 // temp file is then written to the environment variable 35 // `_RCLONE_CONFIG_KEY_FILE`. If `_RCLONE_CONFIG_KEY_FILE` is 36 // present, password prompt is skipped and 37 // `RCLONE_CONFIG_PASS` ignored. For security reasons, the 38 // temp file is deleted once the configKey is successfully 39 // loaded. This can be used to pass the configKey to a child 40 // process. 41 PassConfigKeyForDaemonization = false 42 ) 43 44 // Decrypt will automatically decrypt a reader 45 func Decrypt(b io.ReadSeeker) (io.Reader, error) { 46 ctx := context.Background() 47 ci := fs.GetConfig(ctx) 48 var usingPasswordCommand bool 49 50 // Find first non-empty line 51 r := bufio.NewReader(b) 52 for { 53 line, _, err := r.ReadLine() 54 if err != nil { 55 if err == io.EOF { 56 if _, err := b.Seek(0, io.SeekStart); err != nil { 57 return nil, err 58 } 59 return b, nil 60 } 61 return nil, err 62 } 63 l := strings.TrimSpace(string(line)) 64 if len(l) == 0 || strings.HasPrefix(l, ";") || strings.HasPrefix(l, "#") { 65 continue 66 } 67 // First non-empty or non-comment must be ENCRYPT_V0 68 if l == "RCLONE_ENCRYPT_V0:" { 69 break 70 } 71 if strings.HasPrefix(l, "RCLONE_ENCRYPT_V") { 72 return nil, errors.New("unsupported configuration encryption - update rclone for support") 73 } 74 if _, err := b.Seek(0, io.SeekStart); err != nil { 75 return nil, err 76 } 77 return b, nil 78 } 79 80 if len(configKey) == 0 { 81 if len(ci.PasswordCommand) != 0 { 82 var stdout bytes.Buffer 83 var stderr bytes.Buffer 84 85 cmd := exec.Command(ci.PasswordCommand[0], ci.PasswordCommand[1:]...) 86 87 cmd.Stdout = &stdout 88 cmd.Stderr = &stderr 89 cmd.Stdin = os.Stdin 90 91 if err := cmd.Run(); err != nil { 92 // One does not always get the stderr returned in the wrapped error. 93 fs.Errorf(nil, "Using --password-command returned: %v", err) 94 if ers := strings.TrimSpace(stderr.String()); ers != "" { 95 fs.Errorf(nil, "--password-command stderr: %s", ers) 96 } 97 return nil, fmt.Errorf("password command failed: %w", err) 98 } 99 if pass := strings.Trim(stdout.String(), "\r\n"); pass != "" { 100 err := SetConfigPassword(pass) 101 if err != nil { 102 return nil, fmt.Errorf("incorrect password: %w", err) 103 } 104 } else { 105 return nil, errors.New("password-command returned empty string") 106 } 107 108 if len(configKey) == 0 { 109 return nil, errors.New("unable to decrypt configuration: incorrect password") 110 } 111 usingPasswordCommand = true 112 } else { 113 usingPasswordCommand = false 114 115 envpw := os.Getenv("RCLONE_CONFIG_PASS") 116 117 if envpw != "" { 118 err := SetConfigPassword(envpw) 119 if err != nil { 120 fs.Errorf(nil, "Using RCLONE_CONFIG_PASS returned: %v", err) 121 } else { 122 fs.Debugf(nil, "Using RCLONE_CONFIG_PASS password.") 123 } 124 } 125 } 126 } 127 128 // Encrypted content is base64 encoded. 129 dec := base64.NewDecoder(base64.StdEncoding, r) 130 box, err := io.ReadAll(dec) 131 if err != nil { 132 return nil, fmt.Errorf("failed to load base64 encoded data: %w", err) 133 } 134 if len(box) < 24+secretbox.Overhead { 135 return nil, errors.New("configuration data too short") 136 } 137 138 var out []byte 139 for { 140 if envKeyFile := os.Getenv("_RCLONE_CONFIG_KEY_FILE"); len(envKeyFile) > 0 { 141 fs.Debugf(nil, "attempting to obtain configKey from temp file %s", envKeyFile) 142 obscuredKey, err := os.ReadFile(envKeyFile) 143 if err != nil { 144 errRemove := os.Remove(envKeyFile) 145 if errRemove != nil { 146 return nil, fmt.Errorf("unable to read obscured config key and unable to delete the temp file: %w", err) 147 } 148 return nil, fmt.Errorf("unable to read obscured config key: %w", err) 149 } 150 errRemove := os.Remove(envKeyFile) 151 if errRemove != nil { 152 return nil, fmt.Errorf("unable to delete temp file with configKey: %w", errRemove) 153 } 154 configKey = []byte(obscure.MustReveal(string(obscuredKey))) 155 fs.Debugf(nil, "using _RCLONE_CONFIG_KEY_FILE for configKey") 156 } else { 157 if len(configKey) == 0 { 158 if usingPasswordCommand { 159 return nil, errors.New("using --password-command derived password, unable to decrypt configuration") 160 } 161 if !ci.AskPassword { 162 return nil, errors.New("unable to decrypt configuration and not allowed to ask for password - set RCLONE_CONFIG_PASS to your configuration password") 163 } 164 getConfigPassword("Enter configuration password:") 165 } 166 } 167 168 // Nonce is first 24 bytes of the ciphertext 169 var nonce [24]byte 170 copy(nonce[:], box[:24]) 171 var key [32]byte 172 copy(key[:], configKey[:32]) 173 174 // Attempt to decrypt 175 var ok bool 176 out, ok = secretbox.Open(nil, box[24:], &nonce, &key) 177 if ok { 178 break 179 } 180 181 // Retry 182 fs.Errorf(nil, "Couldn't decrypt configuration, most likely wrong password.") 183 configKey = nil 184 } 185 return bytes.NewReader(out), nil 186 } 187 188 // Encrypt the config file 189 func Encrypt(src io.Reader, dst io.Writer) error { 190 if len(configKey) == 0 { 191 _, err := io.Copy(dst, src) 192 return err 193 } 194 195 _, _ = fmt.Fprintln(dst, "# Encrypted rclone configuration File") 196 _, _ = fmt.Fprintln(dst, "") 197 _, _ = fmt.Fprintln(dst, "RCLONE_ENCRYPT_V0:") 198 199 // Generate new nonce and write it to the start of the ciphertext 200 var nonce [24]byte 201 n, _ := rand.Read(nonce[:]) 202 if n != 24 { 203 return fmt.Errorf("nonce short read: %d", n) 204 } 205 enc := base64.NewEncoder(base64.StdEncoding, dst) 206 _, err := enc.Write(nonce[:]) 207 if err != nil { 208 return fmt.Errorf("failed to write config file: %w", err) 209 } 210 211 var key [32]byte 212 copy(key[:], configKey[:32]) 213 214 data, err := io.ReadAll(src) 215 if err != nil { 216 return err 217 } 218 b := secretbox.Seal(nil, data, &nonce, &key) 219 _, err = enc.Write(b) 220 if err != nil { 221 return fmt.Errorf("failed to write config file: %w", err) 222 } 223 return enc.Close() 224 } 225 226 // getConfigPassword will query the user for a password the 227 // first time it is required. 228 func getConfigPassword(q string) { 229 if len(configKey) != 0 { 230 return 231 } 232 for { 233 password := GetPassword(q) 234 err := SetConfigPassword(password) 235 if err == nil { 236 return 237 } 238 _, _ = fmt.Fprintln(os.Stderr, "Error:", err) 239 } 240 } 241 242 // SetConfigPassword will set the configKey to the hash of 243 // the password. If the length of the password is 244 // zero after trimming+normalization, an error is returned. 245 func SetConfigPassword(password string) error { 246 password, err := checkPassword(password) 247 if err != nil { 248 return err 249 } 250 // Create SHA256 has of the password 251 sha := sha256.New() 252 _, err = sha.Write([]byte("[" + password + "][rclone-config]")) 253 if err != nil { 254 return err 255 } 256 configKey = sha.Sum(nil) 257 if PassConfigKeyForDaemonization { 258 tempFile, err := os.CreateTemp("", "rclone") 259 if err != nil { 260 return fmt.Errorf("cannot create temp file to store configKey: %w", err) 261 } 262 _, err = tempFile.WriteString(obscure.MustObscure(string(configKey))) 263 if err != nil { 264 errRemove := os.Remove(tempFile.Name()) 265 if errRemove != nil { 266 return fmt.Errorf("error writing configKey to temp file and also error deleting it: %w", err) 267 } 268 return fmt.Errorf("error writing configKey to temp file: %w", err) 269 } 270 err = tempFile.Close() 271 if err != nil { 272 errRemove := os.Remove(tempFile.Name()) 273 if errRemove != nil { 274 return fmt.Errorf("error closing temp file with configKey and also error deleting it: %w", err) 275 } 276 return fmt.Errorf("error closing temp file with configKey: %w", err) 277 } 278 fs.Debugf(nil, "saving configKey to temp file") 279 err = os.Setenv("_RCLONE_CONFIG_KEY_FILE", tempFile.Name()) 280 if err != nil { 281 errRemove := os.Remove(tempFile.Name()) 282 if errRemove != nil { 283 return fmt.Errorf("unable to set environment variable _RCLONE_CONFIG_KEY_FILE and unable to delete the temp file: %w", err) 284 } 285 return fmt.Errorf("unable to set environment variable _RCLONE_CONFIG_KEY_FILE: %w", err) 286 } 287 } 288 return nil 289 } 290 291 // ClearConfigPassword sets the current the password to empty 292 func ClearConfigPassword() { 293 configKey = nil 294 } 295 296 // changeConfigPassword will query the user twice 297 // for a password. If the same password is entered 298 // twice the key is updated. 299 func changeConfigPassword() { 300 err := SetConfigPassword(ChangePassword("NEW configuration")) 301 if err != nil { 302 fmt.Printf("Failed to set config password: %v\n", err) 303 return 304 } 305 }