diff --git a/.gitignore b/.gitignore index fc67b7f..1ffb583 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,8 @@ Makefile.in /po/remove-potcdate.sed /po/Makefile.in.in /po/Rules-quot +/po/*.gmo +/po/stamp-po /m4/*.m4 !/m4/ax_boost_base.m4 diff --git a/ChangeLog b/ChangeLog index 8691eeb..2b1df33 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +Sun Mar 22 2015 Jakob Unterwurzacher + * release version 1.8.1 + * reverse: re-enable kernel cache (bug #60) + * reverse mode: disable unique IV by default + * add "make benchmark-reverse" + * remove "-o default_permissions" to improve performance + +Fri Mar 20 2015 Eric Swanson + * add option "--require-macs" (bug #14) + +Fri Mar 13 2015 Valient Gough + * add po files to git (bug #63) + +Mon Mar 9 2015 Jakob Unterwurzacher + * release version 1.8 + * improve automatic test converage: also test reverse mode (make test) + * add automatic benchmark (make benchmark) + * compare MAC in constant time ( fixes bug #12 ) + * lots of fixes to make building on OSX easier + Sun Nov 23 2014 Jakob Unterwurzacher * add per-file IVs to reverse mode * add --nocache option diff --git a/Makefile.am b/Makefile.am index 981ddbd..7e8ed35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,3 +26,8 @@ test-verbose: .PHONY: benchmark benchmark: sudo tests/benchmark.pl /var/tmp + +.PHONY: benchmark-reverse +benchmark-reverse: + tests/benchmark-reverse.pl /var/tmp + tests/benchmark-reverse.pl /var/tmp --nocache diff --git a/configure.ac b/configure.ac index cbf003e..7db5d12 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this file with autoconf to produce a configure script. -AC_INIT([encfs], [1.8]) +AC_INIT([encfs], [1.8.1]) AC_CONFIG_SRCDIR([encfs/encfs.h]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/encfs/FileUtils.cpp b/encfs/FileUtils.cpp index f658bf8..60ca86f 100644 --- a/encfs/FileUtils.cpp +++ b/encfs/FileUtils.cpp @@ -855,14 +855,21 @@ static bool boolDefaultYes(const char *prompt) { /** * Ask the user whether to enable block MAC and random header bytes */ -static void selectBlockMAC(int *macBytes, int *macRandBytes) { - // xgroup(setup) - bool addMAC = boolDefaultNo( - _("Enable block authentication code headers\n" - "on every block in a file? This adds about 12 bytes per block\n" - "to the storage requirements for a file, and significantly affects\n" - "performance but it also means [almost] any modifications or errors\n" - "within a block will be caught and will cause a read error.")); +static void selectBlockMAC(int *macBytes, int *macRandBytes, bool forceMac) { + bool addMAC = false; + if (!forceMac) { + // xgroup(setup) + addMAC = boolDefaultNo( + _("Enable block authentication code headers\n" + "on every block in a file? This adds about 12 bytes per block\n" + "to the storage requirements for a file, and significantly affects\n" + "performance but it also means [almost] any modifications or errors\n" + "within a block will be caught and will cause a read error.")); + } else { + cout << _("\n\nYou specified --require-macs. " + "Enabling block authentication code headers...\n\n"); + addMAC = true; + } if (addMAC) *macBytes = 8; @@ -893,13 +900,13 @@ static void selectBlockMAC(int *macBytes, int *macRandBytes) { /** * Ask the user if per-file unique IVs should be used */ -static bool selectUniqueIV() { +static bool selectUniqueIV(bool default_answer) { // xgroup(setup) - return boolDefaultYes( + return boolDefault( _("Enable per-file initialization vectors?\n" "This adds about 8 bytes per file to the storage requirements.\n" "It should not affect performance except possibly with applications\n" - "which rely on block-aligned file io for performance.")); + "which rely on block-aligned file io for performance."), default_answer); } /** @@ -977,8 +984,8 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { Interface nameIOIface; // selectNameCoding() int blockMACBytes = 0; // selectBlockMAC() int blockMACRandBytes = 0; // selectBlockMAC() - bool uniqueIV = false; // selectUniqueIV() - bool chainedIV = false; // selectChainedIV() + bool uniqueIV = true; // selectUniqueIV() + bool chainedIV = true; // selectChainedIV() bool externalIV = false; // selectExternalChainedIV() bool allowHoles = true; // selectZeroBlockPassThrough() long desiredKDFDuration = NormalKDFDuration; @@ -986,6 +993,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { if (reverseEncryption) { chainedIV = false; externalIV = false; + uniqueIV = false; blockMACBytes = 0; blockMACRandBytes = 0; } @@ -1009,8 +1017,6 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { nameIOIface = BlockNameIO::CurrentInterface(); blockMACBytes = 8; blockMACRandBytes = 0; // using uniqueIV, so this isn't necessary - uniqueIV = true; - chainedIV = true; externalIV = true; desiredKDFDuration = ParanoiaKDFDuration; } else if (configMode == Config_Standard || answer[0] != 'x') { @@ -1021,15 +1027,10 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { keySize = 192; blockSize = DefaultBlockSize; alg = findCipherAlgorithm("AES", keySize); - blockMACBytes = 0; - externalIV = false; nameIOIface = BlockNameIO::CurrentInterface(); - uniqueIV = true; - if (reverseEncryption) { - cout << _("reverse encryption - chained IV disabled") << "\n"; - } else { - chainedIV = true; + if (opts->requireMac) { + blockMACBytes = 8; } } @@ -1052,7 +1053,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { nameIOIface = selectNameCoding(); if (reverseEncryption) { cout << _("reverse encryption - chained IV and MAC disabled") << "\n"; - uniqueIV = selectUniqueIV(); + uniqueIV = selectUniqueIV(false); /* Reverse mounts are read-only by default (set in main.cpp). * If uniqueIV is off, writing can be allowed, because there * is no header that could be overwritten */ @@ -1060,7 +1061,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { opts->readOnly = false; } else { chainedIV = selectChainedIV(); - uniqueIV = selectUniqueIV(); + uniqueIV = selectUniqueIV(true); if (chainedIV && uniqueIV) externalIV = selectExternalChainedIV(); else { @@ -1070,7 +1071,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr &opts) { << "\n"; externalIV = false; } - selectBlockMAC(&blockMACBytes, &blockMACRandBytes); + selectBlockMAC(&blockMACBytes, &blockMACRandBytes, opts->requireMac); allowHoles = selectZeroBlockPassThrough(); } } @@ -1500,6 +1501,12 @@ RootPtr initFS(EncFS_Context *ctx, const shared_ptr &opts) { shared_ptr config(new EncFSConfig); if (readConfig(opts->rootDir, config) != Config_None) { + if (config->blockMACBytes == 0 && opts->requireMac) { + cout + << _("The configuration disabled MAC, but you passed --require-macs\n"); + return rootInfo; + } + if (opts->reverseEncryption) { if (config->blockMACBytes != 0 || config->blockMACRandBytes != 0 || config->externalIVChaining || diff --git a/encfs/FileUtils.h b/encfs/FileUtils.h index 0cedb14..2e4f512 100644 --- a/encfs/FileUtils.h +++ b/encfs/FileUtils.h @@ -88,6 +88,8 @@ struct EncFS_Opts { bool readOnly; // Mount read-only + bool requireMac; // Throw an error if MAC is disabled + ConfigMode configMode; EncFS_Opts() { @@ -104,6 +106,7 @@ struct EncFS_Opts { configMode = Config_Prompt; noCache = false; readOnly = false; + requireMac = false; } }; diff --git a/encfs/encfs.pod b/encfs/encfs.pod index 125ca1e..9cfc581 100644 --- a/encfs/encfs.pod +++ b/encfs/encfs.pod @@ -121,6 +121,15 @@ password. If this succeeds, then the filesystem becomes available again. Do not mount the filesystem when encfs starts; instead, delay mounting until first use. This option only makes sense with B<--ondemand>. +=item B<--require-macs> + +If creating a new filesystem, this forces block authentication code headers to +be enabled. When mounting an existing filesystem, this causes encfs to exit +if block authentication code headers are not enabled. + +This can be used to improve security in case the ciphertext is vulnerable to +tampering, by preventing an attacker from disabling MACs in the config file. + =item B<--reverse> Normally B provides a plaintext view of data on demand. Normally it @@ -134,7 +143,7 @@ For example, the following would create an encrypted view in /tmp/crypt-view. encfs --reverse /home/me /tmp/crypt-view You could then copy the /tmp/crypt-view directory in order to have a copy of -the encrypted data. You must also keep a copy of the file /home/me/.encfs5 +the encrypted data. You must also keep a copy of the file /home/me/.encfs6.xml which contains the filesystem information. Together, the two can be used to reproduce the unencrypted data: @@ -145,6 +154,14 @@ Now /tmp/plain-view contains the same data as /home/me Note that B<--reverse> mode only works with limited configuration options, so many settings may be disabled when used. +=item B<--nocache> + +Disable the kernel's cache of file attributes. +Setting this option makes EncFS pass "attr_timeout=0" and "entry_timeout=0" to +FUSE. This makes sure that modifications to the backing files that occour +outside EncFS show up immediately in the EncFS mount. The main use case +for "--nocache" is reverse mode. + =item B<--standard> If creating a new filesystem, this automatically selects standard configuration diff --git a/encfs/main.cpp b/encfs/main.cpp index 728517f..0fdcb0c 100644 --- a/encfs/main.cpp +++ b/encfs/main.cpp @@ -57,6 +57,12 @@ extern "C" void fuse_unmount_compat22(const char *mountpoint); #define fuse_unmount fuse_unmount_compat22 +/* Arbitrary identifiers for long options that do + * not have a short version */ +#define LONG_OPT_ANNOTATE 513 +#define LONG_OPT_NOCACHE 514 +#define LONG_OPT_REQUIRE_MAC 515 + using namespace std; using namespace rlog; using namespace rel; @@ -190,6 +196,7 @@ static bool processArgs(int argc, char *argv[], out->opts->useStdin = false; out->opts->annotate = false; out->opts->reverseEncryption = false; + out->opts->requireMac = false; bool useDefaultFlags = true; @@ -215,15 +222,16 @@ static bool processArgs(int argc, char *argv[], {"delaymount", 0, 0, 'M'}, // delay initial mount until use {"public", 0, 0, 'P'}, // public mode {"extpass", 1, 0, 'p'}, // external password program - // {"single-thread", 0, 0, 's'}, // single-threaded mode - {"stdinpass", 0, 0, 'S'}, // read password from stdin - {"annotate", 0, 0, 513}, // Print annotation lines to stderr - {"nocache", 0, 0, 514}, // disable caching + // {"single-thread", 0, 0, 's'}, // single-threaded mode + {"stdinpass", 0, 0, 'S'}, // read password from stdin + {"annotate", 0, 0, LONG_OPT_ANNOTATE}, // Print annotation lines to stderr + {"nocache", 0, 0, LONG_OPT_NOCACHE}, // disable caching {"verbose", 0, 0, 'v'}, // verbose mode {"version", 0, 0, 'V'}, // version {"reverse", 0, 0, 'r'}, // reverse encryption {"standard", 0, 0, '1'}, // standard configuration {"paranoia", 0, 0, '2'}, // standard configuration + {"require-macs", 0, 0, LONG_OPT_REQUIRE_MAC}, // require MACs {0, 0, 0, 0}}; while (1) { @@ -255,9 +263,12 @@ static bool processArgs(int argc, char *argv[], case 'S': out->opts->useStdin = true; break; - case 513: + case LONG_OPT_ANNOTATE: out->opts->annotate = true; break; + case LONG_OPT_REQUIRE_MAC: + out->opts->requireMac = true; + break; case 'f': out->isDaemon = false; // this option was added in fuse 2.x @@ -295,8 +306,11 @@ static bool processArgs(int argc, char *argv[], * filesystem where something can change the underlying * filesystem without going through fuse can run into * inconsistencies." - * Enabling reverse automatically enables noCache */ - case 514: + * However, disabling the caches causes a factor 3 + * slowdown. If you are concerned about inconsistencies, + * please use --nocache. */ + break; + case LONG_OPT_NOCACHE: /* Disable EncFS block cache * Causes reverse grow tests to fail because short reads * are returned */ @@ -359,13 +373,6 @@ static bool processArgs(int argc, char *argv[], if (!out->isThreaded) PUSHARG("-s"); - if (useDefaultFlags) { - PUSHARG("-o"); - PUSHARG("use_ino"); - PUSHARG("-o"); - PUSHARG("default_permissions"); - } - // we should have at least 2 arguments left over - the source directory and // the mount point. if (optind + 2 <= argc) { @@ -388,6 +395,26 @@ static bool processArgs(int argc, char *argv[], } } + // Add default flags unless --no-default-flags was passed + if (useDefaultFlags) { + + // Expose the underlying stable inode number + PUSHARG("-o"); + PUSHARG("use_ino"); + + // "default_permissions" comes with a performance cost. Only enable + // it if makes sense. + for(int i=0; i < out->fuseArgc; i++) { + if ( out->fuseArgv[i] == NULL ) { + continue; + } else if (strcmp(out->fuseArgv[i], "allow_other") == 0) { + PUSHARG("-o"); + PUSHARG("default_permissions"); + break; + } + } + } + // sanity check if (out->isDaemon && (!isAbsolutePath(out->mountPoint.c_str()) || !isAbsolutePath(out->opts->rootDir.c_str()))) { diff --git a/tests/benchmark-reverse.pl b/tests/benchmark-reverse.pl new file mode 100755 index 0000000..1ba214c --- /dev/null +++ b/tests/benchmark-reverse.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +# Benchmark EncFS reverse mode + +use File::Temp; +use warnings; + +require("tests/common.pl"); + +sub mount_encfs_reverse { + my $p = shift; + my $c = shift; + my $opts = shift; + + my $cmdline = "./encfs/encfs --extpass=\"echo test\" --standard $p $c --reverse $opts 2>&1 > /dev/null"; + # print "mounting encfs: $cmdline\n"; + my $status = system($cmdline); + if ( $status != 0 ) { die("command returned error: $status"); } + waitForFile("$p/.encfs6.xml") or die("Control file not created"); + + # print "encfs --reverse mounted on $c\n"; +} + +sub cleanup { + print "cleaning up... "; + my $workingDir = shift; + for(my $i=0; $i<2; $i++) { + system("fusermount -u $workingDir/c") == 0 and last; + system("lsof $workingDir/c"); + printf "retrying... "; + sleep(1); + } + system("rm -Rf $workingDir 2> /dev/null"); + print "done\n"; +} + +sub main { + + my $prefix = shift(@ARGV) or die("Missing DIR argument"); + my $workingDir = mkdtemp("$prefix/encfs-performance-XXXX") + || die("Could not create temporary directory"); + + my $c = "$workingDir/c"; + my $p = "$workingDir/p"; + + my $opts = ""; + if ( @ARGV > 0 ) { + $opts = shift(@ARGV) + }; + + mkdir($c); + mkdir($p); + + dl_linuxgz(); + our $linuxgz; + system("tar xzf $linuxgz -C $p"); + + mount_encfs_reverse($p, $c, $opts); + + my @results = (); + stopwatch_start("rsync 1 (initial copy)"); + system("rsync -a $c/ $workingDir/rsync-target"); + stopwatch_stop(\@results); + + stopwatch_start("rsync 2 (no changes)"); + system("rsync -a $c/ $workingDir/rsync-target"); + stopwatch_stop(\@results); + + cleanup($workingDir); +} + +main(); diff --git a/tests/benchmark.pl b/tests/benchmark.pl index 0e134a4..f67f30d 100755 --- a/tests/benchmark.pl +++ b/tests/benchmark.pl @@ -2,21 +2,11 @@ # Benchmark EncFS against eCryptfs -use Time::HiRes qw( time ); use File::Temp; use warnings; -use feature 'state'; require("tests/common.pl"); -# Download linux-3.0.tar.gz unless it already exists ("-c" flag) -sub dl { - our $linuxgz = "/var/tmp/linux-3.0.tar.gz"; - print "# downloading linux-3.0.tar.gz (92MiB)... "; - system("wget -nv -c https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.0.tar.gz -O $linuxgz"); - print "done\n"; -} - # Create a new empty working directory sub newWorkingDir { my $prefix = shift; @@ -76,42 +66,6 @@ sub mount_ecryptfs { return $p; } -# Returns integer $milliseconds from float $seconds -sub ms { - my $seconds = shift; - my $milliseconds = int( $seconds * 1000 ); - return $milliseconds; -} - -# stopwatch_start($name) -# start the stopwatch for test "$name" -sub stopwatch_start { - stopwatch(1, shift); -} - -# stopwatch_stop(\@results) -# stop the stopwatch, save time into @results -sub stopwatch_stop { - stopwatch(0, shift); -} - -sub stopwatch { - state $start_time; - state $name; - my $start = shift; - - if($start) { - $name = shift; - print("# $name... "); - $start_time = time(); - } else { - my $delta = ms(time() - $start_time); - print("$delta ms\n"); - my $results = shift; - push( $results, [ $name, $delta, 'ms' ] ); - } -} - sub benchmark { my $dir = shift; our $linuxgz; @@ -205,7 +159,7 @@ sub main { exit(2); } - dl(); + dl_linuxgz(); my $workingDir; my $mountpoint; my $prefix; diff --git a/tests/common.pl b/tests/common.pl index 3a14e36..f3bf059 100755 --- a/tests/common.pl +++ b/tests/common.pl @@ -106,5 +106,53 @@ sub writeZeroes } } +# Returns integer $milliseconds from float $seconds +sub ms { + my $seconds = shift; + my $milliseconds = int( $seconds * 1000 ); + return $milliseconds; +} + +# stopwatch_start($name) +# start the stopwatch for test "$name" +sub stopwatch_start { + stopwatch(1, shift); +} + +# stopwatch_stop(\@results) +# stop the stopwatch, save time into @results +sub stopwatch_stop { + stopwatch(0, shift); +} + +# See stopwatch_{start,stop} above +use feature 'state'; +use Time::HiRes qw( time ); +sub stopwatch { + state $start_time; + state $name; + my $start = shift; + + if($start) { + $name = shift; + print("* $name... "); + $start_time = time(); + } else { + my $delta = ms(time() - $start_time); + print("$delta ms\n"); + my $results = shift; + push( $results, [ $name, $delta, 'ms' ] ); + } +} + +# Download linux-3.0.tar.gz unless it already exists ("-c" flag) +sub dl_linuxgz { + our $linuxgz = "/var/tmp/linux-3.0.tar.gz"; + if ( -e $linuxgz ) { return; } + print "downloading linux-3.0.tar.gz (92MiB)... "; + system("wget -nv -c https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.0.tar.gz -O $linuxgz"); + print "done\n"; +} + # As this file will be require()'d, it needs to return true return 1; diff --git a/tests/normal.t.pl b/tests/normal.t.pl index b38a19d..e30befe 100755 --- a/tests/normal.t.pl +++ b/tests/normal.t.pl @@ -2,7 +2,7 @@ # Test EncFS normal and paranoid mode -use Test::More tests => 101; +use Test::More tests => 103; use File::Path; use File::Copy; use File::Temp; @@ -307,9 +307,11 @@ sub mount mkdir($crypt) || BAIL_OUT("Could not create $crypt: $!"); delete $ENV{"ENCFS6_CONFIG"}; - qx(./encfs/encfs --extpass="echo test" $args $raw $crypt); - - ok( -f "$raw/.encfs6.xml", "created control file"); + my $cmdline = "./encfs/encfs --extpass=\"echo test\" $args $raw $crypt 2>&1"; + # This makes sure we get to see stderr ^ + my $status = system($cmdline); + ok( $status == 0, "encfs command returns 0") || BAIL_OUT(""); + ok( -f "$raw/.encfs6.xml", "created control file") || BAIL_OUT(""); } # Helper function diff --git a/tests/reverse.t.pl b/tests/reverse.t.pl index b7f9196..a8405a0 100755 --- a/tests/reverse.t.pl +++ b/tests/reverse.t.pl @@ -46,7 +46,7 @@ sub cleanup sub mount { delete $ENV{"ENCFS6_CONFIG"}; - system("./encfs/encfs --extpass=\"echo test\" --standard $plain $ciphertext --reverse"); + system("./encfs/encfs --extpass=\"echo test\" --standard $plain $ciphertext --reverse --nocache"); ok(waitForFile("$plain/.encfs6.xml"), "plain .encfs6.xml exists") or BAIL_OUT("'$plain/.encfs6.xml'"); my $e = encName(".encfs6.xml"); ok(waitForFile("$ciphertext/$e"), "encrypted .encfs6.xml exists") or BAIL_OUT("'$ciphertext/$e'"); @@ -116,7 +116,7 @@ sub grow { # autoflush should make sure the write goes to the kernel # immediately. Just to be sure, check it here. sizeVerify($vfh, $i) or die("unexpected plain file size"); - sizeVerify($cfh, $i+8) or $ok = 0; + sizeVerify($cfh, $i) or $ok = 0; sizeVerify($dfh, $i) or $ok = 0; if(md5fh($vfh) ne md5fh($dfh)) @@ -137,7 +137,7 @@ sub largeRead { my $cname = encName("largeRead"); # cfh ... ciphertext file handle ok(open(my $cfh, "<", "$ciphertext/$cname"), "open ciphertext largeRead file"); - ok(sizeVerify($cfh, 1024*1024+8), "1M file size"); + ok(sizeVerify($cfh, 1024*1024), "1M file size"); } # Check that the reverse mount is read-only