mirror of
https://github.com/cryptomator/cryptomator.git
synced 2025-02-19 09:41:10 +00:00
Merge remote-tracking branch 'origin/master' into 0.5.2
Conflicts: main/core/pom.xml main/crypto-aes/pom.xml main/crypto-api/pom.xml main/pom.xml main/ui/pom.xml
This commit is contained in:
commit
1dd8a28a9d
@ -5,7 +5,7 @@ Cryptomator
|
|||||||
|
|
||||||
Multiplatform transparent client-side encryption of your files in the cloud.
|
Multiplatform transparent client-side encryption of your files in the cloud.
|
||||||
|
|
||||||
If you want to take a look at the current beta version, go ahead and get your copy of cryptomator on [Cryptomator.org](http://cryptomator.org) or clone and build Cryptomator using Maven (instructions below).
|
If you want to take a look at the current beta version, go ahead and get your copy of cryptomator on [Cryptomator.org](https://cryptomator.org) or clone and build Cryptomator using Maven (instructions below).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Totally transparent: Just work on the encrypted volume, as if it was an USB drive
|
- Totally transparent: Just work on the encrypted volume, as if it was an USB drive
|
||||||
|
@ -78,9 +78,7 @@ public class EncryptedDir extends AbstractEncryptedNode {
|
|||||||
|
|
||||||
private void addMemberFile(DavResource resource, InputContext inputContext) throws DavException {
|
private void addMemberFile(DavResource resource, InputContext inputContext) throws DavException {
|
||||||
final Path childPath = ResourcePathUtils.getPhysicalPath(resource);
|
final Path childPath = ResourcePathUtils.getPhysicalPath(resource);
|
||||||
SeekableByteChannel channel = null;
|
try (final SeekableByteChannel channel = Files.newByteChannel(childPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
try {
|
|
||||||
channel = Files.newByteChannel(childPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
|
|
||||||
cryptor.encryptFile(inputContext.getInputStream(), channel);
|
cryptor.encryptFile(inputContext.getInputStream(), channel);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
|
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
|
||||||
@ -88,7 +86,6 @@ public class EncryptedDir extends AbstractEncryptedNode {
|
|||||||
LOG.error("Failed to create file.", e);
|
LOG.error("Failed to create file.", e);
|
||||||
throw new IORuntimeException(e);
|
throw new IORuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(channel);
|
|
||||||
IOUtils.closeQuietly(inputContext.getInputStream());
|
IOUtils.closeQuietly(inputContext.getInputStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +121,9 @@ public class EncryptedDir extends AbstractEncryptedNode {
|
|||||||
public void removeMember(DavResource member) throws DavException {
|
public void removeMember(DavResource member) throws DavException {
|
||||||
final Path memberPath = ResourcePathUtils.getPhysicalPath(member);
|
final Path memberPath = ResourcePathUtils.getPhysicalPath(member);
|
||||||
try {
|
try {
|
||||||
Files.walkFileTree(memberPath, new DeletingFileVisitor());
|
if (Files.exists(memberPath)) {
|
||||||
|
Files.walkFileTree(memberPath, new DeletingFileVisitor());
|
||||||
|
}
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
|
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -16,7 +16,6 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.jackrabbit.webdav.DavException;
|
import org.apache.jackrabbit.webdav.DavException;
|
||||||
import org.apache.jackrabbit.webdav.DavResource;
|
import org.apache.jackrabbit.webdav.DavResource;
|
||||||
import org.apache.jackrabbit.webdav.DavResourceFactory;
|
import org.apache.jackrabbit.webdav.DavResourceFactory;
|
||||||
@ -30,6 +29,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
|||||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||||
import org.cryptomator.crypto.Cryptor;
|
import org.cryptomator.crypto.Cryptor;
|
||||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||||
|
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
|
||||||
import org.cryptomator.webdav.exceptions.IORuntimeException;
|
import org.cryptomator.webdav.exceptions.IORuntimeException;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
@ -70,19 +70,20 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
|||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
|
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
|
||||||
outputContext.setProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString());
|
outputContext.setProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString());
|
||||||
SeekableByteChannel channel = null;
|
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
|
||||||
try {
|
final Long contentLength = cryptor.decryptedContentLength(channel);
|
||||||
channel = Files.newByteChannel(path, StandardOpenOption.READ);
|
if (contentLength != null) {
|
||||||
outputContext.setContentLength(cryptor.decryptedContentLength(channel));
|
outputContext.setContentLength(contentLength);
|
||||||
|
}
|
||||||
if (outputContext.hasStream()) {
|
if (outputContext.hasStream()) {
|
||||||
cryptor.decryptedFile(channel, outputContext.getOutputStream());
|
cryptor.decryptFile(channel, outputContext.getOutputStream());
|
||||||
}
|
}
|
||||||
} catch (EOFException e) {
|
} catch (EOFException e) {
|
||||||
LOG.warn("Unexpected end of stream (possibly client hung up).");
|
LOG.warn("Unexpected end of stream (possibly client hung up).");
|
||||||
|
} catch (MacAuthenticationFailedException e) {
|
||||||
|
LOG.warn("MAC authentication failed, file content {} might be compromised.", getLocator().getResourcePath());
|
||||||
} catch (DecryptFailedException e) {
|
} catch (DecryptFailedException e) {
|
||||||
throw new IOException("Error decrypting file " + path.toString(), e);
|
throw new IOException("Error decrypting file " + path.toString(), e);
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(channel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,12 +92,15 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
|||||||
protected void determineProperties() {
|
protected void determineProperties() {
|
||||||
final Path path = ResourcePathUtils.getPhysicalPath(this);
|
final Path path = ResourcePathUtils.getPhysicalPath(this);
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
SeekableByteChannel channel = null;
|
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
|
||||||
try {
|
|
||||||
channel = Files.newByteChannel(path, StandardOpenOption.READ);
|
|
||||||
final Long contentLength = cryptor.decryptedContentLength(channel);
|
final Long contentLength = cryptor.decryptedContentLength(channel);
|
||||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
|
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Error reading filesize " + path.toString(), e);
|
||||||
|
throw new IORuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
||||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
|
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
|
||||||
@ -104,8 +108,6 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Error determining metadata " + path.toString(), e);
|
LOG.error("Error determining metadata " + path.toString(), e);
|
||||||
throw new IORuntimeException(e);
|
throw new IORuntimeException(e);
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(channel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import java.nio.file.StandardOpenOption;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.MutablePair;
|
import org.apache.commons.lang3.tuple.MutablePair;
|
||||||
@ -113,9 +112,7 @@ public class EncryptedFilePart extends EncryptedFile {
|
|||||||
final Path path = ResourcePathUtils.getPhysicalPath(this);
|
final Path path = ResourcePathUtils.getPhysicalPath(this);
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
|
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
|
||||||
SeekableByteChannel channel = null;
|
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
|
||||||
try {
|
|
||||||
channel = Files.newByteChannel(path, StandardOpenOption.READ);
|
|
||||||
final Long fileSize = cryptor.decryptedContentLength(channel);
|
final Long fileSize = cryptor.decryptedContentLength(channel);
|
||||||
final Pair<Long, Long> range = getUnionRange(fileSize);
|
final Pair<Long, Long> range = getUnionRange(fileSize);
|
||||||
final Long rangeLength = range.getRight() - range.getLeft() + 1;
|
final Long rangeLength = range.getRight() - range.getLeft() + 1;
|
||||||
@ -130,8 +127,6 @@ public class EncryptedFilePart extends EncryptedFile {
|
|||||||
}
|
}
|
||||||
} catch (DecryptFailedException e) {
|
} catch (DecryptFailedException e) {
|
||||||
throw new IOException("Error decrypting file " + path.toString(), e);
|
throw new IOException("Error decrypting file " + path.toString(), e);
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(channel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ import org.bouncycastle.crypto.generators.SCrypt;
|
|||||||
import org.cryptomator.crypto.AbstractCryptor;
|
import org.cryptomator.crypto.AbstractCryptor;
|
||||||
import org.cryptomator.crypto.CryptorIOSupport;
|
import org.cryptomator.crypto.CryptorIOSupport;
|
||||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||||
|
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
|
||||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||||
import org.cryptomator.crypto.io.SeekableByteChannelInputStream;
|
import org.cryptomator.crypto.io.SeekableByteChannelInputStream;
|
||||||
@ -369,34 +370,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
ioSupport.writePathSpecificMetadata(metadataFile, objectMapper.writeValueAsBytes(metadata));
|
ioSupport.writePathSpecificMetadata(metadataFile, objectMapper.writeValueAsBytes(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticateContent(SeekableByteChannel encryptedFile) throws IOException, DecryptFailedException {
|
|
||||||
// init mac:
|
|
||||||
final Mac calculatedMac = this.hmacSha256(hMacMasterKey);
|
|
||||||
|
|
||||||
// read stored mac:
|
|
||||||
encryptedFile.position(16);
|
|
||||||
final ByteBuffer storedMac = ByteBuffer.allocate(calculatedMac.getMacLength());
|
|
||||||
final int numMacBytesRead = encryptedFile.read(storedMac);
|
|
||||||
|
|
||||||
// check validity of header:
|
|
||||||
if (numMacBytesRead != calculatedMac.getMacLength()) {
|
|
||||||
throw new IOException("Failed to read file header.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// read all encrypted data and calculate mac:
|
|
||||||
encryptedFile.position(64);
|
|
||||||
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
|
|
||||||
final InputStream macIn = new MacInputStream(in, calculatedMac);
|
|
||||||
IOUtils.copyLarge(macIn, new NullOutputStream());
|
|
||||||
|
|
||||||
// compare (in constant time):
|
|
||||||
boolean macMatches = MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal());
|
|
||||||
|
|
||||||
if (!macMatches) {
|
|
||||||
throw new DecryptFailedException("MAC authentication failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
|
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
|
||||||
// skip 128bit IV + 256 bit MAC:
|
// skip 128bit IV + 256 bit MAC:
|
||||||
@ -422,24 +395,49 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void encryptedContentLength(SeekableByteChannel encryptedFile, Long contentLength) throws IOException {
|
||||||
|
final ByteBuffer encryptedFileSizeBuffer;
|
||||||
|
|
||||||
|
// encrypt content length in ECB mode (content length is less than one block):
|
||||||
|
try {
|
||||||
|
final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);
|
||||||
|
fileSizeBuffer.putLong(contentLength);
|
||||||
|
final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.ENCRYPT_MODE);
|
||||||
|
final byte[] encryptedFileSize = sizeCipher.doFinal(fileSizeBuffer.array());
|
||||||
|
encryptedFileSizeBuffer = ByteBuffer.wrap(encryptedFileSize);
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||||
|
throw new IllegalStateException("Block size must be valid, as padding is requested. BadPaddingException not possible in encrypt mode.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip 128bit IV + 256 bit MAC:
|
||||||
|
encryptedFile.position(48);
|
||||||
|
|
||||||
|
// write result:
|
||||||
|
encryptedFile.write(encryptedFileSizeBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
|
public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
|
||||||
// read iv:
|
// read iv:
|
||||||
encryptedFile.position(0);
|
encryptedFile.position(0);
|
||||||
final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
|
final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
|
||||||
final int numIvBytesRead = encryptedFile.read(countingIv);
|
final int numIvBytesRead = encryptedFile.read(countingIv);
|
||||||
|
|
||||||
|
// init mac:
|
||||||
|
final Mac calculatedMac = this.hmacSha256(hMacMasterKey);
|
||||||
|
|
||||||
|
// read stored mac:
|
||||||
|
final ByteBuffer storedMac = ByteBuffer.allocate(calculatedMac.getMacLength());
|
||||||
|
final int numMacBytesRead = encryptedFile.read(storedMac);
|
||||||
|
|
||||||
// read file size:
|
// read file size:
|
||||||
final Long fileSize = decryptedContentLength(encryptedFile);
|
final Long fileSize = decryptedContentLength(encryptedFile);
|
||||||
|
|
||||||
// check validity of header:
|
// check validity of header:
|
||||||
if (numIvBytesRead != AES_BLOCK_LENGTH || fileSize == null) {
|
if (numIvBytesRead != AES_BLOCK_LENGTH || numMacBytesRead != calculatedMac.getMacLength() || fileSize == null) {
|
||||||
throw new IOException("Failed to read file header.");
|
throw new IOException("Failed to read file header.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check MAC:
|
|
||||||
this.authenticateContent(encryptedFile);
|
|
||||||
|
|
||||||
// go to begin of content:
|
// go to begin of content:
|
||||||
encryptedFile.position(64);
|
encryptedFile.position(64);
|
||||||
|
|
||||||
@ -448,8 +446,25 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
|
|
||||||
// read content
|
// read content
|
||||||
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
|
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
|
||||||
final InputStream cipheredIn = new CipherInputStream(in, cipher);
|
final InputStream macIn = new MacInputStream(in, calculatedMac);
|
||||||
return IOUtils.copyLarge(cipheredIn, plaintextFile, 0, fileSize);
|
final InputStream cipheredIn = new CipherInputStream(macIn, cipher);
|
||||||
|
final long bytesDecrypted = IOUtils.copyLarge(cipheredIn, plaintextFile, 0, fileSize);
|
||||||
|
|
||||||
|
// drain remaining bytes to /dev/null to complete MAC calculation:
|
||||||
|
IOUtils.copyLarge(macIn, new NullOutputStream());
|
||||||
|
|
||||||
|
// compare (in constant time):
|
||||||
|
final boolean macMatches = MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal());
|
||||||
|
if (!macMatches) {
|
||||||
|
// This exception will be thrown AFTER we sent the decrypted content to the user.
|
||||||
|
// This has two advantages:
|
||||||
|
// - we don't need to read files twice
|
||||||
|
// - we can still restore files suffering from non-malicious bit rotting
|
||||||
|
// Anyway me MUST make sure to warn the user. This will be done by the UI when catching this exception.
|
||||||
|
throw new MacAuthenticationFailedException("MAC authentication failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesDecrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -464,9 +479,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
throw new IOException("Failed to read file header.");
|
throw new IOException("Failed to read file header.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check MAC:
|
|
||||||
this.authenticateContent(encryptedFile);
|
|
||||||
|
|
||||||
// seek relevant position and update iv:
|
// seek relevant position and update iv:
|
||||||
long firstRelevantBlock = pos / AES_BLOCK_LENGTH; // cut of fraction!
|
long firstRelevantBlock = pos / AES_BLOCK_LENGTH; // cut of fraction!
|
||||||
long beginOfFirstRelevantBlock = firstRelevantBlock * AES_BLOCK_LENGTH;
|
long beginOfFirstRelevantBlock = firstRelevantBlock * AES_BLOCK_LENGTH;
|
||||||
@ -493,7 +505,6 @@ 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.
|
// 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));
|
final ByteBuffer countingIv = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
|
||||||
countingIv.putLong(AES_BLOCK_LENGTH - Long.BYTES, 0l);
|
countingIv.putLong(AES_BLOCK_LENGTH - Long.BYTES, 0l);
|
||||||
countingIv.position(0);
|
|
||||||
encryptedFile.write(countingIv);
|
encryptedFile.write(countingIv);
|
||||||
|
|
||||||
// init crypto stuff:
|
// init crypto stuff:
|
||||||
@ -504,9 +515,8 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
final ByteBuffer macBuffer = ByteBuffer.allocate(mac.getMacLength());
|
final ByteBuffer macBuffer = ByteBuffer.allocate(mac.getMacLength());
|
||||||
encryptedFile.write(macBuffer);
|
encryptedFile.write(macBuffer);
|
||||||
|
|
||||||
// init filesize buffer and skip 16 bytes
|
// encrypt and write "zero length" as a placeholder, which will be read by concurrent requests, as long as encryption didn't finish:
|
||||||
final ByteBuffer encryptedFileSizeBuffer = ByteBuffer.allocate(AES_BLOCK_LENGTH);
|
encryptedContentLength(encryptedFile, 0l);
|
||||||
encryptedFile.write(encryptedFileSizeBuffer);
|
|
||||||
|
|
||||||
// write content:
|
// write content:
|
||||||
final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
|
final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
|
||||||
@ -529,26 +539,14 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
|||||||
blockSizeBufferedOut.flush();
|
blockSizeBufferedOut.flush();
|
||||||
|
|
||||||
// write MAC of total ciphertext:
|
// write MAC of total ciphertext:
|
||||||
macBuffer.position(0);
|
macBuffer.clear();
|
||||||
macBuffer.put(mac.doFinal());
|
macBuffer.put(mac.doFinal());
|
||||||
macBuffer.position(0);
|
macBuffer.flip();
|
||||||
encryptedFile.position(16); // right behind the IV
|
encryptedFile.position(16); // right behind the IV
|
||||||
encryptedFile.write(macBuffer); // 256 bit MAC
|
encryptedFile.write(macBuffer); // 256 bit MAC
|
||||||
|
|
||||||
// encrypt and write plaintextSize
|
// encrypt and write plaintextSize:
|
||||||
try {
|
encryptedContentLength(encryptedFile, plaintextSize);
|
||||||
final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);
|
|
||||||
fileSizeBuffer.putLong(plaintextSize);
|
|
||||||
final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.ENCRYPT_MODE);
|
|
||||||
final byte[] encryptedFileSize = sizeCipher.doFinal(fileSizeBuffer.array());
|
|
||||||
encryptedFileSizeBuffer.position(0);
|
|
||||||
encryptedFileSizeBuffer.put(encryptedFileSize);
|
|
||||||
encryptedFileSizeBuffer.position(0);
|
|
||||||
encryptedFile.position(48); // right behind the IV and MAC
|
|
||||||
encryptedFile.write(encryptedFileSizeBuffer);
|
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
|
||||||
throw new IllegalStateException("Block size must be valid, as padding is requested. BadPaddingException not possible in encrypt mode.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return plaintextSize;
|
return plaintextSize;
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ public class Aes256CryptorTest {
|
|||||||
// decrypt modified content (should fail with DecryptFailedException):
|
// decrypt modified content (should fail with DecryptFailedException):
|
||||||
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
|
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
|
||||||
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
||||||
cryptor.decryptedFile(encryptedIn, plaintextOut);
|
cryptor.decryptFile(encryptedIn, plaintextOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -126,7 +126,7 @@ public class Aes256CryptorTest {
|
|||||||
|
|
||||||
// decrypt:
|
// decrypt:
|
||||||
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
|
||||||
final Long numDecryptedBytes = cryptor.decryptedFile(encryptedIn, plaintextOut);
|
final Long numDecryptedBytes = cryptor.decryptFile(encryptedIn, plaintextOut);
|
||||||
IOUtils.closeQuietly(encryptedIn);
|
IOUtils.closeQuietly(encryptedIn);
|
||||||
IOUtils.closeQuietly(plaintextOut);
|
IOUtils.closeQuietly(plaintextOut);
|
||||||
Assert.assertEquals(filesize.longValue(), numDecryptedBytes.longValue());
|
Assert.assertEquals(filesize.longValue(), numDecryptedBytes.longValue());
|
||||||
|
@ -79,7 +79,7 @@ public interface Cryptor extends SensitiveDataSwipeListener {
|
|||||||
* @return Number of decrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
|
* @return Number of decrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
|
||||||
* @throws DecryptFailedException If decryption failed
|
* @throws DecryptFailedException If decryption failed
|
||||||
*/
|
*/
|
||||||
Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException;
|
Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pos First byte (inclusive)
|
* @param pos First byte (inclusive)
|
||||||
|
@ -82,9 +82,9 @@ public class SamplingDecorator implements Cryptor, CryptorIOSampling {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
|
public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
|
||||||
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
|
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
|
||||||
return cryptor.decryptedFile(encryptedFile, countingInputStream);
|
return cryptor.decryptFile(encryptedFile, countingInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.cryptomator.crypto.exceptions;
|
||||||
|
|
||||||
|
public class MacAuthenticationFailedException extends DecryptFailedException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -5577052361643658772L;
|
||||||
|
|
||||||
|
public MacAuthenticationFailedException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user