Browse Source

- fixed size obfuscation padding
- fixed behaviour when serving invalid content ranges, thus improving random access performance (thats why we created the 0.8.2 workaround)
- reduced loglevels of some frequent messages

Sebastian Stenzel 9 years ago
parent
commit
09b4130c3e

+ 0 - 2
main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java

@@ -40,7 +40,6 @@ public final class WebDavServer {
 	private static final int MAX_THREADS = 200;
 	private static final int MIN_THREADS = 4;
 	private static final int THREAD_IDLE_SECONDS = 20;
-	private static final int CONNECTION_IDLE_MILLIS = 100; // idle connection slow down random access on WebDAVFS for some reason. reconnect overhead can be tolerated
 	private final Server server;
 	private final ServerConnector localConnector;
 	private final ContextHandlerCollection servletCollection;
@@ -51,7 +50,6 @@ public final class WebDavServer {
 		server = new Server(tp);
 		localConnector = new ServerConnector(server);
 		localConnector.setHost(LOCALHOST);
-		localConnector.setIdleTimeout(CONNECTION_IDLE_MILLIS);
 		servletCollection = new ContextHandlerCollection();
 
 		if (SystemUtils.IS_OS_WINDOWS) {

+ 2 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java

@@ -108,8 +108,9 @@ class EncryptedFile extends AbstractEncryptedNode implements FileConstants {
 				if (outputContext.hasStream()) {
 					final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath());
 					cryptor.decryptFile(c, outputContext.getOutputStream(), authenticate);
+					outputContext.getOutputStream().flush();
 				}
-				outputContext.getOutputStream().flush();
+
 			} catch (EOFException e) {
 				LOG.warn("Unexpected end of stream (possibly client hung up).");
 			}

+ 11 - 7
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java

@@ -41,7 +41,7 @@ class EncryptedFilePart extends EncryptedFile {
 			} else if (upper == null) {
 				range = new ImmutablePair<Long, Long>(lower, contentLength - 1);
 			} else {
-				range = new ImmutablePair<Long, Long>(lower, upper);
+				range = new ImmutablePair<Long, Long>(lower, Math.min(upper, contentLength - 1));
 			}
 		} catch (NumberFormatException e) {
 			throw new IllegalArgumentException("Invalid byte range: " + requestRange, e);
@@ -51,27 +51,31 @@ class EncryptedFilePart extends EncryptedFile {
 	@Override
 	public void spool(OutputContext outputContext) throws IOException {
 		assert Files.isRegularFile(filePath);
-		assert this.contentLength != null;
+		assert contentLength != null;
 
-		final Long rangeLength = range.getRight() - range.getLeft() + 1;
+		final Long rangeLength = range.getRight() - range.getLeft();
 		outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
-		if (rangeLength <= 0) {
+		if (rangeLength <= 0 || range.getLeft() > contentLength - 1) {
 			// unsatisfiable content range:
 			outputContext.setContentLength(0);
-			outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getRight(), range.getRight(), contentLength));
-			LOG.debug("Unsatisfiable content range: " + getContentRangeHeader(range.getLeft(), range.getRight(), contentLength));
+			outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
+			LOG.debug("Requested content range unsatisfiable: " + getContentRangeHeader(range.getLeft(), range.getRight(), contentLength));
 			return;
 		} else {
 			outputContext.setContentLength(rangeLength);
 			outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getLeft(), range.getRight(), contentLength));
 		}
 
+		assert range.getLeft() > 0;
+		assert range.getLeft() < contentLength;
+		assert range.getRight() < contentLength;
+
 		try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.READ)) {
 			if (outputContext.hasStream()) {
 				final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath());
 				cryptor.decryptRange(c, outputContext.getOutputStream(), range.getLeft(), rangeLength, authenticate);
+				outputContext.getOutputStream().flush();
 			}
