diff --git a/app/build.gradle b/app/build.gradle index ec6ab60..1e8f5ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId "sushi.hardcore.droidfs" minSdkVersion 21 targetSdkVersion 29 - versionCode 9 - versionName "1.4.1" + versionCode 10 + versionName "1.4.2" ndk { abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a' @@ -56,11 +56,11 @@ dependencies { implementation "com.github.bumptech.glide:glide:4.11.0" implementation "com.google.android.exoplayer:exoplayer-core:2.11.7" implementation "com.google.android.exoplayer:exoplayer-ui:2.11.7" - implementation "androidx.biometric:biometric:1.0.1" + implementation "androidx.biometric:biometric:1.1.0" - def camerax_version = "1.0.0-rc01" + def camerax_version = "1.1.0-alpha01" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" - implementation "androidx.camera:camera-view:1.0.0-alpha20" - implementation "androidx.camera:camera-extensions:1.0.0-alpha20" + implementation "androidx.camera:camera-view:1.0.0-alpha21" + implementation "androidx.camera:camera-extensions:1.0.0-alpha21" } diff --git a/app/libgocryptfs/main.go b/app/libgocryptfs/main.go index 55bdf71..6762673 100644 --- a/app/libgocryptfs/main.go +++ b/app/libgocryptfs/main.go @@ -1,99 +1,110 @@ package main import ( - "C" - "crypto/cipher" - "crypto/aes" - "syscall" - "strings" - "bytes" - "unsafe" - "os" - "io" - "fmt" - "path/filepath" - "golang.org/x/sys/unix" + "C" + "bytes" + "crypto/aes" + "crypto/cipher" + "fmt" + "golang.org/x/sys/unix" + "io" + "os" + "path/filepath" + "strings" + "syscall" + "unsafe" - "./gocryptfs_internal/cryptocore" - "./gocryptfs_internal/stupidgcm" - "./gocryptfs_internal/eme" - "./gocryptfs_internal/nametransform" - "./rewrites/syscallcompat" - "./rewrites/configfile" - "./rewrites/contentenc" + "./gocryptfs_internal/cryptocore" + "./gocryptfs_internal/eme" + "./gocryptfs_internal/nametransform" + "./gocryptfs_internal/stupidgcm" + "./rewrites/configfile" + "./rewrites/contentenc" + "./rewrites/syscallcompat" ) const ( - file_mode = uint32(0660) - folder_mode = uint32(0770) + file_mode = uint32(0660) + folder_mode = uint32(0770) ) type Directory struct { - fd int - iv []byte + fd int + iv []byte } type File struct { - fd *os.File - path string + fd *os.File + path string } type SessionVars struct { - root_cipher_dir string - nameTransform *nametransform.NameTransform - cryptoCore *cryptocore.CryptoCore - contentEnc *contentenc.ContentEnc - dirCache map[string]Directory - file_handles map[int]File - fileIDs map[int][]byte + root_cipher_dir string + plainTextNames bool + nameTransform *nametransform.NameTransform + cryptoCore *cryptocore.CryptoCore + contentEnc *contentenc.ContentEnc + dirCache map[string]Directory + file_handles map[int]File + fileIDs map[int][]byte } var sessions map[int]SessionVars func err_to_bool(e error) bool { - if e == nil { - return true - } - return false + if e == nil { + return true + } + return false } -func wipe(d []byte){ - for i := range d { - d[i] = 0 - } - d = nil +func wipe(d []byte) { + for i := range d { + d[i] = 0 + } + d = nil } func clear_dirCache(sessionID int) { - for k, _ := range sessions[sessionID].dirCache { - delete(sessions[sessionID].dirCache, k) - } + for k, _ := range sessions[sessionID].dirCache { + delete(sessions[sessionID].dirCache, k) + } } func openBackingDir(sessionID int, relPath string) (dirfd int, cName string, err error) { - dirRelPath := nametransform.Dir(relPath) - dir, ok := sessions[sessionID].dirCache[dirRelPath] - if ok { - // If relPath is empty, cName is ".". - if relPath == "" { - cache_dirfd, err := syscall.Dup(dir.fd) - if err != nil { - return -1, "", err - } + dirRelPath := nametransform.Dir(relPath) + // With PlaintextNames, we don't need to read DirIVs. Easy. + if sessions[sessionID].plainTextNames { + dirfd, err = syscallcompat.OpenDirNofollow(sessions[sessionID].root_cipher_dir, dirRelPath) + if err != nil { + return -1, "", err + } + // If relPath is empty, cName is ".". + cName = filepath.Base(relPath) + return dirfd, cName, nil + } + dir, ok := sessions[sessionID].dirCache[dirRelPath] + if ok { + // If relPath is empty, cName is ".". + if relPath == "" { + cache_dirfd, err := syscall.Dup(dir.fd) + if err != nil { + return -1, "", err + } return cache_dirfd, ".", nil } - name := filepath.Base(relPath) + name := filepath.Base(relPath) cName, err = sessions[sessionID].nameTransform.EncryptAndHashName(name, dir.iv) if err != nil { syscall.Close(dir.fd) return -1, "", err } - cache_dirfd, err := syscall.Dup(dir.fd) - if err != nil { - return -1, "", err - } + cache_dirfd, err := syscall.Dup(dir.fd) + if err != nil { + return -1, "", err + } return cache_dirfd, cName, nil - } + } // Open cipherdir (following symlinks) dirfd, err = syscall.Open(sessions[sessionID].root_cipher_dir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) if err != nil { @@ -118,17 +129,17 @@ func openBackingDir(sessionID int, relPath string) (dirfd int, cName string, err } // Last part? We are done. if i == len(parts)-1 { - cache_dirfd, err := syscall.Dup(dirfd) - if err == nil { - var dirRelPathCopy strings.Builder - dirRelPathCopy.WriteString(dirRelPath) - sessions[sessionID].dirCache[dirRelPathCopy.String()] = Directory{cache_dirfd, iv} - } + cache_dirfd, err := syscall.Dup(dirfd) + if err == nil { + var dirRelPathCopy strings.Builder + dirRelPathCopy.WriteString(dirRelPath) + sessions[sessionID].dirCache[dirRelPathCopy.String()] = Directory{cache_dirfd, iv} + } break } // Not the last part? Descend into next directory. dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) - syscall.Close(dirfd) + syscall.Close(dirfd) if err != nil { return -1, "", err } @@ -180,17 +191,17 @@ func mangleOpenFlags(flags uint32) (newFlags int) { } func register_file_handle(sessionID int, file File) int { - handleID := -1 - c := 0 - for handleID == -1 { - _, ok := sessions[sessionID].file_handles[c] - if !ok { - handleID = c - } - c++ - } - sessions[sessionID].file_handles[handleID] = file - return handleID + handleID := -1 + c := 0 + for handleID == -1 { + _, ok := sessions[sessionID].file_handles[c] + if !ok { + handleID = c + } + c++ + } + sessions[sessionID].file_handles[handleID] = file + return handleID } func readFileID(fd *os.File) ([]byte, error) { @@ -212,14 +223,14 @@ func readFileID(fd *os.File) ([]byte, error) { } func createHeader(fd *os.File) (fileID []byte, err error) { - h := contentenc.RandomHeader() + h := contentenc.RandomHeader() buf := h.Pack() // Prevent partially written (=corrupt) header by preallocating the space beforehand //NoPrealloc - err = syscallcompat.EnospcPrealloc(int(fd.Fd()), 0, contentenc.HeaderLen) - if err != nil { - return nil, err - } + err = syscallcompat.EnospcPrealloc(int(fd.Fd()), 0, contentenc.HeaderLen) + if err != nil { + return nil, err + } // Actually write header _, err = fd.WriteAt(buf, 0) if err != nil { @@ -229,23 +240,23 @@ func createHeader(fd *os.File) (fileID []byte, err error) { } func doRead(sessionID, handleID int, dst_buff []byte, offset uint64, length uint64) ([]byte, bool) { - f, ok := sessions[sessionID].file_handles[handleID] - if !ok { - return nil, false - } - fd := f.fd - var fileID []byte - test_fileID, ok := sessions[sessionID].fileIDs[handleID] - if ok { - fileID = test_fileID - } else { - var err error - fileID, err = readFileID(fd) - if err != nil || fileID == nil { - return nil, false - } - sessions[sessionID].fileIDs[handleID] = fileID - } + f, ok := sessions[sessionID].file_handles[handleID] + if !ok { + return nil, false + } + fd := f.fd + var fileID []byte + test_fileID, ok := sessions[sessionID].fileIDs[handleID] + if ok { + fileID = test_fileID + } else { + var err error + fileID, err = readFileID(fd) + if err != nil || fileID == nil { + return nil, false + } + sessions[sessionID].fileIDs[handleID] = fileID + } // Read the backing ciphertext in one go blocks := sessions[sessionID].contentEnc.ExplodePlainRange(offset, length) @@ -256,7 +267,7 @@ func doRead(sessionID, handleID int, dst_buff []byte, offset uint64, length uint ciphertext = ciphertext[:int(alignedLength)] n, err := fd.ReadAt(ciphertext, int64(alignedOffset)) if err != nil && err != io.EOF { - return nil, false + return nil, false } // The ReadAt came back empty. We can skip all the decryption and return early. if n == 0 { @@ -288,76 +299,76 @@ func doRead(sessionID, handleID int, dst_buff []byte, offset uint64, length uint out = append(dst_buff, out...) sessions[sessionID].contentEnc.PReqPool.Put(plaintext) - return out, true + return out, true } -func doWrite(sessionID, handleID int, data []byte, offset uint64) (uint32, bool){ - fileWasEmpty := false - f, ok := sessions[sessionID].file_handles[handleID] - if !ok { - return 0, false - } - fd := f.fd - var err error - var fileID []byte - test_fileID, ok := sessions[sessionID].fileIDs[handleID] - if ok { - fileID = test_fileID - } else { - fileID, err = readFileID(fd) - // Write a new file header if the file is empty - if err == io.EOF { - fileID, err = createHeader(fd) - fileWasEmpty = true - } - if err != nil { - return 0, false - } - sessions[sessionID].fileIDs[handleID] = fileID - } - // Handle payload data - dataBuf := bytes.NewBuffer(data) - blocks := sessions[sessionID].contentEnc.ExplodePlainRange(offset, uint64(len(data))) - toEncrypt := make([][]byte, len(blocks)) - for i, b := range blocks { - blockData := dataBuf.Next(int(b.Length)) - // Incomplete block -> Read-Modify-Write - if b.IsPartial() { - // Read - oldData, success := doRead(sessionID, handleID, nil, b.BlockPlainOff(), sessions[sessionID].contentEnc.PlainBS()) - if !success { - return 0, false - } - // Modify - blockData = sessions[sessionID].contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) - } - // Write into the to-encrypt list - toEncrypt[i] = blockData - } - // Encrypt all blocks - ciphertext := sessions[sessionID].contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, fileID) - // Preallocate so we cannot run out of space in the middle of the write. - // This prevents partially written (=corrupt) blocks. - cOff := int64(blocks[0].BlockCipherOff()) +func doWrite(sessionID, handleID int, data []byte, offset uint64) (uint32, bool) { + fileWasEmpty := false + f, ok := sessions[sessionID].file_handles[handleID] + if !ok { + return 0, false + } + fd := f.fd + var err error + var fileID []byte + test_fileID, ok := sessions[sessionID].fileIDs[handleID] + if ok { + fileID = test_fileID + } else { + fileID, err = readFileID(fd) + // Write a new file header if the file is empty + if err == io.EOF { + fileID, err = createHeader(fd) + fileWasEmpty = true + } + if err != nil { + return 0, false + } + sessions[sessionID].fileIDs[handleID] = fileID + } + // Handle payload data + dataBuf := bytes.NewBuffer(data) + blocks := sessions[sessionID].contentEnc.ExplodePlainRange(offset, uint64(len(data))) + toEncrypt := make([][]byte, len(blocks)) + for i, b := range blocks { + blockData := dataBuf.Next(int(b.Length)) + // Incomplete block -> Read-Modify-Write + if b.IsPartial() { + // Read + oldData, success := doRead(sessionID, handleID, nil, b.BlockPlainOff(), sessions[sessionID].contentEnc.PlainBS()) + if !success { + return 0, false + } + // Modify + blockData = sessions[sessionID].contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) + } + // Write into the to-encrypt list + toEncrypt[i] = blockData + } + // Encrypt all blocks + ciphertext := sessions[sessionID].contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, fileID) + // Preallocate so we cannot run out of space in the middle of the write. + // This prevents partially written (=corrupt) blocks. + cOff := int64(blocks[0].BlockCipherOff()) - //NoPrealloc - err = syscallcompat.EnospcPrealloc(int(fd.Fd()), cOff, int64(len(ciphertext))) - if err != nil { - if fileWasEmpty { - syscall.Ftruncate(int(fd.Fd()), 0) - // Kill the file header again - gcf_close_file(sessionID, handleID) //f.fileTableEntry.ID = nil - } - return 0, false - } - // Write - _, err = fd.WriteAt(ciphertext, cOff) - // Return memory to CReqPool - sessions[sessionID].contentEnc.CReqPool.Put(ciphertext) - if err != nil { - return 0, false - } - return uint32(len(data)), true + //NoPrealloc + err = syscallcompat.EnospcPrealloc(int(fd.Fd()), cOff, int64(len(ciphertext))) + if err != nil { + if fileWasEmpty { + syscall.Ftruncate(int(fd.Fd()), 0) + // Kill the file header again + gcf_close_file(sessionID, handleID) //f.fileTableEntry.ID = nil + } + return 0, false + } + // Write + _, err = fd.WriteAt(ciphertext, cOff) + // Return memory to CReqPool + sessions[sessionID].contentEnc.CReqPool.Put(ciphertext) + if err != nil { + return 0, false + } + return uint32(len(data)), true } // Zero-pad the file of size plainSize to the next block boundary. This is a no-op @@ -397,7 +408,7 @@ func truncateGrowFile(sessionID, handleID int, oldPlainSz uint64, newPlainSz uin // // Make sure the old last block is padded to the block boundary. This call // is a no-op if it is already block-aligned. - success := zeroPad(sessionID, handleID, oldPlainSz) + success := zeroPad(sessionID, handleID, oldPlainSz) if !success { return false } @@ -423,8 +434,8 @@ func truncateGrowFile(sessionID, handleID int, oldPlainSz uint64, newPlainSz uin } func truncate(sessionID, handleID int, newSize uint64) bool { - fileFD := int(sessions[sessionID].file_handles[handleID].fd.Fd()) - /*// Common case first: Truncate to zero + fileFD := int(sessions[sessionID].file_handles[handleID].fd.Fd()) + /*// Common case first: Truncate to zero if newSize == 0 { err = syscall.Ftruncate(fileFD, 0) if err != nil { @@ -434,177 +445,180 @@ func truncate(sessionID, handleID int, newSize uint64) bool { f.fileTableEntry.ID = nil return true }*/ - // We need the old file size to determine if we are growing or shrinking - // the file - oldSize, _, success := gcf_get_attrs(sessionID, sessions[sessionID].file_handles[handleID].path) - if !success { - return false - } + // We need the old file size to determine if we are growing or shrinking + // the file + oldSize, _, success := gcf_get_attrs(sessionID, sessions[sessionID].file_handles[handleID].path) + if !success { + return false + } - // File size stays the same - nothing to do - if newSize == oldSize { - return true - } - // File grows - if newSize > oldSize { - return truncateGrowFile(sessionID, handleID, oldSize, newSize) - } + // File size stays the same - nothing to do + if newSize == oldSize { + return true + } + // File grows + if newSize > oldSize { + return truncateGrowFile(sessionID, handleID, oldSize, newSize) + } - // File shrinks - blockNo := sessions[sessionID].contentEnc.PlainOffToBlockNo(newSize) - cipherOff := sessions[sessionID].contentEnc.BlockNoToCipherOff(blockNo) - plainOff := sessions[sessionID].contentEnc.BlockNoToPlainOff(blockNo) - lastBlockLen := newSize - plainOff - var data []byte - if lastBlockLen > 0 { - data, success = doRead(sessionID, handleID, nil, plainOff, lastBlockLen) - if !success { - return false - } - } - // Truncate down to the last complete block - err := syscall.Ftruncate(fileFD, int64(cipherOff)) - if err != nil { - return false - } - // Append partial block - if lastBlockLen > 0 { - _, success := doWrite(sessionID, handleID, data, plainOff) - return success - } - return true + // File shrinks + blockNo := sessions[sessionID].contentEnc.PlainOffToBlockNo(newSize) + cipherOff := sessions[sessionID].contentEnc.BlockNoToCipherOff(blockNo) + plainOff := sessions[sessionID].contentEnc.BlockNoToPlainOff(blockNo) + lastBlockLen := newSize - plainOff + var data []byte + if lastBlockLen > 0 { + data, success = doRead(sessionID, handleID, nil, plainOff, lastBlockLen) + if !success { + return false + } + } + // Truncate down to the last complete block + err := syscall.Ftruncate(fileFD, int64(cipherOff)) + if err != nil { + return false + } + // Append partial block + if lastBlockLen > 0 { + _, success := doWrite(sessionID, handleID, data, plainOff) + return success + } + return true } func init_new_session(root_cipher_dir string, masterkey []byte, cf *configfile.ConfFile) int { - // Initialize EME for filename encryption. - var emeCipher *eme.EMECipher - var err error - var emeBlockCipher cipher.Block - emeKey := cryptocore.HkdfDerive(masterkey, cryptocore.HkdfInfoEMENames, cryptocore.KeyLen) - emeBlockCipher, err = aes.NewCipher(emeKey) - for i := range emeKey { - emeKey[i] = 0 - } - if err == nil { - var new_session SessionVars - emeCipher = eme.New(emeBlockCipher) - new_session.nameTransform = nametransform.New(emeCipher, true, true) + // Initialize EME for filename encryption. + var emeCipher *eme.EMECipher + var err error + var emeBlockCipher cipher.Block + emeKey := cryptocore.HkdfDerive(masterkey, cryptocore.HkdfInfoEMENames, cryptocore.KeyLen) + emeBlockCipher, err = aes.NewCipher(emeKey) + for i := range emeKey { + emeKey[i] = 0 + } + if err == nil { + var new_session SessionVars - // Initialize contentEnc - cryptoBackend := cryptocore.BackendGoGCM - if cf.IsFeatureFlagSet(configfile.FlagAESSIV) { - cryptoBackend = cryptocore.BackendAESSIV - } else if stupidgcm.PreferOpenSSL() { - cryptoBackend = cryptocore.BackendOpenSSL - } - forcedecode := false - new_session.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode) - new_session.contentEnc = contentenc.New(new_session.cryptoCore, contentenc.DefaultBS, forcedecode) + new_session.plainTextNames = cf.IsFeatureFlagSet(configfile.FlagPlaintextNames) - //copying root_cipher_dir - var grcd strings.Builder - grcd.WriteString(root_cipher_dir) - new_session.root_cipher_dir = grcd.String() + emeCipher = eme.New(emeBlockCipher) + new_session.nameTransform = nametransform.New(emeCipher, true, true) - // New empty caches - new_session.dirCache = make(map[string]Directory) - new_session.file_handles = make(map[int]File) - new_session.fileIDs = make(map[int][]byte) + // Initialize contentEnc + cryptoBackend := cryptocore.BackendGoGCM + if cf.IsFeatureFlagSet(configfile.FlagAESSIV) { + cryptoBackend = cryptocore.BackendAESSIV + } else if stupidgcm.PreferOpenSSL() { + cryptoBackend = cryptocore.BackendOpenSSL + } + forcedecode := false + new_session.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode) + new_session.contentEnc = contentenc.New(new_session.cryptoCore, contentenc.DefaultBS, forcedecode) - //find unused sessionID - sessionID := -1 - c := 0 - for sessionID == -1 { - _, ok := sessions[c] - if !ok { - sessionID = c - } - c++ - } - if sessions == nil { - sessions = make(map[int]SessionVars) - } - sessions[sessionID] = new_session; - return sessionID - } - return -1 + //copying root_cipher_dir + var grcd strings.Builder + grcd.WriteString(root_cipher_dir) + new_session.root_cipher_dir = grcd.String() + + // New empty caches + new_session.dirCache = make(map[string]Directory) + new_session.file_handles = make(map[int]File) + new_session.fileIDs = make(map[int][]byte) + + //find unused sessionID + sessionID := -1 + c := 0 + for sessionID == -1 { + _, ok := sessions[c] + if !ok { + sessionID = c + } + c++ + } + if sessions == nil { + sessions = make(map[int]SessionVars) + } + sessions[sessionID] = new_session + return sessionID + } + return -1 } //export gcf_init func gcf_init(root_cipher_dir string, password, givenScryptHash, returnedScryptHashBuff []byte) int { - sessionID := -1 - cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName)) - if err == nil { - masterkey := cf.GetMasterkey(password, givenScryptHash, returnedScryptHashBuff) - if masterkey != nil { - sessionID = init_new_session(root_cipher_dir, masterkey, cf) - wipe(masterkey) - } - } - return sessionID + sessionID := -1 + cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName)) + if err == nil { + masterkey := cf.GetMasterkey(password, givenScryptHash, returnedScryptHashBuff) + if masterkey != nil { + sessionID = init_new_session(root_cipher_dir, masterkey, cf) + wipe(masterkey) + } + } + return sessionID } //export gcf_close -func gcf_close(sessionID int){ - sessions[sessionID].cryptoCore.Wipe() - for handleID, _ := range sessions[sessionID].file_handles { - gcf_close_file(sessionID, handleID) - } - clear_dirCache(sessionID) - delete(sessions, sessionID) +func gcf_close(sessionID int) { + sessions[sessionID].cryptoCore.Wipe() + for handleID, _ := range sessions[sessionID].file_handles { + gcf_close_file(sessionID, handleID) + } + clear_dirCache(sessionID) + delete(sessions, sessionID) } //export gcf_is_closed func gcf_is_closed(sessionID int) bool { - _, ok := sessions[sessionID] - return !ok + _, ok := sessions[sessionID] + return !ok } //export gcf_create_volume func gcf_create_volume(root_cipher_dir string, password []byte, logN int, creator string) bool { - err := configfile.Create(filepath.Join(root_cipher_dir, configfile.ConfDefaultName), password, false, logN, creator, false, false) - if err == nil { - dirfd, err := syscall.Open(root_cipher_dir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) - if err == nil { - err = nametransform.WriteDirIVAt(dirfd) - syscall.Close(dirfd) - return err_to_bool(err) - } - } - return false + err := configfile.Create(filepath.Join(root_cipher_dir, configfile.ConfDefaultName), password, false, logN, creator, false, false) + if err == nil { + dirfd, err := syscall.Open(root_cipher_dir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0) + if err == nil { + err = nametransform.WriteDirIVAt(dirfd) + syscall.Close(dirfd) + return err_to_bool(err) + } + } + return false } //export gcf_change_password func gcf_change_password(root_cipher_dir string, old_password, givenScryptHash, new_password, returnedScryptHashBuff []byte) bool { - success := false - cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName)) - if err == nil { - masterkey := cf.GetMasterkey(old_password, givenScryptHash, nil) - if masterkey != nil { - logN := cf.ScryptObject.LogN() - scryptHash := cf.EncryptKey(masterkey, new_password, logN, len(returnedScryptHashBuff)>0) - wipe(masterkey) - for i := range scryptHash { - returnedScryptHashBuff[i] = scryptHash[i] - scryptHash[i] = 0 - } - success = err_to_bool(cf.WriteFile()) - } - } - return success + success := false + cf, err := configfile.Load(filepath.Join(root_cipher_dir, configfile.ConfDefaultName)) + if err == nil { + masterkey := cf.GetMasterkey(old_password, givenScryptHash, nil) + if masterkey != nil { + logN := cf.ScryptObject.LogN() + scryptHash := cf.EncryptKey(masterkey, new_password, logN, len(returnedScryptHashBuff) > 0) + wipe(masterkey) + for i := range scryptHash { + returnedScryptHashBuff[i] = scryptHash[i] + scryptHash[i] = 0 + } + success = err_to_bool(cf.WriteFile()) + } + } + return success } //export gcf_list_dir func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { - parentDirFd, cDirName, err := openBackingDir(sessionID, dirName) - if err != nil { + parentDirFd, cDirName, err := openBackingDir(sessionID, dirName) + if err != nil { return nil, nil, 0 } - defer syscall.Close(parentDirFd) + defer syscall.Close(parentDirFd) // Read ciphertext directory var cipherEntries []syscallcompat.DirEntry fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) - if err != nil { + if err != nil { return nil, nil, 0 } defer syscall.Close(fd) @@ -614,14 +628,16 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { } // Get DirIV (stays nil if PlaintextNames is used) var cachedIV []byte - // Read the DirIV from disk - cachedIV, err = nametransform.ReadDirIVAt(fd) - if err != nil { - return nil, nil, 0 - } + if !sessions[sessionID].plainTextNames { + // Read the DirIV from disk + cachedIV, err = nametransform.ReadDirIVAt(fd) + if err != nil { + return nil, nil, 0 + } + } // Decrypted directory entries - var plain strings.Builder - var modes []uint32 + var plain strings.Builder + var modes []uint32 // Filter and decrypt filenames for i := range cipherEntries { cName := cipherEntries[i].Name @@ -629,6 +645,11 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { // silently ignore "gocryptfs.conf" in the top level dir continue } + if sessions[sessionID].plainTextNames { + plain.WriteString(cipherEntries[i].Name + "\x00") + modes = append(modes, cipherEntries[i].Mode) + continue + } if cName == nametransform.DirIVFilename { // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled continue @@ -652,15 +673,15 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { // Override the ciphertext name with the plaintext name but reuse the rest // of the structure cipherEntries[i].Name = name - plain.WriteString(cipherEntries[i].Name+"\x00") - modes = append(modes, cipherEntries[i].Mode) + plain.WriteString(cipherEntries[i].Name + "\x00") + modes = append(modes, cipherEntries[i].Mode) } - p := C.malloc(C.ulong(C.sizeof_int*len(modes))) - for i := 0; i < len(modes); i++ { - offset := C.sizeof_int*uintptr(i) - *(*C.int)(unsafe.Pointer(uintptr(p)+offset)) = (C.int)(modes[i]) - } - return C.CString(plain.String()), (*C.int)(p), (C.int)(len(modes)) + p := C.malloc(C.ulong(C.sizeof_int * len(modes))) + for i := 0; i < len(modes); i++ { + offset := C.sizeof_int * uintptr(i) + *(*C.int)(unsafe.Pointer(uintptr(p) + offset)) = (C.int)(modes[i]) + } + return C.CString(plain.String()), (*C.int)(p), (C.int)(len(modes)) } //export gcf_mkdir @@ -670,52 +691,65 @@ func gcf_mkdir(sessionID int, newPath string) bool { return false } defer syscall.Close(dirfd) - // We need write and execute permissions to create gocryptfs.diriv. - // Also, we need read permissions to open the directory (to avoid - // race-conditions between getting and setting the mode). - origMode := folder_mode - mode := folder_mode | 0700 - // Handle long file name - if nametransform.IsLongContent(cName) { - // Create ".name" - err = sessions[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, newPath) + if sessions[sessionID].plainTextNames { + err = syscallcompat.Mkdirat(dirfd, cName, folder_mode) if err != nil { return false } - - // Create directory - err = mkdirWithIv(dirfd, cName, mode) + var ust unix.Stat_t + err = syscallcompat.Fstatat(dirfd, cName, &ust, unix.AT_SYMLINK_NOFOLLOW) if err != nil { - nametransform.DeleteLongNameAt(dirfd, cName) return false } } else { - err = mkdirWithIv(dirfd, cName, mode) - if err != nil { - return false - } - } - // Set mode - if origMode != mode { - dirfd2, err := syscallcompat.Openat(dirfd, cName, - syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) - if err != nil { - return false - } - defer syscall.Close(dirfd2) + // We need write and execute permissions to create gocryptfs.diriv. + // Also, we need read permissions to open the directory (to avoid + // race-conditions between getting and setting the mode). + origMode := folder_mode + mode := folder_mode | 0700 - var st syscall.Stat_t - err = syscall.Fstat(dirfd2, &st) - if err != nil { - return false - } + // Handle long file name + if nametransform.IsLongContent(cName) { + // Create ".name" + err = sessions[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, newPath) + if err != nil { + return false + } - // Preserve SGID bit if it was set due to inheritance. - origMode = uint32(st.Mode&^0777) | origMode - err = syscall.Fchmod(dirfd2, origMode) - if err != nil { - return false + // Create directory + err = mkdirWithIv(dirfd, cName, mode) + if err != nil { + nametransform.DeleteLongNameAt(dirfd, cName) + return false + } + } else { + err = mkdirWithIv(dirfd, cName, mode) + if err != nil { + return false + } + } + // Set mode + if origMode != mode { + dirfd2, err := syscallcompat.Openat(dirfd, cName, + syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + return false + } + defer syscall.Close(dirfd2) + + var st syscall.Stat_t + err = syscall.Fstat(dirfd2, &st) + if err != nil { + return false + } + + // Preserve SGID bit if it was set due to inheritance. + origMode = uint32(st.Mode&^0777) | origMode + err = syscall.Fchmod(dirfd2, origMode) + if err != nil { + return false + } } } return true @@ -723,12 +757,17 @@ func gcf_mkdir(sessionID int, newPath string) bool { //export gcf_rmdir func gcf_rmdir(sessionID int, relPath string) bool { - defer clear_dirCache(sessionID) - parentDirFd, cName, err := openBackingDir(sessionID, relPath) + defer clear_dirCache(sessionID) + parentDirFd, cName, err := openBackingDir(sessionID, relPath) if err != nil { return false } defer syscall.Close(parentDirFd) + if sessions[sessionID].plainTextNames { + // Unlinkat with AT_REMOVEDIR is equivalent to Rmdir + err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) + return err_to_bool(err) + } dirfd, err := syscallcompat.Openat(parentDirFd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) if err != nil { return false @@ -774,8 +813,8 @@ func gcf_rmdir(sessionID int, relPath string) bool { //export gcf_open_read_mode func gcf_open_read_mode(sessionID int, path string) int { - newFlags := mangleOpenFlags(0) - dirfd, cName, err := openBackingDir(sessionID, path) + newFlags := mangleOpenFlags(0) + dirfd, cName, err := openBackingDir(sessionID, path) if err != nil { return -1 } @@ -797,14 +836,14 @@ func gcf_open_write_mode(sessionID int, path string) int { defer syscall.Close(dirfd) fd := -1 // Handle long file name - if nametransform.IsLongContent(cName) { + if !sessions[sessionID].plainTextNames && nametransform.IsLongContent(cName) { // Create ".name" err = sessions[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path) if err != nil { return -1 } // Create content - fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, file_mode) + fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, file_mode) if err != nil { nametransform.DeleteLongNameAt(dirfd, cName) } @@ -825,50 +864,50 @@ func gcf_open_write_mode(sessionID int, path string) int { //export gcf_truncate func gcf_truncate(sessionID int, path string, offset uint64) bool { - handleID := gcf_open_write_mode(sessionID, path) - if handleID != -1 { - success := truncate(sessionID, handleID, offset) - gcf_close_file(sessionID, handleID) - return success - } - return false + handleID := gcf_open_write_mode(sessionID, path) + if handleID != -1 { + success := truncate(sessionID, handleID, offset) + gcf_close_file(sessionID, handleID) + return success + } + return false } //export gcf_close_file -func gcf_close_file(sessionID, handleID int){ - f, ok := sessions[sessionID].file_handles[handleID] - if ok { - f.fd.Close() - delete(sessions[sessionID].file_handles, handleID) - _, ok := sessions[sessionID].fileIDs[handleID] - if ok { - delete(sessions[sessionID].fileIDs, handleID) - } - } +func gcf_close_file(sessionID, handleID int) { + f, ok := sessions[sessionID].file_handles[handleID] + if ok { + f.fd.Close() + delete(sessions[sessionID].file_handles, handleID) + _, ok := sessions[sessionID].fileIDs[handleID] + if ok { + delete(sessions[sessionID].fileIDs, handleID) + } + } } //export gcf_read_file func gcf_read_file(sessionID, handleID int, offset uint64, dst_buff []byte) uint32 { - length := uint64(len(dst_buff)) - if length > contentenc.MAX_KERNEL_WRITE { - return 0; - } + length := uint64(len(dst_buff)) + if length > contentenc.MAX_KERNEL_WRITE { + return 0 + } - out, _ := doRead(sessionID, handleID, dst_buff[:0], offset, length) + out, _ := doRead(sessionID, handleID, dst_buff[:0], offset, length) return uint32(len(out)) } //export gcf_write_file func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32 { - length := uint64(len(data)) - if length > contentenc.MAX_KERNEL_WRITE { - return 0; - } + length := uint64(len(data)) + if length > contentenc.MAX_KERNEL_WRITE { + return 0 + } - written, _ := doWrite(sessionID, handleID, data, offset) + written, _ := doWrite(sessionID, handleID, data, offset) - return written + return written } //export gcf_get_attrs @@ -883,7 +922,7 @@ func gcf_get_attrs(sessionID int, relPath string) (uint64, int64, bool) { if err != nil { return 0, 0, false } - return sessions[sessionID].contentEnc.CipherSizeToPlainSize(uint64(st.Size)), st.Mtim.Sec, true + return sessions[sessionID].contentEnc.CipherSizeToPlainSize(uint64(st.Size)), st.Mtim.Sec, true } //export gcf_rename @@ -899,6 +938,11 @@ func gcf_rename(sessionID int, oldPath string, newPath string) bool { return false } defer syscall.Close(newDirfd) + + // Easy case. + if sessions[sessionID].plainTextNames { + return err_to_bool(syscallcompat.Renameat(oldDirfd, oldCName, newDirfd, newCName)) + } // Long destination file name: create .name file nameFileAlreadyThere := false if nametransform.IsLongContent(newCName) { @@ -950,10 +994,10 @@ func gcf_remove_file(sessionID int, path string) bool { return false } // Delete ".name" file - if nametransform.IsLongContent(cName) { + if !sessions[sessionID].plainTextNames && nametransform.IsLongContent(cName) { err = nametransform.DeleteLongNameAt(dirfd, cName) } return err_to_bool(err) } -func main(){} +func main() {}