This commit is contained in:
Sebastian Stenzel
2015-12-19 11:26:35 +01:00
parent 69b192fe82
commit 951a02a9a5
14 changed files with 42 additions and 95 deletions

View File

@@ -41,6 +41,6 @@ public interface ReadableBytes {
* if an {@link IOException} occurs while reading from this
* {@code ReadableBytes}
*/
void read(ByteBuffer target, int position) throws UncheckedIOException;
void read(ByteBuffer target, long position) throws UncheckedIOException;
}

View File

@@ -3,9 +3,10 @@ package org.cryptomator.crypto.engine;
import java.nio.ByteBuffer;
import java.util.Optional;
import javax.security.auth.Destroyable;
public interface FileContentCryptor extends Destroyable {
/**
* Factory for stateful {@link FileContentEncryptor Encryptor}/{@link FileContentDecryptor Decryptor} instances, that are capable of processing data exactly once.
*/
public interface FileContentCryptor {
/**
* @return The fixed number of bytes of the file header. The header length is implementation-specific.
@@ -16,13 +17,13 @@ public interface FileContentCryptor extends Destroyable {
* @param header The full fixed-length header of an encrypted file. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
* @return A possibly new FileContentDecryptor instance which is capable of decrypting ciphertexts associated with the given file header.
*/
FileContentDecryptor getFileContentDecryptor(ByteBuffer header);
FileContentDecryptor createFileContentDecryptor(ByteBuffer header);
/**
* @param header The full fixed-length header of an encrypted file or {@link Optional#empty()}. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}.
* If the header is empty, a new one will be created by the returned encryptor.
* @return A possibly new FileContentEncryptor instance which is capable of encrypting cleartext associated with the given file header.
*/
FileContentEncryptor getFileContentEncryptor(Optional<ByteBuffer> header);
FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header);
}

View File

