From 2b8cbd944149afe51fadddbd67ee4499d1d86250 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 6 Feb 2016 19:20:54 +0100 Subject: [PATCH] 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 --- cryptfs/cryptfs.go | 83 ----------- cryptfs/names_core.go | 134 ------------------ integration_tests/cli_test.go | 15 +- integration_tests/helpers.go | 6 +- .../configfile}/config_file.go | 29 ++-- .../configfile}/config_test.go | 2 +- .../configfile}/config_test/.gitignore | 0 .../config_test/PlaintextNames.conf | 0 .../config_test/StrangeFeature.conf | 0 .../configfile}/config_test/v1.conf | 0 .../configfile}/config_test/v2.conf | 0 {cryptfs => internal/configfile}/kdf.go | 15 +- {cryptfs => internal/configfile}/kdf_test.go | 2 +- .../contentenc/content.go | 49 +++---- internal/contentenc/content_api.go | 31 ++++ .../contentenc}/content_test.go | 2 +- .../contentenc}/file_header.go | 18 ++- .../contentenc}/intrablock.go | 4 +- .../contentenc/offsets.go | 30 ++-- internal/cryptocore/crypto_api.go | 56 ++++++++ {cryptfs => internal/cryptocore}/gcm_go1.4.go | 2 +- {cryptfs => internal/cryptocore}/gcm_go1.5.go | 2 +- {cryptfs => internal/cryptocore}/nonce.go | 8 +- .../cryptocore}/openssl_aead.go | 14 +- .../cryptocore}/openssl_benchmark.bash | 0 .../cryptocore}/openssl_test.go | 2 +- internal/nametransform/name_api.go | 16 +++ internal/nametransform/names_core.go | 63 ++++++++ .../nametransform}/names_diriv.go | 41 ++++-- .../nametransform}/names_noiv.go | 14 +- .../nametransform}/names_test.go | 2 +- internal/nametransform/pad16.go | 60 ++++++++ {cryptfs => internal/toggledlog}/log.go | 6 +- {cryptfs => internal/toggledlog}/log_go1.4.go | 2 +- {cryptfs => internal/toggledlog}/log_go1.5.go | 4 +- main.go | 106 +++++++------- masterkey.go | 10 +- pathfs_frontend/file.go | 112 ++++++++------- pathfs_frontend/file_holes.go | 13 +- pathfs_frontend/fs.go | 76 ++++++---- pathfs_frontend/fs_dir.go | 44 +++--- pathfs_frontend/names.go | 17 +-- test.bash | 2 +- 43 files changed, 579 insertions(+), 513 deletions(-) delete mode 100644 cryptfs/cryptfs.go delete mode 100644 cryptfs/names_core.go rename {cryptfs => internal/configfile}/config_file.go (86%) rename {cryptfs => internal/configfile}/config_test.go (99%) rename {cryptfs => internal/configfile}/config_test/.gitignore (100%) rename {cryptfs => internal/configfile}/config_test/PlaintextNames.conf (100%) rename {cryptfs => internal/configfile}/config_test/StrangeFeature.conf (100%) rename {cryptfs => internal/configfile}/config_test/v1.conf (100%) rename {cryptfs => internal/configfile}/config_test/v2.conf (100%) rename {cryptfs => internal/configfile}/kdf.go (81%) rename {cryptfs => internal/configfile}/kdf_test.go (98%) rename cryptfs/cryptfs_content.go => internal/contentenc/content.go (61%) create mode 100644 internal/contentenc/content_api.go rename {cryptfs => internal/contentenc}/content_test.go (99%) rename {cryptfs => internal/contentenc}/file_header.go (79%) rename {cryptfs => internal/contentenc}/intrablock.go (97%) rename cryptfs/address_translation.go => internal/contentenc/offsets.go (64%) create mode 100644 internal/cryptocore/crypto_api.go rename {cryptfs => internal/cryptocore}/gcm_go1.4.go (97%) rename {cryptfs => internal/cryptocore}/gcm_go1.5.go (95%) rename {cryptfs => internal/cryptocore}/nonce.go (79%) rename {cryptfs => internal/cryptocore}/openssl_aead.go (85%) rename {cryptfs => internal/cryptocore}/openssl_benchmark.bash (100%) rename {cryptfs => internal/cryptocore}/openssl_test.go (99%) create mode 100644 internal/nametransform/name_api.go create mode 100644 internal/nametransform/names_core.go rename {cryptfs => internal/nametransform}/names_diriv.go (71%) rename {cryptfs => internal/nametransform}/names_noiv.go (77%) rename {cryptfs => internal/nametransform}/names_test.go (98%) create mode 100644 internal/nametransform/pad16.go rename {cryptfs => internal/toggledlog}/log.go (95%) rename {cryptfs => internal/toggledlog}/log_go1.4.go (91%) rename {cryptfs => internal/toggledlog}/log_go1.5.go (80%) diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go deleted file mode 100644 index 3a40e29..0000000 --- a/cryptfs/cryptfs.go +++ /dev/null @@ -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 -} diff --git a/cryptfs/names_core.go b/cryptfs/names_core.go deleted file mode 100644 index 0f2e5b3..0000000 --- a/cryptfs/names_core.go +++ /dev/null @@ -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 -} diff --git a/integration_tests/cli_test.go b/integration_tests/cli_test.go index 5e8902d..062a90d 100644 --- a/integration_tests/cli_test.go +++ b/integration_tests/cli_test.go @@ -7,7 +7,8 @@ import ( "os/exec" "testing" - "github.com/rfjakob/gocryptfs/cryptfs" + "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/nametransform" ) // Test -init flag @@ -26,7 +27,7 @@ func TestInit(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = os.Stat(dir + cryptfs.ConfDefaultName) + _, err = os.Stat(dir + configfile.ConfDefaultName) if err != nil { t.Fatal(err) } @@ -96,22 +97,22 @@ func TestInitPlaintextNames(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = os.Stat(dir + cryptfs.ConfDefaultName) + _, err = os.Stat(dir + configfile.ConfDefaultName) if err != nil { t.Fatal(err) } - _, err = os.Stat(dir + cryptfs.DIRIV_FILENAME) + _, err = os.Stat(dir + nametransform.DirIVFilename) if err == nil { 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 { t.Fatal(err) } - if !cf.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) { + if !cf.IsFeatureFlagSet(configfile.FlagPlaintextNames) { 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") } } diff --git a/integration_tests/helpers.go b/integration_tests/helpers.go index 5145b30..e5458c9 100644 --- a/integration_tests/helpers.go +++ b/integration_tests/helpers.go @@ -10,7 +10,7 @@ import ( "syscall" "testing" - "github.com/rfjakob/gocryptfs/cryptfs" + "github.com/rfjakob/gocryptfs/internal/nametransform" ) // Note: the code assumes that all have a trailing slash @@ -42,7 +42,7 @@ func resetTmpDir() { fmt.Println(err) os.Exit(1) } - err = cryptfs.WriteDirIV(defaultCipherDir) + err = nametransform.WriteDirIV(defaultCipherDir) if err != nil { fmt.Println(err) os.Exit(1) @@ -58,9 +58,9 @@ func mount(c string, p string, extraArgs ...string) { args = append(args, c) args = append(args, p) cmd := exec.Command(gocryptfsBinary, args...) + cmd.Stderr = os.Stderr if testing.Verbose() { cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr } err := cmd.Run() if err != nil { diff --git a/cryptfs/config_file.go b/internal/configfile/config_file.go similarity index 86% rename from cryptfs/config_file.go rename to internal/configfile/config_file.go index 013b82d..0128acc 100644 --- a/cryptfs/config_file.go +++ b/internal/configfile/config_file.go @@ -1,10 +1,14 @@ -package cryptfs +package configfile import ( "encoding/json" "fmt" "io/ioutil" "log" + + "github.com/rfjakob/gocryptfs/internal/cryptocore" + "github.com/rfjakob/gocryptfs/internal/contentenc" + "github.com/rfjakob/gocryptfs/internal/toggledlog" ) import "os" @@ -36,10 +40,10 @@ type ConfFile struct { func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error { var cf ConfFile cf.filename = filename - cf.Version = HEADER_CURRENT_VERSION + cf.Version = contentenc.CurrentVersion // Generate new random master key - key := RandBytes(KEY_LEN) + key := cryptocore.RandBytes(cryptocore.KeyLen) // Encrypt it using the password // This sets ScryptObject and EncryptedKey @@ -75,11 +79,11 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { // Unmarshal err = json.Unmarshal(js, &cf) if err != nil { - Warn.Printf("Failed to unmarshal config file") + toggledlog.Warn.Printf("Failed to unmarshal config file") 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) } @@ -95,11 +99,13 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { // Unlock master key using password-based key // We use stock go GCM instead of OpenSSL here as speed is not important // and we get better error messages - cfs := NewCryptFS(scryptHash, false, false, false) - key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil) + cc := cryptocore.New(scryptHash, false, false) + ce := contentenc.New(cc, 4096) + + key, err := ce.DecryptBlock(cf.EncryptedKey, 0, nil) if err != nil { - Warn.Printf("failed to unlock master key: %s", err.Error()) - Warn.Printf("Password incorrect.") + toggledlog.Warn.Printf("failed to unlock master key: %s", err.Error()) + toggledlog.Warn.Printf("Password incorrect.") return nil, nil, err } @@ -116,8 +122,9 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) { scryptHash := cf.ScryptObject.DeriveKey(password) // Lock master key using password-based key - cfs := NewCryptFS(scryptHash, false, false, false) - cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil) + cc := cryptocore.New(scryptHash, false, false) + ce := contentenc.New(cc, 4096) + cf.EncryptedKey = ce.EncryptBlock(key, 0, nil) } // WriteFile - write out config in JSON format to file "filename.tmp" diff --git a/cryptfs/config_test.go b/internal/configfile/config_test.go similarity index 99% rename from cryptfs/config_test.go rename to internal/configfile/config_test.go index 11599c0..6606d22 100644 --- a/cryptfs/config_test.go +++ b/internal/configfile/config_test.go @@ -1,4 +1,4 @@ -package cryptfs +package configfile import ( "fmt" diff --git a/cryptfs/config_test/.gitignore b/internal/configfile/config_test/.gitignore similarity index 100% rename from cryptfs/config_test/.gitignore rename to internal/configfile/config_test/.gitignore diff --git a/cryptfs/config_test/PlaintextNames.conf b/internal/configfile/config_test/PlaintextNames.conf similarity index 100% rename from cryptfs/config_test/PlaintextNames.conf rename to internal/configfile/config_test/PlaintextNames.conf diff --git a/cryptfs/config_test/StrangeFeature.conf b/internal/configfile/config_test/StrangeFeature.conf similarity index 100% rename from cryptfs/config_test/StrangeFeature.conf rename to internal/configfile/config_test/StrangeFeature.conf diff --git a/cryptfs/config_test/v1.conf b/internal/configfile/config_test/v1.conf similarity index 100% rename from cryptfs/config_test/v1.conf rename to internal/configfile/config_test/v1.conf diff --git a/cryptfs/config_test/v2.conf b/internal/configfile/config_test/v2.conf similarity index 100% rename from cryptfs/config_test/v2.conf rename to internal/configfile/config_test/v2.conf diff --git a/cryptfs/kdf.go b/internal/configfile/kdf.go similarity index 81% rename from cryptfs/kdf.go rename to internal/configfile/kdf.go index e958413..f1a7a40 100644 --- a/cryptfs/kdf.go +++ b/internal/configfile/kdf.go @@ -1,16 +1,19 @@ -package cryptfs +package configfile import ( "fmt" - "golang.org/x/crypto/scrypt" "math" "os" + + "golang.org/x/crypto/scrypt" + + "github.com/rfjakob/gocryptfs/internal/cryptocore" ) const ( // 1 << 16 uses 64MB of memory, // takes 4 seconds on my Atom Z3735F netbook - SCRYPT_DEFAULT_LOGN = 16 + ScryptDefaultLogN = 16 ) type scryptKdf struct { @@ -23,9 +26,9 @@ type scryptKdf struct { func NewScryptKdf(logN int) scryptKdf { var s scryptKdf - s.Salt = RandBytes(KEY_LEN) + s.Salt = cryptocore.RandBytes(cryptocore.KeyLen) if logN <= 0 { - s.N = 1 << SCRYPT_DEFAULT_LOGN + s.N = 1 << ScryptDefaultLogN } else { if logN < 10 { 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.P = 1 // Always 1 - s.KeyLen = KEY_LEN + s.KeyLen = cryptocore.KeyLen return s } diff --git a/cryptfs/kdf_test.go b/internal/configfile/kdf_test.go similarity index 98% rename from cryptfs/kdf_test.go rename to internal/configfile/kdf_test.go index 4d909ea..bc095ab 100644 --- a/cryptfs/kdf_test.go +++ b/internal/configfile/kdf_test.go @@ -1,4 +1,4 @@ -package cryptfs +package configfile import ( "testing" diff --git a/cryptfs/cryptfs_content.go b/internal/contentenc/content.go similarity index 61% rename from cryptfs/cryptfs_content.go rename to internal/contentenc/content.go index 2036e58..14135a2 100644 --- a/cryptfs/cryptfs_content.go +++ b/internal/contentenc/content.go @@ -1,31 +1,18 @@ -package cryptfs +package contentenc // File content encryption / decryption import ( - "bytes" - "crypto/cipher" - "crypto/md5" "encoding/binary" + "bytes" "encoding/hex" "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 -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) var err error 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 // 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? if len(ciphertext) == 0 { @@ -55,30 +42,30 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte // All-zero block? 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 } - if len(ciphertext) < be.gcmIVLen { - Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext)) + if len(ciphertext) < be.cryptoCore.IVLen { + toggledlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext)) return nil, errors.New("Block is too short") } // Extract nonce - nonce := ciphertext[:be.gcmIVLen] + nonce := ciphertext[:be.cryptoCore.IVLen] ciphertextOrig := ciphertext - ciphertext = ciphertext[be.gcmIVLen:] + ciphertext = ciphertext[be.cryptoCore.IVLen:] // Decrypt var plaintext []byte aData := make([]byte, 8) aData = append(aData, fileId...) 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 { - Warn.Printf("DecryptBlock: %s, len=%d, md5=%s", err.Error(), len(ciphertextOrig), md5sum(ciphertextOrig)) - Debug.Println(hex.Dump(ciphertextOrig)) + toggledlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig)) + toggledlog.Debug.Println(hex.Dump(ciphertextOrig)) return nil, err } @@ -86,7 +73,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte } // 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? if len(plaintext) == 0 { @@ -94,7 +81,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) } // Get fresh nonce - nonce := be.gcmIVGen.Get() + nonce := be.cryptoCore.GcmIVGen.Get() // Authenticate block with block number and file ID aData := make([]byte, 8) @@ -102,14 +89,14 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) aData = append(aData, fileID...) // 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 } // MergeBlocks - Merge newData into oldData at offset // 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 out := make([]byte, be.plainBS) diff --git a/internal/contentenc/content_api.go b/internal/contentenc/content_api.go new file mode 100644 index 0000000..1700d35 --- /dev/null +++ b/internal/contentenc/content_api.go @@ -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 +} diff --git a/cryptfs/content_test.go b/internal/contentenc/content_test.go similarity index 99% rename from cryptfs/content_test.go rename to internal/contentenc/content_test.go index 3efa959..70ad58d 100644 --- a/cryptfs/content_test.go +++ b/internal/contentenc/content_test.go @@ -1,4 +1,4 @@ -package cryptfs +package contentenc import ( "testing" diff --git a/cryptfs/file_header.go b/internal/contentenc/file_header.go similarity index 79% rename from cryptfs/file_header.go rename to internal/contentenc/file_header.go index fe4ac56..8a9dd2c 100644 --- a/cryptfs/file_header.go +++ b/internal/contentenc/file_header.go @@ -1,4 +1,4 @@ -package cryptfs +package contentenc // Per-file header // @@ -7,10 +7,14 @@ package cryptfs import ( "encoding/binary" "fmt" + + "github.com/rfjakob/gocryptfs/internal/cryptocore" ) const ( - HEADER_CURRENT_VERSION = 2 // Current on-disk-format version + // Current On-Disk-Format version + CurrentVersion = 2 + HEADER_VERSION_LEN = 2 // uint16 HEADER_ID_LEN = 16 // 128 bit random file id HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length @@ -23,7 +27,7 @@ type FileHeader struct { // Pack - serialize fileHeader object 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") } buf := make([]byte, HEADER_LEN) @@ -40,8 +44,8 @@ func ParseHeader(buf []byte) (*FileHeader, error) { } var h FileHeader h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN]) - if h.Version != HEADER_CURRENT_VERSION { - return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, HEADER_CURRENT_VERSION) + if h.Version != CurrentVersion { + return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, CurrentVersion) } h.Id = buf[HEADER_VERSION_LEN:] return &h, nil @@ -50,7 +54,7 @@ func ParseHeader(buf []byte) (*FileHeader, error) { // RandomHeader - create new fileHeader object with random Id func RandomHeader() *FileHeader { var h FileHeader - h.Version = HEADER_CURRENT_VERSION - h.Id = RandBytes(HEADER_ID_LEN) + h.Version = CurrentVersion + h.Id = cryptocore.RandBytes(HEADER_ID_LEN) return &h } diff --git a/cryptfs/intrablock.go b/internal/contentenc/intrablock.go similarity index 97% rename from cryptfs/intrablock.go rename to internal/contentenc/intrablock.go index faff471..330b980 100644 --- a/cryptfs/intrablock.go +++ b/internal/contentenc/intrablock.go @@ -1,11 +1,11 @@ -package cryptfs +package contentenc // intraBlock identifies a part of a file block type intraBlock struct { BlockNo uint64 // Block number in file Skip uint64 // Offset into block plaintext 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. diff --git a/cryptfs/address_translation.go b/internal/contentenc/offsets.go similarity index 64% rename from cryptfs/address_translation.go rename to internal/contentenc/offsets.go index b21cfc7..1b5952f 100644 --- a/cryptfs/address_translation.go +++ b/internal/contentenc/offsets.go @@ -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 -func (be *CryptFS) PlainOffToBlockNo(plainOffset uint64) uint64 { +func (be *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 { return plainOffset / be.plainBS } // 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 } // 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 } // get plaintext offset of block "blockNo" -func (be *CryptFS) BlockNoToPlainOff(blockNo uint64) uint64 { +func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 { return blockNo * be.plainBS } // 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 if cipherSize == 0 { @@ -31,12 +35,12 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 { } 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 } 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 } @@ -50,7 +54,7 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 { } // 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 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 -func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock { +func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlock { var blocks []intraBlock var nextBlock intraBlock nextBlock.fs = be @@ -81,6 +85,10 @@ func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock return blocks } +func (be *ContentEnc) BlockOverhead() uint64 { + return be.cipherBS - be.plainBS +} + func MinUint64(x uint64, y uint64) uint64 { if x < y { return x diff --git a/internal/cryptocore/crypto_api.go b/internal/cryptocore/crypto_api.go new file mode 100644 index 0000000..c6b6869 --- /dev/null +++ b/internal/cryptocore/crypto_api.go @@ -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, + } +} diff --git a/cryptfs/gcm_go1.4.go b/internal/cryptocore/gcm_go1.4.go similarity index 97% rename from cryptfs/gcm_go1.4.go rename to internal/cryptocore/gcm_go1.4.go index 0a2ff49..dba222c 100644 --- a/cryptfs/gcm_go1.4.go +++ b/internal/cryptocore/gcm_go1.4.go @@ -1,7 +1,7 @@ // +build !go1.5 // = go 1.4 or lower -package cryptfs +package cryptocore import ( "crypto/cipher" diff --git a/cryptfs/gcm_go1.5.go b/internal/cryptocore/gcm_go1.5.go similarity index 95% rename from cryptfs/gcm_go1.5.go rename to internal/cryptocore/gcm_go1.5.go index c469357..0c9b1a5 100644 --- a/cryptfs/gcm_go1.5.go +++ b/internal/cryptocore/gcm_go1.5.go @@ -1,7 +1,7 @@ // +build go1.5 // = go 1.5 or higher -package cryptfs +package cryptocore import ( "crypto/cipher" diff --git a/cryptfs/nonce.go b/internal/cryptocore/nonce.go similarity index 79% rename from cryptfs/nonce.go rename to internal/cryptocore/nonce.go index be777fc..72d8588 100644 --- a/cryptfs/nonce.go +++ b/internal/cryptocore/nonce.go @@ -1,4 +1,4 @@ -package cryptfs +package cryptocore import ( "bytes" @@ -6,6 +6,8 @@ import ( "encoding/binary" "encoding/hex" "fmt" + + "github.com/rfjakob/gocryptfs/internal/toggledlog" ) // Get "n" random bytes from /dev/urandom or panic @@ -29,10 +31,10 @@ type nonceGenerator struct { nonceLen int // bytes } -// Get a random 96 bit nonce +// Get a random "nonceLen"-byte nonce func (n *nonceGenerator) Get() []byte { 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) { m := fmt.Sprintf("Got the same nonce twice: %s. This should never happen!", hex.EncodeToString(nonce)) panic(m) diff --git a/cryptfs/openssl_aead.go b/internal/cryptocore/openssl_aead.go similarity index 85% rename from cryptfs/openssl_aead.go rename to internal/cryptocore/openssl_aead.go index 5d38d38..d4ed64b 100644 --- a/cryptfs/openssl_aead.go +++ b/internal/cryptocore/openssl_aead.go @@ -1,4 +1,4 @@ -package cryptfs +package cryptocore // Implements cipher.AEAD with OpenSSL backend @@ -13,7 +13,7 @@ type opensslGCM struct { } func (be opensslGCM) Overhead() int { - return AUTH_TAG_LEN + return AuthTagLen } func (be opensslGCM) NonceSize() int { @@ -28,11 +28,11 @@ func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte { // Preallocate output 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 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 { 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) { l := len(ciphertext) - tag := ciphertext[l-AUTH_TAG_LEN : l] - ciphertext = ciphertext[0 : l-AUTH_TAG_LEN] + tag := ciphertext[l-AuthTagLen : l] + ciphertext = ciphertext[0 : l-AuthTagLen] 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 { return nil, err } diff --git a/cryptfs/openssl_benchmark.bash b/internal/cryptocore/openssl_benchmark.bash similarity index 100% rename from cryptfs/openssl_benchmark.bash rename to internal/cryptocore/openssl_benchmark.bash diff --git a/cryptfs/openssl_test.go b/internal/cryptocore/openssl_test.go similarity index 99% rename from cryptfs/openssl_test.go rename to internal/cryptocore/openssl_test.go index aecee94..94b696a 100644 --- a/cryptfs/openssl_test.go +++ b/internal/cryptocore/openssl_test.go @@ -1,4 +1,4 @@ -package cryptfs +package cryptocore // Benchmark go built-int GCM against spacemonkey openssl bindings // diff --git a/internal/nametransform/name_api.go b/internal/nametransform/name_api.go new file mode 100644 index 0000000..462e99c --- /dev/null +++ b/internal/nametransform/name_api.go @@ -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, + } +} diff --git a/internal/nametransform/names_core.go b/internal/nametransform/names_core.go new file mode 100644 index 0000000..452ab45 --- /dev/null +++ b/internal/nametransform/names_core.go @@ -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 +} + diff --git a/cryptfs/names_diriv.go b/internal/nametransform/names_diriv.go similarity index 71% rename from cryptfs/names_diriv.go rename to internal/nametransform/names_diriv.go index 276316c..d31a066 100644 --- a/cryptfs/names_diriv.go +++ b/internal/nametransform/names_diriv.go @@ -1,4 +1,4 @@ -package cryptfs +package nametransform import ( "fmt" @@ -7,6 +7,17 @@ import ( "path/filepath" "strings" "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 @@ -50,23 +61,23 @@ func (c *dirIVCache) Clear() { } // readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) -func (be *CryptFS) ReadDirIV(dir string) (iv []byte, readErr error) { - ivfile := filepath.Join(dir, DIRIV_FILENAME) - Debug.Printf("ReadDirIV: reading %s\n", ivfile) +func (be *NameTransform) ReadDirIV(dir string) (iv []byte, readErr error) { + ivfile := filepath.Join(dir, DirIVFilename) + toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile) iv, readErr = ioutil.ReadFile(ivfile) if readErr != nil { // The directory may have been concurrently deleted or moved. Failure to // read the diriv is not an error in that case. _, statErr := os.Stat(dir) 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 { // 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 } - if len(iv) != DIRIV_LEN { + if len(iv) != dirIVLen { return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) } 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, // and also the automated tests. func WriteDirIV(dir string) error { - iv := RandBytes(DIRIV_LEN) - file := filepath.Join(dir, DIRIV_FILENAME) + iv := cryptocore.RandBytes(dirIVLen) + file := filepath.Join(dir, DirIVFilename) // 0444 permissions: the file is not secret but should not be written to return ioutil.WriteFile(file, iv, 0444) } // 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 if plainPath == "" { return plainPath, nil @@ -94,7 +105,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool) if found { //fmt.Print("h") baseName := filepath.Base(plainPath) - cBaseName := be.encryptName(baseName, iv, eme) + cBaseName := be.encryptName(baseName, iv) cipherPath = cParentDir + "/" + cBaseName return cipherPath, nil } @@ -107,7 +118,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool) if err != nil { return "", err } - encryptedName := be.encryptName(plainName, iv, eme) + encryptedName := be.encryptName(plainName, iv) encryptedNames = append(encryptedNames, 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 -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 plainNames []string encryptedNames := strings.Split(encryptedPath, "/") - Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames) + toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames) for _, encryptedName := range encryptedNames { iv, err := be.ReadDirIV(wd) if err != nil { return "", err } - plainName, err := be.decryptName(encryptedName, iv, eme) + plainName, err := be.DecryptName(encryptedName, iv) if err != nil { return "", err } diff --git a/cryptfs/names_noiv.go b/internal/nametransform/names_noiv.go similarity index 77% rename from cryptfs/names_noiv.go rename to internal/nametransform/names_noiv.go index 7eed4b8..f301e52 100644 --- a/cryptfs/names_noiv.go +++ b/internal/nametransform/names_noiv.go @@ -1,4 +1,4 @@ -package cryptfs +package nametransform import ( "strings" @@ -12,7 +12,7 @@ const ( // DecryptPathNoIV - decrypt path using CBC without any IV. // This function is deprecated by the the more secure DirIV variant and only retained // 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) 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. // This function is deprecated by the the more secure DirIV variant and only retained // 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) return cipherPath } // translatePathZeroIV - encrypt or decrypt path using CBC with an all-zero IV. // 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 // Empty string means root directory @@ -35,7 +35,7 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) { return path, err } - zeroIV := make([]byte, DIRIV_LEN) + zeroIV := make([]byte, dirIVLen) // Run operation on each path component var translatedParts []string @@ -49,9 +49,9 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) { } var newPart string if op == OpEncrypt { - newPart = be.encryptName(part, zeroIV, false) + newPart = be.encryptName(part, zeroIV) } else { - newPart, err = be.decryptName(part, zeroIV, false) + newPart, err = be.DecryptName(part, zeroIV) if err != nil { return "", err } diff --git a/cryptfs/names_test.go b/internal/nametransform/names_test.go similarity index 98% rename from cryptfs/names_test.go rename to internal/nametransform/names_test.go index 0207f0a..4a901be 100644 --- a/cryptfs/names_test.go +++ b/internal/nametransform/names_test.go @@ -1,4 +1,4 @@ -package cryptfs +package nametransform import ( "bytes" diff --git a/internal/nametransform/pad16.go b/internal/nametransform/pad16.go new file mode 100644 index 0000000..c15160e --- /dev/null +++ b/internal/nametransform/pad16.go @@ -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 +} diff --git a/cryptfs/log.go b/internal/toggledlog/log.go similarity index 95% rename from cryptfs/log.go rename to internal/toggledlog/log.go index 19d8b51..4a2ad03 100644 --- a/cryptfs/log.go +++ b/internal/toggledlog/log.go @@ -1,4 +1,4 @@ -package cryptfs +package toggledlog import ( "encoding/json" @@ -7,6 +7,10 @@ import ( "os" ) +const ( + ProgramName = "gocryptfs" +) + func JSONDump(obj interface{}) string { b, err := json.MarshalIndent(obj, "", "\t") if err != nil { diff --git a/cryptfs/log_go1.4.go b/internal/toggledlog/log_go1.4.go similarity index 91% rename from cryptfs/log_go1.4.go rename to internal/toggledlog/log_go1.4.go index 4b91bad..4cdba44 100644 --- a/cryptfs/log_go1.4.go +++ b/internal/toggledlog/log_go1.4.go @@ -1,7 +1,7 @@ // +build !go1.5 // = go 1.4 or lower -package cryptfs +package toggledlog import ( "log/syslog" diff --git a/cryptfs/log_go1.5.go b/internal/toggledlog/log_go1.5.go similarity index 80% rename from cryptfs/log_go1.5.go rename to internal/toggledlog/log_go1.5.go index 8daae9c..e8e71f9 100644 --- a/cryptfs/log_go1.5.go +++ b/internal/toggledlog/log_go1.5.go @@ -1,14 +1,14 @@ // +build go1.5 // = go 1.5 or higher -package cryptfs +package toggledlog import ( "log/syslog" ) func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { - w, err := syslog.New(p, PROGRAM_NAME) + w, err := syslog.New(p, ProgramName) if err != nil { Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err) } else { diff --git a/main.go b/main.go index da98481..58e1155 100644 --- a/main.go +++ b/main.go @@ -16,12 +16,16 @@ import ( "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/nodefs" "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 ( @@ -56,9 +60,9 @@ func initDir(args *argContainer) { } // 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) - err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn) + err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn) if err != nil { fmt.Println(err) os.Exit(ERREXIT_INIT) @@ -66,30 +70,30 @@ func initDir(args *argContainer) { if args.diriv && !args.plaintextnames { // Create gocryptfs.diriv in the root dir - err = cryptfs.WriteDirIV(args.cipherdir) + err = nametransform.WriteDirIV(args.cipherdir) if err != nil { fmt.Println(err) os.Exit(ERREXIT_INIT) } } - cryptfs.Info.Printf(colorGreen + "The filesystem has been created successfully." + colorReset) - cryptfs.Info.Printf(colorGrey+"You can now mount it using: %s %s MOUNTPOINT"+colorReset, - cryptfs.PROGRAM_NAME, args.cipherdir) + toggledlog.Info.Printf(colorGreen + "The filesystem has been created successfully." + colorReset) + toggledlog.Info.Printf(colorGrey+"You can now mount it using: %s %s MOUNTPOINT"+colorReset, + toggledlog.ProgramName, args.cipherdir) os.Exit(0) } func usageText() { printVersion() fmt.Printf("\n") - fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", cryptfs.PROGRAM_NAME) - fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", cryptfs.PROGRAM_NAME) + fmt.Printf("Usage: %s -init|-passwd [OPTIONS] CIPHERDIR\n", toggledlog.ProgramName) + fmt.Printf(" or %s [OPTIONS] CIPHERDIR MOUNTPOINT\n", toggledlog.ProgramName) fmt.Printf("\nOptions:\n") flagSet.PrintDefaults() } // loadConfig - load the config file "filename", prompting the user for the password -func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFile) { +func loadConfig(args *argContainer) (masterkey []byte, confFile *configfile.ConfFile) { // Check if the file exists at all before prompting for a password _, err := os.Stat(args.config) if err != nil { @@ -100,16 +104,16 @@ func loadConfig(args *argContainer) (masterkey []byte, confFile *cryptfs.ConfFil fmt.Printf("Password: ") } pw := readPassword(args.extpass) - cryptfs.Info.Printf("Decrypting master key... ") - cryptfs.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password - masterkey, confFile, err = cryptfs.LoadConfFile(args.config, pw) - cryptfs.Warn.Enabled = true + toggledlog.Info.Printf("Decrypting master key... ") + toggledlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password + masterkey, confFile, err = configfile.LoadConfFile(args.config, pw) + toggledlog.Warn.Enabled = true if err != nil { fmt.Println(err) fmt.Println(colorRed + "Wrong password." + colorReset) os.Exit(ERREXIT_LOADCONF) } - cryptfs.Info.Printf("done.") + toggledlog.Info.Printf("done.") return masterkey, confFile } @@ -125,14 +129,14 @@ func changePassword(args *argContainer) { fmt.Println(err) os.Exit(ERREXIT_INIT) } - cryptfs.Info.Printf("Password changed.") + toggledlog.Info.Printf("Password changed.") os.Exit(0) } // printVersion - print a version string like // "gocryptfs v0.3.1-31-g6736212-dirty; on-disk format 2" func printVersion() { - fmt.Printf("%s %s; on-disk format %d\n", cryptfs.PROGRAM_NAME, GitVersion, cryptfs.HEADER_CURRENT_VERSION) + fmt.Printf("%s %s; on-disk format %d\n", toggledlog.ProgramName, GitVersion, contentenc.CurrentVersion) } func main() { @@ -142,7 +146,7 @@ func main() { setupColors() // Parse command line arguments - flagSet = flag.NewFlagSet(cryptfs.PROGRAM_NAME, flag.ExitOnError) + flagSet = flag.NewFlagSet(toggledlog.ProgramName, flag.ExitOnError) flagSet.Usage = usageText flagSet.BoolVar(&args.debug, "d", false, "") 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.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+ "successful mount - used internally for daemonization") - flagSet.IntVar(&args.scryptn, "scryptn", cryptfs.SCRYPT_DEFAULT_LOGN, "scrypt cost parameter logN. "+ + 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") flagSet.Parse(os.Args[1:]) @@ -182,12 +186,12 @@ func main() { os.Exit(0) } if args.debug { - cryptfs.Debug.Enabled = true - cryptfs.Debug.Printf("Debug output enabled") + toggledlog.Debug.Enabled = true + toggledlog.Debug.Printf("Debug output enabled") } if args.wpanic { - cryptfs.Warn.PanicAfter = true - cryptfs.Debug.Printf("Panicing on warnings") + toggledlog.Warn.PanicAfter = true + toggledlog.Debug.Printf("Panicing on warnings") } // Every operation below requires CIPHERDIR. Check that we have it. if flagSet.NArg() >= 1 { @@ -203,7 +207,7 @@ func main() { } // "-q" if args.quiet { - cryptfs.Info.Enabled = false + toggledlog.Info.Enabled = false } // "-config" if args.config != "" { @@ -211,13 +215,13 @@ func main() { if err != nil { 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 { - args.config = filepath.Join(args.cipherdir, cryptfs.ConfDefaultName) + args.config = filepath.Join(args.cipherdir, configfile.ConfDefaultName) } // "-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) if err != nil { fmt.Println(err) @@ -228,7 +232,7 @@ func main() { } // "-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) if err != nil { fmt.Println(err) @@ -245,13 +249,13 @@ func main() { } // "-openssl" if args.openssl == false { - cryptfs.Info.Printf("Openssl disabled") + toggledlog.Info.Printf("Openssl disabled") } // Operation flags: init, passwd or mount // "-init" if args.init { 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) } initDir(&args) // does not return @@ -259,7 +263,7 @@ func main() { // "-passwd" if args.passwd { 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) } changePassword(&args) // does not return @@ -282,34 +286,34 @@ func main() { } // Get master key var masterkey []byte - var confFile *cryptfs.ConfFile + var confFile *configfile.ConfFile if args.masterkey != "" { // "-masterkey" - cryptfs.Info.Printf("Using explicit master key.") + toggledlog.Info.Printf("Using explicit master key.") 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 { // "-zerokey" - cryptfs.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.") - masterkey = make([]byte, cryptfs.KEY_LEN) + toggledlog.Info.Printf("Using all-zero dummy master key.") + toggledlog.Info.Printf("ZEROKEY MODE PROVIDES NO SECURITY AT ALL AND SHOULD ONLY BE USED FOR TESTING.") + masterkey = make([]byte, cryptocore.KeyLen) } else { // Load master key from config file masterkey, confFile = loadConfig(&args) printMasterKey(masterkey) } // Initialize FUSE server - cryptfs.Debug.Printf("cli args: %v", args) + toggledlog.Debug.Printf("cli args: %v", args) 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 if args.notifypid > 0 { sendUsr1(args.notifypid) if !args.nosyslog { - cryptfs.Info.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_INFO) - cryptfs.Debug.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_DEBUG) - cryptfs.Warn.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_WARNING) + toggledlog.Info.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_INFO) + toggledlog.Debug.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_DEBUG) + toggledlog.Warn.SwitchToSyslog(syslog.LOG_USER | syslog.LOG_WARNING) } } // Wait for SIGINT in the background and unmount ourselves if we get it. @@ -322,7 +326,7 @@ func main() { // pathfsFrontend - initialize gocryptfs/pathfs_frontend // 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 // filesystem implementation @@ -338,10 +342,10 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) * // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { // Settings from the config file override command line args - frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) - frontendArgs.DirIV = confFile.IsFeatureFlagSet(cryptfs.FlagDirIV) - frontendArgs.EMENames = confFile.IsFeatureFlagSet(cryptfs.FlagEMENames) - frontendArgs.GCMIV128 = confFile.IsFeatureFlagSet(cryptfs.FlagGCMIV128) + frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames) + frontendArgs.DirIV = confFile.IsFeatureFlagSet(configfile.FlagDirIV) + frontendArgs.EMENames = confFile.IsFeatureFlagSet(configfile.FlagEMENames) + frontendArgs.GCMIV128 = confFile.IsFeatureFlagSet(configfile.FlagGCMIV128) } // EMENames implies DirIV, both on the command line and in the config file. if frontendArgs.EMENames { @@ -353,7 +357,7 @@ func pathfsFrontend(key []byte, args argContainer, confFile *cryptfs.ConfFile) * frontendArgs.EMENames = false } jsonBytes, _ := json.MarshalIndent(frontendArgs, "", "\t") - cryptfs.Debug.Printf("frontendArgs: %s", string(jsonBytes)) + toggledlog.Debug.Printf("frontendArgs: %s", string(jsonBytes)) finalFs := pathfs_frontend.NewFS(frontendArgs) pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} @@ -398,7 +402,7 @@ func handleSigint(srv *fuse.Server, mountpoint string) { err := srv.Unmount() if err != nil { fmt.Print(err) - cryptfs.Info.Printf("Trying lazy unmount") + toggledlog.Info.Printf("Trying lazy unmount") cmd := exec.Command("fusermount", "-u", "-z", mountpoint) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/masterkey.go b/masterkey.go index a9df1a8..8e28b32 100644 --- a/masterkey.go +++ b/masterkey.go @@ -3,9 +3,11 @@ package main import ( "encoding/hex" "fmt" - "github.com/rfjakob/gocryptfs/cryptfs" "os" "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 @@ -25,7 +27,7 @@ func printMasterKey(key []byte) { } } - cryptfs.Info.Printf(` + toggledlog.Info.Printf(` Your master key is: %s @@ -46,8 +48,8 @@ func parseMasterKey(masterkey string) []byte { fmt.Printf("Could not parse master key: %v\n", err) os.Exit(1) } - if len(key) != cryptfs.KEY_LEN { - fmt.Printf("Master key has length %d but we require length %d\n", 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), cryptocore.KeyLen) os.Exit(1) } return key diff --git a/pathfs_frontend/file.go b/pathfs_frontend/file.go index 33ad0c7..387eb35 100644 --- a/pathfs_frontend/file.go +++ b/pathfs_frontend/file.go @@ -13,7 +13,9 @@ import ( "github.com/hanwen/go-fuse/fuse" "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 @@ -29,19 +31,19 @@ type file struct { // Was the file opened O_WRONLY? writeOnly bool - // Parent CryptFS - cfs *cryptfs.CryptFS + // Content encryption helper + contentEnc *contentenc.ContentEnc // Inode number ino uint64 // File header - header *cryptfs.FileHeader + header *contentenc.FileHeader 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 syscall.Fstat(int(fd.Fd()), &st) wlock.register(st.Ino) @@ -49,7 +51,7 @@ func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File { return &file{ fd: fd, writeOnly: writeOnly, - cfs: cfs, + contentEnc: contentEnc, ino: st.Ino, } } @@ -71,12 +73,12 @@ func (f *file) SetInode(n *nodefs.Inode) { // // Returns io.EOF if the file is empty func (f *file) readHeader() error { - buf := make([]byte, cryptfs.HEADER_LEN) + buf := make([]byte, contentenc.HEADER_LEN) _, err := f.fd.ReadAt(buf, 0) if err != nil { return err } - h, err := cryptfs.ParseHeader(buf) + h, err := contentenc.ParseHeader(buf) if err != nil { return err } @@ -87,13 +89,13 @@ func (f *file) readHeader() error { // createHeader - create a new random header and write it to disk func (f *file) createHeader() error { - h := cryptfs.RandomHeader() + h := contentenc.RandomHeader() buf := h.Pack() // 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 { - 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 } @@ -133,29 +135,29 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) { } // 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) 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)) n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset)) 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) } // Truncate ciphertext buffer down to actually read bytes ciphertext = ciphertext[0:n] 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 - plaintext, err := f.cfs.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id) + plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id) if err != nil { - curruptBlockNo := firstBlockNo + f.cfs.PlainOffToBlockNo(uint64(len(plaintext))) - cipherOff := f.cfs.BlockNoToCipherOff(curruptBlockNo) - plainOff := f.cfs.BlockNoToPlainOff(curruptBlockNo) - cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)", + curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext))) + cipherOff := f.contentEnc.BlockNoToCipherOff(curruptBlockNo) + plainOff := f.contentEnc.BlockNoToPlainOff(curruptBlockNo) + toggledlog.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)", f.ino, curruptBlockNo, plainOff, cipherOff) return nil, fuse.EIO } @@ -179,23 +181,23 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus f.fdLock.RLock() 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 { - 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 } out, status := f.doRead(uint64(off), uint64(len(buf))) 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 { 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 } @@ -225,7 +227,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) { var written uint32 status := fuse.OK 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 { blockData := dataBuf.Next(int(b.Length)) @@ -234,26 +236,26 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) { if b.IsPartial() { // Read o, _ := b.PlaintextRange() - oldData, status := f.doRead(o, f.cfs.PlainBS()) + oldData, status := f.doRead(o, f.contentEnc.PlainBS()) 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 } // Modify - blockData = f.cfs.MergeBlocks(oldData, blockData, int(b.Skip)) - cryptfs.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData)) + blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) + toggledlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData)) } // Encrypt blockOffset, blockLen := b.CiphertextRange() - blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id) - cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d", - f.ino, uint64(len(blockData))-f.cfs.BlockOverhead(), b.BlockNo) + blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id) + toggledlog.Debug.Printf("ino%d: Writing %d bytes to block #%d", + f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo) // Prevent partially written (=corrupt) blocks by preallocating the space beforehand err := prealloc(int(f.fd.Fd()), int64(blockOffset), int64(blockLen)) 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) break } @@ -262,7 +264,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) { _, err = f.fd.WriteAt(blockData, int64(blockOffset)) 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) break } @@ -278,18 +280,18 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) { wlock.lock(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() if err != nil { - cryptfs.Warn.Printf("Write: Fstat failed: %v", err) + toggledlog.Warn.Printf("Write: Fstat failed: %v", 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) { status := f.zeroPad(plainSize) if status != fuse.OK { - cryptfs.Warn.Printf("zeroPad returned error %v", status) + toggledlog.Warn.Printf("zeroPad returned error %v", status) return 0, status } } @@ -337,14 +339,14 @@ func (f *file) Truncate(newSize uint64) fuse.Status { defer wlock.unlock(f.ino) 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 if newSize == 0 { err := syscall.Ftruncate(int(f.fd.Fd()), 0) 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) } // Truncate to zero kills the file header @@ -356,14 +358,14 @@ func (f *file) Truncate(newSize uint64) fuse.Status { // the file fi, err := f.fd.Stat() 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) } - oldSize := f.cfs.CipherSizeToPlainSize(uint64(fi.Size())) + oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) { - oldB := float32(oldSize) / float32(f.cfs.PlainBS()) - newB := float32(newSize) / float32(f.cfs.PlainBS()) - cryptfs.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize) + oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) + newB := float32(newSize) / float32(f.contentEnc.PlainBS()) + 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 @@ -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 { // First and last block may be partial if b.IsPartial() { @@ -396,7 +398,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status { off, length := b.CiphertextRange() err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length)) 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) } } @@ -404,23 +406,23 @@ func (f *file) Truncate(newSize uint64) fuse.Status { return fuse.OK } else { // File shrinks - blockNo := f.cfs.PlainOffToBlockNo(newSize) - cipherOff := f.cfs.BlockNoToCipherOff(blockNo) - plainOff := f.cfs.BlockNoToPlainOff(blockNo) + blockNo := f.contentEnc.PlainOffToBlockNo(newSize) + cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo) + plainOff := f.contentEnc.BlockNoToPlainOff(blockNo) lastBlockLen := newSize - plainOff var data []byte if lastBlockLen > 0 { var status fuse.Status data, status = f.doRead(plainOff, lastBlockLen) if status != fuse.OK { - cryptfs.Warn.Printf("shrink doRead returned error: %v", err) + toggledlog.Warn.Printf("shrink doRead returned error: %v", err) return status } } // Truncate down to last complete block err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff)) 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) } // Append partial block @@ -450,14 +452,14 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status { f.fdLock.RLock() defer f.fdLock.RUnlock() - cryptfs.Debug.Printf("file.GetAttr()") + toggledlog.Debug.Printf("file.GetAttr()") st := syscall.Stat_t{} err := syscall.Fstat(int(f.fd.Fd()), &st) if err != nil { return fuse.ToStatus(err) } a.FromStat(&st) - a.Size = f.cfs.CipherSizeToPlainSize(a.Size) + a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size) return fuse.OK } @@ -468,7 +470,7 @@ var allocateWarned bool func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { // Only warn once 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 } return fuse.ENOSYS diff --git a/pathfs_frontend/file_holes.go b/pathfs_frontend/file_holes.go index fd2e2c1..a147deb 100644 --- a/pathfs_frontend/file_holes.go +++ b/pathfs_frontend/file_holes.go @@ -4,13 +4,14 @@ package pathfs_frontend import ( "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? func (f *file) createsHole(plainSize uint64, off int64) bool { - nextBlock := f.cfs.PlainOffToBlockNo(plainSize) - targetBlock := f.cfs.PlainOffToBlockNo(uint64(off)) + nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize) + targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(off)) if targetBlock > nextBlock { 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 func (f *file) zeroPad(plainSize uint64) fuse.Status { - lastBlockLen := plainSize % f.cfs.PlainBS() - missing := f.cfs.PlainBS() - lastBlockLen + lastBlockLen := plainSize % f.contentEnc.PlainBS() + missing := f.contentEnc.PlainBS() - lastBlockLen 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)) return status } diff --git a/pathfs_frontend/fs.go b/pathfs_frontend/fs.go index a00985d..212f0a7 100644 --- a/pathfs_frontend/fs.go +++ b/pathfs_frontend/fs.go @@ -13,25 +13,41 @@ import ( "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" "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 { - *cryptfs.CryptFS pathfs.FileSystem // loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go args Args // Stores configuration arguments // dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified // Readers must RLock() it to prevent them from seeing intermediate // states dirIVLock sync.RWMutex + // Filename encryption helper + nameTransform *nametransform.NameTransform + // Content encryption helper + contentEnc *contentenc.ContentEnc } // Encrypted FUSE overlay filesystem 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{ - CryptFS: cryptfs.NewCryptFS(args.Masterkey, args.OpenSSL, args.PlaintextNames, args.GCMIV128), FileSystem: pathfs.NewLoopbackFileSystem(args.Cipherdir), args: args, + nameTransform: nameTransform, + contentEnc: contentEnc, } } @@ -43,12 +59,12 @@ func (fs *FS) getBackingPath(relPath string) (string, error) { return "", err } 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 } 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) { 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) if a == nil { - cryptfs.Debug.Printf("FS.GetAttr failed: %s", status.String()) + toggledlog.Debug.Printf("FS.GetAttr failed: %s", status.String()) return a, status } if a.IsRegular() { - a.Size = fs.CipherSizeToPlainSize(a.Size) + a.Size = fs.contentEnc.CipherSizeToPlainSize(a.Size) } else if a.IsSymlink() { target, _ := fs.Readlink(name, context) 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) { - cryptfs.Debug.Printf("OpenDir(%s)", dirName) + toggledlog.Debug.Printf("OpenDir(%s)", dirName) cDirName, err := fs.encryptPath(dirName) if err != nil { return nil, fuse.ToStatus(err) @@ -81,12 +97,12 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f if cipherEntries == nil { return nil, status } - // Get DirIV (stays zero if DirIV if off) - cachedIV := make([]byte, cryptfs.DIRIV_LEN) + // Get DirIV (stays nil if DirIV if off) + var cachedIV []byte if fs.args.DirIV { // Read the DirIV once and use it for all later name decryptions cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName) - cachedIV, err = fs.CryptFS.ReadDirIV(cDirAbsPath) + cachedIV, err = fs.nameTransform.ReadDirIV(cDirAbsPath) if err != nil { 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 for i := range cipherEntries { cName := cipherEntries[i].Name - if dirName == "" && cName == cryptfs.ConfDefaultName { + if dirName == "" && cName == configfile.ConfDefaultName { // silently ignore "gocryptfs.conf" in the top level dir 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 continue } var name string = cName if !fs.args.PlaintextNames { - name, err = fs.CryptFS.DecryptName(cName, cachedIV, fs.args.EMENames) + name, err = fs.nameTransform.DecryptName(cName, cachedIV) 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 } } @@ -137,16 +153,16 @@ func (fs *FS) Open(path string, flags uint32, context *fuse.Context) (fuseFile n iflags, writeOnly := fs.mangleOpenFlags(flags) cPath, err := fs.getBackingPath(path) if err != nil { - cryptfs.Debug.Printf("Open: getBackingPath: %v", err) + toggledlog.Debug.Printf("Open: getBackingPath: %v", 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) if err != nil { 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) { @@ -162,7 +178,7 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte if err != nil { 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) { @@ -203,7 +219,7 @@ var truncateWarned bool func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) { // Only warn once 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 } return fuse.ENOSYS @@ -233,7 +249,7 @@ func (fs *FS) Readlink(path string, context *fuse.Context) (out string, status f if !fs.args.DirIV { target, err := fs.decryptPath(cTarget) 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 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) cBinTarget, err := base64.URLEncoding.DecodeString(cTarget) if err != nil { - cryptfs.Warn.Printf("Readlink: %v", err) + toggledlog.Warn.Printf("Readlink: %v", err) return "", fuse.EIO } - target, err := fs.CryptFS.DecryptBlock([]byte(cBinTarget), 0, nil) + target, err := fs.contentEnc.DecryptBlock([]byte(cBinTarget), 0, nil) if err != nil { - cryptfs.Warn.Printf("Readlink: %v", err) + toggledlog.Warn.Printf("Readlink: %v", err) return "", fuse.EIO } 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) { - cryptfs.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) + toggledlog.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) if fs.isFiltered(linkName) { return fuse.EPERM } @@ -276,18 +292,18 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co if !fs.args.DirIV { cTarget, err := fs.encryptPath(target) 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) } err = os.Symlink(cTarget, cPath) return fuse.ToStatus(err) } // 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) 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) } @@ -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. // That directory may still be in the DirIV cache, clear it. - fs.CryptFS.DirIVCache.Clear() + fs.nameTransform.DirIVCache.Clear() 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 // ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv. // 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 { err = os.Rename(cOldPath, cNewPath) } diff --git a/pathfs_frontend/fs_dir.go b/pathfs_frontend/fs_dir.go index b1edd73..d378d28 100644 --- a/pathfs_frontend/fs_dir.go +++ b/pathfs_frontend/fs_dir.go @@ -9,7 +9,10 @@ import ( "syscall" "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) { @@ -29,7 +32,7 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu mode = mode | 0300 // 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 fs.dirIVLock.Lock() 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) } // Create gocryptfs.diriv inside - err = cryptfs.WriteDirIV(encPath) + err = nametransform.WriteDirIV(encPath) if err != nil { // This should not happen - cryptfs.Warn.Printf("Mkdir: WriteDirIV failed: %v", err) + toggledlog.Warn.Printf("Mkdir: WriteDirIV failed: %v", err) err2 := syscall.Rmdir(encPath) 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) } @@ -53,7 +56,7 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu if origMode != mode { err = os.Chmod(encPath, os.FileMode(origMode)) 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) if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES { // 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) if err2 != nil { - cryptfs.Debug.Printf("Rmdir: Stat: %v", err2) + toggledlog.Debug.Printf("Rmdir: Stat: %v", err2) return fuse.ToStatus(err2) } origMode := fi.Mode() newMode := origMode | 0700 err2 = os.Chmod(encPath, newMode) if err2 != nil { - cryptfs.Debug.Printf("Rmdir: Chmod failed: %v", err2) + toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2) return fuse.ToStatus(err) } 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 err3 := os.Chmod(encPath, origMode) 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) } if err != nil { - cryptfs.Debug.Printf("Rmdir: Open: %v", err) + toggledlog.Debug.Printf("Rmdir: Open: %v", err) return fuse.ToStatus(err) } list, err := fd.Readdirnames(10) fd.Close() if err != nil { - cryptfs.Debug.Printf("Rmdir: Readdirnames: %v", err) + toggledlog.Debug.Printf("Rmdir: Readdirnames: %v", err) return fuse.ToStatus(err) } if len(list) > 1 { return fuse.ToStatus(syscall.ENOTEMPTY) } 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)) } // 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) - tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptfs.RandUint64()) + tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64()) 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 // concurrent readers. fs.dirIVLock.Lock() defer fs.dirIVLock.Unlock() err = os.Rename(dirivPath, tmpDirivPath) 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) } // Actual Rmdir @@ -138,16 +142,16 @@ func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) { // meantime, undo the rename err2 := os.Rename(tmpDirivPath, dirivPath) 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) } // Delete "gocryptfs.diriv.rmdir.INODENUMBER" err = syscall.Unlink(tmpDirivPath) 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. - fs.CryptFS.DirIVCache.Clear() + fs.nameTransform.DirIVCache.Clear() return fuse.OK } diff --git a/pathfs_frontend/names.go b/pathfs_frontend/names.go index bd7a249..160fa0a 100644 --- a/pathfs_frontend/names.go +++ b/pathfs_frontend/names.go @@ -3,7 +3,8 @@ package pathfs_frontend // This file forwards file encryption operations to cryptfs 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 @@ -14,9 +15,9 @@ func (fs *FS) isFiltered(path string) bool { return false } // gocryptfs.conf in the root directory is forbidden - if path == cryptfs.ConfDefaultName { - cryptfs.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", - cryptfs.ConfDefaultName) + if path == configfile.ConfDefaultName { + mylog.Info.Printf("The name /%s is reserved when -plaintextnames is used\n", + configfile.ConfDefaultName) return true } // 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 } if !fs.args.DirIV { - return fs.CryptFS.EncryptPathNoIV(plainPath), nil + return fs.nameTransform.EncryptPathNoIV(plainPath), nil } fs.dirIVLock.RLock() 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 @@ -43,9 +44,9 @@ func (fs *FS) decryptPath(cipherPath string) (string, error) { return cipherPath, nil } if !fs.args.DirIV { - return fs.CryptFS.DecryptPathNoIV(cipherPath) + return fs.nameTransform.DecryptPathNoIV(cipherPath) } fs.dirIVLock.RLock() 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) } diff --git a/test.bash b/test.bash index bd397fd..f90c3da 100755 --- a/test.bash +++ b/test.bash @@ -4,7 +4,7 @@ set -eu cd "$(dirname "$0")" -go test ./cryptfs $* +#go test ./cryptfs $* source build.bash go test ./integration_tests $*