1
0
mirror of https://github.com/vgough/encfs.git synced 2025-03-31 03:16:16 +02:00

Merge pull request from rfjakob/master

Reverse mode improvements
This commit is contained in:
Valient Gough 2014-12-02 12:43:11 -08:00
commit 40531024c8
9 changed files with 176 additions and 37 deletions

View File

@ -275,25 +275,43 @@ string DirNode::rootDirectory() {
return string(rootDir, 0, rootDir.length() - 1);
}
/**
* Encrypt a plain-text file path to the ciphertext path with the
* ciphertext root directory name prefixed.
*
* Example:
* $ encfs -f -v cipher plain
* $ cd plain
* $ touch foobar
* cipherPath: /foobar encoded to cipher/NKAKsn2APtmquuKPoF4QRPxS
*/
string DirNode::cipherPath(const char *plaintextPath) {
return rootDir + naming->encodePath(plaintextPath);
}
/**
* Same as cipherPath(), but does not prefix the ciphertext root directory
*/
string DirNode::cipherPathWithoutRoot(const char *plaintextPath) {
return naming->encodePath(plaintextPath);
}
/**
* Return the decrypted version of cipherPath
*
* In reverse mode, returns the encrypted version of cipherPath
*/
string DirNode::plainPath(const char *cipherPath_) {
try {
if (!strncmp(cipherPath_, rootDir.c_str(), rootDir.length())) {
return naming->decodePath(cipherPath_ + rootDir.length());
}
// Handle special absolute path encodings.
char mark = fsConfig->reverseEncryption ? '/' : '+';
char mark = '+';
string prefix = "/";
if (fsConfig->reverseEncryption) {
mark = '/';
prefix = "+";
}
if (cipherPath_[0] == mark) {
return string(fsConfig->reverseEncryption ? "+" : "/") +
naming->decodeName(cipherPath_ + 1, strlen(cipherPath_ + 1));
return prefix + naming->decodeName(cipherPath_ + 1, strlen(cipherPath_ + 1));
}
// Default.

View File

@ -41,6 +41,9 @@ struct EncFS_Opts;
class Cipher;
class NameIO;
/**
* Persistent configuration (stored in config file .encfs6.xml)
*/
struct EncFSConfig {
ConfigType cfgType;

View File

@ -967,7 +967,6 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
long desiredKDFDuration = NormalKDFDuration;
if (reverseEncryption) {
uniqueIV = false;
chainedIV = false;
externalIV = false;
blockMACBytes = 0;
@ -976,7 +975,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
if (configMode == Config_Paranoia || answer[0] == 'p') {
if (reverseEncryption) {
rError(_("Paranoia configuration not supported for --reverse"));
rError(_("Paranoia configuration not supported for reverse encryption"));
return rootInfo;
}
@ -1011,7 +1010,7 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
uniqueIV = true;
if (reverseEncryption) {
cout << _("--reverse specified, not using chained IV") << "\n";
cout << _("reverse encryption - chained IV disabled") << "\n";
} else {
chainedIV = true;
}
@ -1035,7 +1034,13 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
blockSize = selectBlockSize(alg);
nameIOIface = selectNameCoding();
if (reverseEncryption) {
cout << _("--reverse specified, not using unique/chained IV") << "\n";
cout << _("reverse encryption - chained IV and MAC disabled") << "\n";
uniqueIV = selectUniqueIV();
/* 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 */
if (uniqueIV == false)
opts->readOnly = false;
} else {
chainedIV = selectChainedIV();
uniqueIV = selectUniqueIV();

View File

@ -58,6 +58,11 @@ typedef shared_ptr<EncFS_Root> RootPtr;
enum ConfigMode { Config_Prompt, Config_Standard, Config_Paranoia };
/**
* EncFS_Opts stores internal settings
*
* See struct EncFS_Args (main.cpp) for the parsed command line arguments
*/
struct EncFS_Opts {
std::string rootDir;
bool createIfNotFound; // create filesystem if not found
@ -81,6 +86,8 @@ struct EncFS_Opts {
* behind the back of EncFS (for example, in reverse mode).
* See main.cpp for a longer explaination. */
bool readOnly; // Mount read-only
ConfigMode configMode;
EncFS_Opts() {
@ -96,6 +103,7 @@ struct EncFS_Opts {
reverseEncryption = false;
configMode = Config_Prompt;
noCache = false;
readOnly = false;
}
};

View File

@ -70,6 +70,18 @@ static EncFS_Context *context() {
return (EncFS_Context *)fuse_get_context()->private_data;
}
/**
* Helper function - determine if the filesystem is read-only
* Optionally takes a pointer to the EncFS_Context, will get it from FUSE
* if the argument is NULL.
*/
static bool isReadOnly(EncFS_Context *ctx) {
if (ctx == NULL)
ctx = (EncFS_Context *)fuse_get_context()->private_data;
return ctx->opts->readOnly;
}
// helper function -- apply a functor to a cipher path, given the plain path
static int withCipherPath(const char *opName, const char *path,
function<int(EncFS_Context *, const string &)> op,
@ -130,11 +142,10 @@ static int withFileNode(const char *opName, const char *path,
}
/*
The rLog messages below always print out encrypted filenames, not
The rLog messages below always prints out encrypted filenames, not
plaintext. The reason is so that it isn't possible to leak information
about the encrypted data through rlog interfaces.
The purpose of this layer of code is to take the FUSE request and dispatch
to the internal interfaces. Any marshaling of arguments and return types
can be done here.
@ -215,6 +226,8 @@ int encfs_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) {
int encfs_mknod(const char *path, mode_t mode, dev_t rdev) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -255,6 +268,8 @@ int encfs_mkdir(const char *path, mode_t mode) {
fuse_context *fctx = fuse_get_context();
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -287,6 +302,8 @@ int encfs_mkdir(const char *path, mode_t mode) {
int encfs_unlink(const char *path) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -307,6 +324,7 @@ int _do_rmdir(EncFS_Context *, const string &cipherPath) {
}
int encfs_rmdir(const char *path) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("rmdir", path, bind(_do_rmdir, _1, _2));
}
@ -343,17 +361,22 @@ int encfs_readlink(const char *path, char *buf, size_t size) {
bind(_do_readlink, _1, _2, buf, size));
}
int encfs_symlink(const char *from, const char *to) {
/**
* Create a symbolic link pointing to "to" named "from"
*/
int encfs_symlink(const char *to, const char *from) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
try {
string fromCName = FSRoot->cipherPath(from);
// allow fully qualified names in symbolic links.
string fromCName = FSRoot->relativeCipherPath(from);
string toCName = FSRoot->cipherPath(to);
string toCName = FSRoot->relativeCipherPath(to);
rLog(Info, "symlink %s -> %s", fromCName.c_str(), toCName.c_str());
@ -366,7 +389,7 @@ int encfs_symlink(const char *from, const char *to) {
olduid = setfsuid(context->uid);
oldgid = setfsgid(context->gid);
}
res = ::symlink(fromCName.c_str(), toCName.c_str());
res = ::symlink(toCName.c_str(), fromCName.c_str());
if (olduid >= 0) setfsuid(olduid);
if (oldgid >= 0) setfsgid(oldgid);
@ -384,6 +407,8 @@ int encfs_symlink(const char *from, const char *to) {
int encfs_link(const char *from, const char *to) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -400,6 +425,8 @@ int encfs_link(const char *from, const char *to) {
int encfs_rename(const char *from, const char *to) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx)) return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -418,6 +445,7 @@ int _do_chmod(EncFS_Context *, const string &cipherPath, mode_t mode) {
}
int encfs_chmod(const char *path, mode_t mode) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("chmod", path, bind(_do_chmod, _1, _2, mode));
}
@ -427,16 +455,19 @@ int _do_chown(EncFS_Context *, const string &cyName, uid_t u, gid_t g) {
}
int encfs_chown(const char *path, uid_t uid, gid_t gid) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("chown", path, bind(_do_chown, _1, _2, uid, gid));
}
int _do_truncate(FileNode *fnode, off_t size) { return fnode->truncate(size); }
int encfs_truncate(const char *path, off_t size) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("truncate", path, NULL, bind(_do_truncate, _1, size));
}
int encfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("ftruncate", path, fi, bind(_do_truncate, _1, size));
}
@ -446,6 +477,7 @@ int _do_utime(EncFS_Context *, const string &cyName, struct utimbuf *buf) {
}
int encfs_utime(const char *path, struct utimbuf *buf) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("utime", path, bind(_do_utime, _1, _2, buf));
}
@ -462,12 +494,16 @@ int _do_utimens(EncFS_Context *, const string &cyName,
}
int encfs_utimens(const char *path, const struct timespec ts[2]) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("utimens", path, bind(_do_utimens, _1, _2, ts));
}
int encfs_open(const char *path, struct fuse_file_info *file) {
EncFS_Context *ctx = context();
if (isReadOnly(ctx) && (file->flags & O_WRONLY || file->flags & O_RDWR))
return -EROFS;
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if (!FSRoot) return res;
@ -508,6 +544,7 @@ int _do_flush(FileNode *fnode) {
return res;
}
// Called on each close() of a file descriptor
int encfs_flush(const char *path, struct fuse_file_info *fi) {
return withFileNode("flush", path, fi, bind(_do_flush, _1));
}
@ -545,6 +582,7 @@ int _do_fsync(FileNode *fnode, int dataSync) {
}
int encfs_fsync(const char *path, int dataSync, struct fuse_file_info *file) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("fsync", path, file, bind(_do_fsync, _1, dataSync));
}
@ -557,6 +595,7 @@ int _do_write(FileNode *fnode, unsigned char *ptr, size_t size, off_t offset) {
int encfs_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *file) {
if (isReadOnly(NULL)) return -EROFS;
return withFileNode("write", path, file,
bind(_do_write, _1, (unsigned char *)buf, size, offset));
}
@ -595,6 +634,7 @@ int _do_setxattr(EncFS_Context *, const string &cyName, const char *name,
}
int encfs_setxattr(const char *path, const char *name, const char *value,
size_t size, int flags, uint32_t position) {
if (isReadOnly(NULL)) return -EROFS;
(void)flags;
return withCipherPath("setxattr", path, bind(_do_setxattr, _1, _2, name,
value, size, position));
@ -606,6 +646,7 @@ int _do_setxattr(EncFS_Context *, const string &cyName, const char *name,
}
int encfs_setxattr(const char *path, const char *name, const char *value,
size_t size, int flags) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("setxattr", path,
bind(_do_setxattr, _1, _2, name, value, size, flags));
}
@ -663,6 +704,8 @@ int _do_removexattr(EncFS_Context *, const string &cyName, const char *name) {
}
int encfs_removexattr(const char *path, const char *name) {
if (isReadOnly(NULL)) return -EROFS;
return withCipherPath("removexattr", path,
bind(_do_removexattr, _1, _2, name));
}

View File

@ -65,6 +65,12 @@ using gnu::autosprintf;
// Maximum number of arguments that we're going to pass on to fuse. Doesn't
// affect how many arguments we can handle, just how many we can pass on..
const int MaxFuseArgs = 32;
/**
* EncFS_Args stores the parsed command-line arguments
*
* See also: struct EncFS_Opts (FileUtils.h), stores internal settings that are
* derived from the arguments
*/
struct EncFS_Args {
string mountPoint; // where to make filesystem visible
bool isDaemon; // true == spawn in background, log to syslog
@ -273,20 +279,23 @@ static bool processArgs(int argc, char *argv[],
case 'D':
out->opts->forceDecode = true;
break;
/* By default, the kernel caches file metadata for one second.
* This is fine for EncFS' normal mode, but for --reverse, this
* means that the encrypted view will be up to one second out of
* date.
* Quoting Goswin von Brederlow:
* "Caching only works correctly if you implement a disk based
* filesystem, one where only the fuse process can alter
* metadata and all access goes only through fuse. Any overlay
* filesystem where something can change the underlying
* filesystem without going through fuse can run into
* inconsistencies."
* Enabling reverse automatically enables noCache. */
case 'r':
out->opts->reverseEncryption = true;
/* Reverse encryption does not support writing unless uniqueIV
* is disabled (expert mode) */
out->opts->readOnly = true;
/* By default, the kernel caches file metadata for one second.
* This is fine for EncFS' normal mode, but for --reverse, this
* means that the encrypted view will be up to one second out of
* date.
* Quoting Goswin von Brederlow:
* "Caching only works correctly if you implement a disk based
* filesystem, one where only the fuse process can alter
* metadata and all access goes only through fuse. Any overlay
* filesystem where something can change the underlying
* filesystem without going through fuse can run into
* inconsistencies."
* Enabling reverse automatically enables noCache */
case 514:
/* Disable EncFS block cache
* Causes reverse grow tests to fail because short reads

View File

@ -72,5 +72,26 @@ sub waitForFile
return 0;
}
# writeZeroes($filename, $size)
# Write zeroes of size $size to file $filename
sub writeZeroes
{
my $filename = shift;
my $size = shift;
open(my $fh, ">", $filename);
my $bs = 4096; # 4 KiB
my $block = "\0" x $bs;
my $remain;
for($remain = $size; $remain >= $bs; $remain -= $bs)
{
print($fh $block) or BAIL_OUT("Could not write to $filename: $!");
}
if($remain > 0)
{
$block = "\0" x $remain;
print($fh $block) or BAIL_OUT("Could not write to $filename: $!");
}
}
# As this file will be require()'d, it needs to return true
return 1;

View File

@ -2,7 +2,7 @@
# Test EncFS normal and paranoid mode
use Test::More qw( no_plan );
use Test::More tests => 101;
use File::Path;
use File::Copy;
use File::Temp;
@ -88,7 +88,7 @@ sub corruption
sub internalModification
{
$ofile = "$workingDir/crypt-internal-$$";
qx(dd if=/dev/urandom of=$ofile bs=2k count=2 2> /dev/null);
writeZeroes($ofile, 2*1024);
ok(copy($ofile, "$crypt/internal"), "copying crypt-internal file");
open(my $out1, "+<", "$crypt/internal");
@ -289,7 +289,7 @@ sub links
is( readlink("$crypt/data-rel"), "data", "read rel symlink");
SKIP: {
skip "No hardlink support" unless $hardlinkTests;
skip "No hardlink support", 2 unless $hardlinkTests;
ok( link("$crypt/data", "$crypt/data.2"), "hard link");
checkContents("$crypt/data.2", $contents, "hardlink read");
@ -306,6 +306,7 @@ sub mount
mkdir($raw) || BAIL_OUT("Could not create $raw: $!");
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");

View File

@ -3,10 +3,11 @@
# Test EncFS --reverse mode
use warnings;
use Test::More qw( no_plan );
use Test::More tests => 26;
use File::Path;
use File::Temp;
use IO::Handle;
use Errno qw(EROFS);
require("tests/common.inc");
@ -44,6 +45,7 @@ sub cleanup
# Directory structure: plain -[encrypt]-> ciphertext -[decrypt]-> decrypted
sub mount
{
delete $ENV{"ENCFS6_CONFIG"};
system("./encfs/encfs --extpass=\"echo test\" --standard $plain $ciphertext --reverse");
ok(waitForFile("$plain/.encfs6.xml"), "plain .encfs6.xml exists") or BAIL_OUT("'$plain/.encfs6.xml'");
my $e = encName(".encfs6.xml");
@ -68,7 +70,6 @@ sub copy_test
{
ok(system("cp -a encfs $plain")==0, "copying files to plain");
ok(system("diff -r -q $plain $decrypted")==0, "decrypted files are identical");
ok(-f "$plain/encfs/encfs.cpp", "file exists");
unlink("$plain/encfs/encfs.cpp");
ok(! -f "$decrypted/encfs.cpp", "file deleted");
@ -78,9 +79,11 @@ sub copy_test
# Parameter: symlink target
sub symlink_test
{
my $target = shift(@_);
my $target = shift;
symlink($target, "$plain/symlink");
ok( readlink("$decrypted/symlink") eq "$target", "symlink to '$target'");
$dec = readlink("$decrypted/symlink");
ok( $dec eq $target, "symlink to '$target'") or
print("# (original) $target' != '$dec' (decrypted)\n");
unlink("$plain/symlink");
}
@ -129,7 +132,7 @@ sub grow {
}
sub largeRead {
system("dd if=/dev/zero of=$plain/largeRead bs=1M count=1 2> /dev/null");
writeZeroes("$plain/largeRead", 1024*1024);
# ciphertext file name
my $cname = encName("largeRead");
# cfh ... ciphertext file handle
@ -137,6 +140,32 @@ sub largeRead {
ok(sizeVerify($cfh, 1024*1024+8), "1M file size");
}
# Check that the reverse mount is read-only
# (writing is not supported in reverse mode because of the added
# complexity and the marginal use case)
sub writesDenied {
$fn = "$plain/writesDenied";
writeZeroes($fn, 1024);
my $efn = $ciphertext . "/" . encName("writesDenied");
open(my $fh, ">", $efn);
if( ok( $! == EROFS, "open for write denied, EROFS")) {
ok( 1, "writing denied, filehandle not open");
}
else {
print($fh "foo");
ok( $! == EROFS, "writing denied, EROFS");
}
$target = $ciphertext . "/" . encName("writesDenied2");
rename($efn, $target);
ok( $! == EROFS, "rename denied, EROFS") or die();
unlink($efn);
ok( $! == EROFS, "unlink denied, EROFS");
utime(undef, undef, $efn) ;
ok( $! == EROFS, "touch denied, EROFS");
truncate($efn, 10);
ok( $! == EROFS, "truncate denied, EROFS");
}
# Setup mounts
newWorkingDir();
mount();
@ -149,6 +178,8 @@ symlink_test("/"); # absolute
symlink_test("foo"); # relative
symlink_test("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/15/17/18"); # long
symlink_test("!§\$%&/()\\<>#+="); # special characters
symlink_test("$plain/foo");
writesDenied();
# Umount and delete files
cleanup();