Major refactoring: Split up "cryptfs" into several internal packages

"git status" for reference:

deleted:    cryptfs/cryptfs.go
deleted:    cryptfs/names_core.go
modified:   integration_tests/cli_test.go
modified:   integration_tests/helpers.go
renamed:    cryptfs/config_file.go -> internal/configfile/config_file.go
renamed:    cryptfs/config_test.go -> internal/configfile/config_test.go
renamed:    cryptfs/config_test/.gitignore -> internal/configfile/config_test/.gitignore
renamed:    cryptfs/config_test/PlaintextNames.conf -> internal/configfile/config_test/PlaintextNames.conf
renamed:    cryptfs/config_test/StrangeFeature.conf -> internal/configfile/config_test/StrangeFeature.conf
renamed:    cryptfs/config_test/v1.conf -> internal/configfile/config_test/v1.conf
renamed:    cryptfs/config_test/v2.conf -> internal/configfile/config_test/v2.conf
renamed:    cryptfs/kdf.go -> internal/configfile/kdf.go
renamed:    cryptfs/kdf_test.go -> internal/configfile/kdf_test.go
renamed:    cryptfs/cryptfs_content.go -> internal/contentenc/content.go
new file:   internal/contentenc/content_api.go
renamed:    cryptfs/content_test.go -> internal/contentenc/content_test.go
renamed:    cryptfs/file_header.go -> internal/contentenc/file_header.go
renamed:    cryptfs/intrablock.go -> internal/contentenc/intrablock.go
renamed:    cryptfs/address_translation.go -> internal/contentenc/offsets.go
new file:   internal/cryptocore/crypto_api.go
renamed:    cryptfs/gcm_go1.4.go -> internal/cryptocore/gcm_go1.4.go
renamed:    cryptfs/gcm_go1.5.go -> internal/cryptocore/gcm_go1.5.go
renamed:    cryptfs/nonce.go -> internal/cryptocore/nonce.go
renamed:    cryptfs/openssl_aead.go -> internal/cryptocore/openssl_aead.go
renamed:    cryptfs/openssl_benchmark.bash -> internal/cryptocore/openssl_benchmark.bash
renamed:    cryptfs/openssl_test.go -> internal/cryptocore/openssl_test.go
new file:   internal/nametransform/name_api.go
new file:   internal/nametransform/names_core.go
renamed:    cryptfs/names_diriv.go -> internal/nametransform/names_diriv.go
renamed:    cryptfs/names_noiv.go -> internal/nametransform/names_noiv.go
renamed:    cryptfs/names_test.go -> internal/nametransform/names_test.go
new file:   internal/nametransform/pad16.go
renamed:    cryptfs/log.go -> internal/toggledlog/log.go
renamed:    cryptfs/log_go1.4.go -> internal/toggledlog/log_go1.4.go
renamed:    cryptfs/log_go1.5.go -> internal/toggledlog/log_go1.5.go
modified:   main.go
modified:   masterkey.go
modified:   pathfs_frontend/file.go
modified:   pathfs_frontend/file_holes.go
modified:   pathfs_frontend/fs.go
modified:   pathfs_frontend/fs_dir.go
modified:   pathfs_frontend/names.go
modified:   test.bash
This commit is contained in:
Jakob Unterwurzacher 2016-02-06 19:20:54 +01:00
parent adcfbd79a8
commit 2b8cbd9441
43 changed files with 579 additions and 513 deletions

View File

@ -1,83 +0,0 @@
package cryptfs
// CryptFS is the crypto backend of GoCryptFS
import (
"crypto/aes"
"crypto/cipher"
"fmt"
)
const (
PROGRAM_NAME = "gocryptfs"
DEFAULT_PLAINBS = 4096
KEY_LEN = 32 // AES-256
AUTH_TAG_LEN = 16
DIRIV_LEN = 16 // identical to AES block size
DIRIV_FILENAME = "gocryptfs.diriv"
)
type CryptFS struct {
blockCipher cipher.Block
gcm cipher.AEAD
gcmIVLen int
gcmIVGen nonceGenerator
plainBS uint64
cipherBS uint64
// Stores an all-zero block of size cipherBS
allZeroBlock []byte
// DirIV cache for filename encryption
DirIVCache dirIVCache
}
func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool, GCMIV128 bool) *CryptFS {
if len(key) != KEY_LEN {
panic(fmt.Sprintf("Unsupported key length %d", len(key)))
}
b, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// We want the IV size in bytes
gcmIV := 96 / 8
if GCMIV128 {
gcmIV = 128 / 8
}
var gcm cipher.AEAD
if useOpenssl {
gcm = opensslGCM{key}
} else {
gcm, err = goGCMWrapper(b, gcmIV)
if err != nil {
panic(err)
}
}
plainBS := DEFAULT_PLAINBS
cipherBS := plainBS + gcmIV + AUTH_TAG_LEN
return &CryptFS{
blockCipher: b,
gcm: gcm,
gcmIVLen: gcmIV,
gcmIVGen: nonceGenerator{nonceLen: gcmIV},
plainBS: uint64(plainBS),
cipherBS: uint64(cipherBS),
allZeroBlock: make([]byte, cipherBS),
}
}
// Get plaintext block size
func (be *CryptFS) PlainBS() uint64 {
return be.plainBS
}
// Per-block storage overhead
func (be *CryptFS) BlockOverhead() uint64 {
return be.cipherBS - be.plainBS
}

View File

@ -1,134 +0,0 @@
package cryptfs
// Filename encryption / decryption functions
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"github.com/rfjakob/eme"
)
// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
//
// This function is exported because it allows for a very efficient readdir
// implementation (read IV once, decrypt all names using this function).
func (be *CryptFS) DecryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
return be.decryptName(cipherName, iv, EMENames)
}
// decryptName - decrypt base64-encoded encrypted filename "cipherName".
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
func (be *CryptFS) decryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
// Make sure relative symlinks still work after encryption
// by passing these through unchanged
if cipherName == "." || cipherName == ".." {
return cipherName, nil
}
bin, err := base64.URLEncoding.DecodeString(cipherName)
if err != nil {
return "", err
}
if len(bin)%aes.BlockSize != 0 {
return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
}
if EMENames {
bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionDecrypt)
} else {
cbc := cipher.NewCBCDecrypter(be.blockCipher, iv)
cbc.CryptBlocks(bin, bin)
}
bin, err = be.unPad16(bin)
if err != nil {
return "", err
}
plain := string(bin)
return plain, err
}
// encryptName - encrypt "plainName", return base64-encoded "cipherName64"
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
func (be *CryptFS) encryptName(plainName string, iv []byte, EMENames bool) (cipherName64 string) {
// Make sure relative symlinks still work after encryption
// by passing these trough unchanged
if plainName == "." || plainName == ".." {
return plainName
}
bin := []byte(plainName)
bin = be.pad16(bin)
if EMENames {
bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionEncrypt)
} else {
cbc := cipher.NewCBCEncrypter(be.blockCipher, iv)
cbc.CryptBlocks(bin, bin)
}
cipherName64 = base64.URLEncoding.EncodeToString(bin)
return cipherName64
}
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
// https://tools.ietf.org/html/rfc5652#section-6.3
func (be *CryptFS) pad16(orig []byte) (padded []byte) {
oldLen := len(orig)
if oldLen == 0 {
panic("Padding zero-length string makes no sense")
}
padLen := aes.BlockSize - oldLen%aes.BlockSize
if padLen == 0 {
padLen = aes.BlockSize
}
newLen := oldLen + padLen
padded = make([]byte, newLen)
copy(padded, orig)
padByte := byte(padLen)
for i := oldLen; i < newLen; i++ {
padded[i] = padByte
}
return padded
}
// unPad16 - remove padding
func (be *CryptFS) unPad16(orig []byte) ([]byte, error) {
oldLen := len(orig)
if oldLen%aes.BlockSize != 0 {
return nil, errors.New("Unaligned size")
}
// The last byte is always a padding byte
padByte := orig[oldLen-1]
// The padding byte's value is the padding length
padLen := int(padByte)
// Padding must be at least 1 byte
if padLen <= 0 {
return nil, errors.New("Padding cannot be zero-length")
}
// Larger paddings make no sense
if padLen > aes.BlockSize {
return nil, errors.New("Padding cannot be larger than 16")
}
// All padding bytes must be identical
for i := oldLen - padLen; i < oldLen; i++ {
if orig[i] != padByte {
return nil, errors.New(fmt.Sprintf("Padding byte at i=%d is invalid", i))
}
}
newLen := oldLen - padLen
// Padding an empty string makes no sense
if newLen == 0 {
return nil, errors.New("Unpadded length is zero")
}
return orig[0:newLen], nil
}

View File

@ -7,7 +7,8 @@ import (
"os/exec" "os/exec"
"testing" "testing"
"github.com/rfjakob/gocryptfs/cryptfs" "github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/nametransform"
) )
// Test -init flag // Test -init flag
@ -26,7 +27,7 @@ func TestInit(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = os.Stat(dir + cryptfs.ConfDefaultName) _, err = os.Stat(dir + configfile.ConfDefaultName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -96,22 +97,22 @@ func TestInitPlaintextNames(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = os.Stat(dir + cryptfs.ConfDefaultName) _, err = os.Stat(dir + configfile.ConfDefaultName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = os.Stat(dir + cryptfs.DIRIV_FILENAME) _, err = os.Stat(dir + nametransform.DirIVFilename)
if err == nil { if err == nil {
t.Errorf("gocryptfs.diriv should not have been created with -plaintextnames") t.Errorf("gocryptfs.diriv should not have been created with -plaintextnames")
} }
_, cf, err := cryptfs.LoadConfFile(dir+cryptfs.ConfDefaultName, "test") _, cf, err := configfile.LoadConfFile(dir+configfile.ConfDefaultName, "test")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !cf.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) { if !cf.IsFeatureFlagSet(configfile.FlagPlaintextNames) {
t.Error("PlaintextNames flag should be set but isnt") t.Error("PlaintextNames flag should be set but isnt")
} }
if cf.IsFeatureFlagSet(cryptfs.FlagEMENames) || cf.IsFeatureFlagSet(cryptfs.FlagDirIV) { if cf.IsFeatureFlagSet(configfile.FlagEMENames) || cf.IsFeatureFlagSet(configfile.FlagDirIV) {
t.Error("FlagEMENames and FlagDirIV should be not set") t.Error("FlagEMENames and FlagDirIV should be not set")
} }
} }

View File

@ -10,7 +10,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/rfjakob/gocryptfs/cryptfs" "github.com/rfjakob/gocryptfs/internal/nametransform"
) )
// Note: the code assumes that all have a trailing slash // Note: the code assumes that all have a trailing slash
@ -42,7 +42,7 @@ func resetTmpDir() {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
err = cryptfs.WriteDirIV(defaultCipherDir) err = nametransform.WriteDirIV(defaultCipherDir)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@ -58,9 +58,9 @@ func mount(c string, p string, extraArgs ...string) {
args = append(args, c) args = append(args, c)
args = append(args, p) args = append(args, p)
cmd := exec.Command(gocryptfsBinary, args...) cmd := exec.Command(gocryptfsBinary, args...)
cmd.Stderr = os.Stderr
if testing.Verbose() { if testing.Verbose() {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} }
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {

View File

@ -1,10 +1,14 @@
package cryptfs package configfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
import "os" import "os"
@ -36,10 +40,10 @@ type ConfFile struct {
func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error { func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error {
var cf ConfFile var cf ConfFile
cf.filename = filename cf.filename = filename
cf.Version = HEADER_CURRENT_VERSION cf.Version = contentenc.CurrentVersion
// Generate new random master key // Generate new random master key
key := RandBytes(KEY_LEN) key := cryptocore.RandBytes(cryptocore.KeyLen)
// Encrypt it using the password // Encrypt it using the password
// This sets ScryptObject and EncryptedKey // This sets ScryptObject and EncryptedKey
@ -75,11 +79,11 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
// Unmarshal // Unmarshal
err = json.Unmarshal(js, &cf) err = json.Unmarshal(js, &cf)
if err != nil { if err != nil {
Warn.Printf("Failed to unmarshal config file") toggledlog.Warn.Printf("Failed to unmarshal config file")
return nil, nil, err return nil, nil, err
} }
if cf.Version != HEADER_CURRENT_VERSION { if cf.Version != contentenc.CurrentVersion {
return nil, nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version) return nil, nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version)
} }
@ -95,11 +99,13 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
// Unlock master key using password-based key // Unlock master key using password-based key
// We use stock go GCM instead of OpenSSL here as speed is not important // We use stock go GCM instead of OpenSSL here as speed is not important
// and we get better error messages // and we get better error messages
cfs := NewCryptFS(scryptHash, false, false, false) cc := cryptocore.New(scryptHash, false, false)
key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil) ce := contentenc.New(cc, 4096)
key, err := ce.DecryptBlock(cf.EncryptedKey, 0, nil)
if err != nil { if err != nil {
Warn.Printf("failed to unlock master key: %s", err.Error()) toggledlog.Warn.Printf("failed to unlock master key: %s", err.Error())
Warn.Printf("Password incorrect.") toggledlog.Warn.Printf("Password incorrect.")
return nil, nil, err return nil, nil, err
} }
@ -116,8 +122,9 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) {
scryptHash := cf.ScryptObject.DeriveKey(password) scryptHash := cf.ScryptObject.DeriveKey(password)
// Lock master key using password-based key // Lock master key using password-based key
cfs := NewCryptFS(scryptHash, false, false, false) cc := cryptocore.New(scryptHash, false, false)
cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil) ce := contentenc.New(cc, 4096)
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
} }
// WriteFile - write out config in JSON format to file "filename.tmp" // WriteFile - write out config in JSON format to file "filename.tmp"

