mirror of
https://github.com/vgough/encfs.git
synced 2024-11-22 07:53:31 +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 <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(); }
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user