mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
- 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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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).");
|
||||
}
|
||||
|
||||
@@ -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).");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user