View File

@ -1,4 +1,4 @@
package cryptfs package configfile
import ( import (
"fmt" "fmt"

View File

@ -1,16 +1,19 @@
package cryptfs package configfile
import ( import (
"fmt" "fmt"
"golang.org/x/crypto/scrypt"
"math" "math"
"os" "os"
"golang.org/x/crypto/scrypt"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
) )
const ( const (
// 1 << 16 uses 64MB of memory, // 1 << 16 uses 64MB of memory,
// takes 4 seconds on my Atom Z3735F netbook // takes 4 seconds on my Atom Z3735F netbook
SCRYPT_DEFAULT_LOGN = 16 ScryptDefaultLogN = 16
) )
type scryptKdf struct { type scryptKdf struct {
@ -23,9 +26,9 @@ type scryptKdf struct {
func NewScryptKdf(logN int) scryptKdf { func NewScryptKdf(logN int) scryptKdf {
var s scryptKdf var s scryptKdf
s.Salt = RandBytes(KEY_LEN) s.Salt = cryptocore.RandBytes(cryptocore.KeyLen)
if logN <= 0 { if logN <= 0 {
s.N = 1 << SCRYPT_DEFAULT_LOGN s.N = 1 << ScryptDefaultLogN
} else { } else {
if logN < 10 { if logN < 10 {
fmt.Println("Error: scryptn below 10 is too low to make sense. Aborting.") fmt.Println("Error: scryptn below 10 is too low to make sense. Aborting.")
@ -35,7 +38,7 @@ func NewScryptKdf(logN int) scryptKdf {
} }
s.R = 8 // Always 8 s.R = 8 // Always 8
s.P = 1 // Always 1 s.P = 1 // Always 1
s.KeyLen = KEY_LEN s.KeyLen = cryptocore.KeyLen
return s return s
} }

View File

@ -1,4 +1,4 @@
package cryptfs package configfile
import ( import (
"testing" "testing"

View File

@ -1,31 +1,18 @@
package cryptfs package contentenc
// File content encryption / decryption // File content encryption / decryption
import ( import (
"bytes"
"crypto/cipher"
"crypto/md5"
"encoding/binary" "encoding/binary"
"bytes"
"encoding/hex" "encoding/hex"
"errors" "errors"
"os"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// md5sum - debug helper, return md5 hex string
func md5sum(buf []byte) string {
rawHash := md5.Sum(buf)
hash := hex.EncodeToString(rawHash[:])
return hash
}
type CryptFile struct {
file *os.File
gcm cipher.AEAD
}
// DecryptBlocks - Decrypt a number of blocks // DecryptBlocks - Decrypt a number of blocks
func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) { func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
cBuf := bytes.NewBuffer(ciphertext) cBuf := bytes.NewBuffer(ciphertext)
var err error var err error
var pBuf bytes.Buffer var pBuf bytes.Buffer
@ -46,7 +33,7 @@ func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId
// //
// Corner case: A full-sized block of all-zero ciphertext bytes is translated // Corner case: A full-sized block of all-zero ciphertext bytes is translated
// to an all-zero plaintext block, i.e. file hole passtrough. // to an all-zero plaintext block, i.e. file hole passtrough.
func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) { func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) {
// Empty block? // Empty block?
if len(ciphertext) == 0 { if len(ciphertext) == 0 {
@ -55,30 +42,30 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte
// All-zero block? // All-zero block?
if bytes.Equal(ciphertext, be.allZeroBlock) { if bytes.Equal(ciphertext, be.allZeroBlock) {
Debug.Printf("DecryptBlock: file hole encountered") toggledlog.Debug.Printf("DecryptBlock: file hole encountered")
return make([]byte, be.plainBS), nil return make([]byte, be.plainBS), nil
} }
if len(ciphertext) < be.gcmIVLen { if len(ciphertext) < be.cryptoCore.IVLen {
Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext)) toggledlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
return nil, errors.New("Block is too short") return nil, errors.New("Block is too short")
} }
// Extract nonce // Extract nonce
nonce := ciphertext[:be.gcmIVLen] nonce := ciphertext[:be.cryptoCore.IVLen]
ciphertextOrig := ciphertext ciphertextOrig := ciphertext
ciphertext = ciphertext[be.gcmIVLen:] ciphertext = ciphertext[be.cryptoCore.IVLen:]
// Decrypt // Decrypt
var plaintext []byte var plaintext []byte
aData := make([]byte, 8) aData := make([]byte, 8)
aData = append(aData, fileId...) aData = append(aData, fileId...)
binary.BigEndian.PutUint64(aData, blockNo) binary.BigEndian.PutUint64(aData, blockNo)
plaintext, err := be.gcm.Open(plaintext, nonce, ciphertext, aData) plaintext, err := be.cryptoCore.Gcm.Open(plaintext, nonce, ciphertext, aData)
if err != nil { if err != nil {
Warn.Printf("DecryptBlock: %s, len=%d, md5=%s", err.Error(), len(ciphertextOrig), md5sum(ciphertextOrig)) toggledlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
Debug.Println(hex.Dump(ciphertextOrig)) toggledlog.Debug.Println(hex.Dump(ciphertextOrig))
return nil, err return nil, err
} }
@ -86,7 +73,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte
} }
// encryptBlock - Encrypt and add IV and MAC // encryptBlock - Encrypt and add IV and MAC
func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte { func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
// Empty block? // Empty block?
if len(plaintext) == 0 { if len(plaintext) == 0 {
@ -94,7 +81,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte)
} }
// Get fresh nonce // Get fresh nonce
nonce := be.gcmIVGen.Get() nonce := be.cryptoCore.GcmIVGen.Get()
// Authenticate block with block number and file ID // Authenticate block with block number and file ID
aData := make([]byte, 8) aData := make([]byte, 8)
@ -102,14 +89,14 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte)
aData = append(aData, fileID...) aData = append(aData, fileID...)
// Encrypt plaintext and append to nonce // Encrypt plaintext and append to nonce
ciphertext := be.gcm.Seal(nonce, nonce, plaintext, aData) ciphertext := be.cryptoCore.Gcm.Seal(nonce, nonce, plaintext, aData)
return ciphertext return ciphertext
} }
// MergeBlocks - Merge newData into oldData at offset // MergeBlocks - Merge newData into oldData at offset
// New block may be bigger than both newData and oldData // New block may be bigger than both newData and oldData
func (be *CryptFS) MergeBlocks(oldData []byte, newData []byte, offset int) []byte { func (be *ContentEnc) MergeBlocks(oldData []byte, newData []byte, offset int) []byte {
// Make block of maximum size // Make block of maximum size
out := make([]byte, be.plainBS) out := make([]byte, be.plainBS)

View File

@ -0,0 +1,31 @@
package contentenc
import "github.com/rfjakob/gocryptfs/internal/cryptocore"
type ContentEnc struct {
// Cryptographic primitives
cryptoCore *cryptocore.CryptoCore
// Plaintext block size
plainBS uint64
// Ciphertext block size
cipherBS uint64
// All-zero block of size cipherBS, for fast compares
allZeroBlock []byte
}
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
return &ContentEnc{
cryptoCore: cc,
plainBS: plainBS,
cipherBS: cipherBS,
allZeroBlock: make([]byte, cipherBS),
}
}
func (be *ContentEnc) PlainBS() uint64 {
return be.plainBS
}

View File

@ -1,4 +1,4 @@
package cryptfs package contentenc
import ( import (
"testing" "testing"

View File

@ -1,4 +1,4 @@
package cryptfs package contentenc
// Per-file header // Per-file header
// //
@ -7,10 +7,14 @@ package cryptfs
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
) )
const ( const (
HEADER_CURRENT_VERSION = 2 // Current on-disk-format version // Current On-Disk-Format version
CurrentVersion = 2
HEADER_VERSION_LEN = 2 // uint16 HEADER_VERSION_LEN = 2 // uint16
HEADER_ID_LEN = 16 // 128 bit random file id HEADER_ID_LEN = 16 // 128 bit random file id
HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length
@ -23,7 +27,7 @@ type FileHeader struct {
// Pack - serialize fileHeader object // Pack - serialize fileHeader object
func (h *FileHeader) Pack() []byte { func (h *FileHeader) Pack() []byte {
if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION { if len(h.Id) != HEADER_ID_LEN || h.Version != CurrentVersion {
panic("FileHeader object not properly initialized") panic("FileHeader object not properly initialized")
} }
buf := make([]byte, HEADER_LEN) buf := make([]byte, HEADER_LEN)
@ -40,8 +44,8 @@ func ParseHeader(buf []byte) (*FileHeader, error) {
} }
var h FileHeader var h FileHeader
h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN]) h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN])
if h.Version != HEADER_CURRENT_VERSION { if h.Version != CurrentVersion {
return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, HEADER_CURRENT_VERSION) return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, CurrentVersion)
} }
h.Id = buf[HEADER_VERSION_LEN:] h.Id = buf[HEADER_VERSION_LEN:]
return &h, nil return &h, nil
@ -50,7 +54,7 @@ func ParseHeader(buf []byte) (*FileHeader, error) {
// RandomHeader - create new fileHeader object with random Id // RandomHeader - create new fileHeader object with random Id
func RandomHeader() *FileHeader { func RandomHeader() *FileHeader {
var h FileHeader var h FileHeader
h.Version = HEADER_CURRENT_VERSION h.Version = CurrentVersion
h.Id = RandBytes(HEADER_ID_LEN) h.Id = cryptocore.RandBytes(HEADER_ID_LEN)
return &h return &h
} }

View File

@ -1,11 +1,11 @@
package cryptfs package contentenc
// intraBlock identifies a part of a file block // intraBlock identifies a part of a file block
type intraBlock struct { type intraBlock struct {
BlockNo uint64 // Block number in file BlockNo uint64 // Block number in file
Skip uint64 // Offset into block plaintext Skip uint64 // Offset into block plaintext
Length uint64 // Length of data from this block Length uint64 // Length of data from this block
fs *CryptFS fs *ContentEnc
} }
// isPartial - is the block partial? This means we have to do read-modify-write. // isPartial - is the block partial? This means we have to do read-modify-write.

View File

@ -1,29 +1,33 @@
package cryptfs package contentenc
// CryptFS methods that translate offsets between ciphertext and plaintext import (
"github.com/rfjakob/gocryptfs/internal/toggledlog"
)
// Contentenc methods that translate offsets between ciphertext and plaintext
// get the block number at plain-text offset // get the block number at plain-text offset
func (be *CryptFS) PlainOffToBlockNo(plainOffset uint64) uint64 { func (be *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 {
return plainOffset / be.plainBS return plainOffset / be.plainBS
} }
// get the block number at ciphter-text offset // get the block number at ciphter-text offset
func (be *CryptFS) CipherOffToBlockNo(cipherOffset uint64) uint64 { func (be *ContentEnc) CipherOffToBlockNo(cipherOffset uint64) uint64 {
return (cipherOffset - HEADER_LEN) / be.cipherBS return (cipherOffset - HEADER_LEN) / be.cipherBS
} }
// get ciphertext offset of block "blockNo" // get ciphertext offset of block "blockNo"
func (be *CryptFS) BlockNoToCipherOff(blockNo uint64) uint64 { func (be *ContentEnc) BlockNoToCipherOff(blockNo uint64) uint64 {
return HEADER_LEN + blockNo*be.cipherBS return HEADER_LEN + blockNo*be.cipherBS
} }
// get plaintext offset of block "blockNo" // get plaintext offset of block "blockNo"
func (be *CryptFS) BlockNoToPlainOff(blockNo uint64) uint64 { func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 {
return blockNo * be.plainBS return blockNo * be.plainBS
} }
// PlainSize - calculate plaintext size from ciphertext size // PlainSize - calculate plaintext size from ciphertext size
func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 { func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
// Zero sized files stay zero-sized // Zero sized files stay zero-sized
if cipherSize == 0 { if cipherSize == 0 {
@ -31,12 +35,12 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
} }
if cipherSize == HEADER_LEN { if cipherSize == HEADER_LEN {
Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize) toggledlog.Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
return 0 return 0
} }
if cipherSize < HEADER_LEN { if cipherSize < HEADER_LEN {
Warn.Printf("cipherSize %d < header size: corrupt file\n", cipherSize) toggledlog.Warn.Printf("cipherSize %d < header size: corrupt file\n", cipherSize)
return 0 return 0
} }
@ -50,7 +54,7 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
} }
// CipherSize - calculate ciphertext size from plaintext size // CipherSize - calculate ciphertext size from plaintext size
func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 { func (be *ContentEnc) PlainSizeToCipherSize(plainSize uint64) uint64 {
// Block number at last byte // Block number at last byte
blockNo := be.PlainOffToBlockNo(plainSize - 1) blockNo := be.PlainOffToBlockNo(plainSize - 1)
@ -62,7 +66,7 @@ func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 {
} }
// Split a plaintext byte range into (possibly partial) blocks // Split a plaintext byte range into (possibly partial) blocks
func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock { func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlock {
var blocks []intraBlock var blocks []intraBlock
var nextBlock intraBlock var nextBlock intraBlock
nextBlock.fs = be nextBlock.fs = be
@ -81,6 +85,10 @@ func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock
return blocks return blocks
} }
func (be *ContentEnc) BlockOverhead() uint64 {
return be.cipherBS - be.plainBS
}
func MinUint64(x uint64, y uint64) uint64 { func MinUint64(x uint64, y uint64) uint64 {
if x < y { if x < y {
return x return x

View File

@ -0,0 +1,56 @@
package cryptocore
import (
"crypto/cipher"
"crypto/aes"
"fmt"
)
const (
KeyLen = 32 // AES-256
AuthTagLen = 16
)
type CryptoCore struct {
BlockCipher cipher.Block
Gcm cipher.AEAD
GcmIVGen *nonceGenerator
IVLen int
}
func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore {
if len(key) != KeyLen {
panic(fmt.Sprintf("Unsupported key length %d", len(key)))
}
// We want the IV size in bytes
IVLen := 96 / 8
if GCMIV128 {
IVLen = 128 / 8
}
// We always use built-in Go crypto for blockCipher because it is not
// performance-critical.
blockCipher, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
var gcm cipher.AEAD
if useOpenssl {
gcm = opensslGCM{key}
} else {
gcm, err = goGCMWrapper(blockCipher, IVLen)
if err != nil {
panic(err)
}
}
return &CryptoCore{
BlockCipher: blockCipher,
Gcm: gcm,
GcmIVGen: &nonceGenerator{nonceLen: IVLen},
IVLen: IVLen,
}
}

View File

@ -1,7 +1,7 @@
// +build !go1.5 // +build !go1.5
// = go 1.4 or lower // = go 1.4 or lower
package cryptfs package cryptocore
import ( import (
"crypto/cipher" "crypto/cipher"

View File

@ -1,7 +1,7 @@
// +build go1.5 // +build go1.5
// = go 1.5 or higher // = go 1.5 or higher
package cryptfs package cryptocore
import ( import (
"crypto/cipher" "crypto/cipher"

View File

@ -1,4 +1,4 @@
package cryptfs package cryptocore
import ( import (
"bytes" "bytes"
@ -6,6 +6,8 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// Get "n" random bytes from /dev/urandom or panic // Get "n" random bytes from /dev/urandom or panic
@ -29,10 +31,10 @@ type nonceGenerator struct {
nonceLen int // bytes nonceLen int // bytes
} }
// Get a random 96 bit nonce // Get a random "nonceLen"-byte nonce
func (n *nonceGenerator) Get() []byte { func (n *nonceGenerator) Get() []byte {
nonce := RandBytes(n.nonceLen) nonce := RandBytes(n.nonceLen)
Debug.Printf("nonceGenerator.Get(): %s\n", hex.EncodeToString(nonce)) toggledlog.Debug.Printf("nonceGenerator.Get(): %s\n", hex.EncodeToString(nonce))
if bytes.Equal(nonce, n.lastNonce) { if bytes.Equal(nonce, n.lastNonce) {
m := fmt.Sprintf("Got the same nonce twice: %s. This should never happen!", hex.EncodeToString(nonce)) m := fmt.Sprintf("Got the same nonce twice: %s. This should never happen!", hex.EncodeToString(nonce))
panic(m) panic(m)

View File

@ -1,4 +1,4 @@
package cryptfs package cryptocore
// Implements cipher.AEAD with OpenSSL backend // Implements cipher.AEAD with OpenSSL backend
@ -13,7 +13,7 @@ type opensslGCM struct {
} }
func (be opensslGCM) Overhead() int { func (be opensslGCM) Overhead() int {
return AUTH_TAG_LEN return AuthTagLen
} }
func (be opensslGCM) NonceSize() int { func (be opensslGCM) NonceSize() int {
@ -28,11 +28,11 @@ func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte {
// Preallocate output buffer // Preallocate output buffer
var cipherBuf bytes.Buffer var cipherBuf bytes.Buffer
cipherBuf.Grow(len(dst) + len(plaintext) + AUTH_TAG_LEN) cipherBuf.Grow(len(dst) + len(plaintext) + AuthTagLen)
// Output will be appended to dst // Output will be appended to dst
cipherBuf.Write(dst) cipherBuf.Write(dst)
ectx, err := openssl.NewGCMEncryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce) ectx, err := openssl.NewGCMEncryptionCipherCtx(KeyLen*8, nil, be.key, nonce)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -69,11 +69,11 @@ func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte {
func (be opensslGCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { func (be opensslGCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
l := len(ciphertext) l := len(ciphertext)
tag := ciphertext[l-AUTH_TAG_LEN : l] tag := ciphertext[l-AuthTagLen : l]
ciphertext = ciphertext[0 : l-AUTH_TAG_LEN] ciphertext = ciphertext[0 : l-AuthTagLen]
plainBuf := bytes.NewBuffer(dst) plainBuf := bytes.NewBuffer(dst)
dctx, err := openssl.NewGCMDecryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce) dctx, err := openssl.NewGCMDecryptionCipherCtx(KeyLen*8, nil, be.key, nonce)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,4 +1,4 @@
package cryptfs package cryptocore
// Benchmark go built-int GCM against spacemonkey openssl bindings // Benchmark go built-int GCM against spacemonkey openssl bindings
// //

View File

@ -0,0 +1,16 @@
package nametransform
import "github.com/rfjakob/gocryptfs/internal/cryptocore"
type NameTransform struct {
cryptoCore *cryptocore.CryptoCore
useEME bool
DirIVCache dirIVCache
}
func New(c *cryptocore.CryptoCore, useEME bool) *NameTransform {
return &NameTransform{
cryptoCore: c,
useEME: useEME,
}
}

View File

@ -0,0 +1,63 @@
package nametransform
// Filename encryption / decryption functions
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"github.com/rfjakob/eme"
)
// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
// The used encryption is either CBC or EME, depending on "useEME".
//
// This function is exported because it allows for a very efficient readdir
// implementation (read IV once, decrypt all names using this function).
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
bin, err := base64.URLEncoding.DecodeString(cipherName)
if err != nil {
return "", err
}
if len(bin)%aes.BlockSize != 0 {
return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
}
if n.useEME {
bin = eme.Transform(n.cryptoCore.BlockCipher, iv, bin, eme.DirectionDecrypt)
} else {
cbc := cipher.NewCBCDecrypter(n.cryptoCore.BlockCipher, iv)
cbc.CryptBlocks(bin, bin)
}
bin, err = unPad16(bin)
if err != nil {
return "", err
}
plain := string(bin)
return plain, err
}
// encryptName - encrypt "plainName", return base64-encoded "cipherName64"
// The used encryption is either CBC or EME, depending on "useEME".
func (n *NameTransform) encryptName(plainName string, iv []byte) (cipherName64 string) {
bin := []byte(plainName)
bin = pad16(bin)
if n.useEME {
bin = eme.Transform(n.cryptoCore.BlockCipher, iv, bin, eme.DirectionEncrypt)
} else {
cbc := cipher.NewCBCEncrypter(n.cryptoCore.BlockCipher, iv)
cbc.CryptBlocks(bin, bin)
}
cipherName64 = base64.URLEncoding.EncodeToString(bin)
return cipherName64
}

View File

@ -1,4 +1,4 @@
package cryptfs package nametransform
import ( import (
"fmt" "fmt"
@ -7,6 +7,17 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
)
const (
// identical to AES block size
dirIVLen = 16
// dirIV is stored in this file. Exported because we have to ignore this
// name in directory listing.
DirIVFilename = "gocryptfs.diriv"
) )
// A simple one-entry DirIV cache // A simple one-entry DirIV cache
@ -50,23 +61,23 @@ func (c *dirIVCache) Clear() {
} }
// readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) // readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
func (be *CryptFS) ReadDirIV(dir string) (iv []byte, readErr error) { func (be *NameTransform) ReadDirIV(dir string) (iv []byte, readErr error) {
ivfile := filepath.Join(dir, DIRIV_FILENAME) ivfile := filepath.Join(dir, DirIVFilename)
Debug.Printf("ReadDirIV: reading %s\n", ivfile) toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile)
iv, readErr = ioutil.ReadFile(ivfile) iv, readErr = ioutil.ReadFile(ivfile)
if readErr != nil { if readErr != nil {
// The directory may have been concurrently deleted or moved. Failure to // The directory may have been concurrently deleted or moved. Failure to
// read the diriv is not an error in that case. // read the diriv is not an error in that case.
_, statErr := os.Stat(dir) _, statErr := os.Stat(dir)
if os.IsNotExist(statErr) { if os.IsNotExist(statErr) {
Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir) toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir)
} else { } else {
// This should not happen // This should not happen
Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr) toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr)
} }
return nil, readErr return nil, readErr
} }
if len(iv) != DIRIV_LEN { if len(iv) != dirIVLen {
return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv))
} }
return iv, nil return iv, nil
@ -76,14 +87,14 @@ func (be *CryptFS) ReadDirIV(dir string) (iv []byte, readErr error) {
// This function is exported because it is used from pathfs_frontend, main, // This function is exported because it is used from pathfs_frontend, main,
// and also the automated tests. // and also the automated tests.
func WriteDirIV(dir string) error { func WriteDirIV(dir string) error {
iv := RandBytes(DIRIV_LEN) iv := cryptocore.RandBytes(dirIVLen)
file := filepath.Join(dir, DIRIV_FILENAME) file := filepath.Join(dir, DirIVFilename)
// 0444 permissions: the file is not secret but should not be written to // 0444 permissions: the file is not secret but should not be written to
return ioutil.WriteFile(file, iv, 0444) return ioutil.WriteFile(file, iv, 0444)
} }
// EncryptPathDirIV - encrypt path using EME with DirIV // EncryptPathDirIV - encrypt path using EME with DirIV
func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool) (cipherPath string, err error) { func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {
// Empty string means root directory // Empty string means root directory
if plainPath == "" { if plainPath == "" {
return plainPath, nil return plainPath, nil
@ -94,7 +105,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
if found { if found {
//fmt.Print("h") //fmt.Print("h")
baseName := filepath.Base(plainPath) baseName := filepath.Base(plainPath)
cBaseName := be.encryptName(baseName, iv, eme) cBaseName := be.encryptName(baseName, iv)
cipherPath = cParentDir + "/" + cBaseName cipherPath = cParentDir + "/" + cBaseName
return cipherPath, nil return cipherPath, nil
} }
@ -107,7 +118,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
if err != nil { if err != nil {
return "", err return "", err
} }
encryptedName := be.encryptName(plainName, iv, eme) encryptedName := be.encryptName(plainName, iv)
encryptedNames = append(encryptedNames, encryptedName) encryptedNames = append(encryptedNames, encryptedName)
wd = filepath.Join(wd, encryptedName) wd = filepath.Join(wd, encryptedName)
} }
@ -119,17 +130,17 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
} }
// DecryptPathDirIV - decrypt path using EME with DirIV // DecryptPathDirIV - decrypt path using EME with DirIV
func (be *CryptFS) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) { func (be *NameTransform) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) {
var wd = rootDir var wd = rootDir
var plainNames []string var plainNames []string
encryptedNames := strings.Split(encryptedPath, "/") encryptedNames := strings.Split(encryptedPath, "/")
Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames) toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames)
for _, encryptedName := range encryptedNames { for _, encryptedName := range encryptedNames {
iv, err := be.ReadDirIV(wd) iv, err := be.ReadDirIV(wd)
if err != nil { if err != nil {
return "", err return "", err
} }
plainName, err := be.decryptName(encryptedName, iv, eme) plainName, err := be.DecryptName(encryptedName, iv)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -1,4 +1,4 @@
package cryptfs package nametransform
import ( import (
"strings" "strings"
@ -12,7 +12,7 @@ const (
// DecryptPathNoIV - decrypt path using CBC without any IV. // DecryptPathNoIV - decrypt path using CBC without any IV.
// This function is deprecated by the the more secure DirIV variant and only retained // This function is deprecated by the the more secure DirIV variant and only retained
// for compatability with old filesystems. // for compatability with old filesystems.
func (be *CryptFS) DecryptPathNoIV(cipherPath string) (plainPath string, err error) { func (be *NameTransform) DecryptPathNoIV(cipherPath string) (plainPath string, err error) {
plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt) plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt)
return plainPath, err return plainPath, err
} }
@ -20,14 +20,14 @@ func (be *CryptFS) DecryptPathNoIV(cipherPath string) (plainPath string, err err
// EncryptPathNoIV - decrypt path using CBC without any IV. // EncryptPathNoIV - decrypt path using CBC without any IV.
// This function is deprecated by the the more secure DirIV variant and only retained // This function is deprecated by the the more secure DirIV variant and only retained
// for compatability with old filesystems. // for compatability with old filesystems.
func (be *CryptFS) EncryptPathNoIV(plainPath string) (cipherPath string) { func (be *NameTransform) EncryptPathNoIV(plainPath string) (cipherPath string) {
cipherPath, _ = be.translatePathNoIV(plainPath, OpEncrypt) cipherPath, _ = be.translatePathNoIV(plainPath, OpEncrypt)
return cipherPath return cipherPath
} }
// translatePathZeroIV - encrypt or decrypt path using CBC with an all-zero IV. // translatePathZeroIV - encrypt or decrypt path using CBC with an all-zero IV.
// Just splits the string on "/" and hands the parts to encryptName() / decryptName() // Just splits the string on "/" and hands the parts to encryptName() / decryptName()
func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) { func (be *NameTransform) translatePathNoIV(path string, op int) (string, error) {
var err error var err error
// Empty string means root directory // Empty string means root directory
@ -35,7 +35,7 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
return path, err return path, err
} }
zeroIV := make([]byte, DIRIV_LEN) zeroIV := make([]byte, dirIVLen)
// Run operation on each path component // Run operation on each path component
var translatedParts []string var translatedParts []string
@ -49,9 +49,9 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
} }
var newPart string var newPart string
if op == OpEncrypt { if op == OpEncrypt {
newPart = be.encryptName(part, zeroIV, false) newPart = be.encryptName(part, zeroIV)
} else { } else {
newPart, err = be.decryptName(part, zeroIV, false) newPart, err = be.DecryptName(part, zeroIV)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -1,4 +1,4 @@
package cryptfs package nametransform
import ( import (
"bytes" "bytes"

View File

@ -0,0 +1,60 @@
package nametransform
import (
"fmt"
"crypto/aes"
"errors"
)
// pad16 - pad data to AES block size (=16 byte) using standard PKCS#7 padding
// https://tools.ietf.org/html/rfc5652#section-6.3
func pad16(orig []byte) (padded []byte) {
oldLen := len(orig)
if oldLen == 0 {
panic("Padding zero-length string makes no sense")
}
padLen := aes.BlockSize - oldLen%aes.BlockSize
if padLen == 0 {
padLen = aes.BlockSize
}
newLen := oldLen + padLen
padded = make([]byte, newLen)
copy(padded, orig)
padByte := byte(padLen)
for i := oldLen; i < newLen; i++ {
padded[i] = padByte
}
return padded
}
// unPad16 - remove padding
func unPad16(padded []byte) ([]byte, error) {
oldLen := len(padded)
if oldLen%aes.BlockSize != 0 {
return nil, errors.New("Unaligned size")
}
// The last byte is always a padding byte
padByte := padded[oldLen-1]
// The padding byte's value is the padding length
padLen := int(padByte)
// Padding must be at least 1 byte
if padLen <= 0 {
return nil, errors.New("Padding cannot be zero-length")
}
// Larger paddings make no sense
if padLen > aes.BlockSize {
return nil, fmt.Errorf("Padding too long, padLen = %d > 16", padLen)
}
// All padding bytes must be identical
for i := oldLen - padLen; i < oldLen; i++ {
if padded[i] != padByte {
return nil, fmt.Errorf("Padding byte at i=%d is invalid", i)
}
}
newLen := oldLen - padLen
// Padding an empty string makes no sense
if newLen == 0 {
return nil, errors.New("Unpadded length is zero")
}
return padded[0:newLen], nil
}

View File

@ -1,4 +1,4 @@
package cryptfs package toggledlog
import ( import (
"encoding/json" "encoding/json"
@ -7,6 +7,10 @@ import (
"os" "os"
) )
const (
ProgramName = "gocryptfs"
)
func JSONDump(obj interface{}) string { func JSONDump(obj interface{}) string {
b, err := json.MarshalIndent(obj, "", "\t") b, err := json.MarshalIndent(obj, "", "\t")
if err != nil { if err != nil {

View File

@ -1,7 +1,7 @@
// +build !go1.5 // +build !go1.5
// = go 1.4 or lower // = go 1.4 or lower
package cryptfs package toggledlog
import ( import (
"log/syslog" "log/syslog"

View File

@ -1,14 +1,14 @@
// +build go1.5 // +build go1.5
// = go 1.5 or higher // = go 1.5 or higher
package cryptfs package toggledlog
import ( import (
"log/syslog" "log/syslog"
) )
func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) {
w, err := syslog.New(p, PROGRAM_NAME) w, err := syslog.New(p, ProgramName)
if err != nil { if err != nil {
Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err) Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err)
} else { } else {

106
main.go
View File

@ -16,12 +16,16 @@ import (
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/pathfs_frontend"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/rfjakob/gocryptfs/pathfs_frontend"
"github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
) )
const ( const (
@ -56,9 +60,9 @@ func initDir(args *argContainer) {
} }
// Create gocryptfs.conf // Create gocryptfs.conf
cryptfs.Info.Printf("Choose a password for protecting your files.") toggledlog.Info.Printf("Choose a password for protecting your files.")
password := readPasswordTwice(args.extpass) password := readPasswordTwice(args.extpass)
err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn) err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(ERREXIT_INIT) os.Exit(ERREXIT_INIT)
@ -66,30 +70,30 @@ func initDir(args *argContainer) {
if args.diriv && !args.plaintextnames { if args.diriv && !args.plaintextnames {
// Create gocryptfs.diriv in the root dir // Create gocryptfs.diriv in the root dir
err = cryptfs.WriteDirIV(args.cipherdir) err = nametransform.WriteDirIV(args.cipherdir)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(ERREXIT_INIT) os.Exit(ERREXIT_INIT)
} }
} }
cryptfs.Info.Printf(colorGreen + "The filesystem has been created successfully." + colorReset) toggledlog.Info.Printf(colorGreen + "The filesystem has been created successfully." + colorReset)
cryptfs.Info.Printf(colorGrey+"You can now mount it using: %s %s MOUNTPOINT"+colorReset, toggledlog.Info.Printf(colorGrey+"You can now mount it using: %s %s MOUNTPOINT"+colorReset,
cryptfs.PROGRAM_NAME, args.cipherdir) toggledlog.ProgramName, args.cipherdir)
os.Exit(0) os.Exit(0)
} }
func usageText() { func usageText() {
printVersion() printVersion()
fmt.Printf("\n") fmt.Printf("\n")
fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", cryptfs.PROGRAM_NAME) fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", toggledlog.ProgramName)
fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", cryptfs.PROGRAM_NAME) fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", toggledlog.ProgramName)
fmt.Printf("\nOptions:\n") fmt.Printf("\nOptions:\n")
flagSet.PrintDefaults() flagSet.PrintDefaults()
} }
// loadConfig - load the config file "filename", prompting the user for the password // loadConfig - load the config file "filename", prompting the user for the password
func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFile) { func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.ConfFile) {
// Check if the file exists at all before prompting for a password // Check if the file exists at all before prompting for a password
_, err := os.Stat(args.config) _, err := os.Stat(args.config)
if err != nil { if err != nil {
@ -100,16 +104,16 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFil
fmt.Printf("Password: ") fmt.Printf("Password: ")
} }
pw := readPassword(args.extpass) pw := readPassword(args.extpass)
cryptfs.Info.Printf("Decrypting master key... ") toggledlog.Info.Printf("Decrypting master key... ")
cryptfs.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password toggledlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password
masterkey, confFile, err = cryptfs.LoadConfFile(args.config, pw) masterkey, confFile, err = configfile.LoadConfFile(args.config, pw)
cryptfs.Warn.Enabled = true toggledlog.Warn.Enabled = true
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println(colorRed + "Wrong password." + colorReset) fmt.Println(colorRed + "Wrong password." + colorReset)
os.Exit(ERREXIT_LOADCONF) os.Exit(ERREXIT_LOADCONF)
} }
cryptfs.Info.Printf("done.") toggledlog.Info.Printf("done.")
return masterkey, confFile return masterkey, confFile
} }
@ -125,14 +129,14 @@ func changePassword(args *argContainer) {
fmt.Println(err) fmt.Println(err)
os.Exit(ERREXIT_INIT) os.Exit(ERREXIT_INIT)
} }
cryptfs.Info.Printf("Password changed.") toggledlog.Info.Printf("Password changed.")
os.Exit(0) os.Exit(0)
} }
// printVersion - print a version string like // printVersion - print a version string like
// "gocryptfs v0.3.1-31-g6736212-dirty; on-disk format 2" // "gocryptfs v0.3.1-31-g6736212-dirty; on-disk format 2"
func printVersion() { func printVersion() {
fmt.Printf("%s %s; on-disk format %d\n", cryptfs.PROGRAM_NAME, GitVersion, cryptfs.HEADER_CURRENT_VERSION) fmt.Printf("%s %s; on-disk format %d\n", toggledlog.ProgramName, GitVersion, contentenc.CurrentVersion)
} }
func main() { func main() {
@ -142,7 +146,7 @@ func main() {
setupColors() setupColors()
// Parse command line arguments // Parse command line arguments
flagSet = flag.NewFlagSet(cryptfs.PROGRAM_NAME, flag.ExitOnError) flagSet = flag.NewFlagSet(toggledlog.ProgramName, flag.ExitOnError)
flagSet.Usage = usageText flagSet.Usage = usageText
flagSet.BoolVar(&args.debug, "d", false, "") flagSet.BoolVar(&args.debug, "d", false, "")
flagSet.BoolVar(&args.debug, "debug", false, "Enable debug output") flagSet.BoolVar(&args.debug, "debug", false, "Enable debug output")
@ -168,7 +172,7 @@ func main() {
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt") 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 "+ flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
"successful mount - used internally for daemonization") "successful mount - used internally for daemonization")
flagSet.IntVar(&args.scryptn, "scryptn", cryptfs.SCRYPT_DEFAULT_LOGN, "scrypt cost parameter logN. "+ flagSet.IntVar(&args.scryptn, "scryptn", configfile.ScryptDefaultLogN, "scrypt cost parameter logN. "+
"Setting this to a lower value speeds up mounting but makes the password susceptible to brute-force attacks") "Setting this to a lower value speeds up mounting but makes the password susceptible to brute-force attacks")
flagSet.Parse(os.Args[1:]) flagSet.Parse(os.Args[1:])
@ -182,12 +186,12 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if args.debug { if args.debug {
cryptfs.Debug.Enabled = true toggledlog.Debug.Enabled = true
cryptfs.Debug.Printf("Debug output enabled") toggledlog.Debug.Printf("Debug output enabled")
} }
if args.wpanic { if args.wpanic {
cryptfs.Warn.PanicAfter = true toggledlog.Warn.PanicAfter = true
cryptfs.Debug.Printf("Panicing on warnings") toggledlog.Debug.Printf("Panicing on warnings")
} }
// Every operation below requires CIPHERDIR. Check that we have it. // Every operation below requires CIPHERDIR. Check that we have it.
if flagSet.NArg() >= 1 { if flagSet.NArg() >= 1 {
@ -203,7 +207,7 @@ func main() {
} }
// "-q" // "-q"
if args.quiet { if args.quiet {
cryptfs.Info.Enabled = false toggledlog.Info.Enabled = false
} }
// "-config" // "-config"
if args.config != "" { if args.config != "" {
@ -211,13 +215,13 @@ func main() {
if err != nil { if err != nil {
fmt.Printf(colorRed+"Invalid \"-config\" setting: %v\n"+colorReset, err) fmt.Printf(colorRed+"Invalid \"-config\" setting: %v\n"+colorReset, err)
} }
cryptfs.Info.Printf("Using config file at custom location %s", args.config) toggledlog.Info.Printf("Using config file at custom location %s", args.config)
} else { } else {
args.config = filepath.Join(args.cipherdir, cryptfs.ConfDefaultName) args.config = filepath.Join(args.cipherdir, configfile.ConfDefaultName)
} }
// "-cpuprofile" // "-cpuprofile"
if args.cpuprofile != "" { if args.cpuprofile != "" {
cryptfs.Info.Printf("Writing CPU profile to %s", args.cpuprofile) toggledlog.Info.Printf("Writing CPU profile to %s", args.cpuprofile)
f, err := os.Create(args.cpuprofile) f, err := os.Create(args.cpuprofile)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -228,7 +232,7 @@ func main() {
} }
// "-memprofile" // "-memprofile"
if args.memprofile != "" { if args.memprofile != "" {
cryptfs.Info.Printf("Writing mem profile to %s", args.memprofile) toggledlog.Info.Printf("Writing mem profile to %s", args.memprofile)
f, err := os.Create(args.memprofile) f, err := os.Create(args.memprofile)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -245,13 +249,13 @@ func main() {
} }
// "-openssl" // "-openssl"
if args.openssl == false { if args.openssl == false {
cryptfs.Info.Printf("Openssl disabled") toggledlog.Info.Printf("Openssl disabled")
} }
// Operation flags: init, passwd or mount // Operation flags: init, passwd or mount
// "-init" // "-init"
if args.init { if args.init {
if flagSet.NArg() > 1 { if flagSet.NArg() > 1 {
fmt.Printf("Usage: %s -init [OPTIONS] CIPHERDIR\n", cryptfs.PROGRAM_NAME) fmt.Printf("Usage: %s -init [OPTIONS] CIPHERDIR\n", toggledlog.ProgramName)
os.Exit(ERREXIT_USAGE) os.Exit(ERREXIT_USAGE)
} }
initDir(&args) // does not return initDir(&args) // does not return
@ -259,7 +263,7 @@ func main() {
// "-passwd" // "-passwd"
if args.passwd { if args.passwd {
if flagSet.NArg() > 1 { if flagSet.NArg() > 1 {
fmt.Printf("Usage: %s -passwd [OPTIONS] CIPHERDIR\n", cryptfs.PROGRAM_NAME) fmt.Printf("Usage: %s -passwd [OPTIONS] CIPHERDIR\n", toggledlog.ProgramName)
os.Exit(ERREXIT_USAGE) os.Exit(ERREXIT_USAGE)
} }
changePassword(&args) // does not return changePassword(&args) // does not return
@ -282,34 +286,34 @@ func main() {
} }
// Get master key // Get master key
var masterkey []byte var masterkey []byte
var confFile *cryptfs.ConfFile var confFile *configfile.ConfFile
if args.masterkey != "" { if args.masterkey != "" {
// "-masterkey" // "-masterkey"
cryptfs.Info.Printf("Using explicit master key.") toggledlog.Info.Printf("Using explicit master key.")
masterkey = parseMasterKey(args.masterkey) masterkey = parseMasterKey(args.masterkey)
cryptfs.Info.Printf("THE MASTER KEY IS VISIBLE VIA \"ps -auxwww\", ONLY USE THIS MODE FOR EMERGENCIES.") toggledlog.Info.Printf("THE MASTER KEY IS VISIBLE VIA \"ps -auxwww\", ONLY USE THIS MODE FOR EMERGENCIES.")
} else if args.zerokey { } else if args.zerokey {
// "-zerokey" // "-zerokey"
cryptfs.Info.Printf("Using all-zero dummy master key.") toggledlog.Info.Printf("Using all-zero dummy master key.")
cryptfs.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.") toggledlog.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.")
masterkey = make([]byte, cryptfs.KEY_LEN) masterkey = make([]byte, cryptocore.KeyLen)
} else { } else {
// Load master key from config file // Load master key from config file
masterkey, confFile = loadConfig(&args) masterkey, confFile = loadConfig(&args)
printMasterKey(masterkey) printMasterKey(masterkey)
} }
// Initialize FUSE server // Initialize FUSE server
cryptfs.Debug.Printf("cli args: %v", args) toggledlog.Debug.Printf("cli args: %v", args)
srv := pathfsFrontend(masterkey, args, confFile) srv := pathfsFrontend(masterkey, args, confFile)
cryptfs.Info.Println(colorGreen + "Filesystem mounted and ready." + colorReset) toggledlog.Info.Println(colorGreen + "Filesystem mounted and ready." + colorReset)
// We are ready - send USR1 signal to our parent and switch to syslog // We are ready - send USR1 signal to our parent and switch to syslog
if args.notifypid > 0 { if args.notifypid > 0 {
sendUsr1(args.notifypid) sendUsr1(args.notifypid)
if !args.nosyslog { if !args.nosyslog {
cryptfs.Info.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_INFO) toggledlog.Info.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_INFO)
cryptfs.Debug.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_DEBUG) toggledlog.Debug.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_DEBUG)
cryptfs.Warn.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_WARNING) toggledlog.Warn.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_WARNING)
} }
} }
// Wait for SIGINT in the background and unmount ourselves if we get it. // Wait for SIGINT in the background and unmount ourselves if we get it.
@ -322,7 +326,7 @@ func main() {
// pathfsFrontend - initialize gocryptfs/pathfs_frontend // pathfsFrontend - initialize gocryptfs/pathfs_frontend
// Calls os.Exit on errors // Calls os.Exit on errors
func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) *fuse.Server { func pathfsFrontend(key []byte, args argContainer, confFile *configfile.ConfFile) *fuse.Server {
// Reconciliate CLI and config file arguments into a Args struct that is passed to the // Reconciliate CLI and config file arguments into a Args struct that is passed to the
// filesystem implementation // filesystem implementation
@ -338,10 +342,10 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) *
// confFile is nil when "-zerokey" or "-masterkey" was used // confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil { if confFile != nil {
// Settings from the config file override command line args // Settings from the config file override command line args
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
frontendArgs.DirIV = confFile.IsFeatureFlagSet(cryptfs.FlagDirIV) frontendArgs.DirIV = confFile.IsFeatureFlagSet(configfile.FlagDirIV)
frontendArgs.EMENames = confFile.IsFeatureFlagSet(cryptfs.FlagEMENames) frontendArgs.EMENames = confFile.IsFeatureFlagSet(configfile.FlagEMENames)
frontendArgs.GCMIV128 = confFile.IsFeatureFlagSet(cryptfs.FlagGCMIV128) frontendArgs.GCMIV128 = confFile.IsFeatureFlagSet(configfile.FlagGCMIV128)
} }
// EMENames implies DirIV, both on the command line and in the config file. // EMENames implies DirIV, both on the command line and in the config file.
if frontendArgs.EMENames { if frontendArgs.EMENames {
@ -353,7 +357,7 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) *
frontendArgs.EMENames = false frontendArgs.EMENames = false
} }
jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t") jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t")
cryptfs.Debug.Printf("frontendArgs: %s", string(jsonBytes)) toggledlog.Debug.Printf("frontendArgs: %s", string(jsonBytes))
finalFs := pathfs_frontend.NewFS(frontendArgs) finalFs := pathfs_frontend.NewFS(frontendArgs)
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
@ -398,7 +402,7 @@ func handleSigint(srv *fuse.Server, mountpoint string) {
err := srv.Unmount() err := srv.Unmount()
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
cryptfs.Info.Printf("Trying lazy unmount") toggledlog.Info.Printf("Trying lazy unmount")
cmd := exec.Command("fusermount", "-u", "-z", mountpoint) cmd := exec.Command("fusermount", "-u", "-z", mountpoint)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr

View File

@ -3,9 +3,11 @@ package main
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/rfjakob/gocryptfs/cryptfs"
"os" "os"
"strings" "strings"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// printMasterKey - remind the user that he should store the master key in // printMasterKey - remind the user that he should store the master key in
@ -25,7 +27,7 @@ func printMasterKey(key []byte) {
} }
} }
cryptfs.Info.Printf(` toggledlog.Info.Printf(`
Your master key is: Your master key is:
%s %s
@ -46,8 +48,8 @@ func parseMasterKey(masterkey string) []byte {
fmt.Printf("Could not parse master key: %v\n", err) fmt.Printf("Could not parse master key: %v\n", err)
os.Exit(1) os.Exit(1)
} }
if len(key) != cryptfs.KEY_LEN { if len(key) != cryptocore.KeyLen {
fmt.Printf("Master key has length %d but we require length %d\n", len(key), cryptfs.KEY_LEN) fmt.Printf("Master key has length %d but we require length %d\n", len(key), cryptocore.KeyLen)
os.Exit(1) os.Exit(1)
} }
return key return key

View File

@ -13,7 +13,9 @@ import (
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// File - based on loopbackFile in go-fuse/fuse/nodefs/files.go // File - based on loopbackFile in go-fuse/fuse/nodefs/files.go
@ -29,19 +31,19 @@ type file struct {
// Was the file opened O_WRONLY? // Was the file opened O_WRONLY?
writeOnly bool writeOnly bool
// Parent CryptFS // Content encryption helper
cfs *cryptfs.CryptFS contentEnc *contentenc.ContentEnc
// Inode number // Inode number
ino uint64 ino uint64
// File header // File header
header *cryptfs.FileHeader header *contentenc.FileHeader
forgotten bool forgotten bool
} }
func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File { func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) nodefs.File {
var st syscall.Stat_t var st syscall.Stat_t
syscall.Fstat(int(fd.Fd()), &st) syscall.Fstat(int(fd.Fd()), &st)
wlock.register(st.Ino) wlock.register(st.Ino)
@ -49,7 +51,7 @@ func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File {
return &file{ return &file{
fd: fd, fd: fd,
writeOnly: writeOnly, writeOnly: writeOnly,
cfs: cfs, contentEnc: contentEnc,
ino: st.Ino, ino: st.Ino,
} }
} }
@ -71,12 +73,12 @@ func (f *file) SetInode(n *nodefs.Inode) {
// //
// Returns io.EOF if the file is empty // Returns io.EOF if the file is empty
func (f *file) readHeader() error { func (f *file) readHeader() error {
buf := make([]byte, cryptfs.HEADER_LEN) buf := make([]byte, contentenc.HEADER_LEN)
_, err := f.fd.ReadAt(buf, 0) _, err := f.fd.ReadAt(buf, 0)
if err != nil { if err != nil {
return err return err
} }
h, err := cryptfs.ParseHeader(buf) h, err := contentenc.ParseHeader(buf)
if err != nil { if err != nil {
return err return err
} }
@ -87,13 +89,13 @@ func (f *file) readHeader() error {
// createHeader - create a new random header and write it to disk // createHeader - create a new random header and write it to disk
func (f *file) createHeader() error { func (f *file) createHeader() error {
h := cryptfs.RandomHeader() h := contentenc.RandomHeader()
buf := h.Pack() buf := h.Pack()
// Prevent partially written (=corrupt) header by preallocating the space beforehand // Prevent partially written (=corrupt) header by preallocating the space beforehand
err := prealloc(int(f.fd.Fd()), 0, cryptfs.HEADER_LEN) err := prealloc(int(f.fd.Fd()), 0, contentenc.HEADER_LEN)
if err != nil { if err != nil {
cryptfs.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.ino, err.Error()) toggledlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.ino, err.Error())
return err return err
} }
@ -133,29 +135,29 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
} }
// Read the backing ciphertext in one go // Read the backing ciphertext in one go
blocks := f.cfs.ExplodePlainRange(off, length) blocks := f.contentEnc.ExplodePlainRange(off, length)
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
skip := blocks[0].Skip skip := blocks[0].Skip
cryptfs.Debug.Printf("JointCiphertextRange(%d, %d) -> %d, %d, %d", off, length, alignedOffset, alignedLength, skip) toggledlog.Debug.Printf("JointCiphertextRange(%d, %d) -> %d, %d, %d", off, length, alignedOffset, alignedLength, skip)
ciphertext := make([]byte, int(alignedLength)) ciphertext := make([]byte, int(alignedLength))
n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset)) n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
cryptfs.Warn.Printf("read: ReadAt: %s", err.Error()) toggledlog.Warn.Printf("read: ReadAt: %s", err.Error())
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
// Truncate ciphertext buffer down to actually read bytes // Truncate ciphertext buffer down to actually read bytes
ciphertext = ciphertext[0:n] ciphertext = ciphertext[0:n]
firstBlockNo := blocks[0].BlockNo firstBlockNo := blocks[0].BlockNo
cryptfs.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n) toggledlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n)
// Decrypt it // Decrypt it
plaintext, err := f.cfs.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id) plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id)
if err != nil { if err != nil {
curruptBlockNo := firstBlockNo + f.cfs.PlainOffToBlockNo(uint64(len(plaintext))) curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
cipherOff := f.cfs.BlockNoToCipherOff(curruptBlockNo) cipherOff := f.contentEnc.BlockNoToCipherOff(curruptBlockNo)
plainOff := f.cfs.BlockNoToPlainOff(curruptBlockNo) plainOff := f.contentEnc.BlockNoToPlainOff(curruptBlockNo)
cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)", toggledlog.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)",
f.ino, curruptBlockNo, plainOff, cipherOff) f.ino, curruptBlockNo, plainOff, cipherOff)
return nil, fuse.EIO return nil, fuse.EIO
} }
@ -179,23 +181,23 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
f.fdLock.RLock() f.fdLock.RLock()
defer f.fdLock.RUnlock() defer f.fdLock.RUnlock()
cryptfs.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.ino, len(buf), off) toggledlog.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.ino, len(buf), off)
if f.writeOnly { if f.writeOnly {
cryptfs.Warn.Printf("ino%d: Tried to read from write-only file", f.ino) toggledlog.Warn.Printf("ino%d: Tried to read from write-only file", f.ino)
return nil, fuse.EBADF return nil, fuse.EBADF
} }
out, status := f.doRead(uint64(off), uint64(len(buf))) out, status := f.doRead(uint64(off), uint64(len(buf)))
if status == fuse.EIO { if status == fuse.EIO {
cryptfs.Warn.Printf("ino%d: Read failed with EIO, offset=%d, length=%d", f.ino, len(buf), off) toggledlog.Warn.Printf("ino%d: Read failed with EIO, offset=%d, length=%d", f.ino, len(buf), off)
} }
if status != fuse.OK { if status != fuse.OK {
return nil, status return nil, status
} }
cryptfs.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.ino, status, len(out)) toggledlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.ino, status, len(out))
return fuse.ReadResultData(out), status return fuse.ReadResultData(out), status
} }
@ -225,7 +227,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
var written uint32 var written uint32
status := fuse.OK status := fuse.OK
dataBuf := bytes.NewBuffer(data) dataBuf := bytes.NewBuffer(data)
blocks := f.cfs.ExplodePlainRange(uint64(off), uint64(len(data))) blocks := f.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
for _, b := range blocks { for _, b := range blocks {
blockData := dataBuf.Next(int(b.Length)) blockData := dataBuf.Next(int(b.Length))
@ -234,26 +236,26 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
if b.IsPartial() { if b.IsPartial() {
// Read // Read
o, _ := b.PlaintextRange() o, _ := b.PlaintextRange()
oldData, status := f.doRead(o, f.cfs.PlainBS()) oldData, status := f.doRead(o, f.contentEnc.PlainBS())
if status != fuse.OK { if status != fuse.OK {
cryptfs.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.ino, f.intFd(), status.String()) toggledlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.ino, f.intFd(), status.String())
return written, status return written, status
} }
// Modify // Modify
blockData = f.cfs.MergeBlocks(oldData, blockData, int(b.Skip)) blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
cryptfs.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData)) toggledlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData))
} }
// Encrypt // Encrypt
blockOffset, blockLen := b.CiphertextRange() blockOffset, blockLen := b.CiphertextRange()
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id) blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id)
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d", toggledlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
f.ino, uint64(len(blockData))-f.cfs.BlockOverhead(), b.BlockNo) f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
// Prevent partially written (=corrupt) blocks by preallocating the space beforehand // Prevent partially written (=corrupt) blocks by preallocating the space beforehand
err := prealloc(int(f.fd.Fd()), int64(blockOffset), int64(blockLen)) err := prealloc(int(f.fd.Fd()), int64(blockOffset), int64(blockLen))
if err != nil { if err != nil {
cryptfs.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.ino, f.intFd(), err.Error()) toggledlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.ino, f.intFd(), err.Error())
status = fuse.ToStatus(err) status = fuse.ToStatus(err)
break break
} }
@ -262,7 +264,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
_, err = f.fd.WriteAt(blockData, int64(blockOffset)) _, err = f.fd.WriteAt(blockData, int64(blockOffset))
if err != nil { if err != nil {
cryptfs.Warn.Printf("doWrite: Write failed: %s", err.Error()) toggledlog.Warn.Printf("doWrite: Write failed: %s", err.Error())
status = fuse.ToStatus(err) status = fuse.ToStatus(err)
break break
} }
@ -278,18 +280,18 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
wlock.lock(f.ino) wlock.lock(f.ino)
defer wlock.unlock(f.ino) defer wlock.unlock(f.ino)
cryptfs.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data)) toggledlog.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data))
fi, err := f.fd.Stat() fi, err := f.fd.Stat()
if err != nil { if err != nil {
cryptfs.Warn.Printf("Write: Fstat failed: %v", err) toggledlog.Warn.Printf("Write: Fstat failed: %v", err)
return 0, fuse.ToStatus(err) return 0, fuse.ToStatus(err)
} }
plainSize := f.cfs.CipherSizeToPlainSize(uint64(fi.Size())) plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
if f.createsHole(plainSize, off) { if f.createsHole(plainSize, off) {
status := f.zeroPad(plainSize) status := f.zeroPad(plainSize)
if status != fuse.OK { if status != fuse.OK {
cryptfs.Warn.Printf("zeroPad returned error %v", status) toggledlog.Warn.Printf("zeroPad returned error %v", status)
return 0, status return 0, status
} }
} }
@ -337,14 +339,14 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
defer wlock.unlock(f.ino) defer wlock.unlock(f.ino)
if f.forgotten { if f.forgotten {
cryptfs.Warn.Printf("ino%d fh%d: Truncate on forgotten file", f.ino, f.intFd()) toggledlog.Warn.Printf("ino%d fh%d: Truncate on forgotten file", f.ino, f.intFd())
} }
// Common case first: Truncate to zero // Common case first: Truncate to zero
if newSize == 0 { if newSize == 0 {
err := syscall.Ftruncate(int(f.fd.Fd()), 0) err := syscall.Ftruncate(int(f.fd.Fd()), 0)
if err != nil { if err != nil {
cryptfs.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err) toggledlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Truncate to zero kills the file header // Truncate to zero kills the file header
@ -356,14 +358,14 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
// the file // the file
fi, err := f.fd.Stat() fi, err := f.fd.Stat()
if err != nil { if err != nil {
cryptfs.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err) toggledlog.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
oldSize := f.cfs.CipherSizeToPlainSize(uint64(fi.Size())) oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
{ {
oldB := float32(oldSize) / float32(f.cfs.PlainBS()) oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
newB := float32(newSize) / float32(f.cfs.PlainBS()) newB := float32(newSize) / float32(f.contentEnc.PlainBS())
cryptfs.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize) toggledlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
} }
// File size stays the same - nothing to do // File size stays the same - nothing to do
@ -382,7 +384,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
} }
} }
blocks := f.cfs.ExplodePlainRange(oldSize, newSize-oldSize) blocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize)
for _, b := range blocks { for _, b := range blocks {
// First and last block may be partial // First and last block may be partial
if b.IsPartial() { if b.IsPartial() {
@ -396,7 +398,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
off, length := b.CiphertextRange() off, length := b.CiphertextRange()
err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length)) err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length))
if err != nil { if err != nil {
cryptfs.Warn.Printf("grow Ftruncate returned error: %v", err) toggledlog.Warn.Printf("grow Ftruncate returned error: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
} }
@ -404,23 +406,23 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
return fuse.OK return fuse.OK
} else { } else {
// File shrinks // File shrinks
blockNo := f.cfs.PlainOffToBlockNo(newSize) blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
cipherOff := f.cfs.BlockNoToCipherOff(blockNo) cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo)
plainOff := f.cfs.BlockNoToPlainOff(blockNo) plainOff := f.contentEnc.BlockNoToPlainOff(blockNo)
lastBlockLen := newSize - plainOff lastBlockLen := newSize - plainOff
var data []byte var data []byte
if lastBlockLen > 0 { if lastBlockLen > 0 {
var status fuse.Status var status fuse.Status
data, status = f.doRead(plainOff, lastBlockLen) data, status = f.doRead(plainOff, lastBlockLen)
if status != fuse.OK { if status != fuse.OK {
cryptfs.Warn.Printf("shrink doRead returned error: %v", err) toggledlog.Warn.Printf("shrink doRead returned error: %v", err)
return status return status
} }
} }
// Truncate down to last complete block // Truncate down to last complete block
err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff)) err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))
if err != nil { if err != nil {
cryptfs.Warn.Printf("shrink Ftruncate returned error: %v", err) toggledlog.Warn.Printf("shrink Ftruncate returned error: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Append partial block // Append partial block
@ -450,14 +452,14 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
f.fdLock.RLock() f.fdLock.RLock()
defer f.fdLock.RUnlock() defer f.fdLock.RUnlock()
cryptfs.Debug.Printf("file.GetAttr()") toggledlog.Debug.Printf("file.GetAttr()")
st := syscall.Stat_t{} st := syscall.Stat_t{}
err := syscall.Fstat(int(f.fd.Fd()), &st) err := syscall.Fstat(int(f.fd.Fd()), &st)
if err != nil { if err != nil {
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
a.FromStat(&st) a.FromStat(&st)
a.Size = f.cfs.CipherSizeToPlainSize(a.Size) a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size)
return fuse.OK return fuse.OK
} }
@ -468,7 +470,7 @@ var allocateWarned bool
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
// Only warn once // Only warn once
if !allocateWarned { if !allocateWarned {
cryptfs.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1") toggledlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1")
allocateWarned = true allocateWarned = true
} }
return fuse.ENOSYS return fuse.ENOSYS

View File

@ -4,13 +4,14 @@ package pathfs_frontend
import ( import (
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// Will a write to offset "off" create a file hole? // Will a write to offset "off" create a file hole?
func (f *file) createsHole(plainSize uint64, off int64) bool { func (f *file) createsHole(plainSize uint64, off int64) bool {
nextBlock := f.cfs.PlainOffToBlockNo(plainSize) nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize)
targetBlock := f.cfs.PlainOffToBlockNo(uint64(off)) targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(off))
if targetBlock > nextBlock { if targetBlock > nextBlock {
return true return true
} }
@ -19,10 +20,10 @@ func (f *file) createsHole(plainSize uint64, off int64) bool {
// Zero-pad the file of size plainSize to the next block boundary // Zero-pad the file of size plainSize to the next block boundary
func (f *file) zeroPad(plainSize uint64) fuse.Status { func (f *file) zeroPad(plainSize uint64) fuse.Status {
lastBlockLen := plainSize % f.cfs.PlainBS() lastBlockLen := plainSize % f.contentEnc.PlainBS()
missing := f.cfs.PlainBS() - lastBlockLen missing := f.contentEnc.PlainBS() - lastBlockLen
pad := make([]byte, missing) pad := make([]byte, missing)
cryptfs.Debug.Printf("zeroPad: Writing %d bytes\n", missing) toggledlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing)
_, status := f.doWrite(pad, int64(plainSize)) _, status := f.doWrite(pad, int64(plainSize))
return status return status
} }

View File

@ -13,25 +13,41 @@ import (
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/configfile"
) )
const plainBS = 4096
type FS struct { type FS struct {
*cryptfs.CryptFS
pathfs.FileSystem // loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go pathfs.FileSystem // loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go
args Args // Stores configuration arguments args Args // Stores configuration arguments
// dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified // dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified
// Readers must RLock() it to prevent them from seeing intermediate // Readers must RLock() it to prevent them from seeing intermediate
// states // states
dirIVLock sync.RWMutex dirIVLock sync.RWMutex
// Filename encryption helper
nameTransform *nametransform.NameTransform
// Content encryption helper
contentEnc *contentenc.ContentEnc
} }
// Encrypted FUSE overlay filesystem // Encrypted FUSE overlay filesystem
func NewFS(args Args) *FS { func NewFS(args Args) *FS {
cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, args.GCMIV128)
contentEnc := contentenc.New(cryptoCore, plainBS)
nameTransform := nametransform.New(cryptoCore, args.EMENames)
return &FS{ return &FS{
CryptFS: cryptfs.NewCryptFS(args.Masterkey, args.OpenSSL, args.PlaintextNames, args.GCMIV128),
FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir),
args: args, args: args,
nameTransform: nameTransform,
contentEnc: contentEnc,
} }
} }
@ -43,12 +59,12 @@ func (fs *FS) getBackingPath(relPath string) (string, error) {
return "", err return "", err
} }
cAbsPath := filepath.Join(fs.args.Cipherdir, cPath) cAbsPath := filepath.Join(fs.args.Cipherdir, cPath)
cryptfs.Debug.Printf("getBackingPath: %s + %s -> %s", fs.args.Cipherdir, relPath, cAbsPath) toggledlog.Debug.Printf("getBackingPath: %s + %s -> %s", fs.args.Cipherdir, relPath, cAbsPath)
return cAbsPath, nil return cAbsPath, nil
} }
func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
cryptfs.Debug.Printf("FS.GetAttr('%s')", name) toggledlog.Debug.Printf("FS.GetAttr('%s')", name)
if fs.isFiltered(name) { if fs.isFiltered(name) {
return nil, fuse.EPERM return nil, fuse.EPERM
} }
@ -58,11 +74,11 @@ func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Stat
} }
a, status := fs.FileSystem.GetAttr(cName, context) a, status := fs.FileSystem.GetAttr(cName, context)
if a == nil { if a == nil {
cryptfs.Debug.Printf("FS.GetAttr failed: %s", status.String()) toggledlog.Debug.Printf("FS.GetAttr failed: %s", status.String())
return a, status return a, status
} }
if a.IsRegular() { if a.IsRegular() {
a.Size = fs.CipherSizeToPlainSize(a.Size) a.Size = fs.contentEnc.CipherSizeToPlainSize(a.Size)
} else if a.IsSymlink() { } else if a.IsSymlink() {
target, _ := fs.Readlink(name, context) target, _ := fs.Readlink(name, context)
a.Size = uint64(len(target)) a.Size = uint64(len(target))
@ -71,7 +87,7 @@ func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Stat
} }
func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
cryptfs.Debug.Printf("OpenDir(%s)", dirName) toggledlog.Debug.Printf("OpenDir(%s)", dirName)
cDirName, err := fs.encryptPath(dirName) cDirName, err := fs.encryptPath(dirName)
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
@ -81,12 +97,12 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
if cipherEntries == nil { if cipherEntries == nil {
return nil, status return nil, status
} }
// Get DirIV (stays zero if DirIV if off) // Get DirIV (stays nil if DirIV if off)
cachedIV := make([]byte, cryptfs.DIRIV_LEN) var cachedIV []byte
if fs.args.DirIV { if fs.args.DirIV {
// Read the DirIV once and use it for all later name decryptions // Read the DirIV once and use it for all later name decryptions
cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName) cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName)
cachedIV, err = fs.CryptFS.ReadDirIV(cDirAbsPath) cachedIV, err = fs.nameTransform.ReadDirIV(cDirAbsPath)
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
@ -95,19 +111,19 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
var plain []fuse.DirEntry var plain []fuse.DirEntry
for i := range cipherEntries { for i := range cipherEntries {
cName := cipherEntries[i].Name cName := cipherEntries[i].Name
if dirName == "" && cName == cryptfs.ConfDefaultName { if dirName == "" && cName == configfile.ConfDefaultName {
// silently ignore "gocryptfs.conf" in the top level dir // silently ignore "gocryptfs.conf" in the top level dir
continue continue
} }
if fs.args.DirIV && cName == cryptfs.DIRIV_FILENAME { if fs.args.DirIV && cName == nametransform.DirIVFilename {
// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
continue continue
} }
var name string = cName var name string = cName
if !fs.args.PlaintextNames { if !fs.args.PlaintextNames {
name, err = fs.CryptFS.DecryptName(cName, cachedIV, fs.args.EMENames) name, err = fs.nameTransform.DecryptName(cName, cachedIV)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s", cName, cDirName, err) toggledlog.Warn.Printf("Invalid name \"%s\" in dir \"%s\": %s", cName, cDirName, err)
continue continue
} }
} }
@ -137,16 +153,16 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n
iflags, writeOnly := fs.mangleOpenFlags(flags) iflags, writeOnly := fs.mangleOpenFlags(flags)
cPath, err := fs.getBackingPath(path) cPath, err := fs.getBackingPath(path)
if err != nil { if err != nil {
cryptfs.Debug.Printf("Open: getBackingPath: %v", err) toggledlog.Debug.Printf("Open: getBackingPath: %v", err)
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
cryptfs.Debug.Printf("Open: %s", cPath) toggledlog.Debug.Printf("Open: %s", cPath)
f, err := os.OpenFile(cPath, iflags, 0666) f, err := os.OpenFile(cPath, iflags, 0666)
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
return NewFile(f, writeOnly, fs.CryptFS), fuse.OK return NewFile(f, writeOnly, fs.contentEnc), fuse.OK
} }
func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
@ -162,7 +178,7 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
} }
return NewFile(f, writeOnly, fs.CryptFS), fuse.OK return NewFile(f, writeOnly, fs.contentEnc), fuse.OK
} }
func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) { func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) {
@ -203,7 +219,7 @@ var truncateWarned bool
func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) { func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) {
// Only warn once // Only warn once
if !truncateWarned { if !truncateWarned {
cryptfs.Warn.Printf("truncate(2) is not supported, returning ENOSYS - use ftruncate(2)") toggledlog.Warn.Printf("truncate(2) is not supported, returning ENOSYS - use ftruncate(2)")
truncateWarned = true truncateWarned = true
} }
return fuse.ENOSYS return fuse.ENOSYS
@ -233,7 +249,7 @@ func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status f
if !fs.args.DirIV { if !fs.args.DirIV {
target, err := fs.decryptPath(cTarget) target, err := fs.decryptPath(cTarget)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Readlink: CBC decryption failed: %v", err) toggledlog.Warn.Printf("Readlink: CBC decryption failed: %v", err)
return "", fuse.EIO return "", fuse.EIO
} }
return target, fuse.OK return target, fuse.OK
@ -241,12 +257,12 @@ func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status f
// Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM) // Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM)
cBinTarget, err := base64.URLEncoding.DecodeString(cTarget) cBinTarget, err := base64.URLEncoding.DecodeString(cTarget)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Readlink: %v", err) toggledlog.Warn.Printf("Readlink: %v", err)
return "", fuse.EIO return "", fuse.EIO
} }
target, err := fs.CryptFS.DecryptBlock([]byte(cBinTarget), 0, nil) target, err := fs.contentEnc.DecryptBlock([]byte(cBinTarget), 0, nil)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Readlink: %v", err) toggledlog.Warn.Printf("Readlink: %v", err)
return "", fuse.EIO return "", fuse.EIO
} }
return string(target), fuse.OK return string(target), fuse.OK
@ -264,7 +280,7 @@ func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) {
} }
func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) { func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) {
cryptfs.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) toggledlog.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName)
if fs.isFiltered(linkName) { if fs.isFiltered(linkName) {
return fuse.EPERM return fuse.EPERM
} }
@ -276,18 +292,18 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
if !fs.args.DirIV { if !fs.args.DirIV {
cTarget, err := fs.encryptPath(target) cTarget, err := fs.encryptPath(target)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Symlink: BUG: we should not get an error here: %v", err) toggledlog.Warn.Printf("Symlink: BUG: we should not get an error here: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
err = os.Symlink(cTarget, cPath) err = os.Symlink(cTarget, cPath)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM) // Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM)
cBinTarget := fs.CryptFS.EncryptBlock([]byte(target), 0, nil) cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil)
cTarget := base64.URLEncoding.EncodeToString(cBinTarget) cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
err = os.Symlink(cTarget, cPath) err = os.Symlink(cTarget, cPath)
cryptfs.Debug.Printf("Symlink: os.Symlink(%s, %s) = %v", cTarget, cPath, err) toggledlog.Debug.Printf("Symlink: os.Symlink(%s, %s) = %v", cTarget, cPath, err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
@ -305,7 +321,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
} }
// The Rename may cause a directory to take the place of another directory. // The Rename may cause a directory to take the place of another directory.
// That directory may still be in the DirIV cache, clear it. // That directory may still be in the DirIV cache, clear it.
fs.CryptFS.DirIVCache.Clear() fs.nameTransform.DirIVCache.Clear()
err = os.Rename(cOldPath, cNewPath) err = os.Rename(cOldPath, cNewPath)
@ -313,7 +329,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
// If an empty directory is overwritten we will always get // If an empty directory is overwritten we will always get
// ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv. // ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv.
// Handle that case by removing the target directory and trying again. // Handle that case by removing the target directory and trying again.
cryptfs.Debug.Printf("Rename: Handling ENOTEMPTY") toggledlog.Debug.Printf("Rename: Handling ENOTEMPTY")
if fs.Rmdir(newPath, context) == fuse.OK { if fs.Rmdir(newPath, context) == fuse.OK {
err = os.Rename(cOldPath, cNewPath) err = os.Rename(cOldPath, cNewPath)
} }

