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:
Jakob Unterwurzacher 2014-11-14 23:01:41 +01:00
parent dee3f628e3
commit 76424a58cb
5 changed files with 197 additions and 37 deletions

View File

@ -28,6 +28,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <cerrno> #include <cerrno>
#include <string.h>
/* /*
- Version 2:0 adds support for a per-file initialization vector with a - 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); 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 { int CipherFileIO::getAttr(struct stat *stbuf) const {
// stat() the backing file
int res = base->getAttr(stbuf); int res = base->getAttr(stbuf);
// adjust size if we have a file header // adjust size if we have a file header
if ((res == 0) && haveHeader && S_ISREG(stbuf->st_mode) && if ((res == 0) && haveHeader && S_ISREG(stbuf->st_mode) &&
(stbuf->st_size > 0)) { (stbuf->st_size > 0)) {
rAssert(stbuf->st_size >= HEADER_SIZE); if(!fsConfig->reverseEncryption)
stbuf->st_size -= HEADER_SIZE; {
/* 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; 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 CipherFileIO::getSize() const {
off_t size = base->getSize(); off_t size = base->getSize();
// No check on S_ISREG here -- don't call getSize over getAttr unless this // No check on S_ISREG here -- don't call getSize over getAttr unless this
// is a normal file! // is a normal file!
if (haveHeader && size > 0) { if (haveHeader && size > 0) {
rAssert(size >= HEADER_SIZE); if(!fsConfig->reverseEncryption)
size -= HEADER_SIZE; {
rAssert(size >= HEADER_SIZE);
size -= HEADER_SIZE;
}
else
{
size += HEADER_SIZE;
}
} }
return size; return size;
} }
@ -233,15 +264,43 @@ bool CipherFileIO::writeHeader() {
return true; 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 { ssize_t CipherFileIO::readOneBlock(const IORequest &req) const {
// read raw data, then decipher it..
int bs = blockSize(); int bs = blockSize();
off_t blockNum = req.offset / bs; off_t blockNum = req.offset / bs;
ssize_t readSize = 0; ssize_t readSize = 0;
IORequest tmpReq = req; 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); readSize = base->read(tmpReq);
bool ok; bool ok;
@ -250,6 +309,7 @@ ssize_t CipherFileIO::readOneBlock(const IORequest &req) const {
const_cast<CipherFileIO *>(this)->initHeader(); const_cast<CipherFileIO *>(this)->initHeader();
if (readSize != bs) { if (readSize != bs) {
rDebug("streamRead(data, %d, IV)", (int)readSize);
ok = streamRead(tmpReq.data, (int)readSize, blockNum ^ fileIV); ok = streamRead(tmpReq.data, (int)readSize, blockNum ^ fileIV);
} else { } else {
ok = blockRead(tmpReq.data, (int)readSize, blockNum ^ fileIV); 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) { 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(); int bs = blockSize();
off_t blockNum = req.offset / bs; off_t blockNum = req.offset / bs;
@ -296,6 +362,7 @@ bool CipherFileIO::writeOneBlock(const IORequest &req) {
bool CipherFileIO::blockWrite(unsigned char *buf, int size, bool CipherFileIO::blockWrite(unsigned char *buf, int size,
uint64_t _iv64) const { uint64_t _iv64) const {
rDebug("Called blockWrite");
if (!fsConfig->reverseEncryption) if (!fsConfig->reverseEncryption)
return cipher->blockEncode(buf, size, _iv64, key); return cipher->blockEncode(buf, size, _iv64, key);
else else
@ -304,6 +371,7 @@ bool CipherFileIO::blockWrite(unsigned char *buf, int size,
bool CipherFileIO::streamWrite(unsigned char *buf, int size, bool CipherFileIO::streamWrite(unsigned char *buf, int size,
uint64_t _iv64) const { uint64_t _iv64) const {
rDebug("Called streamWrite");
if (!fsConfig->reverseEncryption) if (!fsConfig->reverseEncryption)
return cipher->streamEncode(buf, size, _iv64, key); return cipher->streamEncode(buf, size, _iv64, key);
else else
@ -359,4 +427,70 @@ int CipherFileIO::truncate(off_t size) {
return res; 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(); } bool CipherFileIO::isWritable() const { return base->isWritable(); }

View File

@ -57,6 +57,7 @@ class CipherFileIO : public BlockFileIO {
private: private:
virtual ssize_t readOneBlock(const IORequest &req) const; virtual ssize_t readOneBlock(const IORequest &req) const;
virtual bool writeOneBlock(const IORequest &req); virtual bool writeOneBlock(const IORequest &req);
virtual void generateReverseHeader(unsigned char* data);
void initHeader(); void initHeader();
bool writeHeader(); bool writeHeader();
@ -65,6 +66,8 @@ class CipherFileIO : public BlockFileIO {
bool blockWrite(unsigned char *buf, int size, uint64_t iv64) const; bool blockWrite(unsigned char *buf, int size, uint64_t iv64) const;
bool streamWrite(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; shared_ptr<FileIO> base;
FSConfigPtr fsConfig; FSConfigPtr fsConfig;

View File

@ -1008,11 +1008,11 @@ RootPtr createV6Config(EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts) {
blockMACBytes = 0; blockMACBytes = 0;
externalIV = false; externalIV = false;
nameIOIface = BlockNameIO::CurrentInterface(); nameIOIface = BlockNameIO::CurrentInterface();
uniqueIV = true;
if (reverseEncryption) { if (reverseEncryption) {
cout << _("--reverse specified, not using unique/chained IV") << "\n"; cout << _("--reverse specified, not using chained IV") << "\n";
} else { } else {
uniqueIV = true;
chainedIV = 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 (readConfig(opts->rootDir, config) != Config_None) {
if (opts->reverseEncryption) { if (opts->reverseEncryption) {
if (config->blockMACBytes != 0 || config->blockMACRandBytes != 0 || if (config->blockMACBytes != 0 || config->blockMACRandBytes != 0 ||
config->uniqueIV || config->externalIVChaining || config->externalIVChaining ||
config->chainedNameIV) { config->chainedNameIV) {
cout cout
<< _("The configuration loaded is not compatible with --reverse\n"); << _("The configuration loaded is not compatible with --reverse\n");

View File

@ -23,9 +23,16 @@ sub readSize
{ {
my $fh = shift; my $fh = shift;
seek($fh, 0, 0); seek($fh, 0, 0);
my $s = read($fh, my $data, 10240); my $block = 4*1024;
$data = 0; my $s;
return $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 # Verify that the size of the file passed by filehandle matches the target size s0

View File

@ -89,43 +89,59 @@ sub symlink_test
# * check that the decrypted length is correct (stat + read) # * check that the decrypted length is correct (stat + read)
# * check that plaintext and decrypted are identical # * check that plaintext and decrypted are identical
sub grow { sub grow {
# pfh ... plaintext file handle # pfh ... plaintext file handle
open(my $pfh, ">", "$plain/grow"); open(my $pfh, ">", "$plain/grow");
# vfh ... verification file handle # vfh ... verification file handle
open(my $vfh, "<", "$plain/grow"); open(my $vfh, "<", "$plain/grow");
$pfh->autoflush; $pfh->autoflush;
# ciphertext file name # ciphertext file name
my $cname = encName("grow"); my $cname = encName("grow");
# cfh ... ciphertext file handle # cfh ... ciphertext file handle
ok(open(my $cfh, "<", "$ciphertext/$cname"), "open ciphertext grow file"); ok(open(my $cfh, "<", "$ciphertext/$cname"), "open ciphertext grow file");
# dfh ... decrypted file handle # dfh ... decrypted file handle
ok(open(my $dfh, "<", "$decrypted/grow"), "open decrypted grow file"); ok(open(my $dfh, "<", "$decrypted/grow"), "open decrypted grow file");
# csz ... ciphertext size # csz ... ciphertext size
ok(sizeVerify($cfh, 0), "ciphertext of empty file is empty"); ok(sizeVerify($cfh, 0), "ciphertext of empty file is empty");
ok(sizeVerify($dfh, 0), "decrypted empty file is empty"); ok(sizeVerify($dfh, 0), "decrypted empty file is empty");
my $ok = 1; my $ok = 1;
for($i=1; $i < 20; $i++) my $max = 9000;
{ for($i=5; $i < $max; $i += 5)
print($pfh "w") or die("write failed"); {
# autoflush should make sure the write goes to the kernel print($pfh "wwwww") or die("write failed");
# immediately. Just to be sure, check it here. # autoflush should make sure the write goes to the kernel
sizeVerify($vfh, $i) or die("unexpected plain file size"); # immediately. Just to be sure, check it here.
sizeVerify($cfh, $i) or $ok = 0; sizeVerify($vfh, $i) or die("unexpected plain file size");
sizeVerify($cfh, $i+8) or $ok = 0;
sizeVerify($dfh, $i) 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(); newWorkingDir();
mount(); mount();
# Actual tests
grow();
largeRead();
copy_test(); copy_test();
symlink_test("/"); # absolute symlink_test("/"); # absolute
symlink_test("foo"); # relative 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("/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("!§\$%&/()\\<>#+="); # special characters
grow();
# Umount and delete files
cleanup(); cleanup();