diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go new file mode 100644 index 0000000..752ed09 --- /dev/null +++ b/cryptfs/config_file.go @@ -0,0 +1,104 @@ +package cryptfs + +import ( + "errors" + "io/ioutil" + "encoding/json" +) +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 { + // File the config is saved in. Lowercase => not exported to JSON. + filename string + // Unencrypted AES key + Key [16]byte + // GCM ciphertext with auth tag to verify the key is correct + TestBlock []byte +} + +// CreateConfFile - create a new config file with "key" and write to "filename" +func CreateConfFile(filename string, key [16]byte) error { + var cf confFile + cf.filename = filename + cf.Key = key + + // Generate test block + cfs := NewCryptFS(cf.Key, false) + cf.TestBlock = cfs.EncryptBlock([]byte(testBlockData)) + + // Write file to disk + err := cf.WriteFile() + + return err +} + +// LoadConfFile - read config file from disk and verify the key using the +// embedded TestBlock +func LoadConfFile(filename string) (*confFile, error) { + // Read from disk + js, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var cf confFile + err = json.Unmarshal(js, &cf) + if err != nil { + return nil, err + } + cf.filename = filename + + // Try to decrypt the test block to see if the key is correct + // + // Speed does not matter here. Use built-in crypto. + cfs := NewCryptFS(cf.Key, false) + d, err := cfs.DecryptBlock(cf.TestBlock) + if err != nil { + return nil, err + } + ds := string(d) + if ds != testBlockData { + return nil, errors.New("Invalid test block content: " + ds) + } + return &cf, nil +} + +// WriteFile - write out config in JSON format to file "filename.tmp" +// then rename over "filename" +func (cf *confFile) WriteFile() error { + tmp := cf.filename + ".tmp" + fd, err := os.Create(tmp) + if err != nil { + return err + } + js, err := json.Marshal(cf) + if err != nil { + return err + } + _, err = fd.Write(js) + if err != nil { + return err + } + err = fd.Sync() + if err != nil { + return err + } + err = fd.Close() + if err != nil { + return err + } + err = os.Rename(tmp, cf.filename) + if err != nil { + return err + } + + return nil +} diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go index 40a9024..8927d74 100644 --- a/cryptfs/cryptfs.go +++ b/cryptfs/cryptfs.go @@ -8,6 +8,7 @@ import ( ) const ( + KEY_LEN = 16 NONCE_LEN = 12 AUTH_TAG_LEN = 16 DEFAULT_PLAINBS = 4096 diff --git a/cryptfs/nonce.go b/cryptfs/nonce.go index f93f59c..3e464a3 100644 --- a/cryptfs/nonce.go +++ b/cryptfs/nonce.go @@ -16,8 +16,9 @@ type nonce96 struct { var gcmNonce nonce96 -func (n *nonce96) randBytes(len int) []byte { - b := make([]byte, len) +// Get "n" random bytes from /dev/urandom or panic +func RandBytes(n int) []byte { + b := make([]byte, n) _, err := rand.Read(b) if err != nil { panic("Could not get random bytes for nonce") @@ -26,9 +27,9 @@ func (n *nonce96) randBytes(len int) []byte { } func (n *nonce96) init() { - b := n.randBytes(8) + b := RandBytes(8) n.low64 = binary.BigEndian.Uint64(b) - b = n.randBytes(4) + b = RandBytes(4) n.high32 = binary.BigEndian.Uint32(b) n.ready = 1 return diff --git a/main.go b/main.go index 70f0b53..d0aa33f 100644 --- a/main.go +++ b/main.go @@ -33,19 +33,39 @@ const ( ERREXIT_SERVE = 4 ERREXIT_MOUNT2 = 5 ERREXIT_CIPHERDIR = 6 + ERREXIT_INIT = 7 + ERREXIT_LOADCONF = 8 ) func main() { // Parse command line arguments var debug bool + var init bool flag.BoolVar(&debug, "debug", false, "Enable debug output") + flag.BoolVar(&init, "init", false, "Initialize encrypted directory") flag.Parse() if debug { cryptfs.Debug.Enable() cryptfs.Debug.Printf("Debug output enabled\n") } + if init { + if flag.NArg() != 1 { + fmt.Printf("usage: %s --init CIPHERDIR\n", PROGRAM_NAME) + os.Exit(ERREXIT_USAGE) + } + dir, _ := filepath.Abs(flag.Arg(0)) + filename := filepath.Join(dir, cryptfs.ConfDefaultName) + r := cryptfs.RandBytes(cryptfs.KEY_LEN) + var key [16]byte + copy(key[:], r) + err := cryptfs.CreateConfFile(filename, key) + if err != nil { + fmt.Println(err) + os.Exit(ERREXIT_INIT) + } + os.Exit(0) + } if flag.NArg() < 2 { - fmt.Printf("NArg=%d\n", flag.NArg()) fmt.Printf("usage: %s CIPHERDIR MOUNTPOINT\n", PROGRAM_NAME) os.Exit(ERREXIT_USAGE) } @@ -59,12 +79,17 @@ func main() { os.Exit(ERREXIT_CIPHERDIR) } - var key [16]byte + cfname := filepath.Join(cipherdir, cryptfs.ConfDefaultName) + cf, err := cryptfs.LoadConfFile(cfname) + if err != nil { + fmt.Println(err) + os.Exit(ERREXIT_LOADCONF) + } if USE_CLUEFS { - cluefsFrontend(key, cipherdir, mountpoint) + cluefsFrontend(cf.Key, cipherdir, mountpoint) } else { - pathfsFrontend(key, cipherdir, mountpoint, debug) + pathfsFrontend(cf.Key, cipherdir, mountpoint, debug) } }