View File

@ -9,7 +9,10 @@ import (
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse"
"github.com/rfjakob/gocryptfs/cryptfs"
"github.com/rfjakob/gocryptfs/internal/toggledlog"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform"
) )
func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) { func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
@ -29,7 +32,7 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu
mode = mode | 0300 mode = mode | 0300
// The new directory may take the place of an older one that is still in the cache // The new directory may take the place of an older one that is still in the cache
fs.CryptFS.DirIVCache.Clear() fs.nameTransform.DirIVCache.Clear()
// Create directory // Create directory
fs.dirIVLock.Lock() fs.dirIVLock.Lock()
defer fs.dirIVLock.Unlock() defer fs.dirIVLock.Unlock()
@ -38,13 +41,13 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Create gocryptfs.diriv inside // Create gocryptfs.diriv inside
err = cryptfs.WriteDirIV(encPath) err = nametransform.WriteDirIV(encPath)
if err != nil { if err != nil {
// This should not happen // This should not happen
cryptfs.Warn.Printf("Mkdir: WriteDirIV failed: %v", err) toggledlog.Warn.Printf("Mkdir: WriteDirIV failed: %v", err)
err2 := syscall.Rmdir(encPath) err2 := syscall.Rmdir(encPath)
if err2 != nil { if err2 != nil {
cryptfs.Warn.Printf("Mkdir: Rmdir rollback failed: %v", err2) toggledlog.Warn.Printf("Mkdir: Rmdir rollback failed: %v", err2)
} }
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
@ -53,7 +56,7 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu
if origMode != mode { if origMode != mode {
err = os.Chmod(encPath, os.FileMode(origMode)) err = os.Chmod(encPath, os.FileMode(origMode))
if err != nil { if err != nil {
cryptfs.Warn.Printf("Mkdir: Chmod failed: %v", err) toggledlog.Warn.Printf("Mkdir: Chmod failed: %v", err)
} }
} }
@ -74,17 +77,17 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
fd, err := os.Open(encPath) fd, err := os.Open(encPath)
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES { if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES {
// We need permission to read and modify the directory // We need permission to read and modify the directory
cryptfs.Debug.Printf("Rmdir: handling EACCESS") toggledlog.Debug.Printf("Rmdir: handling EACCESS")
fi, err2 := os.Stat(encPath) fi, err2 := os.Stat(encPath)
if err2 != nil { if err2 != nil {
cryptfs.Debug.Printf("Rmdir: Stat: %v", err2) toggledlog.Debug.Printf("Rmdir: Stat: %v", err2)
return fuse.ToStatus(err2) return fuse.ToStatus(err2)
} }
origMode := fi.Mode() origMode := fi.Mode()
newMode := origMode | 0700 newMode := origMode | 0700
err2 = os.Chmod(encPath, newMode) err2 = os.Chmod(encPath, newMode)
if err2 != nil { if err2 != nil {
cryptfs.Debug.Printf("Rmdir: Chmod failed: %v", err2) toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
defer func() { defer func() {
@ -92,7 +95,7 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
// Undo the chmod if removing the directory failed // Undo the chmod if removing the directory failed
err3 := os.Chmod(encPath, origMode) err3 := os.Chmod(encPath, origMode)
if err3 != nil { if err3 != nil {
cryptfs.Warn.Printf("Rmdir: Chmod rollback failed: %v", err2) toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err2)
} }
} }
}() }()
@ -100,35 +103,36 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
fd, err = os.Open(encPath) fd, err = os.Open(encPath)
} }
if err != nil { if err != nil {
cryptfs.Debug.Printf("Rmdir: Open: %v", err) toggledlog.Debug.Printf("Rmdir: Open: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
list, err := fd.Readdirnames(10) list, err := fd.Readdirnames(10)
fd.Close() fd.Close()
if err != nil { if err != nil {
cryptfs.Debug.Printf("Rmdir: Readdirnames: %v", err) toggledlog.Debug.Printf("Rmdir: Readdirnames: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
if len(list) > 1 { if len(list) > 1 {
return fuse.ToStatus(syscall.ENOTEMPTY) return fuse.ToStatus(syscall.ENOTEMPTY)
} else if len(list) == 0 { } else if len(list) == 0 {
cryptfs.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion") toggledlog.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion")
return fuse.ToStatus(syscall.Rmdir(encPath)) return fuse.ToStatus(syscall.Rmdir(encPath))
} }
// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
dirivPath := filepath.Join(encPath, cryptfs.DIRIV_FILENAME) dirivPath := filepath.Join(encPath, nametransform.DirIVFilename)
parentDir := filepath.Dir(encPath) parentDir := filepath.Dir(encPath)
tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptfs.RandUint64()) tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64())
tmpDirivPath := filepath.Join(parentDir, tmpName) tmpDirivPath := filepath.Join(parentDir, tmpName)
cryptfs.Debug.Printf("Rmdir: Renaming %s to %s", cryptfs.DIRIV_FILENAME, tmpDirivPath) toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpDirivPath)
// The directory is in an inconsistent state between rename and rmdir. Protect against // The directory is in an inconsistent state between rename and rmdir. Protect against
// concurrent readers. // concurrent readers.
fs.dirIVLock.Lock() fs.dirIVLock.Lock()
defer fs.dirIVLock.Unlock() defer fs.dirIVLock.Unlock()
err = os.Rename(dirivPath, tmpDirivPath) err = os.Rename(dirivPath, tmpDirivPath)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", cryptfs.DIRIV_FILENAME, tmpDirivPath, err) toggledlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v",
nametransform.DirIVFilename, tmpDirivPath, err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Actual Rmdir // Actual Rmdir
@ -138,16 +142,16 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
// meantime, undo the rename // meantime, undo the rename
err2 := os.Rename(tmpDirivPath, dirivPath) err2 := os.Rename(tmpDirivPath, dirivPath)
if err2 != nil { if err2 != nil {
cryptfs.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) toggledlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2)
} }
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Delete "gocryptfs.diriv.rmdir.INODENUMBER" // Delete "gocryptfs.diriv.rmdir.INODENUMBER"
err = syscall.Unlink(tmpDirivPath) err = syscall.Unlink(tmpDirivPath)
if err != nil { if err != nil {
cryptfs.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) toggledlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err)
} }
// The now-deleted directory may have been in the DirIV cache. Clear it. // The now-deleted directory may have been in the DirIV cache. Clear it.
fs.CryptFS.DirIVCache.Clear() fs.nameTransform.DirIVCache.Clear()
return fuse.OK return fuse.OK
} }

