From 65ba0739d5de756a437e0f840649583fa835a560 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Wed, 7 Oct 2015 21:26:17 +0200 Subject: [PATCH] Implement "gocryptfs --passwd" (pasword changing) --- cryptfs/config_file.go | 42 ++++++++++++----------- cryptfs/log.go | 4 +++ gocryptfs_main/main.go | 68 ++++++++++++++++++++------------------ gocryptfs_main/password.go | 31 +++++++++++++++++ 4 files changed, 94 insertions(+), 51 deletions(-) create mode 100644 gocryptfs_main/password.go diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go index 82f7e72..9be80ca 100644 --- a/cryptfs/config_file.go +++ b/cryptfs/config_file.go @@ -7,15 +7,12 @@ import ( import "os" const ( - // Changing this string breaks backward compatability - testBlockData = "gocryptfs test block" - // The dot "." is not used in base64url (RFC4648), hence // we can never clash with an encrypted file. ConfDefaultName = "gocryptfs.conf" ) -type confFile struct { +type ConfFile struct { // File the config is saved to. Not exported to JSON. filename string // Encrypted AES key, unlocked using a password hashed with scrypt @@ -27,19 +24,14 @@ type confFile struct { // CreateConfFile - create a new config with a random key encrypted with // "password" and write it to "filename" func CreateConfFile(filename string, password string) error { - var cf confFile + var cf ConfFile cf.filename = filename // Generate new random master key key := RandBytes(KEY_LEN) - // Generate derived key from password - cf.ScryptObject = NewScryptKdf() - scryptHash := cf.ScryptObject.DeriveKey(password) - - // Lock master key using password-based key - cfs := NewCryptFS(scryptHash, false) - cf.EncryptedKey = cfs.EncryptBlock(key, 0) + // Encrypt it using the password + cf.EncryptKey(key, password) // Write file to disk err := cf.WriteFile() @@ -49,21 +41,21 @@ func CreateConfFile(filename string, password string) error { // LoadConfFile - read config file from disk and decrypt the // contained key using password -func LoadConfFile(filename string, password string) ([]byte, error) { - var cf confFile +func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { + var cf ConfFile cf.filename = filename // Read from disk js, err := ioutil.ReadFile(filename) if err != nil { - return nil, err + return nil, nil, err } // Unmarshal err = json.Unmarshal(js, &cf) if err != nil { Warn.Printf("Failed to unmarshal config file\n") - return nil, err + return nil, nil, err } // Generate derived key from password @@ -76,15 +68,27 @@ func LoadConfFile(filename string, password string) ([]byte, error) { key, err := cfs.DecryptBlock(cf.EncryptedKey, 0) if err != nil { Warn.Printf("Failed to unlock master key: %s\n", err.Error()) - return nil, err + return nil, nil, err } - return key, nil + return key, &cf, nil +} + +// EncryptKey - encrypt "key" using an scrypt hash generated from "password" +// and store it in cf.EncryptedKey +func (cf *ConfFile) EncryptKey(key []byte, password string) { + // Generate derived key from password + cf.ScryptObject = NewScryptKdf() + scryptHash := cf.ScryptObject.DeriveKey(password) + + // Lock master key using password-based key + cfs := NewCryptFS(scryptHash, false) + cf.EncryptedKey = cfs.EncryptBlock(key, 0) } // WriteFile - write out config in JSON format to file "filename.tmp" // then rename over "filename" -func (cf *confFile) WriteFile() error { +func (cf *ConfFile) WriteFile() error { tmp := cf.filename + ".tmp" fd, err := os.Create(tmp) if err != nil { diff --git a/cryptfs/log.go b/cryptfs/log.go index 0884473..2183679 100644 --- a/cryptfs/log.go +++ b/cryptfs/log.go @@ -30,6 +30,10 @@ func (l *logChannel) Enable() { l.enabled = true } +func (l *logChannel) Disable() { + l.enabled = false +} + // Only actually calculate the md5sum if the log channel is enabled to save // CPU cycles func (l *logChannel) Md5sum(buf []byte) string { diff --git a/gocryptfs_main/main.go b/gocryptfs_main/main.go index ac0b456..35c5723 100644 --- a/gocryptfs_main/main.go +++ b/gocryptfs_main/main.go @@ -13,8 +13,6 @@ import ( "github.com/rfjakob/gocryptfs/cryptfs" "github.com/rfjakob/gocryptfs/pathfs_frontend" - "golang.org/x/crypto/ssh/terminal" - "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/pathfs" @@ -62,7 +60,7 @@ func main() { runtime.GOMAXPROCS(4) // Parse command line arguments - var debug, init, zerokey, fusedebug, openssl bool + var debug, init, zerokey, fusedebug, openssl, passwd bool var masterkey string flag.BoolVar(&debug, "debug", false, "Enable debug output") @@ -70,6 +68,7 @@ func main() { flag.BoolVar(&init, "init", false, "Initialize encrypted directory") flag.BoolVar(&zerokey, "zerokey", false, "Use all-zero dummy master key") flag.BoolVar(&openssl, "openssl", true, "Use OpenSSL instead of built-in Go crypto") + flag.BoolVar(&passwd, "passwd", false, "Change password") flag.StringVar(&masterkey, "masterkey", "", "Mount with explicit master key") var cpuprofile = flag.String("cpuprofile", "", "Write cpu profile to specified file") @@ -97,8 +96,12 @@ func main() { os.Exit(ERREXIT_USAGE) } initDir(flag.Arg(0)) - } - if flag.NArg() < 2 { + } else if passwd { + if flag.NArg() != 1 { + fmt.Printf("usage: %s --passwd CIPHERDIR\n", PROGRAM_NAME) + os.Exit(ERREXIT_USAGE) + } + } else if flag.NArg() < 2 { fmt.Printf("usage: %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", PROGRAM_NAME) os.Exit(ERREXIT_USAGE) } @@ -112,6 +115,8 @@ func main() { os.Exit(ERREXIT_CIPHERDIR) } + var cf *cryptfs.ConfFile + var currentPassword string key := make([]byte, cryptfs.KEY_LEN) if zerokey { fmt.Printf("Zerokey mode active: using all-zero dummy master key.\n") @@ -126,16 +131,39 @@ func main() { fmt.Printf("Please run \"%s --init %s\" first\n", PROGRAM_NAME, flag.Arg(0)) os.Exit(ERREXIT_LOADCONF) } - fmt.Printf("Password: ") - password := readPassword() + if passwd == true { + fmt.Printf("Old password: ") + } else { + fmt.Printf("Password: ") + } + currentPassword = readPassword() fmt.Printf("\nDecrypting master key... ") - key, err = cryptfs.LoadConfFile(cfname, password) + cryptfs.Warn.Disable() // Silence DecryptBlock() error messages on incorrect password + key, cf, err = cryptfs.LoadConfFile(cfname, currentPassword) + cryptfs.Warn.Enable() if err != nil { fmt.Println(err) + fmt.Println("Password incorrect.") os.Exit(ERREXIT_LOADCONF) } fmt.Printf("done.\n") } + if passwd == true { + fmt.Printf("Please enter the new password.\n") + newPassword := readPasswordTwice() + if newPassword == currentPassword { + fmt.Printf("New and old passwords are identical\n") + os.Exit(1) + } + cf.EncryptKey(key, newPassword) + err := cf.WriteFile() + if err != nil { + fmt.Println(err) + os.Exit(ERREXIT_INIT) + } + fmt.Printf("Password changed.\n") + os.Exit(0) + } srv := pathfsFrontend(key, cipherdir, mountpoint, fusedebug, openssl) fmt.Printf("Mounted.\n") @@ -154,30 +182,6 @@ func main() { srv.Serve() } -func readPasswordTwice() string { - fmt.Printf("Password: ") - p1 := readPassword() - fmt.Printf("\nRepeat: ") - p2 := readPassword() - fmt.Printf("\n") - if p1 != p2 { - fmt.Printf("Passwords do not match\n") - os.Exit(ERREXIT_PASSWORD) - } - return p1 -} - -// Get password from terminal -func readPassword() string { - fd := int(os.Stdin.Fd()) - p, err := terminal.ReadPassword(fd) - if err != nil { - fmt.Printf("Error: Could not read password: %v\n", err) - os.Exit(ERREXIT_PASSWORD) - } - return string(p) -} - func dirEmpty(dir string) bool { entries, err := ioutil.ReadDir(dir) if err != nil { diff --git a/gocryptfs_main/password.go b/gocryptfs_main/password.go new file mode 100644 index 0000000..821ecb4 --- /dev/null +++ b/gocryptfs_main/password.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "golang.org/x/crypto/ssh/terminal" + "os" +) + +func readPasswordTwice() string { + fmt.Printf("Password: ") + p1 := readPassword() + fmt.Printf("\nRepeat: ") + p2 := readPassword() + fmt.Printf("\n") + if p1 != p2 { + fmt.Printf("Passwords do not match\n") + os.Exit(ERREXIT_PASSWORD) + } + return p1 +} + +// Get password from terminal +func readPassword() string { + fd := int(os.Stdin.Fd()) + p, err := terminal.ReadPassword(fd) + if err != nil { + fmt.Printf("Error: Could not read password: %v\n", err) + os.Exit(ERREXIT_PASSWORD) + } + return string(p) +}