mirror of
https://github.com/vgough/encfs.git
synced 2024-11-21 23:43:26 +01:00
Implement uniqueIV for reverse mode
For now, the IVs are constant. This is fixed in a later commit. They are enabled by default to make testing easier. The whole thing passes the test suite on x86 and x86_64.
This commit is contained in:
parent
dee3f628e3
commit
76424a58cb
@ -28,6 +28,7 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <cerrno>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
- Version 2:0 adds support for a per-file initialization vector with a
|
||||
@ -128,25 +129,55 @@ bool CipherFileIO::setIV(uint64_t iv) {
|
||||
return base->setIV(iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file attributes (FUSE-speak for "stat()") for an upper file
|
||||
* Upper file = file we present to the user via FUSE
|
||||
* Backing file = file that is actually on disk
|
||||
*/
|
||||
int CipherFileIO::getAttr(struct stat *stbuf) const {
|
||||
|
||||
// stat() the backing file
|
||||
int res = base->getAttr(stbuf);
|
||||
|
||||
// adjust size if we have a file header
|
||||
if ((res == 0) && haveHeader && S_ISREG(stbuf->st_mode) &&
|
||||
(stbuf->st_size > 0)) {
|
||||
rAssert(stbuf->st_size >= HEADER_SIZE);
|
||||
stbuf->st_size -= HEADER_SIZE;
|
||||
if(!fsConfig->reverseEncryption)
|
||||
{
|
||||
/* In normal mode, the upper file (plaintext) is smaller
|
||||
* than the backing ciphertext file */
|
||||
rAssert(stbuf->st_size >= HEADER_SIZE);
|
||||
stbuf->st_size -= HEADER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* In reverse mode, the upper file (ciphertext) is larger than
|
||||
* the backing plaintext file */
|
||||
stbuf->st_size += HEADER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size for an upper file
|
||||
* See getAttr() for an explaination of the reverse handling
|
||||
*/
|
||||
off_t CipherFileIO::getSize() const {
|
||||
off_t size = base->getSize();
|
||||
// No check on S_ISREG here -- don't call getSize over getAttr unless this
|
||||
// is a normal file!
|
||||
if (haveHeader && size > 0) {
|
||||
rAssert(size >= HEADER_SIZE);
|
||||
size -= HEADER_SIZE;
|
||||
if(!fsConfig->reverseEncryption)
|
||||
{
|
||||
rAssert(size >= HEADER_SIZE);
|
||||
size -= HEADER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
size += HEADER_SIZE;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
@ -233,15 +264,43 @@ bool CipherFileIO::writeHeader() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the file IV header bytes in reverse mode
|
||||
*/
|
||||
void CipherFileIO::generateReverseHeader(unsigned char* buf) {
|
||||
rDebug("generating file IV header (reverse mode)");
|
||||
|
||||
// TODO derive from inode
|
||||
for (int i = 0; i < HEADER_SIZE; ++i) {
|
||||
buf[i]=i;
|
||||
}
|
||||
|
||||
// Save plain-text IV
|
||||
fileIV = 0;
|
||||
for (int i = 0; i < HEADER_SIZE; ++i) {
|
||||
fileIV = (fileIV << 8) | (uint64_t)buf[i];
|
||||
}
|
||||
|
||||
// Encrypt externally-visible header
|
||||
cipher->streamEncode(buf, HEADER_SIZE, externalIV, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read block from backing ciphertext file, decrypt it (normal mode)
|
||||
* or
|
||||
* Read block from backing plaintext file, then encrypt it (reverse mode)
|
||||
*/
|
||||
ssize_t CipherFileIO::readOneBlock(const IORequest &req) const {
|
||||
// read raw data, then decipher it..
|
||||
int bs = blockSize();
|
||||
off_t blockNum = req.offset / bs;
|
||||
|
||||
ssize_t readSize = 0;
|
||||
IORequest tmpReq = req;
|
||||
|
||||
if (haveHeader) tmpReq.offset += HEADER_SIZE;
|
||||
// adjust offset if we have a file header
|
||||
if (haveHeader && !fsConfig->reverseEncryption) {
|
||||
tmpReq.offset += HEADER_SIZE;
|
||||
}
|
||||
readSize = base->read(tmpReq);
|
||||
|
||||
bool ok;
|
||||
@ -250,6 +309,7 @@ ssize_t CipherFileIO::readOneBlock(const IORequest &req) const {
|
||||
const_cast<CipherFileIO *>(this)->initHeader();
|
||||
|
||||
if (readSize != bs) {
|
||||
rDebug("streamRead(data, %d, IV)", (int)readSize);
|
||||
ok = streamRead(tmpReq.data, (int)readSize, blockNum ^ fileIV);
|
||||
} else {
|
||||
ok = blockRead(tmpReq.data, (int)readSize, blockNum ^ fileIV);
|
||||
@ -267,6 +327,12 @@ ssize_t CipherFileIO::readOneBlock(const IORequest &req) const {
|
||||
}
|
||||
|
||||
bool CipherFileIO::writeOneBlock(const IORequest &req) {
|
||||
|
||||
if (haveHeader && fsConfig->reverseEncryption) {
|
||||
rDebug("writing to a reverse mount with per-file IVs is not implemented");
|
||||
return -EROFS;
|
||||
}
|
||||
|
||||
int bs = blockSize();
|
||||
off_t blockNum = req.offset / bs;
|
||||
|
||||
@ -296,6 +362,7 @@ bool CipherFileIO::writeOneBlock(const IORequest &req) {
|
||||
|
||||
bool CipherFileIO::blockWrite(unsigned char *buf, int size,
|
||||
uint64_t _iv64) const {
|
||||
rDebug("Called blockWrite");
|
||||
if (!fsConfig->reverseEncryption)
|
||||
return cipher->blockEncode(buf, size, _iv64, key);
|
||||
else
|
||||
@ -304,6 +371,7 @@ bool CipherFileIO::blockWrite(unsigned char *buf, int size,
|
||||
|
||||
bool CipherFileIO::streamWrite(unsigned char *buf, int size,
|
||||
uint64_t _iv64) const {
|
||||
rDebug("Called streamWrite");
|
||||
if (!fsConfig->reverseEncryption)
|
||||
return cipher->streamEncode(buf, size, _iv64, key);
|
||||
else
|
||||
@ -359,4 +427,70 @@ int CipherFileIO::truncate(off_t size) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reads for reverse mode with uniqueIV
|
||||
*/
|
||||
ssize_t CipherFileIO::read(const IORequest &origReq) const {
|
||||
|
||||
/* if reverse mode is not active with uniqueIV,
|
||||
* the read request is handled by the base class */
|
||||
if ( !(fsConfig->reverseEncryption && haveHeader) ) {
|
||||
rDebug("relaying request to base class: offset=%d, dataLen=%d", origReq.offset, origReq.dataLen);
|
||||
return BlockFileIO::read(origReq);
|
||||
}
|
||||
|
||||
rDebug("handling reverse unique IV read: offset=%d, dataLen=%d", origReq.offset, origReq.dataLen);
|
||||
|
||||
// generate the file IV header
|
||||
// this is needed in any case - without IV the file cannot be decoded
|
||||
unsigned char headerBuf[HEADER_SIZE];
|
||||
const_cast<CipherFileIO *>(this)->generateReverseHeader(headerBuf);
|
||||
|
||||
// Copy the request so we can modify it without affecting the caller
|
||||
IORequest req = origReq;
|
||||
|
||||
/* An offset x in the ciphertext file maps to x-8 in the
|
||||
* plain text file. Values below zero are the header. */
|
||||
req.offset -= HEADER_SIZE;
|
||||
|
||||
int headerBytes = 0; // number of header bytes to add
|
||||
|
||||
/* The request contains (a part of) the header, so we prefix that part
|
||||
* to the data. */
|
||||
if (req.offset < 0) {
|
||||
headerBytes = -req.offset;
|
||||
if ( req.dataLen < headerBytes )
|
||||
headerBytes = req.dataLen; // only up to the number of bytes requested
|
||||
rDebug("Adding %d header bytes", headerBytes);
|
||||
|
||||
// copy the header bytes into the data
|
||||
int headerOffset = HEADER_SIZE - headerBytes;
|
||||
memcpy(req.data, &headerBuf[headerOffset], headerBytes);
|
||||
|
||||
// the read does not want data beyond the header
|
||||
if ( headerBytes == req.dataLen)
|
||||
return headerBytes;
|
||||
|
||||
/* The rest of the request will be read from the backing file.
|
||||
* As we have already generated n=headerBytes bytes, the request is
|
||||
* shifted by headerBytes */
|
||||
req.offset += headerBytes;
|
||||
rAssert( req.offset == 0 );
|
||||
req.data += headerBytes;
|
||||
req.dataLen -= headerBytes;
|
||||
}
|
||||
|
||||
// read the payload
|
||||
ssize_t readBytes = BlockFileIO::read(req);
|
||||
rDebug("read %ld bytes from backing file", (long)readBytes);
|
||||
if ( readBytes < 0)
|
||||
return readBytes; // Return error code
|
||||
else
|
||||
{
|
||||
ssize_t sum = headerBytes + readBytes;
|
||||
rDebug("returning sum=%ld", (long)sum);
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
bool CipherFileIO::isWritable() const { return base->isWritable(); }
|
||||
|
@ -57,6 +57,7 @@ class CipherFileIO : public BlockFileIO {
|
||||
private:
|
||||
virtual ssize_t readOneBlock(const IORequest &req) const;
|
||||
virtual bool writeOneBlock(const IORequest &req);
|
||||
virtual void generateReverseHeader(unsigned char* data);
|
||||
|
||||
void initHeader();
|
||||
bool writeHeader();
|
||||
@ -65,6 +66,8 @@ class CipherFileIO : public BlockFileIO {
|
||||
bool blockWrite(unsigned char *buf, int size, uint64_t iv64) const;
|
||||
bool streamWrite(unsigned char *buf, int size, uint64_t iv64) const;
|
||||
|
||||
ssize_t read(const IORequest &req) const;
|
||||
|
||||
shared_ptr<FileIO> base;
|
||||
|
||||
FSConfigPtr fsConfig;
|
||||
|
@ -1008,11 +1008,11 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
|
||||
blockMACBytes = 0;
|
||||
externalIV = false;
|
||||
nameIOIface = BlockNameIO::CurrentInterface();
|
||||
uniqueIV = true;
|
||||
|
||||
if (reverseEncryption) {
|
||||
cout << _("--reverse specified, not using unique/chained IV") << "\n";
|
||||
cout << _("--reverse specified, not using chained IV") << "\n";
|
||||
} else {
|
||||
uniqueIV = true;
|
||||
chainedIV = true;
|
||||
}
|
||||
}
|
||||
@ -1480,7 +1480,7 @@ RootPtr initFS(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
|
||||
if (readConfig(opts->rootDir, config) != Config_None) {
|
||||
if (opts->reverseEncryption) {
|
||||
if (config->blockMACBytes != 0 || config->blockMACRandBytes != 0 ||
|
||||
config->uniqueIV || config->externalIVChaining ||
|
||||
config->externalIVChaining ||
|
||||
config->chainedNameIV) {
|
||||
cout
|
||||
<< _("The configuration loaded is not compatible with --reverse\n");
|
||||
|
@ -23,9 +23,16 @@ sub readSize
|
||||
{
|
||||
my $fh = shift;
|
||||
seek($fh, 0, 0);
|
||||
my $s = read($fh, my $data, 10240);
|
||||
$data = 0;
|
||||
return $s;
|
||||
my $block = 4*1024;
|
||||
my $s;
|
||||
my $data;
|
||||
my $sum = read($fh, $data, $block);
|
||||
while ( $s = read($fh, $data, $block) )
|
||||
{
|
||||
$sum += $s;
|
||||
}
|
||||
$data = "";
|
||||
return $sum;
|
||||
}
|
||||
|
||||
# Verify that the size of the file passed by filehandle matches the target size s0
|
||||
|
@ -89,43 +89,59 @@ sub symlink_test
|
||||
# * check that the decrypted length is correct (stat + read)
|
||||
# * check that plaintext and decrypted are identical
|
||||
sub grow {
|
||||
# pfh ... plaintext file handle
|
||||
open(my $pfh, ">", "$plain/grow");
|
||||
# vfh ... verification file handle
|
||||
open(my $vfh, "<", "$plain/grow");
|
||||
$pfh->autoflush;
|
||||
# ciphertext file name
|
||||
my $cname = encName("grow");
|
||||
# cfh ... ciphertext file handle
|
||||
ok(open(my $cfh, "<", "$ciphertext/$cname"), "open ciphertext grow file");
|
||||
# dfh ... decrypted file handle
|
||||
ok(open(my $dfh, "<", "$decrypted/grow"), "open decrypted grow file");
|
||||
# pfh ... plaintext file handle
|
||||
open(my $pfh, ">", "$plain/grow");
|
||||
# vfh ... verification file handle
|
||||
open(my $vfh, "<", "$plain/grow");
|
||||
$pfh->autoflush;
|
||||
# ciphertext file name
|
||||
my $cname = encName("grow");
|
||||
# cfh ... ciphertext file handle
|
||||
ok(open(my $cfh, "<", "$ciphertext/$cname"), "open ciphertext grow file");
|
||||
# dfh ... decrypted file handle
|
||||
ok(open(my $dfh, "<", "$decrypted/grow"), "open decrypted grow file");
|
||||
|
||||
# csz ... ciphertext size
|
||||
ok(sizeVerify($cfh, 0), "ciphertext of empty file is empty");
|
||||
ok(sizeVerify($dfh, 0), "decrypted empty file is empty");
|
||||
# csz ... ciphertext size
|
||||
ok(sizeVerify($cfh, 0), "ciphertext of empty file is empty");
|
||||
ok(sizeVerify($dfh, 0), "decrypted empty file is empty");
|
||||
|
||||
my $ok = 1;
|
||||
for($i=1; $i < 20; $i++)
|
||||
{
|
||||
print($pfh "w") or die("write failed");
|
||||
# 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) or $ok = 0;
|
||||
my $ok = 1;
|
||||
my $max = 9000;
|
||||
for($i=5; $i < $max; $i += 5)
|
||||
{
|
||||
print($pfh "wwwww") or die("write failed");
|
||||
# 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($dfh, $i) or $ok = 0;
|
||||
}
|
||||
ok($ok, "ciphertext and decrypted size of file grown to $i bytes");
|
||||
|
||||
last unless $ok;
|
||||
}
|
||||
ok($ok, "ciphertext and decrypted size of file grown to $i bytes");
|
||||
}
|
||||
|
||||
sub largeRead {
|
||||
system("dd if=/dev/zero of=$plain/largeRead bs=1M count=1 2> /dev/null");
|
||||
# ciphertext file name
|
||||
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");
|
||||
}
|
||||
|
||||
# Setup mounts
|
||||
newWorkingDir();
|
||||
mount();
|
||||
|
||||
# Actual tests
|
||||
grow();
|
||||
largeRead();
|
||||
copy_test();
|
||||
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
|
||||
grow();
|
||||
|
||||
# Umount and delete files
|
||||
cleanup();
|
||||
|
Loading…
Reference in New Issue
Block a user