View File

@ -3,7 +3,8 @@ package pathfs_frontend
// This file forwards file encryption operations to cryptfs // This file forwards file encryption operations to cryptfs
import ( import (
"github.com/rfjakob/gocryptfs/cryptfs" "github.com/rfjakob/gocryptfs/internal/configfile"
mylog "github.com/rfjakob/gocryptfs/internal/toggledlog"
) )
// isFiltered - check if plaintext "path" should be forbidden // isFiltered - check if plaintext "path" should be forbidden
@ -14,9 +15,9 @@ func (fs *FS) isFiltered(path string) bool {
return false return false
} }
// gocryptfs.conf in the root directory is forbidden // gocryptfs.conf in the root directory is forbidden
if path == cryptfs.ConfDefaultName { if path == configfile.ConfDefaultName {
cryptfs.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", mylog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n",
cryptfs.ConfDefaultName) configfile.ConfDefaultName)
return true return true
} }
// Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames // Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames
@ -30,11 +31,11 @@ func (fs *FS) encryptPath(plainPath string) (string, error) {
return plainPath, nil return plainPath, nil
} }
if !fs.args.DirIV { if !fs.args.DirIV {
return fs.CryptFS.EncryptPathNoIV(plainPath), nil return fs.nameTransform.EncryptPathNoIV(plainPath), nil
} }
fs.dirIVLock.RLock() fs.dirIVLock.RLock()
defer fs.dirIVLock.RUnlock() defer fs.dirIVLock.RUnlock()
return fs.CryptFS.EncryptPathDirIV(plainPath, fs.args.Cipherdir, fs.args.EMENames) return fs.nameTransform.EncryptPathDirIV(plainPath, fs.args.Cipherdir)
} }
// decryptPath - decrypt relative ciphertext path // decryptPath - decrypt relative ciphertext path
@ -43,9 +44,9 @@ func (fs *FS) decryptPath(cipherPath string) (string, error) {
return cipherPath, nil return cipherPath, nil
} }
if !fs.args.DirIV { if !fs.args.DirIV {
return fs.CryptFS.DecryptPathNoIV(cipherPath) return fs.nameTransform.DecryptPathNoIV(cipherPath)
} }
fs.dirIVLock.RLock() fs.dirIVLock.RLock()
defer fs.dirIVLock.RUnlock() defer fs.dirIVLock.RUnlock()
return fs.CryptFS.DecryptPathDirIV(cipherPath, fs.args.Cipherdir, fs.args.EMENames) return fs.nameTransform.DecryptPathDirIV(cipherPath, fs.args.Cipherdir, fs.args.EMENames)
} }

View File

@ -4,7 +4,7 @@ set -eu
cd "$(dirname "$0")" cd "$(dirname "$0")"
go test ./cryptfs $* #go test ./cryptfs $*
source build.bash source build.bash
go test ./integration_tests $* go test ./integration_tests $*