@@ -4,7 +4,7 @@ import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
/**
* Not necessarily thread-safe.
* Stateful, thus not thread-safe.
*/
public interface FileContentDecryptor {

View File

@@ -4,7 +4,7 @@ import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
/**
* Not necessarily thread-safe.
* Stateful, thus not thread-safe.
*/
public interface FileContentEncryptor {

View File

@@ -8,15 +8,13 @@
*******************************************************************************/
package org.cryptomator.crypto.engine;
import javax.security.auth.Destroyable;
/**
* Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
* otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
*
* @see <a href="https://en.wikipedia.org/wiki/Deterministic_encryption">Wikipedia on deterministic encryption</a>
*/
public interface FilenameCryptor extends Destroyable {
public interface FilenameCryptor {
/**
* @return constant length string, that is unlikely to collide with any other name.

View File

@@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.crypto.engine.FileContentCryptor;
@@ -52,12 +53,11 @@ public class CryptorImpl implements Cryptor {
if (existingCryptor != null) {
return existingCryptor;
} else {
final FilenameCryptorImpl newCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
final FilenameCryptor newCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
if (filenameCryptor.compareAndSet(null, newCryptor)) {
return newCryptor;
} else {
// CAS failed: other thread set an object
newCryptor.destroy();
return filenameCryptor.get();
}
}
@@ -70,12 +70,11 @@ public class CryptorImpl implements Cryptor {
if (existingCryptor != null) {
return existingCryptor;
} else {
final FileContentCryptorImpl newCryptor = new FileContentCryptorImpl(encryptionKey, macKey);
final FileContentCryptor newCryptor = new FileContentCryptorImpl(encryptionKey, macKey);
if (fileContentCryptor.compareAndSet(null, newCryptor)) {
return newCryptor;
} else {
// CAS failed: other thread set an object
newCryptor.destroy();
return fileContentCryptor.get();
}
}
@@ -162,22 +161,21 @@ public class CryptorImpl implements Cryptor {
@Override
public void destroy() throws DestroyFailedException {
TheDestroyer.destroyQuietly(encryptionKey);
TheDestroyer.destroyQuietly(macKey);
if (filenameCryptor.get() != null) {
TheDestroyer.destroyQuietly(getFilenameCryptor());
}
if (fileContentCryptor.get() != null) {
TheDestroyer.destroyQuietly(getFileContentCryptor());
}
destroyQuietly(encryptionKey);
destroyQuietly(macKey);
}
@Override
public boolean isDestroyed() {
return encryptionKey.isDestroyed() //
&& macKey.isDestroyed() //
&& (filenameCryptor.get() == null || filenameCryptor.get().isDestroyed()) //
&& (fileContentCryptor.get() == null || fileContentCryptor.get().isDestroyed());
return encryptionKey.isDestroyed() && macKey.isDestroyed();
}
private void destroyQuietly(Destroyable d) {
try {
d.destroy();
} catch (DestroyFailedException e) {
// ignore
}
}
}

View File

@@ -28,26 +28,13 @@ class FileContentCryptorImpl implements FileContentCryptor {
}
@Override
public FileContentDecryptor getFileContentDecryptor(ByteBuffer header) {
public FileContentDecryptor createFileContentDecryptor(ByteBuffer header) {
throw new UnsupportedOperationException("Method not implemented");
}
@Override
public FileContentEncryptor getFileContentEncryptor(Optional<ByteBuffer> header) {
public FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header) {
throw new UnsupportedOperationException("Method not implemented");
}
/* ======================= destruction ======================= */
@Override
public void destroy() {
TheDestroyer.destroyQuietly(encryptionKey);
TheDestroyer.destroyQuietly(macKey);
}
@Override
public boolean isDestroyed() {
return encryptionKey.isDestroyed() && macKey.isDestroyed();
}
}

View File

@@ -84,17 +84,4 @@ class FilenameCryptorImpl implements FilenameCryptor {
}
}
/* ======================= destruction ======================= */
@Override
public void destroy() {
TheDestroyer.destroyQuietly(encryptionKey);
TheDestroyer.destroyQuietly(macKey);
}
@Override
public boolean isDestroyed() {
return encryptionKey.isDestroyed() && macKey.isDestroyed();
}
}

View File

@@ -1,28 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.crypto.engine.impl;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
final class TheDestroyer {
private TheDestroyer() {
}
public static void destroyQuietly(Destroyable d) {
try {
d.destroy();
} catch (DestroyFailedException e) {
// ignore
}
}
}

View File

@@ -15,19 +15,20 @@ import org.cryptomator.io.ByteBuffers;
class CryptoReadableFile implements ReadableFile {
private static final int READ_BUFFER_SIZE = 32 * 1024 + 32; // aligned with encrypted chunk size + MAC size
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final FileContentDecryptor decryptor;
private final ReadableFile file;
private Future<Void> readAheadTask;
private ByteBuffer bufferedCleartext;
private ByteBuffer bufferedCleartext = EMPTY_BUFFER;
public CryptoReadableFile(FileContentCryptor cryptor, ReadableFile file) {
final int headerSize = cryptor.getHeaderSize();
final ByteBuffer header = ByteBuffer.allocate(headerSize);
file.read(header, 0);
header.flip();
this.decryptor = cryptor.getFileContentDecryptor(header);
this.decryptor = cryptor.createFileContentDecryptor(header);
this.file = file;
this.prepareReadAtPhysicalPosition(headerSize + 0);
}
@@ -35,6 +36,7 @@ class CryptoReadableFile implements ReadableFile {
private void prepareReadAtPhysicalPosition(long pos) {
if (readAheadTask != null) {
readAheadTask.cancel(true);
bufferedCleartext = EMPTY_BUFFER;
decryptor.cleartext().clear();
}
readAheadTask = executorService.submit(new Reader(pos));
@@ -53,7 +55,7 @@ class CryptoReadableFile implements ReadableFile {
}
private void bufferCleartext() throws InterruptedException {
if (bufferedCleartext == null || !bufferedCleartext.hasRemaining()) {
if (!bufferedCleartext.hasRemaining()) {
bufferedCleartext = decryptor.cleartext().take();
}
}
@@ -64,7 +66,7 @@ class CryptoReadableFile implements ReadableFile {
}
@Override
public void read(ByteBuffer target, int position) {
public void read(ByteBuffer target, long position) {
throw new UnsupportedOperationException("Partial read not implemented yet.");
}
@@ -89,8 +91,7 @@ class CryptoReadableFile implements ReadableFile {
@Override
public Void call() {
// TODO change to file.read(ByteBuffer, long)
file.read(ByteBuffer.allocate(0), (int) startpos);
file.read(EMPTY_BUFFER, startpos);
int bytesRead = -1;
do {
ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE);

View File

@@ -23,7 +23,7 @@ class CryptoWritableFile implements WritableFile {
private final Future<Void> writeTask;
public CryptoWritableFile(FileContentCryptor cryptor, WritableFile file) {
this.encryptor = cryptor.getFileContentEncryptor(Optional.empty());
this.encryptor = cryptor.createFileContentEncryptor(Optional.empty());
this.file = file;
writeHeader();
this.writeTask = executorService.submit(new Writer());

View File

@@ -13,7 +13,7 @@ class NoFileContentCryptor implements FileContentCryptor {
}
@Override
public FileContentDecryptor getFileContentDecryptor(ByteBuffer header) {
public FileContentDecryptor createFileContentDecryptor(ByteBuffer header) {
if (header.remaining() != getHeaderSize()) {
throw new IllegalArgumentException("Invalid header size.");
}
@@ -21,7 +21,7 @@ class NoFileContentCryptor implements FileContentCryptor {
}
@Override
public FileContentEncryptor getFileContentEncryptor(Optional<ByteBuffer> header) {
public FileContentEncryptor createFileContentEncryptor(Optional<ByteBuffer> header) {
return new Encryptor();
}

View File

@@ -57,8 +57,11 @@ class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableF
}
@Override
public void read(ByteBuffer target, int position) {
content.position(position);
public void read(ByteBuffer target, long position) {
if (position > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Can not keep files of virtually unlimited size in memory.");
}
content.position((int) position);
ByteBuffers.copy(content, target);
}

View File

@@ -44,7 +44,7 @@ class NioFile extends NioNode implements File {
}
@Override
public void read(ByteBuffer target, int position) throws UncheckedIOException {
public void read(ByteBuffer target, long position) throws UncheckedIOException {
}
@Override