mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Manually calculate attachment offsets
The CipherInputStream skip() method is pretty non-functional Fixes #7438
This commit is contained in:
parent
4324f0b7ec
commit
028c6edd8a
@ -3,9 +3,10 @@ package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
@ -21,13 +22,13 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ModernDecryptingPartInputStream {
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file)
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file, long offset)
|
||||
throws IOException
|
||||
{
|
||||
return createFor(attachmentSecret, random, new FileInputStream(file));
|
||||
return createFor(attachmentSecret, random, new FileInputStream(file), offset);
|
||||
}
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file)
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file, long offset)
|
||||
throws IOException
|
||||
{
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
@ -35,25 +36,37 @@ public class ModernDecryptingPartInputStream {
|
||||
|
||||
readFully(inputStream, random);
|
||||
|
||||
return createFor(attachmentSecret, random, inputStream);
|
||||
return createFor(attachmentSecret, random, inputStream, offset);
|
||||
}
|
||||
|
||||
private static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull InputStream inputStream) {
|
||||
private static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull InputStream inputStream, long offset) throws IOException {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
byte[] key = mac.doFinal(random);
|
||||
byte[] iv = new byte[16];
|
||||
int remainder = (int) (offset % 16);
|
||||
Conversions.longTo4ByteArray(iv, 12, offset / 16);
|
||||
|
||||
byte[] key = mac.doFinal(random);
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
return new CipherInputStream(inputStream, cipher);
|
||||
long skipped = inputStream.skip(offset - remainder);
|
||||
|
||||
if (skipped != offset - remainder) {
|
||||
throw new IOException("Skip failed: " + skipped + " vs " + (offset - remainder));
|
||||
}
|
||||
|
||||
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
|
||||
byte[] remainderBuffer = new byte[remainder];
|
||||
|
||||
readFully(cipherInputStream, remainderBuffer);
|
||||
|
||||
return cipherInputStream;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
|
@ -124,10 +124,10 @@ public class AttachmentDatabase extends Database {
|
||||
this.attachmentSecret = attachmentSecret;
|
||||
}
|
||||
|
||||
public @NonNull InputStream getAttachmentStream(AttachmentId attachmentId)
|
||||
public @NonNull InputStream getAttachmentStream(AttachmentId attachmentId, long offset)
|
||||
throws IOException
|
||||
{
|
||||
InputStream dataStream = getDataStream(attachmentId, DATA);
|
||||
InputStream dataStream = getDataStream(attachmentId, DATA, offset);
|
||||
|
||||
if (dataStream == null) throw new IOException("No stream for: " + attachmentId);
|
||||
else return dataStream;
|
||||
@ -137,7 +137,7 @@ public class AttachmentDatabase extends Database {
|
||||
throws IOException
|
||||
{
|
||||
Log.w(TAG, "getThumbnailStream(" + attachmentId + ")");
|
||||
InputStream dataStream = getDataStream(attachmentId, THUMBNAIL);
|
||||
InputStream dataStream = getDataStream(attachmentId, THUMBNAIL, 0);
|
||||
|
||||
if (dataStream != null) {
|
||||
return dataStream;
|
||||
@ -380,7 +380,7 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType)
|
||||
protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType, long offset)
|
||||
{
|
||||
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, dataType);
|
||||
|
||||
@ -390,9 +390,17 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
try {
|
||||
if (dataInfo.random != null && dataInfo.random.length == 32) {
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.random, dataInfo.file);
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.random, dataInfo.file, offset);
|
||||
} else {
|
||||
return ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.file);
|
||||
InputStream stream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.file);
|
||||
long skipped = stream.skip(offset);
|
||||
|
||||
if (skipped != offset) {
|
||||
Log.w(TAG, "Skip failed: " + skipped + " vs " + offset);
|
||||
return null;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
@ -590,7 +598,7 @@ public class AttachmentDatabase extends Database {
|
||||
@Override
|
||||
public @Nullable InputStream call() throws Exception {
|
||||
Log.w(TAG, "Executing thumbnail job...");
|
||||
final InputStream stream = getDataStream(attachmentId, THUMBNAIL);
|
||||
final InputStream stream = getDataStream(attachmentId, THUMBNAIL, 0);
|
||||
|
||||
if (stream != null) {
|
||||
return stream;
|
||||
@ -616,7 +624,7 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
updateAttachmentThumbnail(attachmentId, data.toDataStream(), data.getAspectRatio());
|
||||
|
||||
return getDataStream(attachmentId, THUMBNAIL);
|
||||
return getDataStream(attachmentId, THUMBNAIL, 0);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
|
@ -47,7 +47,7 @@ public class PartAuthority {
|
||||
int match = uriMatcher.match(uri);
|
||||
try {
|
||||
switch (match) {
|
||||
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId());
|
||||
case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
||||
case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId());
|
||||
case PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case SINGLE_USE_ROW: return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
|
||||
|
@ -158,10 +158,10 @@ public class PartProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private ParcelFileDescriptor getParcelStreamForAttachment(AttachmentId attachmentId) throws IOException {
|
||||
long plaintextLength = Util.getStreamLength(DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId));
|
||||
long plaintextLength = Util.getStreamLength(DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId, 0));
|
||||
MemoryFile memoryFile = new MemoryFile(attachmentId.toString(), Util.toIntExact(plaintextLength));
|
||||
|
||||
InputStream in = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId);
|
||||
InputStream in = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId, 0);
|
||||
OutputStream out = memoryFile.getOutputStream();
|
||||
|
||||
Util.copy(in, out);
|
||||
|
@ -160,7 +160,7 @@ public class PersistentBlobProvider {
|
||||
|
||||
FileData fileData = getFile(context, id);
|
||||
|
||||
if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file);
|
||||
if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file, 0);
|
||||
else return ClassicDecryptingPartInputStream.createFor(attachmentSecret, fileData.file);
|
||||
}
|
||||
|
||||
|
@ -58,13 +58,9 @@ public class EncryptedMediaDataSource extends MediaDataSource {
|
||||
private int readAtModern(long position, byte[] bytes, int offset, int length) throws IOException {
|
||||
assert(random != null);
|
||||
|
||||
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, mediaFile);
|
||||
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, mediaFile, position);
|
||||
int returnValue = inputStream.read(bytes, offset, length);
|
||||
|
||||
if (inputStream.skip(position) != position) {
|
||||
throw new IOException("Skip failed: " + position);
|
||||
}
|
||||
|
||||
int returnValue = inputStream.read(bytes, offset, length);
|
||||
inputStream.close();
|
||||
|
||||
return returnValue;
|
||||
|
@ -11,7 +11,6 @@ import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||
@ -28,8 +27,7 @@ public class PartDataSource implements DataSource {
|
||||
private Uri uri;
|
||||
private InputStream inputSteam;
|
||||
|
||||
public PartDataSource(@NonNull Context context, @Nullable TransferListener<? super PartDataSource> listener)
|
||||
{
|
||||
PartDataSource(@NonNull Context context, @Nullable TransferListener<? super PartDataSource> listener) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.listener = listener;
|
||||
}
|
||||
@ -44,13 +42,7 @@ public class PartDataSource implements DataSource {
|
||||
|
||||
if (attachment == null) throw new IOException("Attachment not found");
|
||||
|
||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId());
|
||||
|
||||
if (inputSteam == null) throw new IOException("InputStream not foudn");
|
||||
|
||||
long skipped = this.inputSteam.skip(dataSpec.position);
|
||||
|
||||
if (skipped != dataSpec.position) throw new IOException("Skip failed!");
|
||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
||||
|
||||
if (listener != null) {
|
||||
listener.onTransferStart(this, dataSpec);
|
||||
|
Loading…
Reference in New Issue
Block a user