Multiple updates

1) Bug fix for EncFSInputStream missing off the last byte of the stream
2) Initial support for EncFSFileOutputStream
3) Additional tests
4) EncFSComparer enhanced to check file read/ re-write
This commit is contained in:
aefo 2012-01-01 16:11:28 +00:00
parent 4d1754de1b
commit e7c9798c57
8 changed files with 589 additions and 102 deletions

View file

@ -272,15 +272,28 @@ public class EncFSCrypto {
public static byte[] blockDecode(EncFSVolume volume, byte[] ivSeed, byte[] data)
throws InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if (data.length != volume.getConfig().getBlockSize()) {
throw new IllegalBlockSizeException();
}
// if (data.length != volume.getConfig().getBlockSize()) {
// throw new IllegalBlockSizeException();
// }
Cipher cipher = volume.getBlockCipher();
cipherInit(volume, Cipher.DECRYPT_MODE, cipher, ivSeed);
byte[] result = cipher.doFinal(data);
return result;
}
public static byte[] blockEncode(EncFSVolume volume, byte[] ivSeed, byte[] data) throws IllegalBlockSizeException,
InvalidAlgorithmParameterException, BadPaddingException {
// if (data.length != volume.getConfig().getBlockSize()) {
// throw new
// IllegalBlockSizeException("Data length must match block size ("
// + volume.getConfig().getBlockSize() + " vs. " + data.length);
// }
Cipher cipher = volume.getBlockCipher();
cipherInit(volume, Cipher.ENCRYPT_MODE, cipher, ivSeed);
byte[] result = cipher.doFinal(data);
return result;
}
public static byte[] streamEncode(EncFSVolume volume, byte[] ivSeed, byte[] data) throws EncFSUnsupportedException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return streamEncode(volume.getStreamCipher(), volume.getMac(), volume.getKey(), volume.getIV(), ivSeed, data);
@ -473,40 +486,24 @@ public class EncFSCrypto {
fileIv[i] = (byte) (macBytes[i] ^ chainIv[i]);
}
Cipher cipher;
byte[] decFileName;
if (volume.getConfig().getNameAlgorithm() == EncFSConfig.ENCFS_CONFIG_NAME_ALG_BLOCK) {
// Block decryption
cipher = volume.getBlockCipher();
try {
EncFSCrypto.cipherInit(volume, Cipher.DECRYPT_MODE, cipher, fileIv);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
try {
decFileName = cipher.doFinal(encFileName);
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
} else {
// Stream decryption
try {
try {
if (volume.getConfig().getNameAlgorithm() == EncFSConfig.ENCFS_CONFIG_NAME_ALG_BLOCK) {
// Block decryption
decFileName = EncFSCrypto.blockDecode(volume, fileIv, encFileName);
} else {
// Stream decryption
decFileName = EncFSCrypto.streamDecode(volume, fileIv, encFileName);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (EncFSUnsupportedException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (EncFSUnsupportedException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
// Verify decryption worked
@ -572,9 +569,23 @@ public class EncFSCrypto {
*/
public static String encodeName(EncFSVolume volume, String fileName, String volumePath)
throws EncFSCorruptDataException {
Cipher cipher;
byte[] decFileName = fileName.getBytes();
byte[] paddedDecFileName;
if (volume.getConfig().getNameAlgorithm() == EncFSConfig.ENCFS_CONFIG_NAME_ALG_BLOCK) {
// Pad to the nearest 16 bytes, add a full block if needed
int padBytesSize = 16;
int padLen = padBytesSize - (decFileName.length % padBytesSize);
if (padLen == 0) {
padLen = padBytesSize;
}
paddedDecFileName = Arrays.copyOf(decFileName, decFileName.length + padLen);
Arrays.fill(paddedDecFileName, decFileName.length, paddedDecFileName.length, (byte) padLen);
} else {
// Stream encryption
paddedDecFileName = decFileName;
}
byte[] chainIv = new byte[8];
// Chained IV computation
@ -606,9 +617,9 @@ public class EncFSCrypto {
byte[] mac16;
if (volume.getConfig().isChainedNameIV()) {
mac16 = EncFSCrypto.mac16(volume.getMac(), decFileName, chainIv);
mac16 = EncFSCrypto.mac16(volume.getMac(), paddedDecFileName, Arrays.copyOf(chainIv, chainIv.length));
} else {
mac16 = EncFSCrypto.mac16(volume.getMac(), decFileName);
mac16 = EncFSCrypto.mac16(volume.getMac(), paddedDecFileName);
}
// TODO: make sure its multiple of 16
@ -622,38 +633,23 @@ public class EncFSCrypto {
}
byte[] encFileName;
if (volume.getConfig().getNameAlgorithm() == EncFSConfig.ENCFS_CONFIG_NAME_ALG_BLOCK) {
try {
if (volume.getConfig().getNameAlgorithm() == EncFSConfig.ENCFS_CONFIG_NAME_ALG_BLOCK) {
// Block encryption
encFileName = EncFSCrypto.blockEncode(volume, fileIv, paddedDecFileName);
// Block encryption
cipher = volume.getBlockCipher();
try {
EncFSCrypto.cipherInit(volume, Cipher.ENCRYPT_MODE, cipher, fileIv);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
try {
encFileName = cipher.doFinal(decFileName);
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
} else {
// Stream encryption
try {
encFileName = EncFSCrypto.streamEncode(volume, fileIv, decFileName);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (EncFSUnsupportedException e) {
throw new EncFSCorruptDataException(e.getMessage());
} else {
// Stream encryption
encFileName = EncFSCrypto.streamEncode(volume, fileIv, paddedDecFileName);
}
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e);
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e);
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e);
} catch (EncFSUnsupportedException e) {
throw new EncFSCorruptDataException(e);
}
// current versions store the checksum at the beginning (encfs 0.x

View file

@ -342,4 +342,12 @@ public class EncFSFile {
return result;
}
}
public String getAbsoluteName() {
if (volumePath.endsWith("/")) {
return volumePath + plaintextName;
} else {
return volumePath + "/" + plaintextName;
}
}
}

View file

@ -18,6 +18,7 @@ package org.mrpdaemon.sec.encfs;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.Arrays;
@ -28,7 +29,7 @@ import javax.crypto.IllegalBlockSizeException;
* FileInputStream extension that allows data to be read from a file on an EncFS
* volume.
*/
public class EncFSFileInputStream extends FileInputStream {
public class EncFSFileInputStream extends InputStream {
// Volume that underlying file belongs to
private final EncFSVolume volume;
@ -51,6 +52,8 @@ public class EncFSFileInputStream extends FileInputStream {
// File IV computed from the first 8 bytes of the file
private byte[] fileIv;
private final InputStream inStream;
/**
* Create a new EncFSFileInputStream for reading data off a file on an EncFS
* volume
@ -67,8 +70,14 @@ public class EncFSFileInputStream extends FileInputStream {
*/
public EncFSFileInputStream(EncFSFile file) throws FileNotFoundException, EncFSCorruptDataException,
EncFSUnsupportedException {
super(file.getFile());
this.volume = file.getVolume();
this(file.getVolume(), new FileInputStream(file.getFile()));
}
public EncFSFileInputStream(EncFSVolume volume, InputStream in) throws FileNotFoundException,
EncFSCorruptDataException, EncFSUnsupportedException {
super();
this.inStream = in;
this.volume = volume;
this.config = volume.getConfig();
this.blockSize = config.getBlockSize();
this.blockBuf = null;
@ -79,7 +88,7 @@ public class EncFSFileInputStream extends FileInputStream {
// Compute file IV
byte[] fileHeader = new byte[8];
try {
super.read(fileHeader);
inStream.read(fileHeader);
} catch (IOException e) {
throw new EncFSCorruptDataException("Could't read file IV");
}
@ -115,7 +124,7 @@ public class EncFSFileInputStream extends FileInputStream {
*/
private int readBlock() throws IOException, EncFSCorruptDataException, EncFSUnsupportedException {
byte[] cipherBuf = new byte[blockSize];
int bytesRead = super.read(cipherBuf, 0, blockSize);
int bytesRead = inStream.read(cipherBuf, 0, blockSize);
if (bytesRead == blockSize) { // block decode
try {
blockBuf = EncFSCrypto.blockDecode(volume, getBlockIV(), cipherBuf);
@ -187,7 +196,7 @@ public class EncFSFileInputStream extends FileInputStream {
while (bytesRead < len) {
// Read more data if the data buffer is out
if ((blockBuf == null) || (bufCursor == (blockBuf.length - 1))) {
if ((blockBuf == null) || (bufCursor == (blockBuf.length))) {
try {
ret = readBlock();
} catch (EncFSCorruptDataException e) {
@ -205,7 +214,7 @@ public class EncFSFileInputStream extends FileInputStream {
}
}
bytesToCopy = Math.min(blockBuf.length - bufCursor - 1, len - bytesRead);
bytesToCopy = Math.min(blockBuf.length - bufCursor, len - bytesRead);
System.arraycopy(blockBuf, bufCursor, b, destOffset, bytesToCopy);
bufCursor += bytesToCopy;
@ -257,4 +266,10 @@ public class EncFSFileInputStream extends FileInputStream {
// TODO: could support mark()/reset()
return false;
}
@Override
public void close() throws IOException {
inStream.close();
super.close();
}
}

View file

@ -0,0 +1,212 @@
package org.mrpdaemon.sec.encfs;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
public class EncFSFileOutputStream extends OutputStream {
private final EncFSBufferedOutputStream encfsOs;
private static class EncFSBufferedOutputStream extends FilterOutputStream {
private static final SecureRandom secureRandom = new SecureRandom();
private final EncFSVolume volume;
private final EncFSConfig config;
private final int blockSize;
private byte[] fileIv;
private byte[] fileHeader;
private final byte buf[];
private int count;
private int blockNum;
private final Cipher blockCipher;
private final Cipher streamCipher;
public EncFSBufferedOutputStream(EncFSVolume volume, OutputStream out) throws EncFSUnsupportedException,
EncFSCorruptDataException {
super(out);
this.volume = volume;
this.config = volume.getConfig();
this.blockSize = config.getBlockSize();
if (config.isUniqueIV()) {
// Compute file IV
this.fileHeader = new byte[8];
secureRandom.nextBytes(fileHeader);
byte[] zeroIv = new byte[8];
// TODO: external IV chaining changes zeroIv
try {
this.fileIv = EncFSCrypto
.streamDecode(volume, zeroIv, Arrays.copyOf(fileHeader, fileHeader.length));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
throw new EncFSCorruptDataException(e.getMessage());
} catch (BadPaddingException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
} else {
// No unique IV per file, just use 0
this.fileIv = new byte[8];
}
this.blockCipher = EncFSCrypto.newBlockCipher();
try {
EncFSCrypto.cipherInit(volume, Cipher.ENCRYPT_MODE, blockCipher, fileIv);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
this.streamCipher = EncFSCrypto.newStreamCipher();
try {
EncFSCrypto.cipherInit(volume, Cipher.ENCRYPT_MODE, streamCipher, fileIv);
} catch (InvalidAlgorithmParameterException e) {
throw new EncFSCorruptDataException(e.getMessage());
}
// written blocks are blockSize - 8 bytes for the MAC?
buf = new byte[blockSize];
}
/** Flush the internal buffer */
private void writeBuffer(boolean isFinal) throws IOException {
if (isFinal == false && count != buf.length) {
throw new IllegalStateException("Buffer not full");
}
if (blockNum == 0 && config.isUniqueIV()) {
out.write(this.fileHeader);
}
byte[] encBuffer;
try {
if (count == buf.length) {
encBuffer = EncFSCrypto.blockEncode(volume, getBlockIV(), buf);
} else {
// copy the buffer as stream encode needs the sub-range & it
// also modifies it
byte[] tmpBuf = Arrays.copyOf(buf, count);
encBuffer = EncFSCrypto.streamEncode(volume, getBlockIV(), tmpBuf);
}
} catch (IllegalBlockSizeException e) {
throw new IOException(e);
} catch (BadPaddingException e) {
throw new IOException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new IOException(e);
} catch (EncFSUnsupportedException e) {
throw new IOException(e);
}
out.write(encBuffer);
count = 0;
blockNum++;
}
/*
* Return the block IV for the current block
*/
private byte[] getBlockIV() {
long fileIvLong = EncFSUtil.byteArrayToLong(fileIv);
return EncFSUtil.longToByteArray(blockNum ^ fileIvLong);
}
/** Flush the internal buffer */
private void writeBuffer() throws IOException {
writeBuffer(false);
}
@Override
public synchronized void write(int b) throws IOException {
buf[count++] = (byte) b;
if (count == buf.length) {
writeBuffer();
}
}
@Override
public synchronized void write(byte b[], int off, int len) throws IOException {
if (count + len <= buf.length) {
System.arraycopy(b, off, buf, count, len);
count += len;
if (count == buf.length) {
writeBuffer();
}
} else {
int tmpOff = off;
int remaining = len;
while (remaining > 0) {
int chunk = Math.min(remaining, buf.length - count);
write(b, tmpOff, chunk);
remaining -= chunk;
tmpOff += chunk;
}
}
}
@Override
public void close() throws IOException {
writeBuffer(true);
super.close();
}
}
public EncFSFileOutputStream(EncFSFile encfsFile) throws IOException, EncFSUnsupportedException,
EncFSCorruptDataException {
if (!encfsFile.getFile().exists()) {
encfsFile.getFile().createNewFile();
}
FileOutputStream fop = new FileOutputStream(encfsFile.getFile());
this.encfsOs = new EncFSBufferedOutputStream(encfsFile.getVolume(), fop);
}
public EncFSFileOutputStream(EncFSVolume volume, OutputStream outputStream) throws EncFSUnsupportedException,
EncFSCorruptDataException {
this.encfsOs = new EncFSBufferedOutputStream(volume, outputStream);
}
@Override
public void write(int b) throws IOException {
encfsOs.write(b);
}
@Override
public void write(byte[] b) throws IOException {
encfsOs.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
encfsOs.write(b, off, len);
}
@Override
public void close() throws IOException {
encfsOs.close();
}
@Override
public void flush() throws IOException {
encfsOs.flush();
}
}

View file

@ -396,4 +396,19 @@ public class EncFSVolume {
public EncFSFile getRootDir() {
return rootDir;
}
public EncFSFile getEncFSFile(String volumePath, String fileName) throws EncFSCorruptDataException,
EncFSChecksumException {
if (volumePath.startsWith("/") == false) {
throw new IllegalArgumentException("Volume path must be absolute");
}
String toEncFileName = EncFSCrypto.encodeName(this, fileName, volumePath);
String toEncVolumePath = EncFSCrypto.encodePath(this, volumePath.substring(1), "/");
File rawEncFile = new File(this.getRootDir().getFile().getAbsolutePath() + "/" + toEncVolumePath, toEncFileName);
// TODO Auto-generated method stub
return new EncFSFile(this, volumePath, rawEncFile);
}
}

View file

@ -1,7 +1,13 @@
package org.mrpdaemon.sec.encfs;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
@ -47,8 +53,8 @@ public class EncFSComparer {
this.decodedEncFSOutput = decodedEncFSOutput;
}
private int compare() throws FileNotFoundException, EncFSInvalidPasswordException, EncFSInvalidConfigException,
EncFSCorruptDataException, EncFSUnsupportedException, EncFSChecksumException {
private int compare() throws EncFSInvalidPasswordException, EncFSInvalidConfigException, EncFSCorruptDataException,
EncFSUnsupportedException, EncFSChecksumException, IOException {
logger.info("Performing compare between encfs raw volume at {} and output files at {}", rawEncFSVolume,
decodedEncFSOutput);
@ -65,7 +71,8 @@ public class EncFSComparer {
return result;
}
private int compare(EncFSFile encFsDir, File decodedFsDir) throws EncFSCorruptDataException, EncFSChecksumException {
private int compare(EncFSFile encFsDir, File decodedFsDir) throws EncFSCorruptDataException,
EncFSChecksumException, EncFSUnsupportedException, IOException {
logger.info("Comparing directory {}", decodedFsDir.getAbsoluteFile());
EncFSFile[] encFsFiles = encFsDir.listFiles();
@ -94,7 +101,7 @@ public class EncFSComparer {
if (rawFileName.equals(reEncEncfsName) == false) {
logger.error("Re-encoded name miss match ({}, {}, {}, {})", new Object[] { i, encFsFile.getName(),
rawFileName, reEncEncfsName });
// return -1;
return -1;
}
if (encFsFile.getFile().lastModified() != decodedFsFile.lastModified()) {
@ -115,6 +122,73 @@ public class EncFSComparer {
if (subResult != 0) {
return subResult;
}
} else {
// Check that the EncFSFileInputStream reads the file the
// same as
// reading the file directly from the mounted encfs volume
EncFSFileInputStream encfsIs = new EncFSFileInputStream(encFsFile);
try {
BufferedInputStream decFsIs = new BufferedInputStream(new FileInputStream(decodedFsFile));
String decodedFsFileName = decodedFsFile.getAbsoluteFile().getName();
try {
int streamresult = compareInputStreams(encfsIs, decFsIs, decodedFsFileName);
if (streamresult != 0) {
return streamresult;
}
} finally {
decFsIs.close();
}
} finally {
encfsIs.close();
}
// Copy the file via input/output streams & then check that
// the file is the same
File t = File.createTempFile(this.getClass().getName(), ".tmp");
try {
EncFSFileOutputStream efos = new EncFSFileOutputStream(encFsDir.getVolume(),
new BufferedOutputStream(new FileOutputStream(t)));
try {
EncFSFileInputStream efis = new EncFSFileInputStream(encFsFile);
try {
int bytesRead = 0;
while (bytesRead >= 0) {
byte[] readBuf = new byte[(int) (encFsFile.getVolume().getConfig().getBlockSize() * 0.75)];
bytesRead = efis.read(readBuf);
if (bytesRead >= 0) {
efos.write(readBuf, 0, bytesRead);
}
}
} finally {
efis.close();
}
} finally {
efos.close();
}
FileInputStream reEncFSIs = new FileInputStream(t);
try {
FileInputStream origEncFSIs = new FileInputStream(encFsFile.getFile());
try {
int streamresult = compareInputStreams(origEncFSIs, reEncFSIs, encFsFile.getFile()
.getAbsoluteFile().getName());
if (streamresult != 0) {
return streamresult;
}
} finally {
origEncFSIs.close();
}
} finally {
reEncFSIs.close();
}
} finally {
if (t.exists()) {
t.delete();
}
}
}
}
}
@ -122,6 +196,30 @@ public class EncFSComparer {
return 0;
}
private int compareInputStreams(InputStream encfsIs, InputStream decFsIs, String decodedFsFileName)
throws IOException {
int bytesRead = 0, bytesRead2 = 0;
while (bytesRead >= 0) {
byte[] readBuf = new byte[128];
byte[] readBuf2 = new byte[128];
bytesRead = encfsIs.read(readBuf);
bytesRead2 = decFsIs.read(readBuf2);
if (bytesRead != bytesRead2) {
logger.error("File bytes read missmatch {} ({}, {})", new Object[] { decodedFsFileName, bytesRead,
bytesRead2 });
return -1;
}
if (Arrays.equals(readBuf, readBuf2) == false) {
logger.error("File bytes missmatch {}", decodedFsFileName);
return -1;
}
}
return 0;
}
private static class SimpleEncFSFileComparator implements Comparator<EncFSFile> {
private static final SimpleEncFSFileComparator instance = new SimpleEncFSFileComparator();

View file

@ -32,4 +32,25 @@ public class EncFSCryptoTest {
Assert.assertArrayEquals(orig, b2);
}
@Test
public void testStreamEncodeDecode2() throws EncFSInvalidPasswordException, EncFSInvalidConfigException,
EncFSCorruptDataException, EncFSUnsupportedException, EncFSChecksumException, IOException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
File encFSDir = new File("test/encfs_samples/boxcryptor_1");
Assert.assertTrue(encFSDir.exists());
String password = "test";
EncFSVolume volume = new EncFSVolume(encFSDir, password);
String str = "test file\r";
byte[] orig = str.getBytes();
byte[] ivSeed = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] b1 = EncFSCrypto.streamEncode(volume, ivSeed, Arrays.copyOf(orig, orig.length));
byte[] b2 = EncFSCrypto.streamDecode(volume, ivSeed, Arrays.copyOf(b1, b1.length));
Assert.assertArrayEquals(orig, b2);
}
}

View file

@ -1,9 +1,13 @@
package org.mrpdaemon.sec.encfs;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.After;
import org.junit.AfterClass;
@ -43,6 +47,7 @@ public class EncFSVolumeTest {
Assert.fail();
} catch (EncFSInvalidPasswordException e) {
// this is correct that we should have got this exception
Assert.assertNotNull(e);
}
}
@ -63,7 +68,10 @@ public class EncFSVolumeTest {
Assert.assertEquals("test.txt", encFSFile.getName());
String contents = readInputStreamAsString(encFSFile);
Assert.assertEquals("This is a test file.", contents);
Assert.assertEquals("This is a test file.\n", contents);
assertFileNameEncoding(rootDir);
assertEncFSFileRoundTrip(rootDir);
}
@Test
@ -83,7 +91,10 @@ public class EncFSVolumeTest {
Assert.assertEquals("testfile.txt", encFSFile.getName());
String contents = readInputStreamAsString(encFSFile);
Assert.assertEquals("Test file for non-unique-IV file.", contents);
Assert.assertEquals("Test file for non-unique-IV file.\n", contents);
assertFileNameEncoding(rootDir);
assertEncFSFileRoundTrip(rootDir);
}
@Test
@ -103,7 +114,10 @@ public class EncFSVolumeTest {
Assert.assertEquals("testfile.txt", encFSFile.getName());
String contents = readInputStreamAsString(encFSFile);
Assert.assertEquals("test file\r", contents);
Assert.assertEquals("test file\r\n", contents);
assertFileNameEncoding(rootDir);
assertEncFSFileRoundTrip(rootDir);
}
@Test
@ -127,14 +141,17 @@ public class EncFSVolumeTest {
Assert.assertEquals("file1.txt", encFSFile.getName());
String contents = readInputStreamAsString(encFSFile);
Assert.assertEquals("Some contents for file", contents);
Assert.assertEquals("Some contents for file1", contents);
String dirListing = GetDirListing(rootDir, true);
String dirListing = getDirListing(rootDir, true);
String expectedListing = "";
expectedListing += "/Dir1" + "\n";
expectedListing += "/Dir1/file2.txt" + "\n";
expectedListing += "/file1.txt";
Assert.assertEquals(expectedListing, dirListing);
assertFileNameEncoding(rootDir);
assertEncFSFileRoundTrip(rootDir);
}
@Test
@ -149,38 +166,120 @@ public class EncFSVolumeTest {
EncFSFile[] files = rootDir.listFiles();
Assert.assertEquals(1, files.length);
String dirListing = GetDirListing(rootDir, true);
String dirListing = getDirListing(rootDir, true);
Assert.assertNotNull(dirListing);
assertFileNameEncoding(rootDir);
assertEncFSFileRoundTrip(rootDir);
}
@Test
public void testBoxCryptor_1_encode() throws EncFSInvalidPasswordException, EncFSInvalidConfigException,
EncFSCorruptDataException, EncFSUnsupportedException, EncFSChecksumException, IOException {
File encFSDir = new File("test/encfs_samples/boxcryptor_1");
Assert.assertTrue(encFSDir.exists());
private void assertFileNameEncoding(EncFSFile encfsFileDir) throws EncFSCorruptDataException,
EncFSChecksumException {
for (EncFSFile encfFile : encfsFileDir.listFiles()) {
EncFSVolume volume = encfsFileDir.getVolume();
String decName = EncFSCrypto.decodeName(volume, encfFile.getFile().getName(), encfFile.getVolumePath());
Assert.assertEquals(encfFile.getAbsoluteName() + " decoded file name", encfFile.getName(), decName);
String password = "test";
EncFSVolume volume = new EncFSVolume(encFSDir, password);
String encName = EncFSCrypto.encodeName(volume, decName, encfFile.getVolumePath());
Assert.assertEquals(encfFile.getAbsoluteName() + " re-encoded file name", encfFile.getFile().getName(),
encName);
String fileName = "W3gLoUqL-0YzUh8udP8";
String volumePath = "/";
String decName = EncFSCrypto.decodeName(volume, fileName, volumePath);
Assert.assertEquals("testfile.txt", decName);
String encName = EncFSCrypto.encodeName(volume, decName, volumePath);
Assert.assertEquals(fileName, encName);
if (encfFile.isDirectory()) {
assertFileNameEncoding(encfFile);
}
}
}
private static String GetDirListing(EncFSFile rootDir, boolean recursive) throws EncFSCorruptDataException,
private void assertEncFSFileRoundTrip(EncFSFile encFsFile) throws IOException, EncFSUnsupportedException,
EncFSCorruptDataException, EncFSChecksumException {
if (encFsFile.isDirectory() == false) {
// Copy the file via input/output streams & then check that
// the file is the same
File t = File.createTempFile(this.getClass().getName(), ".tmp");
try {
EncFSFileOutputStream efos = new EncFSFileOutputStream(encFsFile.getVolume(), new BufferedOutputStream(
new FileOutputStream(t)));
try {
EncFSFileInputStream efis = new EncFSFileInputStream(encFsFile);
try {
int bytesRead = 0;
while (bytesRead >= 0) {
byte[] readBuf = new byte[(int) (encFsFile.getVolume().getConfig().getBlockSize() * 0.75)];
bytesRead = efis.read(readBuf);
if (bytesRead >= 0) {
efos.write(readBuf, 0, bytesRead);
}
}
} finally {
efis.close();
}
} finally {
efos.close();
}
if (encFsFile.getVolume().getConfig().isUniqueIV() == false) {
FileInputStream reEncFSIs = new FileInputStream(t);
try {
FileInputStream origEncFSIs = new FileInputStream(encFsFile.getFile());
try {
assertInputStreamsAreEqual(encFsFile.getAbsoluteName(), origEncFSIs, reEncFSIs);
} finally {
origEncFSIs.close();
}
} finally {
reEncFSIs.close();
}
} else {
EncFSFileInputStream efis = new EncFSFileInputStream(encFsFile);
try {
EncFSFileInputStream efisCopy = new EncFSFileInputStream(encFsFile.getVolume(),
new FileInputStream(t));
try {
assertInputStreamsAreEqual(encFsFile.getAbsoluteName(), efis, efisCopy);
} finally {
efisCopy.close();
}
} finally {
efis.close();
}
}
} finally {
if (t.exists()) {
t.delete();
}
}
} else {
for (EncFSFile subEncfFile : encFsFile.listFiles()) {
assertEncFSFileRoundTrip(subEncfFile);
}
}
}
private void assertInputStreamsAreEqual(String msg, InputStream encfsIs, InputStream decFsIs) throws IOException {
int bytesRead = 0, bytesRead2 = 0;
while (bytesRead >= 0) {
byte[] readBuf = new byte[128];
byte[] readBuf2 = new byte[128];
bytesRead = encfsIs.read(readBuf);
bytesRead2 = decFsIs.read(readBuf2);
Assert.assertEquals(msg, bytesRead, bytesRead2);
Assert.assertArrayEquals(msg, readBuf, readBuf2);
}
}
private static String getDirListing(EncFSFile rootDir, boolean recursive) throws EncFSCorruptDataException,
EncFSChecksumException {
StringBuilder sb = new StringBuilder();
GetDirListing(rootDir, recursive, sb);
getDirListing(rootDir, recursive, sb);
return sb.toString();
}
private static void GetDirListing(EncFSFile rootDir, boolean recursive, StringBuilder sb)
private static void getDirListing(EncFSFile rootDir, boolean recursive, StringBuilder sb)
throws EncFSCorruptDataException, EncFSChecksumException {
for (EncFSFile encFile : rootDir.listFiles()) {
@ -193,7 +292,7 @@ public class EncFSVolumeTest {
}
sb.append(encFile.getName());
if (encFile.isDirectory() && recursive) {
GetDirListing(encFile, recursive, sb);
getDirListing(encFile, recursive, sb);
}
}
}
@ -219,4 +318,27 @@ public class EncFSVolumeTest {
return new String(buf.toByteArray());
}
public static void copyViaStreams(EncFSFile srcEncFSFile, EncFSFile targetEncFSFile) throws IOException,
EncFSCorruptDataException, EncFSUnsupportedException {
EncFSFileOutputStream efos = new EncFSFileOutputStream(targetEncFSFile);
try {
EncFSFileInputStream efis = new EncFSFileInputStream(srcEncFSFile);
try {
int bytesRead = 0;
while (bytesRead >= 0) {
byte[] readBuf = new byte[128];
bytesRead = efis.read(readBuf);
if (bytesRead >= 0) {
efos.write(readBuf, 0, bytesRead);
}
}
} finally {
efis.close();
}
} finally {
efos.close();
}
}
}