libgocryptfs/main.go
Jakob Unterwurzacher 71b94828ed init: create gocryptfs.diriv after creating gocryptfs.conf
Creating the config file can fail easily, for example if the
password is not entered the same twice. This would leave an
orphaned gocryptfs.diriv behind.
2015-12-06 14:24:45 +01:00

347 lines
11 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"runtime/pprof"
"syscall"
"time"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/pathfs_frontend"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
const (
PROGRAM_NAME = "gocryptfs"
// Exit codes
ERREXIT_USAGE = 1
ERREXIT_MOUNT = 3
ERREXIT_CIPHERDIR = 6
ERREXIT_INIT = 7
ERREXIT_LOADCONF = 8
ERREXIT_PASSWORD = 9
ERREXIT_MOUNTPOINT = 10
)
type argContainer struct {
debug, init, zerokey, fusedebug, openssl, passwd, foreground, version,
plaintextnames, quiet, diriv bool
masterkey, mountpoint, cipherdir, cpuprofile, config, extpass string
notifypid, scryptn int
}
var flagSet *flag.FlagSet
// GitVersion will be set by the build script "build.bash"
var GitVersion = "[version not set - please compile using ./build.bash]"
func initDir(args *argContainer) {
err := checkDirEmpty(args.cipherdir)
if err != nil {
fmt.Printf("Invalid CIPHERDIR: %v\n", err)
os.Exit(ERREXIT_INIT)
}
// Create gocryptfs.conf
cryptfs.Info.Printf("Choose a password for protecting your files.\n")
password := readPasswordTwice(args.extpass)
err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn)
if err != nil {
fmt.Println(err)
os.Exit(ERREXIT_INIT)
}
// Create gocryptfs.diriv in the root dir
err = cryptfs.WriteDirIV(args.cipherdir)
if err != nil {
fmt.Println(err)
os.Exit(ERREXIT_INIT)
}
cryptfs.Info.Printf("The filesystem is now ready for mounting.\n")
os.Exit(0)
}
func usageText() {
printVersion()
fmt.Printf("\n")
fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", PROGRAM_NAME)
fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", PROGRAM_NAME)
fmt.Printf("\nOptions:\n")
flagSet.PrintDefaults()
}
// loadConfig - load the config file "filename", prompting the user for the password
func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFile) {
// Check if the file exists at all before prompting for a password
_, err := os.Stat(args.config)
if err != nil {
fmt.Println(err)
os.Exit(ERREXIT_LOADCONF)
}
fmt.Printf("Password: ")
pw := readPassword(args.extpass)
cryptfs.Info.Printf("Decrypting master key... ")
cryptfs.Warn.Disable() // Silence DecryptBlock() error messages on incorrect password
masterkey, confFile, err = cryptfs.LoadConfFile(args.config, pw)
cryptfs.Warn.Enable()
if err != nil {
fmt.Println(err)
fmt.Println("Wrong password.")
os.Exit(ERREXIT_LOADCONF)
}
cryptfs.Info.Printf("done.\n")
return masterkey, confFile
}
// changePassword - change the password of config file "filename"
func changePassword(args *argContainer) {
masterkey, confFile := loadConfig(args)
fmt.Printf("Please enter your new password.\n")
newPw := readPasswordTwice(args.extpass)
confFile.EncryptKey(masterkey, newPw, confFile.ScryptObject.LogN())
err := confFile.WriteFile()
if err != nil {
fmt.Println(err)
os.Exit(ERREXIT_INIT)
}
cryptfs.Info.Printf("Password changed.\n")
os.Exit(0)
}
// printVersion - print a version string like
// "gocryptfs v0.3.1-31-g6736212-dirty; on-disk format 2"
func printVersion() {
fmt.Printf("%s %s; on-disk format %d\n", PROGRAM_NAME, GitVersion, cryptfs.HEADER_CURRENT_VERSION)
}
func main() {
runtime.GOMAXPROCS(4)
var err error
var args argContainer
// Parse command line arguments
flagSet = flag.NewFlagSet(PROGRAM_NAME, flag.ExitOnError)
flagSet.Usage = usageText
flagSet.BoolVar(&args.debug, "debug", false, "Enable debug output")
flagSet.BoolVar(&args.fusedebug, "fusedebug", false, "Enable fuse library debug output")
flagSet.BoolVar(&args.init, "init", false, "Initialize encrypted directory")
flagSet.BoolVar(&args.zerokey, "zerokey", false, "Use all-zero dummy master key")
flagSet.BoolVar(&args.openssl, "openssl", true, "Use OpenSSL instead of built-in Go crypto")
flagSet.BoolVar(&args.passwd, "passwd", false, "Change password")
flagSet.BoolVar(&args.foreground, "f", false, "Stay in the foreground")
flagSet.BoolVar(&args.version, "version", false, "Print version and exit")
flagSet.BoolVar(&args.plaintextnames, "plaintextnames", false, "Do not encrypt "+
"file names")
flagSet.BoolVar(&args.quiet, "q", false, "Quiet - silence informational messages")
flagSet.BoolVar(&args.diriv, "diriv", true, "Use per-directory file name IV")
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf")
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
"successful mount - used internally for daemonization")
flagSet.IntVar(&args.scryptn, "scryptn", cryptfs.SCRYPT_DEFAULT_LOGN, "scrypt cost parameter logN. "+
"Setting this to a lower value speeds up mounting but makes the password susceptible to brute-force attacks")
flagSet.Parse(os.Args[1:])
// Fork a child into the background if "-f" is not set and we are mounting a filesystem
if !args.foreground && flagSet.NArg() == 2 {
forkChild() // does not return
}
// "-v"
if args.version {
printVersion()
os.Exit(0)
}
if args.debug {
cryptfs.Debug.Enable()
cryptfs.Debug.Printf("Debug output enabled\n")
}
// Every operation below requires CIPHERDIR. Check that we have it.
if flagSet.NArg() >= 1 {
args.cipherdir, _ = filepath.Abs(flagSet.Arg(0))
err := checkDir(args.cipherdir)
if err != nil {
fmt.Printf("Invalid CIPHERDIR: %v\n", err)
os.Exit(ERREXIT_CIPHERDIR)
}
} else {
usageText()
os.Exit(ERREXIT_USAGE)
}
// "-q"
if args.quiet {
cryptfs.Info.Disable()
}
// "-config"
if args.config != "" {
args.config, err = filepath.Abs(args.config)
if err != nil {
fmt.Printf("Invalid \"-config\" setting: %v\n", err)
}
cryptfs.Info.Printf("Using config file at custom location %s\n", args.config)
} else {
args.config = filepath.Join(args.cipherdir, cryptfs.ConfDefaultName)
}
// "-cpuprofile"
if args.cpuprofile != "" {
f, err := os.Create(args.cpuprofile)
if err != nil {
fmt.Println(err)
os.Exit(ERREXIT_INIT)
}
cryptfs.Info.Printf("Writing CPU profile to %s\n", args.cpuprofile)
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// "-openssl"
if args.openssl == false {
cryptfs.Info.Printf("Openssl disabled\n")
}
// Operation flags: init, passwd or mount
// "-init"
if args.init {
if flagSet.NArg() > 1 {
fmt.Printf("Usage: %s -init [OPTIONS] CIPHERDIR\n", PROGRAM_NAME)
os.Exit(ERREXIT_USAGE)
}
initDir(&args) // does not return
}
// "-passwd"
if args.passwd {
if flagSet.NArg() > 1 {
fmt.Printf("Usage: %s -passwd [OPTIONS] CIPHERDIR\n", PROGRAM_NAME)
os.Exit(ERREXIT_USAGE)
}
changePassword(&args) // does not return
}
// Mount
// Check mountpoint
if flagSet.NArg() != 2 {
usageText()
os.Exit(ERREXIT_USAGE)
}
args.mountpoint, err = filepath.Abs(flagSet.Arg(1))
if err != nil {
fmt.Printf("Invalid MOUNTPOINT: %v\n", err)
os.Exit(ERREXIT_MOUNTPOINT)
}
err = checkDirEmpty(args.mountpoint)
if err != nil {
fmt.Printf("Invalid MOUNTPOINT: %v\n", err)
os.Exit(ERREXIT_MOUNTPOINT)
}
// Get master key
var masterkey []byte
var confFile *cryptfs.ConfFile
if args.masterkey != "" {
// "-masterkey"
cryptfs.Info.Printf("Using explicit master key.\n")
masterkey = parseMasterKey(args.masterkey)
cryptfs.Info.Printf("THE MASTER KEY IS VISIBLE VIA \"ps -auxwww\", ONLY USE THIS MODE FOR EMERGENCIES.\n")
} else if args.zerokey {
// "-zerokey"
cryptfs.Info.Printf("Using all-zero dummy master key.\n")
cryptfs.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.\n")
masterkey = make([]byte, cryptfs.KEY_LEN)
} else {
// Load master key from config file
masterkey, confFile = loadConfig(&args)
printMasterKey(masterkey)
}
// Initialize FUSE server
cryptfs.Debug.Printf("args: %v\n", args)
srv := pathfsFrontend(masterkey, args, confFile)
cryptfs.Info.Println("Filesystem ready.")
// We are ready - send USR1 signal to our parent
if args.notifypid > 0 {
sendUsr1(args.notifypid)
}
// Wait for SIGINT in the background and unmount ourselves if we get it.
// This prevents a dangling "Transport endpoint is not connected" mountpoint.
handleSigint(srv, args.mountpoint)
// Jump into server loop. Returns when it gets an umount request from the kernel.
srv.Serve()
// main exits with code 0
}
// pathfsFrontend - initialize gocryptfs/pathfs_frontend
// Calls os.Exit on errors
func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) *fuse.Server {
// Reconciliate CLI and config file arguments into a Args struct that is passed to the
// filesystem implementation
frontendArgs := pathfs_frontend.Args{
Cipherdir: args.cipherdir,
Masterkey: key,
OpenSSL: args.openssl,
PlaintextNames: args.plaintextnames,
DirIV: args.diriv,
}
// confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil {
// Settings from the config file override command line args
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(cryptfs.FlagPlaintextNames)
frontendArgs.DirIV = confFile.IsFeatureFlagSet(cryptfs.FlagDirIV)
}
finalFs := pathfs_frontend.NewFS(frontendArgs)
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
fuseOpts := &nodefs.Options{
// These options are to be compatible with libfuse defaults,
// making benchmarking easier.
NegativeTimeout: time.Second,
AttrTimeout: time.Second,
EntryTimeout: time.Second,
}
conn := nodefs.NewFileSystemConnector(pathFs.Root(), fuseOpts)
var mOpts fuse.MountOptions
mOpts.AllowOther = false
// Set values shown in "df -T" and friends
// First column, "Filesystem"
mOpts.Options = append(mOpts.Options, "fsname="+args.cipherdir)
// Second column, "Type", will be shown as "fuse." + Name
mOpts.Name = "gocryptfs"
srv, err := fuse.NewServer(conn.RawFS(), args.mountpoint, &mOpts)
if err != nil {
fmt.Printf("Mount failed: %v", err)
os.Exit(ERREXIT_MOUNT)
}
srv.SetDebug(args.fusedebug)
return srv
}
func handleSigint(srv *fuse.Server, mountpoint string) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
signal.Notify(ch, syscall.SIGTERM)
go func() {
<-ch
err := srv.Unmount()
if err != nil {
fmt.Print(err)
cryptfs.Info.Printf("Trying lazy unmount\n")
cmd := exec.Command("fusermount", "-u", "-z", mountpoint)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
os.Exit(1)
}()
}