Manually calculate attachment offsets

The CipherInputStream skip() method is pretty non-functional

Fixes #7438
This commit is contained in:
Moxie Marlinspike 2018-02-24 11:09:26 -08:00
parent 4324f0b7ec
commit 028c6edd8a
7 changed files with 48 additions and 39 deletions

View File

@ -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.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
byte[] iv = new byte[16];
byte[] key = mac.doFinal(random);
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 {

View File

@ -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")

View File

@ -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));

View File

@ -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);

View File

@ -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);
}

View 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);
if (inputStream.skip(position) != position) {
throw new IOException("Skip failed: " + position);
}
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, mediaFile, position);
int returnValue = inputStream.read(bytes, offset, length);
inputStream.close();
return returnValue;

View File

@ -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);