mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 17:16:53 -04:00
- Made unit tests I/O-independent
This commit is contained in:
@@ -89,7 +89,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
*/
|
||||
private final byte[] masterKey = new byte[MASTER_KEY_LENGTH];
|
||||
|
||||
private static final int SIZE_OF_LONG = Long.SIZE / Byte.SIZE;
|
||||
private static final int SIZE_OF_LONG = Long.BYTES;
|
||||
|
||||
static {
|
||||
try {
|
||||
@@ -444,15 +444,19 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
// use an IV, whose last 8 bytes store a long used in counter mode and write initial value to file.
|
||||
final ByteBuffer countingIv = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
|
||||
countingIv.putLong(AES_BLOCK_LENGTH - SIZE_OF_LONG, 0l);
|
||||
countingIv.position(0);
|
||||
|
||||
// derive secret key and generate cipher:
|
||||
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
|
||||
final Cipher cipher = this.cipher(FILE_CONTENT_CIPHER, key, countingIv.array(), Cipher.ENCRYPT_MODE);
|
||||
|
||||
// skip 8 bytes (reserved for file size):
|
||||
encryptedFile.position(SIZE_OF_LONG);
|
||||
// 8 bytes (file size: temporarily -1):
|
||||
final ByteBuffer fileSize = ByteBuffer.allocate(SIZE_OF_LONG);
|
||||
fileSize.putLong(-1L);
|
||||
fileSize.position(0);
|
||||
encryptedFile.write(fileSize);
|
||||
|
||||
// write iv:
|
||||
// 16 bytes (iv):
|
||||
encryptedFile.write(countingIv);
|
||||
|
||||
// write content:
|
||||
@@ -461,11 +465,11 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
final Long actualSize = IOUtils.copyLarge(plaintextFile, cipheredOut);
|
||||
|
||||
// write filesize
|
||||
final ByteBuffer actualSizeBuffer = ByteBuffer.allocate(SIZE_OF_LONG);
|
||||
actualSizeBuffer.putLong(actualSize);
|
||||
actualSizeBuffer.position(0);
|
||||
fileSize.position(0);
|
||||
fileSize.putLong(actualSize);
|
||||
fileSize.position(0);
|
||||
encryptedFile.position(0);
|
||||
encryptedFile.write(actualSizeBuffer);
|
||||
encryptedFile.write(fileSize);
|
||||
|
||||
return actualSize;
|
||||
}
|
||||
|
||||
@@ -12,96 +12,84 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.cryptomator.crypto.CryptorIOSupport;
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class Aes256CryptorTest {
|
||||
|
||||
private static final Random TEST_PRNG = new Random();
|
||||
|
||||
private Path tmpDir;
|
||||
private Path masterKey;
|
||||
private Path encryptedFile;
|
||||
|
||||
@Before
|
||||
public void prepareTmpDir() throws IOException {
|
||||
final String tmpDirName = (String) System.getProperties().get("java.io.tmpdir");
|
||||
final Path path = FileSystems.getDefault().getPath(tmpDirName);
|
||||
tmpDir = Files.createTempDirectory(path, "oce-crypto-test");
|
||||
masterKey = tmpDir.resolve("test" + Aes256Cryptor.MASTERKEY_FILE_EXT);
|
||||
encryptedFile = tmpDir.resolve("test" + Aes256Cryptor.BASIC_FILE_EXT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropTmpDir() {
|
||||
try {
|
||||
FileUtils.deleteDirectory(tmpDir.toFile());
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
@Test
|
||||
public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
final Aes256Cryptor decryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final InputStream in = Files.newInputStream(masterKey, StandardOpenOption.READ);
|
||||
final InputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
decryptor.decryptMasterKey(in, pw);
|
||||
|
||||
IOUtils.closeQuietly(out);
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
|
||||
@Test(expected = WrongPasswordException.class)
|
||||
public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
final String wrongPw = "foo";
|
||||
final Aes256Cryptor decryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final InputStream in = Files.newInputStream(masterKey, StandardOpenOption.READ);
|
||||
final InputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
decryptor.decryptMasterKey(in, wrongPw);
|
||||
|
||||
IOUtils.closeQuietly(out);
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchFileException.class)
|
||||
public void testWrongLocation() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
@Test
|
||||
public void testEncryptionAndDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException {
|
||||
// our test plaintext data:
|
||||
final byte[] plaintextData = "Hello World".getBytes();
|
||||
final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
|
||||
|
||||
final Path wrongMasterKey = tmpDir.resolve("notExistingMasterKey.json");
|
||||
final Aes256Cryptor decryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final InputStream in = Files.newInputStream(wrongMasterKey, StandardOpenOption.READ);
|
||||
decryptor.decryptMasterKey(in, pw);
|
||||
// init cryptor:
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
|
||||
// encrypt:
|
||||
final ByteBuffer encryptedData = ByteBuffer.allocate(plaintextData.length + 200);
|
||||
final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
|
||||
cryptor.encryptFile(plaintextIn, encryptedOut);
|
||||
IOUtils.closeQuietly(plaintextIn);
|
||||
IOUtils.closeQuietly(encryptedOut);
|
||||
|
||||
// decrypt:
|
||||
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
|
||||
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
||||
final Long numDecryptedBytes = cryptor.decryptedFile(encryptedIn, plaintextOut);
|
||||
IOUtils.closeQuietly(encryptedIn);
|
||||
IOUtils.closeQuietly(plaintextOut);
|
||||
Assert.assertTrue(numDecryptedBytes > 0);
|
||||
|
||||
// check decrypted data:
|
||||
final byte[] result = plaintextOut.toByteArray();
|
||||
Assert.assertArrayEquals(plaintextData, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,16 +106,21 @@ public class Aes256CryptorTest {
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
|
||||
// encrypt:
|
||||
final SeekableByteChannel fileOut = Files.newByteChannel(encryptedFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.encryptFile(plaintextIn, fileOut);
|
||||
fileOut.close();
|
||||
final ByteBuffer encryptedData = ByteBuffer.allocate(plaintextData.length + 200);
|
||||
final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
|
||||
cryptor.encryptFile(plaintextIn, encryptedOut);
|
||||
IOUtils.closeQuietly(plaintextIn);
|
||||
IOUtils.closeQuietly(encryptedOut);
|
||||
|
||||
// decrypt:
|
||||
final SeekableByteChannel fileIn = Files.newByteChannel(encryptedFile, StandardOpenOption.READ);
|
||||
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
|
||||
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
||||
final Long numDecryptedBytes = cryptor.decryptRange(fileIn, plaintextOut, 313 * Integer.BYTES, 50 * Integer.BYTES);
|
||||
final Long numDecryptedBytes = cryptor.decryptRange(encryptedIn, plaintextOut, 313 * Integer.BYTES, 50 * Integer.BYTES);
|
||||
IOUtils.closeQuietly(encryptedIn);
|
||||
IOUtils.closeQuietly(plaintextOut);
|
||||
Assert.assertTrue(numDecryptedBytes > 0);
|
||||
|
||||
// check decrypted data:
|
||||
final byte[] result = plaintextOut.toByteArray();
|
||||
final byte[] expected = new byte[50 * Integer.BYTES];
|
||||
final ByteBuffer bbOut = ByteBuffer.wrap(expected);
|
||||
@@ -137,19 +130,6 @@ public class Aes256CryptorTest {
|
||||
Assert.assertArrayEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test(expected = FileAlreadyExistsException.class)
|
||||
public void testReInitialization() throws IOException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
final OutputStream outAgain = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
cryptor.encryptMasterKey(outAgain, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptionOfFilenames() throws IOException {
|
||||
final CryptorIOSupport ioSupportMock = new CryptoIOSupportMock();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.cryptomator.crypto.aes256;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
class ByteBufferBackedSeekableChannel implements SeekableByteChannel {
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
private boolean open = true;
|
||||
|
||||
ByteBufferBackedSeekableChannel(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
open = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
if (buffer.remaining() == 0) {
|
||||
return -1;
|
||||
}
|
||||
int num = Math.min(dst.remaining(), buffer.remaining());
|
||||
byte[] bytes = new byte[num];
|
||||
buffer.get(bytes);
|
||||
dst.put(bytes);
|
||||
return num;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
int num = src.remaining();
|
||||
if (buffer.remaining() < src.remaining()) {
|
||||
buffer.limit(buffer.limit() + src.remaining());
|
||||
}
|
||||
buffer.put(src);
|
||||
return num;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException {
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||
if (newPosition > Integer.MAX_VALUE) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
if (newPosition > buffer.limit()) {
|
||||
buffer.limit((int) newPosition);
|
||||
}
|
||||
buffer.position((int) newPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
return buffer.limit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate(long size) throws IOException {
|
||||
if (size > Integer.MAX_VALUE) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
buffer.limit((int) size);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user