-			outputContext.getOutputStream().flush();
 		} catch (EOFException e) {
 			if (LOG.isDebugEnabled()) {
 				LOG.trace("Unexpected end of stream during delivery of partial content (client hung up).");

+ 1 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java

@@ -39,7 +39,7 @@ class SilentlyFailingFileLock implements AutoCloseable {
 			lock = channel.tryLock(position, size, shared);
 		} catch (IOException | OverlappingFileLockException e) {
 			if (LOG.isDebugEnabled()) {
-				LOG.warn("Unable to lock file.");
+				LOG.trace("Unable to lock file.");
 			}
 		} finally {
 			this.lock = lock;

+ 2 - 2
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java

@@ -95,7 +95,7 @@ public class WebDavServlet extends AbstractWebdavServlet {
 		super.doPut(request, response, resource);
 		if (LOG.isDebugEnabled()) {
 			long t1 = System.nanoTime();
-			LOG.debug("PUT TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
+			LOG.trace("PUT TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
 		}
 	}
 
@@ -111,7 +111,7 @@ public class WebDavServlet extends AbstractWebdavServlet {
 		}
 		if (LOG.isDebugEnabled()) {
 			long t1 = System.nanoTime();
-			LOG.debug("GET TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
+			LOG.trace("GET TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
 		}
 	}
 

+ 23 - 12
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -37,7 +37,6 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.security.auth.DestroyFailedException;
 import javax.security.auth.Destroyable;
 
-import org.apache.commons.io.IOUtils;
 import org.bouncycastle.crypto.generators.SCrypt;
 import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
@@ -46,7 +45,6 @@ import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
 import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
-import org.cryptomator.crypto.io.SeekableByteChannelInputStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -548,9 +546,11 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		final byte[] fileKeyBytes = new byte[32];
 		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
 		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
-		sensitiveHeaderContentBuf.position(Long.BYTES); // skip file size
+		final Long fileSize = sensitiveHeaderContentBuf.getLong();
 		sensitiveHeaderContentBuf.get(fileKeyBytes);
 
+		assert pos + length < fileSize;
+
 		// find first relevant block:
 		final long startBlock = pos / CONTENT_MAC_BLOCK; // floor
 		final long startByte = startBlock * (CONTENT_MAC_BLOCK + 32) + 104;
@@ -571,40 +571,51 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		try {
 			// reading ciphered input and MACs interleaved:
 			long bytesWritten = 0;
-			final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
-			byte[] buffer = new byte[CONTENT_MAC_BLOCK + 32];
+			final ByteBuffer buf = ByteBuffer.allocate(CONTENT_MAC_BLOCK + 32);
 			int n = 0;
 			long blockNum = startBlock;
-			while ((n = IOUtils.read(in, buffer)) > 0 && bytesWritten < length) {
+			while ((n = readFromChannel(encryptedFile, buf)) > 0 && bytesWritten < length) {
 				if (n < 32) {
 					throw new DecryptFailedException("Invalid file content, missing MAC.");
 				}
 
+				buf.flip();
+				final ByteBuffer ciphertextBuf = buf.asReadOnlyBuffer();
+				ciphertextBuf.limit(n - 32);
+
 				// check MAC of current block:
 				if (authenticate) {
+					final byte[] storedMac = new byte[contentMac.getMacLength()];
+					final ByteBuffer storedMacBuf = buf.asReadOnlyBuffer();
+					storedMacBuf.position(n - 32);
+					storedMacBuf.get(storedMac);
 					contentMac.update(iv);
 					contentMac.update(longToByteArray(blockNum));
-					contentMac.update(buffer, 0, n - 32);
+					contentMac.update(ciphertextBuf);
+					ciphertextBuf.rewind();
 					final byte[] calculatedMac = contentMac.doFinal();
-					final byte[] storedMac = new byte[32];
-					System.arraycopy(buffer, n - 32, storedMac, 0, 32);
 					if (!MessageDigest.isEqual(calculatedMac, storedMac)) {
 						throw new MacAuthenticationFailedException("Content MAC authentication failed.");
 					}
 				}
 
 				// decrypt block:
-				final byte[] plaintext = cipher.update(buffer, 0, n - 32);
+				final ByteBuffer plaintextBuf = ByteBuffer.allocate(cipher.getOutputSize(ciphertextBuf.remaining()));
+				cipher.update(ciphertextBuf, plaintextBuf);
+				plaintextBuf.flip();
 				final int offset = (bytesWritten == 0) ? (int) offsetFromFirstBlock : 0;
 				final long pending = length - bytesWritten;
-				final int available = plaintext.length - offset;
+				final int available = plaintextBuf.remaining() - offset;
 				final int currentBatch = (int) Math.min(pending, available);
 
-				plaintextFile.write(plaintext, offset, currentBatch);
+				plaintextFile.write(plaintextBuf.array(), offset, currentBatch);
 				bytesWritten += currentBatch;
 				blockNum++;
+				buf.rewind();
 			}
 			return bytesWritten;
+		} catch (ShortBufferException e) {
+			throw new IllegalStateException("Output buffer size known to fit.", e);
 		} finally {
 			destroyQuietly(fileKey);
 		}

+ 9 - 2
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java

@@ -19,7 +19,7 @@ public class LengthLimitingOutputStream extends FilterOutputStream {
 	public void write(int b) throws IOException {
 		if (bytesWritten < limit) {
 			out.write(b);
-			bytesWritten++;
+			increaseNumberOfWrittenBytes(1);
 		}
 	}
 
@@ -29,7 +29,7 @@ public class LengthLimitingOutputStream extends FilterOutputStream {
 		final int adjustedLen = (int) Math.min(len, bytesAvailable);
 		if (adjustedLen > 0) {
 			out.write(b, off, adjustedLen);
-			bytesWritten += adjustedLen;
+			increaseNumberOfWrittenBytes(adjustedLen);
 		}
 	}
 
@@ -37,4 +37,11 @@ public class LengthLimitingOutputStream extends FilterOutputStream {
 		return bytesWritten;
 	}
 
+	private void increaseNumberOfWrittenBytes(int amount) throws IOException {
+		bytesWritten += amount;
+		if (bytesWritten >= limit) {
+			out.flush();
+		}
+	}
+
 }

+ 0 - 12
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java

@@ -109,18 +109,6 @@ public class LengthObfuscatingInputStream extends FilterInputStream {
 		throw new IOException("Skip not supported");
 	}
 
-	@Override
-	public int available() throws IOException {
-		final int inputAvailable = in.available();
-		if (inputAvailable > 0) {
-			return inputAvailable;
-		} else {
-			// remaining padding
-			choosePaddingLengthOnce();
-			return paddingLength - paddingBytesRead;
-		}
-	}
-
 	@Override
 	public boolean markSupported() {
 		return false;

+ 0 - 90
main/crypto-api/src/main/java/org/cryptomator/crypto/io/SeekableByteChannelInputStream.java

@@ -1,90 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014 Sebastian Stenzel
- * 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.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-
-public class SeekableByteChannelInputStream extends InputStream {
-	private final SeekableByteChannel channel;
-	private volatile long markedPos = 0;
-
-	public SeekableByteChannelInputStream(SeekableByteChannel channel) {
-		this.channel = channel;
-	}
-
-	@Override
-	public int read() throws IOException {
-		final ByteBuffer buffer = ByteBuffer.allocate(1);
-		final int read = channel.read(buffer);
-		if (read == 1) {
-			return buffer.get(0);
-		} else {
-			return -1;
-		}
-	}
-
-	@Override
-	public int read(byte[] b, int off, int len) throws IOException {
-		final ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
-		return channel.read(buffer);
-	}
-
-	@Override
-	public int available() throws IOException {
-		long available = channel.size() - channel.position();
-		if (available > Integer.MAX_VALUE) {
-			return Integer.MAX_VALUE;
-		} else {
-			return (int) available;
-		}
-	}
-
-	@Override
-	public long skip(long n) throws IOException {
-		final long pos = channel.position();
-		final long max = channel.size();
-		final long maxSkip = max - pos;
-		final long actualSkip = Math.min(n, maxSkip);
-		channel.position(channel.position() + actualSkip);
-		return actualSkip;
-	}
-
-	@Override
-	public void close() throws IOException {
-		channel.close();
-		super.close();
-	}
-
-	@Override
-	public synchronized void mark(int readlimit) {
-		try {
-			markedPos = channel.position();
-		} catch (IOException e) {
-			markedPos = 0;
-		}
-	}
-
-	@Override
-	public synchronized void reset() throws IOException {
-		channel.position(markedPos);
-	}
-
-	public synchronized void resetTo(long position) throws IOException {
-		channel.position(position);
-	}
-
-	@Override
-	public boolean markSupported() {
-		return true;
-	}
-
-}

+ 0 - 64
main/crypto-api/src/main/java/org/cryptomator/crypto/io/SeekableByteChannelOutputStream.java

@@ -1,64 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014 Sebastian Stenzel
- * 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.io;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-
-public class SeekableByteChannelOutputStream extends OutputStream {
-
-	private final SeekableByteChannel channel;
-
-	public SeekableByteChannelOutputStream(SeekableByteChannel channel) {
-		this.channel = channel;
-	}
-
-	@Override
-	public void write(int b) throws IOException {
-		final byte actualByte = (byte) (b & 0x000000FF);
-		final ByteBuffer buffer = ByteBuffer.allocate(1);
-		buffer.put(actualByte);
-		channel.write(buffer);
-	}
-
-	@Override
-	public void write(byte[] b, int off, int len) throws IOException {
-		final ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
-		channel.write(buffer);
-	}
-
-	@Override
-	public void close() throws IOException {
-		channel.close();
-	}
-
-	/**
-	 * @see SeekableByteChannel#truncate(long)
-	 */
-	public void truncate(long size) throws IOException {
-		channel.truncate(size);
-	}
-
-	/**
-	 * @see SeekableByteChannel#position()
-	 */
-	public long position() throws IOException {
-		return channel.position();
-	}
-
-	/**
-	 * @see SeekableByteChannel#position(long)
-	 */
-	public void position(long newPosition) throws IOException {
-		channel.position(newPosition);
-	}
-
-}