From 2b8cbd944149afe51fadddbd67ee4499d1d86250 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 6 Feb 2016 19:20:54 +0100 Subject: 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/address_translation.go | 89 ---------- cryptfs/config_file.go | 188 -------------------- cryptfs/config_test.go | 83 --------- cryptfs/config_test/.gitignore | 1 - cryptfs/config_test/PlaintextNames.conf | 14 -- cryptfs/config_test/StrangeFeature.conf | 14 -- cryptfs/config_test/v1.conf | 11 -- cryptfs/config_test/v2.conf | 11 -- cryptfs/content_test.go | 91 ---------- cryptfs/cryptfs.go | 83 --------- cryptfs/cryptfs_content.go | 129 -------------- cryptfs/file_header.go | 56 ------ cryptfs/gcm_go1.4.go | 22 --- cryptfs/gcm_go1.5.go | 16 -- cryptfs/intrablock.go | 51 ------ cryptfs/kdf.go | 54 ------ cryptfs/kdf_test.go | 60 ------- cryptfs/log.go | 61 ------- cryptfs/log_go1.4.go | 12 -- cryptfs/log_go1.5.go | 17 -- cryptfs/names_core.go | 134 -------------- cryptfs/names_diriv.go | 140 --------------- cryptfs/names_noiv.go | 63 ------- cryptfs/names_test.go | 58 ------ cryptfs/nonce.go | 42 ----- cryptfs/openssl_aead.go | 100 ----------- cryptfs/openssl_benchmark.bash | 3 - cryptfs/openssl_test.go | 75 -------- integration_tests/cli_test.go | 15 +- integration_tests/helpers.go | 6 +- internal/configfile/config_file.go | 195 +++++++++++++++++++++ internal/configfile/config_test.go | 83 +++++++++ internal/configfile/config_test/.gitignore | 1 + .../configfile/config_test/PlaintextNames.conf | 14 ++ .../configfile/config_test/StrangeFeature.conf | 14 ++ internal/configfile/config_test/v1.conf | 11 ++ internal/configfile/config_test/v2.conf | 11 ++ internal/configfile/kdf.go | 57 ++++++ internal/configfile/kdf_test.go | 60 +++++++ internal/contentenc/content.go | 116 ++++++++++++ internal/contentenc/content_api.go | 31 ++++ internal/contentenc/content_test.go | 91 ++++++++++ internal/contentenc/file_header.go | 60 +++++++ internal/contentenc/intrablock.go | 51 ++++++ internal/contentenc/offsets.go | 97 ++++++++++ internal/cryptocore/crypto_api.go | 56 ++++++ internal/cryptocore/gcm_go1.4.go | 22 +++ internal/cryptocore/gcm_go1.5.go | 16 ++ internal/cryptocore/nonce.go | 44 +++++ internal/cryptocore/openssl_aead.go | 100 +++++++++++ internal/cryptocore/openssl_benchmark.bash | 3 + internal/cryptocore/openssl_test.go | 75 ++++++++ internal/nametransform/name_api.go | 16 ++ internal/nametransform/names_core.go | 63 +++++++ internal/nametransform/names_diriv.go | 151 ++++++++++++++++ internal/nametransform/names_noiv.go | 63 +++++++ internal/nametransform/names_test.go | 58 ++++++ internal/nametransform/pad16.go | 60 +++++++ internal/toggledlog/log.go | 65 +++++++ internal/toggledlog/log_go1.4.go | 12 ++ internal/toggledlog/log_go1.5.go | 17 ++ 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 +- 69 files changed, 1929 insertions(+), 1863 deletions(-) delete mode 100644 cryptfs/address_translation.go delete mode 100644 cryptfs/config_file.go delete mode 100644 cryptfs/config_test.go delete mode 100644 cryptfs/config_test/.gitignore delete mode 100644 cryptfs/config_test/PlaintextNames.conf delete mode 100644 cryptfs/config_test/StrangeFeature.conf delete mode 100644 cryptfs/config_test/v1.conf delete mode 100644 cryptfs/config_test/v2.conf delete mode 100644 cryptfs/content_test.go delete mode 100644 cryptfs/cryptfs.go delete mode 100644 cryptfs/cryptfs_content.go delete mode 100644 cryptfs/file_header.go delete mode 100644 cryptfs/gcm_go1.4.go delete mode 100644 cryptfs/gcm_go1.5.go delete mode 100644 cryptfs/intrablock.go delete mode 100644 cryptfs/kdf.go delete mode 100644 cryptfs/kdf_test.go delete mode 100644 cryptfs/log.go delete mode 100644 cryptfs/log_go1.4.go delete mode 100644 cryptfs/log_go1.5.go delete mode 100644 cryptfs/names_core.go delete mode 100644 cryptfs/names_diriv.go delete mode 100644 cryptfs/names_noiv.go delete mode 100644 cryptfs/names_test.go delete mode 100644 cryptfs/nonce.go delete mode 100644 cryptfs/openssl_aead.go delete mode 100755 cryptfs/openssl_benchmark.bash delete mode 100644 cryptfs/openssl_test.go create mode 100644 internal/configfile/config_file.go create mode 100644 internal/configfile/config_test.go create mode 100644 internal/configfile/config_test/.gitignore create mode 100644 internal/configfile/config_test/PlaintextNames.conf create mode 100644 internal/configfile/config_test/StrangeFeature.conf create mode 100644 internal/configfile/config_test/v1.conf create mode 100644 internal/configfile/config_test/v2.conf create mode 100644 internal/configfile/kdf.go create mode 100644 internal/configfile/kdf_test.go create mode 100644 internal/contentenc/content.go create mode 100644 internal/contentenc/content_api.go create mode 100644 internal/contentenc/content_test.go create mode 100644 internal/contentenc/file_header.go create mode 100644 internal/contentenc/intrablock.go create mode 100644 internal/contentenc/offsets.go create mode 100644 internal/cryptocore/crypto_api.go create mode 100644 internal/cryptocore/gcm_go1.4.go create mode 100644 internal/cryptocore/gcm_go1.5.go create mode 100644 internal/cryptocore/nonce.go create mode 100644 internal/cryptocore/openssl_aead.go create mode 100755 internal/cryptocore/openssl_benchmark.bash create mode 100644 internal/cryptocore/openssl_test.go create mode 100644 internal/nametransform/name_api.go create mode 100644 internal/nametransform/names_core.go create mode 100644 internal/nametransform/names_diriv.go create mode 100644 internal/nametransform/names_noiv.go create mode 100644 internal/nametransform/names_test.go create mode 100644 internal/nametransform/pad16.go create mode 100644 internal/toggledlog/log.go create mode 100644 internal/toggledlog/log_go1.4.go create mode 100644 internal/toggledlog/log_go1.5.go diff --git a/cryptfs/address_translation.go b/cryptfs/address_translation.go deleted file mode 100644 index b21cfc7..0000000 --- a/cryptfs/address_translation.go +++ /dev/null @@ -1,89 +0,0 @@ -package cryptfs - -// CryptFS methods that translate offsets between ciphertext and plaintext - -// get the block number at plain-text offset -func (be *CryptFS) PlainOffToBlockNo(plainOffset uint64) uint64 { - return plainOffset / be.plainBS -} - -// get the block number at ciphter-text offset -func (be *CryptFS) CipherOffToBlockNo(cipherOffset uint64) uint64 { - return (cipherOffset - HEADER_LEN) / be.cipherBS -} - -// get ciphertext offset of block "blockNo" -func (be *CryptFS) BlockNoToCipherOff(blockNo uint64) uint64 { - return HEADER_LEN + blockNo*be.cipherBS -} - -// get plaintext offset of block "blockNo" -func (be *CryptFS) BlockNoToPlainOff(blockNo uint64) uint64 { - return blockNo * be.plainBS -} - -// PlainSize - calculate plaintext size from ciphertext size -func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 { - - // Zero sized files stay zero-sized - if cipherSize == 0 { - return 0 - } - - if cipherSize == HEADER_LEN { - 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) - return 0 - } - - // Block number at last byte - blockNo := be.CipherOffToBlockNo(cipherSize - 1) - blockCount := blockNo + 1 - - overhead := be.BlockOverhead()*blockCount + HEADER_LEN - - return cipherSize - overhead -} - -// CipherSize - calculate ciphertext size from plaintext size -func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 { - - // Block number at last byte - blockNo := be.PlainOffToBlockNo(plainSize - 1) - blockCount := blockNo + 1 - - overhead := be.BlockOverhead()*blockCount + HEADER_LEN - - return plainSize + overhead -} - -// Split a plaintext byte range into (possibly partial) blocks -func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock { - var blocks []intraBlock - var nextBlock intraBlock - nextBlock.fs = be - - for length > 0 { - nextBlock.BlockNo = be.PlainOffToBlockNo(offset) - nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo) - - // Minimum of remaining data and remaining space in the block - nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip) - - blocks = append(blocks, nextBlock) - offset += nextBlock.Length - length -= nextBlock.Length - } - return blocks -} - -func MinUint64(x uint64, y uint64) uint64 { - if x < y { - return x - } - return y -} diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go deleted file mode 100644 index 013b82d..0000000 --- a/cryptfs/config_file.go +++ /dev/null @@ -1,188 +0,0 @@ -package cryptfs - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" -) -import "os" - -const ( - // The dot "." is not used in base64url (RFC4648), hence - // we can never clash with an encrypted file. - ConfDefaultName = "gocryptfs.conf" -) - -type ConfFile struct { - // File the config is saved to. Not exported to JSON. - filename string - // Encrypted AES key, unlocked using a password hashed with scrypt - EncryptedKey []byte - // Stores parameters for scrypt hashing (key derivation) - ScryptObject scryptKdf - // The On-Disk-Format version this filesystem uses - Version uint16 - // List of feature flags this filesystem has enabled. - // If gocryptfs encounters a feature flag it does not support, it will refuse - // mounting. This mechanism is analogous to the ext4 feature flags that are - // stored in the superblock. - FeatureFlags []string -} - -// CreateConfFile - create a new config with a random key encrypted with -// "password" and write it to "filename". -// Uses scrypt with cost parameter logN. -func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error { - var cf ConfFile - cf.filename = filename - cf.Version = HEADER_CURRENT_VERSION - - // Generate new random master key - key := RandBytes(KEY_LEN) - - // Encrypt it using the password - // This sets ScryptObject and EncryptedKey - cf.EncryptKey(key, password, logN) - - // Set feature flags - cf.FeatureFlags = append(cf.FeatureFlags, FlagGCMIV128) - if plaintextNames { - cf.FeatureFlags = append(cf.FeatureFlags, FlagPlaintextNames) - } else { - cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV) - cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames) - } - - // Write file to disk - return cf.WriteFile() -} - -// LoadConfFile - read config file from disk and decrypt the -// contained key using password. -// -// Returns the decrypted key and the ConfFile object -func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { - var cf ConfFile - cf.filename = filename - - // Read from disk - js, err := ioutil.ReadFile(filename) - if err != nil { - return nil, nil, err - } - - // Unmarshal - err = json.Unmarshal(js, &cf) - if err != nil { - Warn.Printf("Failed to unmarshal config file") - return nil, nil, err - } - - if cf.Version != HEADER_CURRENT_VERSION { - return nil, nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version) - } - - for _, flag := range cf.FeatureFlags { - if cf.isFeatureFlagKnown(flag) == false { - return nil, nil, fmt.Errorf("Unsupported feature flag %s", flag) - } - } - - // Generate derived key from password - scryptHash := cf.ScryptObject.DeriveKey(password) - - // 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) - if err != nil { - Warn.Printf("failed to unlock master key: %s", err.Error()) - Warn.Printf("Password incorrect.") - return nil, nil, err - } - - return key, &cf, nil -} - -// EncryptKey - encrypt "key" using an scrypt hash generated from "password" -// and store it in cf.EncryptedKey. -// Uses scrypt with cost parameter logN and stores the scrypt parameters in -// cf.ScryptObject. -func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) { - // Generate derived key from password - cf.ScryptObject = NewScryptKdf(logN) - 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) -} - -// WriteFile - write out config in JSON format to file "filename.tmp" -// then rename over "filename". -// This way a password change atomically replaces the file. -func (cf *ConfFile) WriteFile() error { - tmp := cf.filename + ".tmp" - // 0400 permissions: gocryptfs.conf should be kept secret and never be written to. - fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400) - if err != nil { - return err - } - js, err := json.MarshalIndent(cf, "", "\t") - if err != nil { - return err - } - _, err = fd.Write(js) - if err != nil { - return err - } - err = fd.Sync() - if err != nil { - return err - } - err = fd.Close() - if err != nil { - return err - } - err = os.Rename(tmp, cf.filename) - if err != nil { - return err - } - - return nil -} - -const ( - // Understood Feature Flags. - // Also teach isFeatureFlagKnown() about any additions and - // add it to CreateConfFile() if you want to have it enabled by default. - FlagPlaintextNames = "PlaintextNames" - FlagDirIV = "DirIV" - FlagEMENames = "EMENames" - FlagGCMIV128 = "GCMIV128" -) - -// Verify that we understand a feature flag -func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { - switch flag { - case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128: - return true - default: - return false - } -} - -// isFeatureFlagSet - is the feature flag "flagWant" enabled? -func (cf *ConfFile) IsFeatureFlagSet(flagWant string) bool { - if !cf.isFeatureFlagKnown(flagWant) { - log.Panicf("BUG: Tried to use unsupported feature flag %s", flagWant) - } - for _, flag := range cf.FeatureFlags { - if flag == flagWant { - return true - } - } - return false -} diff --git a/cryptfs/config_test.go b/cryptfs/config_test.go deleted file mode 100644 index 11599c0..0000000 --- a/cryptfs/config_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package cryptfs - -import ( - "fmt" - "testing" - "time" -) - -func TestLoadV1(t *testing.T) { - _, _, err := LoadConfFile("config_test/v1.conf", "test") - if err == nil { - t.Errorf("Outdated v1 config file must fail to load but it didn't") - } else if testing.Verbose() { - fmt.Print(err) - } -} - -// Load a known-good config file and verify that it takes at least 100ms -// (brute-force protection) -func TestLoadV2(t *testing.T) { - t1 := time.Now() - - _, _, err := LoadConfFile("config_test/v2.conf", "foo") - if err != nil { - t.Errorf("Could not load v2 config file: %v", err) - } - - elapsed := time.Since(t1) - if elapsed < 100*time.Millisecond { - t.Errorf("scrypt calculation runs too fast: %d ms", elapsed/time.Millisecond) - } -} - -func TestLoadV2PwdError(t *testing.T) { - if !testing.Verbose() { - Warn.Enabled = false - } - _, _, err := LoadConfFile("config_test/v2.conf", "wrongpassword") - if err == nil { - t.Errorf("Loading with wrong password must fail but it didn't") - } -} - -func TestLoadV2Feature(t *testing.T) { - _, _, err := LoadConfFile("config_test/PlaintextNames.conf", "test") - if err != nil { - t.Errorf("Could not load v2 PlaintextNames config file: %v", err) - } -} - -func TestLoadV2StrangeFeature(t *testing.T) { - _, _, err := LoadConfFile("config_test/StrangeFeature.conf", "test") - if err == nil { - t.Errorf("Loading unknown feature must fail but it didn't") - } else if testing.Verbose() { - fmt.Print(err) - } -} - -func TestCreateConfFile(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", "test", false, 10) - if err != nil { - t.Fatal(err) - } - _, _, err = LoadConfFile("config_test/tmp.conf", "test") - if err != nil { - t.Fatal(err) - } - -} - -func TestIsFeatureFlagKnown(t *testing.T) { - var cf ConfFile - if !cf.isFeatureFlagKnown(FlagDirIV) { - t.Errorf("This flag should be known") - } - if !cf.isFeatureFlagKnown(FlagPlaintextNames) { - t.Errorf("This flag should be known") - } - if cf.isFeatureFlagKnown("StrangeFeatureFlag") { - t.Errorf("This flag should be NOT known") - } -} diff --git a/cryptfs/config_test/.gitignore b/cryptfs/config_test/.gitignore deleted file mode 100644 index 0720169..0000000 --- a/cryptfs/config_test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tmp.conf diff --git a/cryptfs/config_test/PlaintextNames.conf b/cryptfs/config_test/PlaintextNames.conf deleted file mode 100644 index c1ff8cc..0000000 --- a/cryptfs/config_test/PlaintextNames.conf +++ /dev/null @@ -1,14 +0,0 @@ -{ - "EncryptedKey": "rG4u0argMq02V5G9Fa+gAaaHtNrj3wn7OZjP44hWOzO4yBFtn+Qn3PW4V6LMuKmGLEhyktCyWOI3K8lj", - "ScryptObject": { - "Salt": "bRjq1V63u5ML3FoTWx/GBXUhUVpTunOX3DPxS+yPjg0=", - "N": 65536, - "R": 8, - "P": 1, - "KeyLen": 32 - }, - "Version": 2, - "FeatureFlags": [ - "PlaintextNames" - ] -} diff --git a/cryptfs/config_test/StrangeFeature.conf b/cryptfs/config_test/StrangeFeature.conf deleted file mode 100644 index 6a97781..0000000 --- a/cryptfs/config_test/StrangeFeature.conf +++ /dev/null @@ -1,14 +0,0 @@ -{ - "EncryptedKey": "rG4u0argMq02V5G9Fa+gAaaHtNrj3wn7OZjP44hWOzO4yBFtn+Qn3PW4V6LMuKmGLEhyktCyWOI3K8lj", - "ScryptObject": { - "Salt": "bRjq1V63u5ML3FoTWx/GBXUhUVpTunOX3DPxS+yPjg0=", - "N": 65536, - "R": 8, - "P": 1, - "KeyLen": 32 - }, - "Version": 2, - "FeatureFlags": [ - "StrangeFeatureFlag" - ] -} diff --git a/cryptfs/config_test/v1.conf b/cryptfs/config_test/v1.conf deleted file mode 100644 index 588a25a..0000000 --- a/cryptfs/config_test/v1.conf +++ /dev/null @@ -1,11 +0,0 @@ -{ - "EncryptedKey": "t6YAvFQJvbv46c93bHQ5IZnvNz80DA9cohGoSPL/2M257LuIigow6jbr8b9HhnbDqHTCcz7aKkMDzneF", - "ScryptObject": { - "Salt": "yT4yQmmRmVNx2P0tJrUswk5SQzZaL6Z8kUteAoNJkXM=", - "N": 65536, - "R": 8, - "P": 1, - "KeyLen": 32 - }, - "Version": 1 -} diff --git a/cryptfs/config_test/v2.conf b/cryptfs/config_test/v2.conf deleted file mode 100644 index 8ef3dcf..0000000 --- a/cryptfs/config_test/v2.conf +++ /dev/null @@ -1,11 +0,0 @@ -{ - "EncryptedKey": "RvxJnZWKTBSU21+7xbl08xlZyNyUCkpIqlK8Z51TUrRiBhqqNPxbdk1WXMvmOf/YzZ85Xbyz+DGM+SDf", - "ScryptObject": { - "Salt": "2OrFRfdW/5SanbMXM3TMINmfMO6oYU9awG+NZ77V8E8=", - "N": 65536, - "R": 8, - "P": 1, - "KeyLen": 32 - }, - "Version": 2 -} diff --git a/cryptfs/content_test.go b/cryptfs/content_test.go deleted file mode 100644 index 3efa959..0000000 --- a/cryptfs/content_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package cryptfs - -import ( - "testing" -) - -type testRange struct { - offset uint64 - length uint64 -} - -func TestSplitRange(t *testing.T) { - var ranges []testRange - - ranges = append(ranges, testRange{0, 70000}, - testRange{0, 10}, - testRange{234, 6511}, - testRange{65444, 54}, - testRange{0, 1024 * 1024}, - testRange{0, 65536}, - testRange{6654, 8945}) - - key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false, true) - - for _, r := range ranges { - parts := f.ExplodePlainRange(r.offset, r.length) - var lastBlockNo uint64 = 1 << 63 - for _, p := range parts { - if p.BlockNo == lastBlockNo { - t.Errorf("Duplicate block number %d", p.BlockNo) - } - lastBlockNo = p.BlockNo - if p.Length > DEFAULT_PLAINBS || p.Skip >= DEFAULT_PLAINBS { - t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip) - } - } - } -} - -func TestCiphertextRange(t *testing.T) { - var ranges []testRange - - ranges = append(ranges, testRange{0, 70000}, - testRange{0, 10}, - testRange{234, 6511}, - testRange{65444, 54}, - testRange{6654, 8945}) - - key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false, true) - - for _, r := range ranges { - - blocks := f.ExplodePlainRange(r.offset, r.length) - alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) - skipBytes := blocks[0].Skip - - if alignedLength < r.length { - t.Errorf("alignedLength=%d is smaller than length=%d", alignedLength, r.length) - } - if (alignedOffset-HEADER_LEN)%f.cipherBS != 0 { - t.Errorf("alignedOffset=%d is not aligned", alignedOffset) - } - if r.offset%f.plainBS != 0 && skipBytes == 0 { - t.Errorf("skipBytes=0") - } - } -} - -func TestBlockNo(t *testing.T) { - key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false, true) - - b := f.CipherOffToBlockNo(788) - if b != 0 { - t.Errorf("actual: %d", b) - } - b = f.CipherOffToBlockNo(HEADER_LEN + f.cipherBS) - if b != 1 { - t.Errorf("actual: %d", b) - } - b = f.PlainOffToBlockNo(788) - if b != 0 { - t.Errorf("actual: %d", b) - } - b = f.PlainOffToBlockNo(f.plainBS) - if b != 1 { - t.Errorf("actual: %d", b) - } -} 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/cryptfs_content.go b/cryptfs/cryptfs_content.go deleted file mode 100644 index 2036e58..0000000 --- a/cryptfs/cryptfs_content.go +++ /dev/null @@ -1,129 +0,0 @@ -package cryptfs - -// File content encryption / decryption - -import ( - "bytes" - "crypto/cipher" - "crypto/md5" - "encoding/binary" - "encoding/hex" - "errors" - "os" -) - -// 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) { - cBuf := bytes.NewBuffer(ciphertext) - var err error - var pBuf bytes.Buffer - for cBuf.Len() > 0 { - cBlock := cBuf.Next(int(be.cipherBS)) - var pBlock []byte - pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileId) - if err != nil { - break - } - pBuf.Write(pBlock) - firstBlockNo++ - } - return pBuf.Bytes(), err -} - -// DecryptBlock - Verify and decrypt GCM block -// -// 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) { - - // Empty block? - if len(ciphertext) == 0 { - return ciphertext, nil - } - - // All-zero block? - if bytes.Equal(ciphertext, be.allZeroBlock) { - 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)) - return nil, errors.New("Block is too short") - } - - // Extract nonce - nonce := ciphertext[:be.gcmIVLen] - ciphertextOrig := ciphertext - ciphertext = ciphertext[be.gcmIVLen:] - - // 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) - - if err != nil { - Warn.Printf("DecryptBlock: %s, len=%d, md5=%s", err.Error(), len(ciphertextOrig), md5sum(ciphertextOrig)) - Debug.Println(hex.Dump(ciphertextOrig)) - return nil, err - } - - return plaintext, nil -} - -// encryptBlock - Encrypt and add IV and MAC -func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte { - - // Empty block? - if len(plaintext) == 0 { - return plaintext - } - - // Get fresh nonce - nonce := be.gcmIVGen.Get() - - // Authenticate block with block number and file ID - aData := make([]byte, 8) - binary.BigEndian.PutUint64(aData, blockNo) - aData = append(aData, fileID...) - - // Encrypt plaintext and append to nonce - ciphertext := be.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 { - - // Make block of maximum size - out := make([]byte, be.plainBS) - - // Copy old and new data into it - copy(out, oldData) - l := len(newData) - copy(out[offset:offset+l], newData) - - // Crop to length - outLen := len(oldData) - newLen := offset + len(newData) - if outLen < newLen { - outLen = newLen - } - return out[0:outLen] -} diff --git a/cryptfs/file_header.go b/cryptfs/file_header.go deleted file mode 100644 index fe4ac56..0000000 --- a/cryptfs/file_header.go +++ /dev/null @@ -1,56 +0,0 @@ -package cryptfs - -// Per-file header -// -// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ] - -import ( - "encoding/binary" - "fmt" -) - -const ( - HEADER_CURRENT_VERSION = 2 // Current on-disk-format version - 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 -) - -type FileHeader struct { - Version uint16 - Id []byte -} - -// Pack - serialize fileHeader object -func (h *FileHeader) Pack() []byte { - if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION { - panic("FileHeader object not properly initialized") - } - buf := make([]byte, HEADER_LEN) - binary.BigEndian.PutUint16(buf[0:HEADER_VERSION_LEN], h.Version) - copy(buf[HEADER_VERSION_LEN:], h.Id) - return buf - -} - -// ParseHeader - parse "buf" into fileHeader object -func ParseHeader(buf []byte) (*FileHeader, error) { - if len(buf) != HEADER_LEN { - return nil, fmt.Errorf("ParseHeader: invalid length: got %d, want %d", len(buf), HEADER_LEN) - } - 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) - } - h.Id = buf[HEADER_VERSION_LEN:] - return &h, nil -} - -// 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) - return &h -} diff --git a/cryptfs/gcm_go1.4.go b/cryptfs/gcm_go1.4.go deleted file mode 100644 index 0a2ff49..0000000 --- a/cryptfs/gcm_go1.4.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build !go1.5 -// = go 1.4 or lower - -package cryptfs - -import ( - "crypto/cipher" - "fmt" -) - -// goGCMWrapper - This wrapper makes sure gocryptfs can be compiled on Go -// versions 1.4 and lower that lack NewGCMWithNonceSize(). -// 128 bit GCM IVs will not work when using built-in Go crypto, obviously, when -// compiled on 1.4. -func goGCMWrapper(bc cipher.Block, nonceSize int) (cipher.AEAD, error) { - if nonceSize != 12 { - Warn.Printf("128 bit GCM IVs are not supported by Go 1.4 and lower.") - Warn.Printf("Please use openssl crypto or recompile using a newer Go runtime.") - return nil, fmt.Errorf("128 bit GCM IVs are not supported by Go 1.4 and lower") - } - return cipher.NewGCM(bc) -} diff --git a/cryptfs/gcm_go1.5.go b/cryptfs/gcm_go1.5.go deleted file mode 100644 index c469357..0000000 --- a/cryptfs/gcm_go1.5.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build go1.5 -// = go 1.5 or higher - -package cryptfs - -import ( - "crypto/cipher" -) - -// goGCMWrapper - This wrapper makes sure gocryptfs can be compiled on Go -// versions 1.4 and lower that lack NewGCMWithNonceSize(). -// 128 bit GCM IVs will not work when using built-in Go crypto, obviously, when -// compiled on 1.4. -func goGCMWrapper(bc cipher.Block, nonceSize int) (cipher.AEAD, error) { - return cipher.NewGCMWithNonceSize(bc, nonceSize) -} diff --git a/cryptfs/intrablock.go b/cryptfs/intrablock.go deleted file mode 100644 index faff471..0000000 --- a/cryptfs/intrablock.go +++ /dev/null @@ -1,51 +0,0 @@ -package cryptfs - -// 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 -} - -// isPartial - is the block partial? This means we have to do read-modify-write. -func (ib *intraBlock) IsPartial() bool { - if ib.Skip > 0 || ib.Length < ib.fs.plainBS { - return true - } - return false -} - -// CiphertextRange - get byte range in ciphertext file corresponding to BlockNo -// (complete block) -func (ib *intraBlock) CiphertextRange() (offset uint64, length uint64) { - return ib.fs.BlockNoToCipherOff(ib.BlockNo), ib.fs.cipherBS -} - -// PlaintextRange - get byte range in plaintext corresponding to BlockNo -// (complete block) -func (ib *intraBlock) PlaintextRange() (offset uint64, length uint64) { - return ib.fs.BlockNoToPlainOff(ib.BlockNo), ib.fs.plainBS -} - -// CropBlock - crop a potentially larger plaintext block down to the relevant part -func (ib *intraBlock) CropBlock(d []byte) []byte { - lenHave := len(d) - lenWant := int(ib.Skip + ib.Length) - if lenHave < lenWant { - return d[ib.Skip:lenHave] - } - return d[ib.Skip:lenWant] -} - -// Ciphertext range corresponding to the sum of all "blocks" (complete blocks) -func (ib *intraBlock) JointCiphertextRange(blocks []intraBlock) (offset uint64, length uint64) { - firstBlock := blocks[0] - lastBlock := blocks[len(blocks)-1] - - offset = ib.fs.BlockNoToCipherOff(firstBlock.BlockNo) - offsetLast := ib.fs.BlockNoToCipherOff(lastBlock.BlockNo) - length = offsetLast + ib.fs.cipherBS - offset - - return offset, length -} diff --git a/cryptfs/kdf.go b/cryptfs/kdf.go deleted file mode 100644 index e958413..0000000 --- a/cryptfs/kdf.go +++ /dev/null @@ -1,54 +0,0 @@ -package cryptfs - -import ( - "fmt" - "golang.org/x/crypto/scrypt" - "math" - "os" -) - -const ( - // 1 << 16 uses 64MB of memory, - // takes 4 seconds on my Atom Z3735F netbook - SCRYPT_DEFAULT_LOGN = 16 -) - -type scryptKdf struct { - Salt []byte - N int - R int - P int - KeyLen int -} - -func NewScryptKdf(logN int) scryptKdf { - var s scryptKdf - s.Salt = RandBytes(KEY_LEN) - if logN <= 0 { - s.N = 1 << SCRYPT_DEFAULT_LOGN - } else { - if logN < 10 { - fmt.Println("Error: scryptn below 10 is too low to make sense. Aborting.") - os.Exit(1) - } - s.N = 1 << uint32(logN) - } - s.R = 8 // Always 8 - s.P = 1 // Always 1 - s.KeyLen = KEY_LEN - return s -} - -func (s *scryptKdf) DeriveKey(pw string) []byte { - k, err := scrypt.Key([]byte(pw), s.Salt, s.N, s.R, s.P, s.KeyLen) - if err != nil { - panic(fmt.Sprintf("DeriveKey failed: %s", err.Error())) - } - return k -} - -// LogN - N is saved as 2^LogN, but LogN is much easier to work with. -// This function gives you LogN = Log2(N). -func (s *scryptKdf) LogN() int { - return int(math.Log2(float64(s.N)) + 0.5) -} diff --git a/cryptfs/kdf_test.go b/cryptfs/kdf_test.go deleted file mode 100644 index 4d909ea..0000000 --- a/cryptfs/kdf_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package cryptfs - -import ( - "testing" -) - -/* -Results on a 2.7GHz Pentium G630: - -gocryptfs/cryptfs$ go test -bench=. -PASS -BenchmarkScrypt10-2 300 6021435 ns/op ... 6ms -BenchmarkScrypt11-2 100 11861460 ns/op -BenchmarkScrypt12-2 100 23420822 ns/op -BenchmarkScrypt13-2 30 47666518 ns/op -BenchmarkScrypt14-2 20 92561590 ns/op ... 92ms -BenchmarkScrypt15-2 10 183971593 ns/op -BenchmarkScrypt16-2 3 368506365 ns/op -BenchmarkScrypt17-2 2 755502608 ns/op ... 755ms -ok github.com/rfjakob/gocryptfs/cryptfs 18.772s -*/ - -func benchmarkScryptN(n int, b *testing.B) { - kdf := NewScryptKdf(n) - for i := 0; i < b.N; i++ { - kdf.DeriveKey("test") - } -} - -func BenchmarkScrypt10(b *testing.B) { - benchmarkScryptN(10, b) -} - -func BenchmarkScrypt11(b *testing.B) { - benchmarkScryptN(11, b) -} - -func BenchmarkScrypt12(b *testing.B) { - benchmarkScryptN(12, b) -} - -func BenchmarkScrypt13(b *testing.B) { - benchmarkScryptN(13, b) -} - -func BenchmarkScrypt14(b *testing.B) { - benchmarkScryptN(14, b) -} - -func BenchmarkScrypt15(b *testing.B) { - benchmarkScryptN(15, b) -} - -func BenchmarkScrypt16(b *testing.B) { - benchmarkScryptN(16, b) -} - -func BenchmarkScrypt17(b *testing.B) { - benchmarkScryptN(17, b) -} diff --git a/cryptfs/log.go b/cryptfs/log.go deleted file mode 100644 index 19d8b51..0000000 --- a/cryptfs/log.go +++ /dev/null @@ -1,61 +0,0 @@ -package cryptfs - -import ( - "encoding/json" - "fmt" - "log" - "os" -) - -func JSONDump(obj interface{}) string { - b, err := json.MarshalIndent(obj, "", "\t") - if err != nil { - return err.Error() - } else { - return string(b) - } -} - -// toggledLogger - a Logger than can be enabled and disabled -type toggledLogger struct { - // Enable or disable output - Enabled bool - // Panic after logging a message, useful in regression tests - PanicAfter bool - *log.Logger -} - -func (l *toggledLogger) Printf(format string, v ...interface{}) { - if !l.Enabled { - return - } - l.Logger.Printf(format, v...) - if l.PanicAfter { - panic("PanicAfter: " + fmt.Sprintf(format, v...)) - } -} -func (l *toggledLogger) Println(v ...interface{}) { - if !l.Enabled { - return - } - l.Logger.Println(v...) - if l.PanicAfter { - panic("PanicAfter: " + fmt.Sprintln(v...)) - } -} - -// As defined by http://elinux.org/Debugging_by_printing#Log_Levels -// Debug messages -var Debug *toggledLogger - -// Informational message e.g. startup information -var Info *toggledLogger - -// A warning, meaning nothing serious by itself but might indicate problems -var Warn *toggledLogger - -func init() { - Debug = &toggledLogger{false, false, log.New(os.Stdout, "", 0)} - Info = &toggledLogger{true, false, log.New(os.Stdout, "", 0)} - Warn = &toggledLogger{true, false, log.New(os.Stderr, "", 0)} -} diff --git a/cryptfs/log_go1.4.go b/cryptfs/log_go1.4.go deleted file mode 100644 index 4b91bad..0000000 --- a/cryptfs/log_go1.4.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !go1.5 -// = go 1.4 or lower - -package cryptfs - -import ( - "log/syslog" -) - -func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { - Debug.Printf("Cannot switch to syslog - need Go 1.5 or higher") -} diff --git a/cryptfs/log_go1.5.go b/cryptfs/log_go1.5.go deleted file mode 100644 index 8daae9c..0000000 --- a/cryptfs/log_go1.5.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build go1.5 -// = go 1.5 or higher - -package cryptfs - -import ( - "log/syslog" -) - -func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { - w, err := syslog.New(p, PROGRAM_NAME) - if err != nil { - Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err) - } else { - l.SetOutput(w) - } -} 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/cryptfs/names_diriv.go b/cryptfs/names_diriv.go deleted file mode 100644 index 276316c..0000000 --- a/cryptfs/names_diriv.go +++ /dev/null @@ -1,140 +0,0 @@ -package cryptfs - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" -) - -// A simple one-entry DirIV cache -type dirIVCache struct { - // Invalidated? - cleared bool - // The DirIV - iv []byte - // Directory the DirIV belongs to - dir string - // Ecrypted version of "dir" - translatedDir string - // Synchronisation - lock sync.RWMutex -} - -// lookup - fetch entry for "dir" from the cache -func (c *dirIVCache) lookup(dir string) (bool, []byte, string) { - c.lock.RLock() - defer c.lock.RUnlock() - if !c.cleared && c.dir == dir { - return true, c.iv, c.translatedDir - } - return false, nil, "" -} - -// store - write entry for "dir" into the caches -func (c *dirIVCache) store(dir string, iv []byte, translatedDir string) { - c.lock.Lock() - defer c.lock.Unlock() - c.cleared = false - c.iv = iv - c.dir = dir - c.translatedDir = translatedDir -} - -func (c *dirIVCache) Clear() { - c.lock.Lock() - defer c.lock.Unlock() - c.cleared = true -} - -// 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) - 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) - } else { - // This should not happen - Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr) - } - return nil, readErr - } - if len(iv) != DIRIV_LEN { - return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) - } - return iv, nil -} - -// WriteDirIV - create diriv file inside "dir" (absolute ciphertext path) -// 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) - // 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) { - // Empty string means root directory - if plainPath == "" { - return plainPath, nil - } - // Check if the DirIV is cached - parentDir := filepath.Dir(plainPath) - found, iv, cParentDir := be.DirIVCache.lookup(parentDir) - if found { - //fmt.Print("h") - baseName := filepath.Base(plainPath) - cBaseName := be.encryptName(baseName, iv, eme) - cipherPath = cParentDir + "/" + cBaseName - return cipherPath, nil - } - // Walk the directory tree - var wd = rootDir - var encryptedNames []string - plainNames := strings.Split(plainPath, "/") - for _, plainName := range plainNames { - iv, err = be.ReadDirIV(wd) - if err != nil { - return "", err - } - encryptedName := be.encryptName(plainName, iv, eme) - encryptedNames = append(encryptedNames, encryptedName) - wd = filepath.Join(wd, encryptedName) - } - // Cache the final DirIV - cipherPath = strings.Join(encryptedNames, "/") - cParentDir = filepath.Dir(cipherPath) - be.DirIVCache.store(parentDir, iv, cParentDir) - return cipherPath, nil -} - -// DecryptPathDirIV - decrypt path using EME with DirIV -func (be *CryptFS) 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) - for _, encryptedName := range encryptedNames { - iv, err := be.ReadDirIV(wd) - if err != nil { - return "", err - } - plainName, err := be.decryptName(encryptedName, iv, eme) - if err != nil { - return "", err - } - plainNames = append(plainNames, plainName) - wd = filepath.Join(wd, encryptedName) - } - return filepath.Join(plainNames...), nil -} diff --git a/cryptfs/names_noiv.go b/cryptfs/names_noiv.go deleted file mode 100644 index 7eed4b8..0000000 --- a/cryptfs/names_noiv.go +++ /dev/null @@ -1,63 +0,0 @@ -package cryptfs - -import ( - "strings" -) - -const ( - OpEncrypt = iota - OpDecrypt -) - -// 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) { - plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt) - return plainPath, 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) { - 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) { - var err error - - // Empty string means root directory - if path == "" { - return path, err - } - - zeroIV := make([]byte, DIRIV_LEN) - - // Run operation on each path component - var translatedParts []string - parts := strings.Split(path, "/") - for _, part := range parts { - if part == "" { - // This happens on "/foo/bar/" on the front and on the end. - // Don't panic. - translatedParts = append(translatedParts, "") - continue - } - var newPart string - if op == OpEncrypt { - newPart = be.encryptName(part, zeroIV, false) - } else { - newPart, err = be.decryptName(part, zeroIV, false) - if err != nil { - return "", err - } - } - translatedParts = append(translatedParts, newPart) - } - - return strings.Join(translatedParts, "/"), err -} diff --git a/cryptfs/names_test.go b/cryptfs/names_test.go deleted file mode 100644 index 0207f0a..0000000 --- a/cryptfs/names_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package cryptfs - -import ( - "bytes" - "testing" -) - -func TestEncryptPathNoIV(t *testing.T) { - var s []string - s = append(s, "foo") - s = append(s, "foo12312312312312312313123123123") - s = append(s, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890") - - key := make([]byte, KEY_LEN) - fs := NewCryptFS(key, true, false, true) - - for _, n := range s { - c := fs.EncryptPathNoIV(n) - d, err := fs.DecryptPathNoIV(c) - if err != nil { - t.Errorf("Got error from DecryptPathNoIV: %s", err) - } - if d != n { - t.Errorf("Content mismatch, n != d: n=%s c=%s d=%s", n, c, d) - } - } -} - -func TestPad16(t *testing.T) { - var s [][]byte - s = append(s, []byte("foo")) - s = append(s, []byte("12345678901234567")) - s = append(s, []byte("12345678901234567abcdefg")) - - key := make([]byte, KEY_LEN) - fs := NewCryptFS(key, true, false, true) - - for i := range s { - orig := s[i] - padded := fs.pad16(orig) - if len(padded) <= len(orig) { - t.Errorf("Padded length not bigger than orig: %d", len(padded)) - } - if len(padded)%16 != 0 { - t.Errorf("Length is not aligend: %d", len(padded)) - } - unpadded, err := fs.unPad16(padded) - if err != nil { - t.Error("unPad16 returned error:", err) - } - if len(unpadded) != len(orig) { - t.Errorf("Size mismatch: orig=%d unpadded=%d", len(s[i]), len(unpadded)) - } - if !bytes.Equal(orig, unpadded) { - t.Error("Content mismatch orig vs unpadded") - } - } -} diff --git a/cryptfs/nonce.go b/cryptfs/nonce.go deleted file mode 100644 index be777fc..0000000 --- a/cryptfs/nonce.go +++ /dev/null @@ -1,42 +0,0 @@ -package cryptfs - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "encoding/hex" - "fmt" -) - -// Get "n" random bytes from /dev/urandom or panic -func RandBytes(n int) []byte { - b := make([]byte, n) - _, err := rand.Read(b) - if err != nil { - panic("Failed to read random bytes: " + err.Error()) - } - return b -} - -// Return a secure random uint64 -func RandUint64() uint64 { - b := RandBytes(8) - return binary.BigEndian.Uint64(b) -} - -type nonceGenerator struct { - lastNonce []byte - nonceLen int // bytes -} - -// Get a random 96 bit nonce -func (n *nonceGenerator) Get() []byte { - nonce := RandBytes(n.nonceLen) - 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) - } - n.lastNonce = nonce - return nonce -} diff --git a/cryptfs/openssl_aead.go b/cryptfs/openssl_aead.go deleted file mode 100644 index 5d38d38..0000000 --- a/cryptfs/openssl_aead.go +++ /dev/null @@ -1,100 +0,0 @@ -package cryptfs - -// Implements cipher.AEAD with OpenSSL backend - -import ( - "bytes" - "github.com/spacemonkeygo/openssl" -) - -// Supports all nonce sizes -type opensslGCM struct { - key []byte -} - -func (be opensslGCM) Overhead() int { - return AUTH_TAG_LEN -} - -func (be opensslGCM) NonceSize() int { - // We support any nonce size - return -1 -} - -// Seal encrypts and authenticates plaintext, authenticates the -// additional data and appends the result to dst, returning the updated -// slice. opensslGCM supports any nonce size. -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) - // Output will be appended to dst - cipherBuf.Write(dst) - - ectx, err := openssl.NewGCMEncryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce) - if err != nil { - panic(err) - } - err = ectx.ExtraData(data) - if err != nil { - panic(err) - } - part, err := ectx.EncryptUpdate(plaintext) - if err != nil { - panic(err) - } - cipherBuf.Write(part) - part, err = ectx.EncryptFinal() - if err != nil { - panic(err) - } - cipherBuf.Write(part) - part, err = ectx.GetTag() - if err != nil { - panic(err) - } - cipherBuf.Write(part) - - return cipherBuf.Bytes() -} - -// Open decrypts and authenticates ciphertext, authenticates the -// additional data and, if successful, appends the resulting plaintext -// to dst, returning the updated slice. The nonce must be NonceSize() -// bytes long and both it and the additional data must match the -// value passed to Seal. -// -// The ciphertext and dst may alias exactly or not at all. -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] - plainBuf := bytes.NewBuffer(dst) - - dctx, err := openssl.NewGCMDecryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce) - if err != nil { - return nil, err - } - err = dctx.ExtraData(data) - if err != nil { - return nil, err - } - part, err := dctx.DecryptUpdate(ciphertext) - if err != nil { - return nil, err - } - plainBuf.Write(part) - err = dctx.SetTag(tag) - if err != nil { - return nil, err - } - part, err = dctx.DecryptFinal() - if err != nil { - return nil, err - } - plainBuf.Write(part) - - return plainBuf.Bytes(), nil -} diff --git a/cryptfs/openssl_benchmark.bash b/cryptfs/openssl_benchmark.bash deleted file mode 100755 index df29628..0000000 --- a/cryptfs/openssl_benchmark.bash +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -go test -run NONE -bench BenchmarkEnc diff --git a/cryptfs/openssl_test.go b/cryptfs/openssl_test.go deleted file mode 100644 index aecee94..0000000 --- a/cryptfs/openssl_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package cryptfs - -// Benchmark go built-int GCM against spacemonkey openssl bindings -// -// Note: The benchmarks in this file supersede the ones in the openssl_benchmark -// directory as they use the same code paths that gocryptfs actually uses. -// -// Run benchmark: -// go test -bench Enc - -import ( - "crypto/aes" - "testing" -) - -func benchmarkGoEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { - b.SetBytes(int64(len(plaintext))) - aes, err := aes.NewCipher(key[:]) - if err != nil { - b.Fatal(err) - } - aesgcm, err := goGCMWrapper(aes, len(nonce)) - if err != nil { - b.Fatal(err) - } - // This would be fileID + blockNo - aData := make([]byte, 24) - b.ResetTimer() - for i := 0; i < b.N; i++ { - // Encrypt plaintext and append to nonce - ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) - } - return ciphertext -} - -func benchmarkOpensslEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { - b.SetBytes(int64(len(plaintext))) - var aesgcm opensslGCM - aesgcm.key = key - // This would be fileID + blockNo - aData := make([]byte, 24) - for i := 0; i < b.N; i++ { - // Encrypt plaintext and append to nonce - ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) - } - return ciphertext -} - -func BenchmarkEnc_Go_4k_AES256_nonce96(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkGoEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_Go_4k_AES256_nonce128(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 128/8) - benchmarkGoEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_OpenSSL_4k_AES256_nonce96(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkOpensslEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_OpenSSL_4k_AES256_nonce128(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkOpensslEnc(b, plaintext, key, nonce) -} 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/internal/configfile/config_file.go b/internal/configfile/config_file.go new file mode 100644 index 0000000..0128acc --- /dev/null +++ b/internal/configfile/config_file.go @@ -0,0 +1,195 @@ +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" + +const ( + // The dot "." is not used in base64url (RFC4648), hence + // we can never clash with an encrypted file. + ConfDefaultName = "gocryptfs.conf" +) + +type ConfFile struct { + // File the config is saved to. Not exported to JSON. + filename string + // Encrypted AES key, unlocked using a password hashed with scrypt + EncryptedKey []byte + // Stores parameters for scrypt hashing (key derivation) + ScryptObject scryptKdf + // The On-Disk-Format version this filesystem uses + Version uint16 + // List of feature flags this filesystem has enabled. + // If gocryptfs encounters a feature flag it does not support, it will refuse + // mounting. This mechanism is analogous to the ext4 feature flags that are + // stored in the superblock. + FeatureFlags []string +} + +// CreateConfFile - create a new config with a random key encrypted with +// "password" and write it to "filename". +// Uses scrypt with cost parameter logN. +func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error { + var cf ConfFile + cf.filename = filename + cf.Version = contentenc.CurrentVersion + + // Generate new random master key + key := cryptocore.RandBytes(cryptocore.KeyLen) + + // Encrypt it using the password + // This sets ScryptObject and EncryptedKey + cf.EncryptKey(key, password, logN) + + // Set feature flags + cf.FeatureFlags = append(cf.FeatureFlags, FlagGCMIV128) + if plaintextNames { + cf.FeatureFlags = append(cf.FeatureFlags, FlagPlaintextNames) + } else { + cf.FeatureFlags = append(cf.FeatureFlags, FlagDirIV) + cf.FeatureFlags = append(cf.FeatureFlags, FlagEMENames) + } + + // Write file to disk + return cf.WriteFile() +} + +// LoadConfFile - read config file from disk and decrypt the +// contained key using password. +// +// Returns the decrypted key and the ConfFile object +func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { + var cf ConfFile + cf.filename = filename + + // Read from disk + js, err := ioutil.ReadFile(filename) + if err != nil { + return nil, nil, err + } + + // Unmarshal + err = json.Unmarshal(js, &cf) + if err != nil { + toggledlog.Warn.Printf("Failed to unmarshal config file") + return nil, nil, err + } + + if cf.Version != contentenc.CurrentVersion { + return nil, nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version) + } + + for _, flag := range cf.FeatureFlags { + if cf.isFeatureFlagKnown(flag) == false { + return nil, nil, fmt.Errorf("Unsupported feature flag %s", flag) + } + } + + // Generate derived key from password + scryptHash := cf.ScryptObject.DeriveKey(password) + + // 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 + cc := cryptocore.New(scryptHash, false, false) + ce := contentenc.New(cc, 4096) + + key, err := ce.DecryptBlock(cf.EncryptedKey, 0, nil) + if err != nil { + toggledlog.Warn.Printf("failed to unlock master key: %s", err.Error()) + toggledlog.Warn.Printf("Password incorrect.") + return nil, nil, err + } + + return key, &cf, nil +} + +// EncryptKey - encrypt "key" using an scrypt hash generated from "password" +// and store it in cf.EncryptedKey. +// Uses scrypt with cost parameter logN and stores the scrypt parameters in +// cf.ScryptObject. +func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) { + // Generate derived key from password + cf.ScryptObject = NewScryptKdf(logN) + scryptHash := cf.ScryptObject.DeriveKey(password) + + // Lock master key using password-based key + 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" +// then rename over "filename". +// This way a password change atomically replaces the file. +func (cf *ConfFile) WriteFile() error { + tmp := cf.filename + ".tmp" + // 0400 permissions: gocryptfs.conf should be kept secret and never be written to. + fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400) + if err != nil { + return err + } + js, err := json.MarshalIndent(cf, "", "\t") + if err != nil { + return err + } + _, err = fd.Write(js) + if err != nil { + return err + } + err = fd.Sync() + if err != nil { + return err + } + err = fd.Close() + if err != nil { + return err + } + err = os.Rename(tmp, cf.filename) + if err != nil { + return err + } + + return nil +} + +const ( + // Understood Feature Flags. + // Also teach isFeatureFlagKnown() about any additions and + // add it to CreateConfFile() if you want to have it enabled by default. + FlagPlaintextNames = "PlaintextNames" + FlagDirIV = "DirIV" + FlagEMENames = "EMENames" + FlagGCMIV128 = "GCMIV128" +) + +// Verify that we understand a feature flag +func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { + switch flag { + case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128: + return true + default: + return false + } +} + +// isFeatureFlagSet - is the feature flag "flagWant" enabled? +func (cf *ConfFile) IsFeatureFlagSet(flagWant string) bool { + if !cf.isFeatureFlagKnown(flagWant) { + log.Panicf("BUG: Tried to use unsupported feature flag %s", flagWant) + } + for _, flag := range cf.FeatureFlags { + if flag == flagWant { + return true + } + } + return false +} diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go new file mode 100644 index 0000000..6606d22 --- /dev/null +++ b/internal/configfile/config_test.go @@ -0,0 +1,83 @@ +package configfile + +import ( + "fmt" + "testing" + "time" +) + +func TestLoadV1(t *testing.T) { + _, _, err := LoadConfFile("config_test/v1.conf", "test") + if err == nil { + t.Errorf("Outdated v1 config file must fail to load but it didn't") + } else if testing.Verbose() { + fmt.Print(err) + } +} + +// Load a known-good config file and verify that it takes at least 100ms +// (brute-force protection) +func TestLoadV2(t *testing.T) { + t1 := time.Now() + + _, _, err := LoadConfFile("config_test/v2.conf", "foo") + if err != nil { + t.Errorf("Could not load v2 config file: %v", err) + } + + elapsed := time.Since(t1) + if elapsed < 100*time.Millisecond { + t.Errorf("scrypt calculation runs too fast: %d ms", elapsed/time.Millisecond) + } +} + +func TestLoadV2PwdError(t *testing.T) { + if !testing.Verbose() { + Warn.Enabled = false + } + _, _, err := LoadConfFile("config_test/v2.conf", "wrongpassword") + if err == nil { + t.Errorf("Loading with wrong password must fail but it didn't") + } +} + +func TestLoadV2Feature(t *testing.T) { + _, _, err := LoadConfFile("config_test/PlaintextNames.conf", "test") + if err != nil { + t.Errorf("Could not load v2 PlaintextNames config file: %v", err) + } +} + +func TestLoadV2StrangeFeature(t *testing.T) { + _, _, err := LoadConfFile("config_test/StrangeFeature.conf", "test") + if err == nil { + t.Errorf("Loading unknown feature must fail but it didn't") + } else if testing.Verbose() { + fmt.Print(err) + } +} + +func TestCreateConfFile(t *testing.T) { + err := CreateConfFile("config_test/tmp.conf", "test", false, 10) + if err != nil { + t.Fatal(err) + } + _, _, err = LoadConfFile("config_test/tmp.conf", "test") + if err != nil { + t.Fatal(err) + } + +} + +func TestIsFeatureFlagKnown(t *testing.T) { + var cf ConfFile + if !cf.isFeatureFlagKnown(FlagDirIV) { + t.Errorf("This flag should be known") + } + if !cf.isFeatureFlagKnown(FlagPlaintextNames) { + t.Errorf("This flag should be known") + } + if cf.isFeatureFlagKnown("StrangeFeatureFlag") { + t.Errorf("This flag should be NOT known") + } +} diff --git a/internal/configfile/config_test/.gitignore b/internal/configfile/config_test/.gitignore new file mode 100644 index 0000000..0720169 --- /dev/null +++ b/internal/configfile/config_test/.gitignore @@ -0,0 +1 @@ +tmp.conf diff --git a/internal/configfile/config_test/PlaintextNames.conf b/internal/configfile/config_test/PlaintextNames.conf new file mode 100644 index 0000000..c1ff8cc --- /dev/null +++ b/internal/configfile/config_test/PlaintextNames.conf @@ -0,0 +1,14 @@ +{ + "EncryptedKey": "rG4u0argMq02V5G9Fa+gAaaHtNrj3wn7OZjP44hWOzO4yBFtn+Qn3PW4V6LMuKmGLEhyktCyWOI3K8lj", + "ScryptObject": { + "Salt": "bRjq1V63u5ML3FoTWx/GBXUhUVpTunOX3DPxS+yPjg0=", + "N": 65536, + "R": 8, + "P": 1, + "KeyLen": 32 + }, + "Version": 2, + "FeatureFlags": [ + "PlaintextNames" + ] +} diff --git a/internal/configfile/config_test/StrangeFeature.conf b/internal/configfile/config_test/StrangeFeature.conf new file mode 100644 index 0000000..6a97781 --- /dev/null +++ b/internal/configfile/config_test/StrangeFeature.conf @@ -0,0 +1,14 @@ +{ + "EncryptedKey": "rG4u0argMq02V5G9Fa+gAaaHtNrj3wn7OZjP44hWOzO4yBFtn+Qn3PW4V6LMuKmGLEhyktCyWOI3K8lj", + "ScryptObject": { + "Salt": "bRjq1V63u5ML3FoTWx/GBXUhUVpTunOX3DPxS+yPjg0=", + "N": 65536, + "R": 8, + "P": 1, + "KeyLen": 32 + }, + "Version": 2, + "FeatureFlags": [ + "StrangeFeatureFlag" + ] +} diff --git a/internal/configfile/config_test/v1.conf b/internal/configfile/config_test/v1.conf new file mode 100644 index 0000000..588a25a --- /dev/null +++ b/internal/configfile/config_test/v1.conf @@ -0,0 +1,11 @@ +{ + "EncryptedKey": "t6YAvFQJvbv46c93bHQ5IZnvNz80DA9cohGoSPL/2M257LuIigow6jbr8b9HhnbDqHTCcz7aKkMDzneF", + "ScryptObject": { + "Salt": "yT4yQmmRmVNx2P0tJrUswk5SQzZaL6Z8kUteAoNJkXM=", + "N": 65536, + "R": 8, + "P": 1, + "KeyLen": 32 + }, + "Version": 1 +} diff --git a/internal/configfile/config_test/v2.conf b/internal/configfile/config_test/v2.conf new file mode 100644 index 0000000..8ef3dcf --- /dev/null +++ b/internal/configfile/config_test/v2.conf @@ -0,0 +1,11 @@ +{ + "EncryptedKey": "RvxJnZWKTBSU21+7xbl08xlZyNyUCkpIqlK8Z51TUrRiBhqqNPxbdk1WXMvmOf/YzZ85Xbyz+DGM+SDf", + "ScryptObject": { + "Salt": "2OrFRfdW/5SanbMXM3TMINmfMO6oYU9awG+NZ77V8E8=", + "N": 65536, + "R": 8, + "P": 1, + "KeyLen": 32 + }, + "Version": 2 +} diff --git a/internal/configfile/kdf.go b/internal/configfile/kdf.go new file mode 100644 index 0000000..f1a7a40 --- /dev/null +++ b/internal/configfile/kdf.go @@ -0,0 +1,57 @@ +package configfile + +import ( + "fmt" + "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 + ScryptDefaultLogN = 16 +) + +type scryptKdf struct { + Salt []byte + N int + R int + P int + KeyLen int +} + +func NewScryptKdf(logN int) scryptKdf { + var s scryptKdf + s.Salt = cryptocore.RandBytes(cryptocore.KeyLen) + if logN <= 0 { + s.N = 1 << ScryptDefaultLogN + } else { + if logN < 10 { + fmt.Println("Error: scryptn below 10 is too low to make sense. Aborting.") + os.Exit(1) + } + s.N = 1 << uint32(logN) + } + s.R = 8 // Always 8 + s.P = 1 // Always 1 + s.KeyLen = cryptocore.KeyLen + return s +} + +func (s *scryptKdf) DeriveKey(pw string) []byte { + k, err := scrypt.Key([]byte(pw), s.Salt, s.N, s.R, s.P, s.KeyLen) + if err != nil { + panic(fmt.Sprintf("DeriveKey failed: %s", err.Error())) + } + return k +} + +// LogN - N is saved as 2^LogN, but LogN is much easier to work with. +// This function gives you LogN = Log2(N). +func (s *scryptKdf) LogN() int { + return int(math.Log2(float64(s.N)) + 0.5) +} diff --git a/internal/configfile/kdf_test.go b/internal/configfile/kdf_test.go new file mode 100644 index 0000000..bc095ab --- /dev/null +++ b/internal/configfile/kdf_test.go @@ -0,0 +1,60 @@ +package configfile + +import ( + "testing" +) + +/* +Results on a 2.7GHz Pentium G630: + +gocryptfs/cryptfs$ go test -bench=. +PASS +BenchmarkScrypt10-2 300 6021435 ns/op ... 6ms +BenchmarkScrypt11-2 100 11861460 ns/op +BenchmarkScrypt12-2 100 23420822 ns/op +BenchmarkScrypt13-2 30 47666518 ns/op +BenchmarkScrypt14-2 20 92561590 ns/op ... 92ms +BenchmarkScrypt15-2 10 183971593 ns/op +BenchmarkScrypt16-2 3 368506365 ns/op +BenchmarkScrypt17-2 2 755502608 ns/op ... 755ms +ok github.com/rfjakob/gocryptfs/cryptfs 18.772s +*/ + +func benchmarkScryptN(n int, b *testing.B) { + kdf := NewScryptKdf(n) + for i := 0; i < b.N; i++ { + kdf.DeriveKey("test") + } +} + +func BenchmarkScrypt10(b *testing.B) { + benchmarkScryptN(10, b) +} + +func BenchmarkScrypt11(b *testing.B) { + benchmarkScryptN(11, b) +} + +func BenchmarkScrypt12(b *testing.B) { + benchmarkScryptN(12, b) +} + +func BenchmarkScrypt13(b *testing.B) { + benchmarkScryptN(13, b) +} + +func BenchmarkScrypt14(b *testing.B) { + benchmarkScryptN(14, b) +} + +func BenchmarkScrypt15(b *testing.B) { + benchmarkScryptN(15, b) +} + +func BenchmarkScrypt16(b *testing.B) { + benchmarkScryptN(16, b) +} + +func BenchmarkScrypt17(b *testing.B) { + benchmarkScryptN(17, b) +} diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go new file mode 100644 index 0000000..14135a2 --- /dev/null +++ b/internal/contentenc/content.go @@ -0,0 +1,116 @@ +package contentenc + +// File content encryption / decryption + +import ( + "encoding/binary" + "bytes" + "encoding/hex" + "errors" + + "github.com/rfjakob/gocryptfs/internal/toggledlog" +) + +// DecryptBlocks - Decrypt a number of blocks +func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) { + cBuf := bytes.NewBuffer(ciphertext) + var err error + var pBuf bytes.Buffer + for cBuf.Len() > 0 { + cBlock := cBuf.Next(int(be.cipherBS)) + var pBlock []byte + pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileId) + if err != nil { + break + } + pBuf.Write(pBlock) + firstBlockNo++ + } + return pBuf.Bytes(), err +} + +// DecryptBlock - Verify and decrypt GCM block +// +// 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 *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) { + + // Empty block? + if len(ciphertext) == 0 { + return ciphertext, nil + } + + // All-zero block? + if bytes.Equal(ciphertext, be.allZeroBlock) { + toggledlog.Debug.Printf("DecryptBlock: file hole encountered") + return make([]byte, be.plainBS), nil + } + + 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.cryptoCore.IVLen] + ciphertextOrig := ciphertext + 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.cryptoCore.Gcm.Open(plaintext, nonce, ciphertext, aData) + + if err != nil { + toggledlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig)) + toggledlog.Debug.Println(hex.Dump(ciphertextOrig)) + return nil, err + } + + return plaintext, nil +} + +// encryptBlock - Encrypt and add IV and MAC +func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte { + + // Empty block? + if len(plaintext) == 0 { + return plaintext + } + + // Get fresh nonce + nonce := be.cryptoCore.GcmIVGen.Get() + + // Authenticate block with block number and file ID + aData := make([]byte, 8) + binary.BigEndian.PutUint64(aData, blockNo) + aData = append(aData, fileID...) + + // Encrypt plaintext and append to nonce + 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 *ContentEnc) MergeBlocks(oldData []byte, newData []byte, offset int) []byte { + + // Make block of maximum size + out := make([]byte, be.plainBS) + + // Copy old and new data into it + copy(out, oldData) + l := len(newData) + copy(out[offset:offset+l], newData) + + // Crop to length + outLen := len(oldData) + newLen := offset + len(newData) + if outLen < newLen { + outLen = newLen + } + return out[0:outLen] +} 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/internal/contentenc/content_test.go b/internal/contentenc/content_test.go new file mode 100644 index 0000000..70ad58d --- /dev/null +++ b/internal/contentenc/content_test.go @@ -0,0 +1,91 @@ +package contentenc + +import ( + "testing" +) + +type testRange struct { + offset uint64 + length uint64 +} + +func TestSplitRange(t *testing.T) { + var ranges []testRange + + ranges = append(ranges, testRange{0, 70000}, + testRange{0, 10}, + testRange{234, 6511}, + testRange{65444, 54}, + testRange{0, 1024 * 1024}, + testRange{0, 65536}, + testRange{6654, 8945}) + + key := make([]byte, KEY_LEN) + f := NewCryptFS(key, true, false, true) + + for _, r := range ranges { + parts := f.ExplodePlainRange(r.offset, r.length) + var lastBlockNo uint64 = 1 << 63 + for _, p := range parts { + if p.BlockNo == lastBlockNo { + t.Errorf("Duplicate block number %d", p.BlockNo) + } + lastBlockNo = p.BlockNo + if p.Length > DEFAULT_PLAINBS || p.Skip >= DEFAULT_PLAINBS { + t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip) + } + } + } +} + +func TestCiphertextRange(t *testing.T) { + var ranges []testRange + + ranges = append(ranges, testRange{0, 70000}, + testRange{0, 10}, + testRange{234, 6511}, + testRange{65444, 54}, + testRange{6654, 8945}) + + key := make([]byte, KEY_LEN) + f := NewCryptFS(key, true, false, true) + + for _, r := range ranges { + + blocks := f.ExplodePlainRange(r.offset, r.length) + alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) + skipBytes := blocks[0].Skip + + if alignedLength < r.length { + t.Errorf("alignedLength=%d is smaller than length=%d", alignedLength, r.length) + } + if (alignedOffset-HEADER_LEN)%f.cipherBS != 0 { + t.Errorf("alignedOffset=%d is not aligned", alignedOffset) + } + if r.offset%f.plainBS != 0 && skipBytes == 0 { + t.Errorf("skipBytes=0") + } + } +} + +func TestBlockNo(t *testing.T) { + key := make([]byte, KEY_LEN) + f := NewCryptFS(key, true, false, true) + + b := f.CipherOffToBlockNo(788) + if b != 0 { + t.Errorf("actual: %d", b) + } + b = f.CipherOffToBlockNo(HEADER_LEN + f.cipherBS) + if b != 1 { + t.Errorf("actual: %d", b) + } + b = f.PlainOffToBlockNo(788) + if b != 0 { + t.Errorf("actual: %d", b) + } + b = f.PlainOffToBlockNo(f.plainBS) + if b != 1 { + t.Errorf("actual: %d", b) + } +} diff --git a/internal/contentenc/file_header.go b/internal/contentenc/file_header.go new file mode 100644 index 0000000..8a9dd2c --- /dev/null +++ b/internal/contentenc/file_header.go @@ -0,0 +1,60 @@ +package contentenc + +// Per-file header +// +// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ] + +import ( + "encoding/binary" + "fmt" + + "github.com/rfjakob/gocryptfs/internal/cryptocore" +) + +const ( + // 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 +) + +type FileHeader struct { + Version uint16 + Id []byte +} + +// Pack - serialize fileHeader object +func (h *FileHeader) Pack() []byte { + if len(h.Id) != HEADER_ID_LEN || h.Version != CurrentVersion { + panic("FileHeader object not properly initialized") + } + buf := make([]byte, HEADER_LEN) + binary.BigEndian.PutUint16(buf[0:HEADER_VERSION_LEN], h.Version) + copy(buf[HEADER_VERSION_LEN:], h.Id) + return buf + +} + +// ParseHeader - parse "buf" into fileHeader object +func ParseHeader(buf []byte) (*FileHeader, error) { + if len(buf) != HEADER_LEN { + return nil, fmt.Errorf("ParseHeader: invalid length: got %d, want %d", len(buf), HEADER_LEN) + } + var h FileHeader + h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN]) + 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 +} + +// RandomHeader - create new fileHeader object with random Id +func RandomHeader() *FileHeader { + var h FileHeader + h.Version = CurrentVersion + h.Id = cryptocore.RandBytes(HEADER_ID_LEN) + return &h +} diff --git a/internal/contentenc/intrablock.go b/internal/contentenc/intrablock.go new file mode 100644 index 0000000..330b980 --- /dev/null +++ b/internal/contentenc/intrablock.go @@ -0,0 +1,51 @@ +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 *ContentEnc +} + +// isPartial - is the block partial? This means we have to do read-modify-write. +func (ib *intraBlock) IsPartial() bool { + if ib.Skip > 0 || ib.Length < ib.fs.plainBS { + return true + } + return false +} + +// CiphertextRange - get byte range in ciphertext file corresponding to BlockNo +// (complete block) +func (ib *intraBlock) CiphertextRange() (offset uint64, length uint64) { + return ib.fs.BlockNoToCipherOff(ib.BlockNo), ib.fs.cipherBS +} + +// PlaintextRange - get byte range in plaintext corresponding to BlockNo +// (complete block) +func (ib *intraBlock) PlaintextRange() (offset uint64, length uint64) { + return ib.fs.BlockNoToPlainOff(ib.BlockNo), ib.fs.plainBS +} + +// CropBlock - crop a potentially larger plaintext block down to the relevant part +func (ib *intraBlock) CropBlock(d []byte) []byte { + lenHave := len(d) + lenWant := int(ib.Skip + ib.Length) + if lenHave < lenWant { + return d[ib.Skip:lenHave] + } + return d[ib.Skip:lenWant] +} + +// Ciphertext range corresponding to the sum of all "blocks" (complete blocks) +func (ib *intraBlock) JointCiphertextRange(blocks []intraBlock) (offset uint64, length uint64) { + firstBlock := blocks[0] + lastBlock := blocks[len(blocks)-1] + + offset = ib.fs.BlockNoToCipherOff(firstBlock.BlockNo) + offsetLast := ib.fs.BlockNoToCipherOff(lastBlock.BlockNo) + length = offsetLast + ib.fs.cipherBS - offset + + return offset, length +} diff --git a/internal/contentenc/offsets.go b/internal/contentenc/offsets.go new file mode 100644 index 0000000..1b5952f --- /dev/null +++ b/internal/contentenc/offsets.go @@ -0,0 +1,97 @@ +package contentenc + +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 *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 { + return plainOffset / be.plainBS +} + +// get the block number at ciphter-text offset +func (be *ContentEnc) CipherOffToBlockNo(cipherOffset uint64) uint64 { + return (cipherOffset - HEADER_LEN) / be.cipherBS +} + +// get ciphertext offset of block "blockNo" +func (be *ContentEnc) BlockNoToCipherOff(blockNo uint64) uint64 { + return HEADER_LEN + blockNo*be.cipherBS +} + +// get plaintext offset of block "blockNo" +func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 { + return blockNo * be.plainBS +} + +// PlainSize - calculate plaintext size from ciphertext size +func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 { + + // Zero sized files stay zero-sized + if cipherSize == 0 { + return 0 + } + + if cipherSize == HEADER_LEN { + toggledlog.Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize) + return 0 + } + + if cipherSize < HEADER_LEN { + toggledlog.Warn.Printf("cipherSize %d < header size: corrupt file\n", cipherSize) + return 0 + } + + // Block number at last byte + blockNo := be.CipherOffToBlockNo(cipherSize - 1) + blockCount := blockNo + 1 + + overhead := be.BlockOverhead()*blockCount + HEADER_LEN + + return cipherSize - overhead +} + +// CipherSize - calculate ciphertext size from plaintext size +func (be *ContentEnc) PlainSizeToCipherSize(plainSize uint64) uint64 { + + // Block number at last byte + blockNo := be.PlainOffToBlockNo(plainSize - 1) + blockCount := blockNo + 1 + + overhead := be.BlockOverhead()*blockCount + HEADER_LEN + + return plainSize + overhead +} + +// Split a plaintext byte range into (possibly partial) blocks +func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlock { + var blocks []intraBlock + var nextBlock intraBlock + nextBlock.fs = be + + for length > 0 { + nextBlock.BlockNo = be.PlainOffToBlockNo(offset) + nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo) + + // Minimum of remaining data and remaining space in the block + nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip) + + blocks = append(blocks, nextBlock) + offset += nextBlock.Length + length -= nextBlock.Length + } + return blocks +} + +func (be *ContentEnc) BlockOverhead() uint64 { + return be.cipherBS - be.plainBS +} + +func MinUint64(x uint64, y uint64) uint64 { + if x < y { + return x + } + return y +} 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/internal/cryptocore/gcm_go1.4.go b/internal/cryptocore/gcm_go1.4.go new file mode 100644 index 0000000..dba222c --- /dev/null +++ b/internal/cryptocore/gcm_go1.4.go @@ -0,0 +1,22 @@ +// +build !go1.5 +// = go 1.4 or lower + +package cryptocore + +import ( + "crypto/cipher" + "fmt" +) + +// goGCMWrapper - This wrapper makes sure gocryptfs can be compiled on Go +// versions 1.4 and lower that lack NewGCMWithNonceSize(). +// 128 bit GCM IVs will not work when using built-in Go crypto, obviously, when +// compiled on 1.4. +func goGCMWrapper(bc cipher.Block, nonceSize int) (cipher.AEAD, error) { + if nonceSize != 12 { + Warn.Printf("128 bit GCM IVs are not supported by Go 1.4 and lower.") + Warn.Printf("Please use openssl crypto or recompile using a newer Go runtime.") + return nil, fmt.Errorf("128 bit GCM IVs are not supported by Go 1.4 and lower") + } + return cipher.NewGCM(bc) +} diff --git a/internal/cryptocore/gcm_go1.5.go b/internal/cryptocore/gcm_go1.5.go new file mode 100644 index 0000000..0c9b1a5 --- /dev/null +++ b/internal/cryptocore/gcm_go1.5.go @@ -0,0 +1,16 @@ +// +build go1.5 +// = go 1.5 or higher + +package cryptocore + +import ( + "crypto/cipher" +) + +// goGCMWrapper - This wrapper makes sure gocryptfs can be compiled on Go +// versions 1.4 and lower that lack NewGCMWithNonceSize(). +// 128 bit GCM IVs will not work when using built-in Go crypto, obviously, when +// compiled on 1.4. +func goGCMWrapper(bc cipher.Block, nonceSize int) (cipher.AEAD, error) { + return cipher.NewGCMWithNonceSize(bc, nonceSize) +} diff --git a/internal/cryptocore/nonce.go b/internal/cryptocore/nonce.go new file mode 100644 index 0000000..72d8588 --- /dev/null +++ b/internal/cryptocore/nonce.go @@ -0,0 +1,44 @@ +package cryptocore + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/rfjakob/gocryptfs/internal/toggledlog" +) + +// Get "n" random bytes from /dev/urandom or panic +func RandBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic("Failed to read random bytes: " + err.Error()) + } + return b +} + +// Return a secure random uint64 +func RandUint64() uint64 { + b := RandBytes(8) + return binary.BigEndian.Uint64(b) +} + +type nonceGenerator struct { + lastNonce []byte + nonceLen int // bytes +} + +// Get a random "nonceLen"-byte nonce +func (n *nonceGenerator) Get() []byte { + nonce := RandBytes(n.nonceLen) + 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) + } + n.lastNonce = nonce + return nonce +} diff --git a/internal/cryptocore/openssl_aead.go b/internal/cryptocore/openssl_aead.go new file mode 100644 index 0000000..d4ed64b --- /dev/null +++ b/internal/cryptocore/openssl_aead.go @@ -0,0 +1,100 @@ +package cryptocore + +// Implements cipher.AEAD with OpenSSL backend + +import ( + "bytes" + "github.com/spacemonkeygo/openssl" +) + +// Supports all nonce sizes +type opensslGCM struct { + key []byte +} + +func (be opensslGCM) Overhead() int { + return AuthTagLen +} + +func (be opensslGCM) NonceSize() int { + // We support any nonce size + return -1 +} + +// Seal encrypts and authenticates plaintext, authenticates the +// additional data and appends the result to dst, returning the updated +// slice. opensslGCM supports any nonce size. +func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte { + + // Preallocate output buffer + var cipherBuf bytes.Buffer + cipherBuf.Grow(len(dst) + len(plaintext) + AuthTagLen) + // Output will be appended to dst + cipherBuf.Write(dst) + + ectx, err := openssl.NewGCMEncryptionCipherCtx(KeyLen*8, nil, be.key, nonce) + if err != nil { + panic(err) + } + err = ectx.ExtraData(data) + if err != nil { + panic(err) + } + part, err := ectx.EncryptUpdate(plaintext) + if err != nil { + panic(err) + } + cipherBuf.Write(part) + part, err = ectx.EncryptFinal() + if err != nil { + panic(err) + } + cipherBuf.Write(part) + part, err = ectx.GetTag() + if err != nil { + panic(err) + } + cipherBuf.Write(part) + + return cipherBuf.Bytes() +} + +// Open decrypts and authenticates ciphertext, authenticates the +// additional data and, if successful, appends the resulting plaintext +// to dst, returning the updated slice. The nonce must be NonceSize() +// bytes long and both it and the additional data must match the +// value passed to Seal. +// +// The ciphertext and dst may alias exactly or not at all. +func (be opensslGCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + + l := len(ciphertext) + tag := ciphertext[l-AuthTagLen : l] + ciphertext = ciphertext[0 : l-AuthTagLen] + plainBuf := bytes.NewBuffer(dst) + + dctx, err := openssl.NewGCMDecryptionCipherCtx(KeyLen*8, nil, be.key, nonce) + if err != nil { + return nil, err + } + err = dctx.ExtraData(data) + if err != nil { + return nil, err + } + part, err := dctx.DecryptUpdate(ciphertext) + if err != nil { + return nil, err + } + plainBuf.Write(part) + err = dctx.SetTag(tag) + if err != nil { + return nil, err + } + part, err = dctx.DecryptFinal() + if err != nil { + return nil, err + } + plainBuf.Write(part) + + return plainBuf.Bytes(), nil +} diff --git a/internal/cryptocore/openssl_benchmark.bash b/internal/cryptocore/openssl_benchmark.bash new file mode 100755 index 0000000..df29628 --- /dev/null +++ b/internal/cryptocore/openssl_benchmark.bash @@ -0,0 +1,3 @@ +#!/bin/bash + +go test -run NONE -bench BenchmarkEnc diff --git a/internal/cryptocore/openssl_test.go b/internal/cryptocore/openssl_test.go new file mode 100644 index 0000000..94b696a --- /dev/null +++ b/internal/cryptocore/openssl_test.go @@ -0,0 +1,75 @@ +package cryptocore + +// Benchmark go built-int GCM against spacemonkey openssl bindings +// +// Note: The benchmarks in this file supersede the ones in the openssl_benchmark +// directory as they use the same code paths that gocryptfs actually uses. +// +// Run benchmark: +// go test -bench Enc + +import ( + "crypto/aes" + "testing" +) + +func benchmarkGoEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { + b.SetBytes(int64(len(plaintext))) + aes, err := aes.NewCipher(key[:]) + if err != nil { + b.Fatal(err) + } + aesgcm, err := goGCMWrapper(aes, len(nonce)) + if err != nil { + b.Fatal(err) + } + // This would be fileID + blockNo + aData := make([]byte, 24) + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Encrypt plaintext and append to nonce + ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) + } + return ciphertext +} + +func benchmarkOpensslEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { + b.SetBytes(int64(len(plaintext))) + var aesgcm opensslGCM + aesgcm.key = key + // This would be fileID + blockNo + aData := make([]byte, 24) + for i := 0; i < b.N; i++ { + // Encrypt plaintext and append to nonce + ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) + } + return ciphertext +} + +func BenchmarkEnc_Go_4k_AES256_nonce96(b *testing.B) { + plaintext := make([]byte, 4048) + key := make([]byte, 256/8) + nonce := make([]byte, 96/8) + benchmarkGoEnc(b, plaintext, key, nonce) +} + +func BenchmarkEnc_Go_4k_AES256_nonce128(b *testing.B) { + plaintext := make([]byte, 4048) + key := make([]byte, 256/8) + nonce := make([]byte, 128/8) + benchmarkGoEnc(b, plaintext, key, nonce) +} + +func BenchmarkEnc_OpenSSL_4k_AES256_nonce96(b *testing.B) { + plaintext := make([]byte, 4048) + key := make([]byte, 256/8) + nonce := make([]byte, 96/8) + benchmarkOpensslEnc(b, plaintext, key, nonce) +} + +func BenchmarkEnc_OpenSSL_4k_AES256_nonce128(b *testing.B) { + plaintext := make([]byte, 4048) + key := make([]byte, 256/8) + nonce := make([]byte, 96/8) + benchmarkOpensslEnc(b, plaintext, key, nonce) +} 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/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go new file mode 100644 index 0000000..d31a066 --- /dev/null +++ b/internal/nametransform/names_diriv.go @@ -0,0 +1,151 @@ +package nametransform + +import ( + "fmt" + "io/ioutil" + "os" + "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 +type dirIVCache struct { + // Invalidated? + cleared bool + // The DirIV + iv []byte + // Directory the DirIV belongs to + dir string + // Ecrypted version of "dir" + translatedDir string + // Synchronisation + lock sync.RWMutex +} + +// lookup - fetch entry for "dir" from the cache +func (c *dirIVCache) lookup(dir string) (bool, []byte, string) { + c.lock.RLock() + defer c.lock.RUnlock() + if !c.cleared && c.dir == dir { + return true, c.iv, c.translatedDir + } + return false, nil, "" +} + +// store - write entry for "dir" into the caches +func (c *dirIVCache) store(dir string, iv []byte, translatedDir string) { + c.lock.Lock() + defer c.lock.Unlock() + c.cleared = false + c.iv = iv + c.dir = dir + c.translatedDir = translatedDir +} + +func (c *dirIVCache) Clear() { + c.lock.Lock() + defer c.lock.Unlock() + c.cleared = true +} + +// readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path) +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) { + toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir) + } else { + // This should not happen + toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr) + } + return nil, readErr + } + if len(iv) != dirIVLen { + return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv)) + } + return iv, nil +} + +// WriteDirIV - create diriv file inside "dir" (absolute ciphertext path) +// This function is exported because it is used from pathfs_frontend, main, +// and also the automated tests. +func WriteDirIV(dir string) error { + 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 *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) { + // Empty string means root directory + if plainPath == "" { + return plainPath, nil + } + // Check if the DirIV is cached + parentDir := filepath.Dir(plainPath) + found, iv, cParentDir := be.DirIVCache.lookup(parentDir) + if found { + //fmt.Print("h") + baseName := filepath.Base(plainPath) + cBaseName := be.encryptName(baseName, iv) + cipherPath = cParentDir + "/" + cBaseName + return cipherPath, nil + } + // Walk the directory tree + var wd = rootDir + var encryptedNames []string + plainNames := strings.Split(plainPath, "/") + for _, plainName := range plainNames { + iv, err = be.ReadDirIV(wd) + if err != nil { + return "", err + } + encryptedName := be.encryptName(plainName, iv) + encryptedNames = append(encryptedNames, encryptedName) + wd = filepath.Join(wd, encryptedName) + } + // Cache the final DirIV + cipherPath = strings.Join(encryptedNames, "/") + cParentDir = filepath.Dir(cipherPath) + be.DirIVCache.store(parentDir, iv, cParentDir) + return cipherPath, nil +} + +// DecryptPathDirIV - decrypt path using EME with DirIV +func (be *NameTransform) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) { + var wd = rootDir + var plainNames []string + encryptedNames := strings.Split(encryptedPath, "/") + 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) + if err != nil { + return "", err + } + plainNames = append(plainNames, plainName) + wd = filepath.Join(wd, encryptedName) + } + return filepath.Join(plainNames...), nil +} diff --git a/internal/nametransform/names_noiv.go b/internal/nametransform/names_noiv.go new file mode 100644 index 0000000..f301e52 --- /dev/null +++ b/internal/nametransform/names_noiv.go @@ -0,0 +1,63 @@ +package nametransform + +import ( + "strings" +) + +const ( + OpEncrypt = iota + OpDecrypt +) + +// 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 *NameTransform) DecryptPathNoIV(cipherPath string) (plainPath string, err error) { + plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt) + return plainPath, 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 *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 *NameTransform) translatePathNoIV(path string, op int) (string, error) { + var err error + + // Empty string means root directory + if path == "" { + return path, err + } + + zeroIV := make([]byte, dirIVLen) + + // Run operation on each path component + var translatedParts []string + parts := strings.Split(path, "/") + for _, part := range parts { + if part == "" { + // This happens on "/foo/bar/" on the front and on the end. + // Don't panic. + translatedParts = append(translatedParts, "") + continue + } + var newPart string + if op == OpEncrypt { + newPart = be.encryptName(part, zeroIV) + } else { + newPart, err = be.DecryptName(part, zeroIV) + if err != nil { + return "", err + } + } + translatedParts = append(translatedParts, newPart) + } + + return strings.Join(translatedParts, "/"), err +} diff --git a/internal/nametransform/names_test.go b/internal/nametransform/names_test.go new file mode 100644 index 0000000..4a901be --- /dev/null +++ b/internal/nametransform/names_test.go @@ -0,0 +1,58 @@ +package nametransform + +import ( + "bytes" + "testing" +) + +func TestEncryptPathNoIV(t *testing.T) { + var s []string + s = append(s, "foo") + s = append(s, "foo12312312312312312313123123123") + s = append(s, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890") + + key := make([]byte, KEY_LEN) + fs := NewCryptFS(key, true, false, true) + + for _, n := range s { + c := fs.EncryptPathNoIV(n) + d, err := fs.DecryptPathNoIV(c) + if err != nil { + t.Errorf("Got error from DecryptPathNoIV: %s", err) + } + if d != n { + t.Errorf("Content mismatch, n != d: n=%s c=%s d=%s", n, c, d) + } + } +} + +func TestPad16(t *testing.T) { + var s [][]byte + s = append(s, []byte("foo")) + s = append(s, []byte("12345678901234567")) + s = append(s, []byte("12345678901234567abcdefg")) + + key := make([]byte, KEY_LEN) + fs := NewCryptFS(key, true, false, true) + + for i := range s { + orig := s[i] + padded := fs.pad16(orig) + if len(padded) <= len(orig) { + t.Errorf("Padded length not bigger than orig: %d", len(padded)) + } + if len(padded)%16 != 0 { + t.Errorf("Length is not aligend: %d", len(padded)) + } + unpadded, err := fs.unPad16(padded) + if err != nil { + t.Error("unPad16 returned error:", err) + } + if len(unpadded) != len(orig) { + t.Errorf("Size mismatch: orig=%d unpadded=%d", len(s[i]), len(unpadded)) + } + if !bytes.Equal(orig, unpadded) { + t.Error("Content mismatch orig vs unpadded") + } + } +} 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/internal/toggledlog/log.go b/internal/toggledlog/log.go new file mode 100644 index 0000000..4a2ad03 --- /dev/null +++ b/internal/toggledlog/log.go @@ -0,0 +1,65 @@ +package toggledlog + +import ( + "encoding/json" + "fmt" + "log" + "os" +) + +const ( + ProgramName = "gocryptfs" +) + +func JSONDump(obj interface{}) string { + b, err := json.MarshalIndent(obj, "", "\t") + if err != nil { + return err.Error() + } else { + return string(b) + } +} + +// toggledLogger - a Logger than can be enabled and disabled +type toggledLogger struct { + // Enable or disable output + Enabled bool + // Panic after logging a message, useful in regression tests + PanicAfter bool + *log.Logger +} + +func (l *toggledLogger) Printf(format string, v ...interface{}) { + if !l.Enabled { + return + } + l.Logger.Printf(format, v...) + if l.PanicAfter { + panic("PanicAfter: " + fmt.Sprintf(format, v...)) + } +} +func (l *toggledLogger) Println(v ...interface{}) { + if !l.Enabled { + return + } + l.Logger.Println(v...) + if l.PanicAfter { + panic("PanicAfter: " + fmt.Sprintln(v...)) + } +} + +// As defined by http://elinux.org/Debugging_by_printing#Log_Levels +// Debug messages +var Debug *toggledLogger + +// Informational message e.g. startup information +var Info *toggledLogger + +// A warning, meaning nothing serious by itself but might indicate problems +var Warn *toggledLogger + +func init() { + Debug = &toggledLogger{false, false, log.New(os.Stdout, "", 0)} + Info = &toggledLogger{true, false, log.New(os.Stdout, "", 0)} + Warn = &toggledLogger{true, false, log.New(os.Stderr, "", 0)} +} diff --git a/internal/toggledlog/log_go1.4.go b/internal/toggledlog/log_go1.4.go new file mode 100644 index 0000000..4cdba44 --- /dev/null +++ b/internal/toggledlog/log_go1.4.go @@ -0,0 +1,12 @@ +// +build !go1.5 +// = go 1.4 or lower + +package toggledlog + +import ( + "log/syslog" +) + +func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { + Debug.Printf("Cannot switch to syslog - need Go 1.5 or higher") +} diff --git a/internal/toggledlog/log_go1.5.go b/internal/toggledlog/log_go1.5.go new file mode 100644 index 0000000..e8e71f9 --- /dev/null +++ b/internal/toggledlog/log_go1.5.go @@ -0,0 +1,17 @@ +// +build go1.5 +// = go 1.5 or higher + +package toggledlog + +import ( + "log/syslog" +) + +func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) { + w, err := syslog.New(p, ProgramName) + if err != nil { + Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err) + } else { + l.SetOutput(w) + } +} 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 $* -- cgit v1.2.3