diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt index d18fba5496..a5cb1322ca 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt @@ -144,6 +144,7 @@ class ConversationItemPreviewer { 1024, Optional.empty(), Optional.empty(), + 0, Optional.of("/not-there.jpg"), false, false, diff --git a/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt b/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt index 98f8c68d54..1a46b261de 100644 --- a/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt +++ b/app/src/benchmark/java/org/signal/benchmark/setup/TestMessages.kt @@ -149,6 +149,7 @@ object TestMessages { 1024, Optional.empty(), Optional.empty(), + 0, Optional.of("/not-there.jpg"), false, false, @@ -171,6 +172,7 @@ object TestMessages { 1024, Optional.empty(), Optional.empty(), + 0, Optional.of("/not-there.aac"), true, false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java index 7973ef911a..bf1b66e7dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java @@ -48,6 +48,7 @@ public abstract class Attachment { private final int height; private final boolean quote; private final long uploadTimestamp; + private final int incrementalMacChunkSize; @Nullable private final String caption; @@ -80,6 +81,7 @@ public abstract class Attachment { boolean videoGif, int width, int height, + int incrementalMacChunkSize, boolean quote, long uploadTimestamp, @Nullable String caption, @@ -88,29 +90,30 @@ public abstract class Attachment { @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties) { - this.contentType = contentType; - this.transferState = transferState; - this.size = size; - this.fileName = fileName; - this.cdnNumber = cdnNumber; - this.location = location; - this.key = key; - this.relay = relay; - this.digest = digest; - this.incrementalDigest = incrementalDigest; - this.fastPreflightId = fastPreflightId; - this.voiceNote = voiceNote; - this.borderless = borderless; - this.videoGif = videoGif; - this.width = width; - this.height = height; - this.quote = quote; - this.uploadTimestamp = uploadTimestamp; - this.stickerLocator = stickerLocator; - this.caption = caption; - this.blurHash = blurHash; - this.audioHash = audioHash; - this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty(); + this.contentType = contentType; + this.transferState = transferState; + this.size = size; + this.fileName = fileName; + this.cdnNumber = cdnNumber; + this.location = location; + this.key = key; + this.relay = relay; + this.digest = digest; + this.incrementalDigest = incrementalDigest; + this.fastPreflightId = fastPreflightId; + this.voiceNote = voiceNote; + this.borderless = borderless; + this.videoGif = videoGif; + this.width = width; + this.height = height; + this.incrementalMacChunkSize = incrementalMacChunkSize; + this.quote = quote; + this.uploadTimestamp = uploadTimestamp; + this.stickerLocator = stickerLocator; + this.caption = caption; + this.blurHash = blurHash; + this.audioHash = audioHash; + this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty(); } @Nullable @@ -200,6 +203,10 @@ public abstract class Attachment { return height; } + public int getIncrementalMacChunkSize() { + return incrementalMacChunkSize; + } + public boolean isQuote() { return quote; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java index 3e7dcdc4b6..650bf80ae8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java @@ -35,6 +35,7 @@ public class DatabaseAttachment extends Attachment { String relay, byte[] digest, byte[] incrementalDigest, + int incrementalMacChunkSize, String fastPreflightId, boolean voiceNote, boolean borderless, @@ -50,7 +51,7 @@ public class DatabaseAttachment extends Attachment { int displayOrder, long uploadTimestamp) { - super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties); + super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties); this.attachmentId = attachmentId; this.hasData = hasData; this.hasThumbnail = hasThumbnail; diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java index e8774b7200..c616f91139 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/MmsNotificationAttachment.java @@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.MessageTable; public class MmsNotificationAttachment extends Attachment { public MmsNotificationAttachment(int status, long size) { - super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, false, 0, null, null, null, null, null); + super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, false, 0, null, null, null, null, null); } @Nullable diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java index ecb616642b..9c87d5d267 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -31,6 +31,7 @@ public class PointerAttachment extends Attachment { @Nullable String relay, @Nullable byte[] digest, @Nullable byte[] incrementalDigest, + int incrementalMacChunkSize, @Nullable String fastPreflightId, boolean voiceNote, boolean borderless, @@ -42,7 +43,7 @@ public class PointerAttachment extends Attachment { @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash) { - super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null); + super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null); } @Nullable @@ -111,9 +112,11 @@ public class PointerAttachment extends Attachment { pointer.get().asPointer().getFileName().orElse(null), pointer.get().asPointer().getCdnNumber(), pointer.get().asPointer().getRemoteId().toString(), - encodedKey, null, + encodedKey, + null, pointer.get().asPointer().getDigest().orElse(null), pointer.get().asPointer().getIncrementalDigest().orElse(null), + pointer.get().asPointer().getIncrementalMacChunkSize(), fastPreflightId, pointer.get().asPointer().getVoiceNote(), pointer.get().asPointer().isBorderless(), @@ -140,6 +143,7 @@ public class PointerAttachment extends Attachment { null, thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null, thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null, + thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0, null, false, false, @@ -170,6 +174,7 @@ public class PointerAttachment extends Attachment { null, thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null, thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null, + thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0, null, false, false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java index 6748b64738..4c9ec6e09e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentTable; public class TombstoneAttachment extends Attachment { public TombstoneAttachment(@NonNull String contentType, boolean quote) { - super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, quote, 0, null, null, null, null, null); + super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, quote, 0, null, null, null, null, null); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java index 4cc9b99875..d7d03fa3bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.java @@ -52,7 +52,7 @@ public class UriAttachment extends Attachment { @Nullable AudioHash audioHash, @Nullable TransformProperties transformProperties) { - super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties); + super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, 0, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties); this.dataUri = Objects.requireNonNull(dataUri); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java index cb63c9b80b..61ad849ccd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java @@ -92,41 +92,42 @@ public class AttachmentTable extends DatabaseTable { public static final String TAG = Log.tag(AttachmentTable.class); - public static final String TABLE_NAME = "part"; - public static final String ROW_ID = "_id"; - static final String ATTACHMENT_JSON_ALIAS = "attachment_json"; - public static final String MMS_ID = "mid"; - static final String CONTENT_TYPE = "ct"; - static final String NAME = "name"; - static final String CONTENT_DISPOSITION = "cd"; - static final String CONTENT_LOCATION = "cl"; - public static final String DATA = "_data"; - static final String TRANSFER_STATE = "pending_push"; - public static final String TRANSFER_FILE = "transfer_file"; - public static final String SIZE = "data_size"; - static final String FILE_NAME = "file_name"; - public static final String UNIQUE_ID = "unique_id"; - static final String DIGEST = "digest"; - static final String VOICE_NOTE = "voice_note"; - static final String BORDERLESS = "borderless"; - static final String VIDEO_GIF = "video_gif"; - static final String QUOTE = "quote"; - public static final String STICKER_PACK_ID = "sticker_pack_id"; - public static final String STICKER_PACK_KEY = "sticker_pack_key"; - static final String STICKER_ID = "sticker_id"; - static final String STICKER_EMOJI = "sticker_emoji"; - static final String FAST_PREFLIGHT_ID = "fast_preflight_id"; - public static final String DATA_RANDOM = "data_random"; - static final String WIDTH = "width"; - static final String HEIGHT = "height"; - static final String CAPTION = "caption"; - static final String DATA_HASH = "data_hash"; - static final String VISUAL_HASH = "blur_hash"; - static final String TRANSFORM_PROPERTIES = "transform_properties"; - static final String DISPLAY_ORDER = "display_order"; - static final String UPLOAD_TIMESTAMP = "upload_timestamp"; - static final String CDN_NUMBER = "cdn_number"; - static final String MAC_DIGEST = "incremental_mac_digest"; + public static final String TABLE_NAME = "part"; + public static final String ROW_ID = "_id"; + static final String ATTACHMENT_JSON_ALIAS = "attachment_json"; + public static final String MMS_ID = "mid"; + static final String CONTENT_TYPE = "ct"; + static final String NAME = "name"; + static final String CONTENT_DISPOSITION = "cd"; + static final String CONTENT_LOCATION = "cl"; + public static final String DATA = "_data"; + static final String TRANSFER_STATE = "pending_push"; + public static final String TRANSFER_FILE = "transfer_file"; + public static final String SIZE = "data_size"; + static final String FILE_NAME = "file_name"; + public static final String UNIQUE_ID = "unique_id"; + static final String DIGEST = "digest"; + static final String VOICE_NOTE = "voice_note"; + static final String BORDERLESS = "borderless"; + static final String VIDEO_GIF = "video_gif"; + static final String QUOTE = "quote"; + public static final String STICKER_PACK_ID = "sticker_pack_id"; + public static final String STICKER_PACK_KEY = "sticker_pack_key"; + static final String STICKER_ID = "sticker_id"; + static final String STICKER_EMOJI = "sticker_emoji"; + static final String FAST_PREFLIGHT_ID = "fast_preflight_id"; + public static final String DATA_RANDOM = "data_random"; + static final String WIDTH = "width"; + static final String HEIGHT = "height"; + static final String CAPTION = "caption"; + static final String DATA_HASH = "data_hash"; + static final String VISUAL_HASH = "blur_hash"; + static final String TRANSFORM_PROPERTIES = "transform_properties"; + static final String DISPLAY_ORDER = "display_order"; + static final String UPLOAD_TIMESTAMP = "upload_timestamp"; + static final String CDN_NUMBER = "cdn_number"; + static final String MAC_DIGEST = "incremental_mac_digest"; + static final String INCREMENTAL_MAC_CHUNK_SIZE = "incremental_mac_chunk_size"; private static final String DIRECTORY = "parts"; @@ -144,53 +145,54 @@ public class AttachmentTable extends DatabaseTable { private static final String[] PROJECTION = new String[] {ROW_ID, MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, CDN_NUMBER, CONTENT_LOCATION, DATA, - TRANSFER_STATE, SIZE, FILE_NAME, UNIQUE_ID, DIGEST, MAC_DIGEST, + TRANSFER_STATE, SIZE, FILE_NAME, UNIQUE_ID, DIGEST, MAC_DIGEST, INCREMENTAL_MAC_CHUNK_SIZE, FAST_PREFLIGHT_ID, VOICE_NOTE, BORDERLESS, VIDEO_GIF, QUOTE, DATA_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID, STICKER_PACK_KEY, STICKER_ID, STICKER_EMOJI, DATA_HASH, VISUAL_HASH, TRANSFORM_PROPERTIES, TRANSFER_FILE, DISPLAY_ORDER, UPLOAD_TIMESTAMP }; - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + - MMS_ID + " INTEGER, " + - "seq" + " INTEGER DEFAULT 0, " + - CONTENT_TYPE + " TEXT, " + - NAME + " TEXT, " + - "chset" + " INTEGER, " + - CONTENT_DISPOSITION + " TEXT, " + - "fn" + " TEXT, " + - "cid" + " TEXT, " + - CONTENT_LOCATION + " TEXT, " + - "ctt_s" + " INTEGER, " + - "ctt_t" + " TEXT, " + - "encrypted" + " INTEGER, " + - TRANSFER_STATE + " INTEGER, " + - DATA + " TEXT, " + - SIZE + " INTEGER, " + - FILE_NAME + " TEXT, " + - UNIQUE_ID + " INTEGER NOT NULL, " + - DIGEST + " BLOB, " + - FAST_PREFLIGHT_ID + " TEXT, " + - VOICE_NOTE + " INTEGER DEFAULT 0, " + - BORDERLESS + " INTEGER DEFAULT 0, " + - VIDEO_GIF + " INTEGER DEFAULT 0, " + - DATA_RANDOM + " BLOB, " + - QUOTE + " INTEGER DEFAULT 0, " + - WIDTH + " INTEGER DEFAULT 0, " + - HEIGHT + " INTEGER DEFAULT 0, " + - CAPTION + " TEXT DEFAULT NULL, " + - STICKER_PACK_ID + " TEXT DEFAULT NULL, " + - STICKER_PACK_KEY + " DEFAULT NULL, " + - STICKER_ID + " INTEGER DEFAULT -1, " + - STICKER_EMOJI + " STRING DEFAULT NULL, " + - DATA_HASH + " TEXT DEFAULT NULL, " + - VISUAL_HASH + " TEXT DEFAULT NULL, " + - TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + - TRANSFER_FILE + " TEXT DEFAULT NULL, " + - DISPLAY_ORDER + " INTEGER DEFAULT 0, " + - UPLOAD_TIMESTAMP + " INTEGER DEFAULT 0, " + - CDN_NUMBER + " INTEGER DEFAULT 0, " + - MAC_DIGEST + " BLOB);"; + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + + MMS_ID + " INTEGER, " + + "seq" + " INTEGER DEFAULT 0, " + + CONTENT_TYPE + " TEXT, " + + NAME + " TEXT, " + + "chset" + " INTEGER, " + + CONTENT_DISPOSITION + " TEXT, " + + "fn" + " TEXT, " + + "cid" + " TEXT, " + + CONTENT_LOCATION + " TEXT, " + + "ctt_s" + " INTEGER, " + + "ctt_t" + " TEXT, " + + "encrypted" + " INTEGER, " + + TRANSFER_STATE + " INTEGER, " + + DATA + " TEXT, " + + SIZE + " INTEGER, " + + FILE_NAME + " TEXT, " + + UNIQUE_ID + " INTEGER NOT NULL, " + + DIGEST + " BLOB, " + + FAST_PREFLIGHT_ID + " TEXT, " + + VOICE_NOTE + " INTEGER DEFAULT 0, " + + BORDERLESS + " INTEGER DEFAULT 0, " + + VIDEO_GIF + " INTEGER DEFAULT 0, " + + DATA_RANDOM + " BLOB, " + + QUOTE + " INTEGER DEFAULT 0, " + + WIDTH + " INTEGER DEFAULT 0, " + + HEIGHT + " INTEGER DEFAULT 0, " + + CAPTION + " TEXT DEFAULT NULL, " + + STICKER_PACK_ID + " TEXT DEFAULT NULL, " + + STICKER_PACK_KEY + " DEFAULT NULL, " + + STICKER_ID + " INTEGER DEFAULT -1, " + + STICKER_EMOJI + " STRING DEFAULT NULL, " + + DATA_HASH + " TEXT DEFAULT NULL, " + + VISUAL_HASH + " TEXT DEFAULT NULL, " + + TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + + TRANSFER_FILE + " TEXT DEFAULT NULL, " + + DISPLAY_ORDER + " INTEGER DEFAULT 0, " + + UPLOAD_TIMESTAMP + " INTEGER DEFAULT 0, " + + CDN_NUMBER + " INTEGER DEFAULT 0, " + + MAC_DIGEST + " BLOB, " + + INCREMENTAL_MAC_CHUNK_SIZE + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", @@ -728,6 +730,7 @@ public class AttachmentTable extends DatabaseTable { contentValues.put(CONTENT_LOCATION, sourceAttachment.getLocation()); contentValues.put(DIGEST, sourceAttachment.getDigest()); contentValues.put(MAC_DIGEST, sourceAttachment.getIncrementalDigest()); + contentValues.put(INCREMENTAL_MAC_CHUNK_SIZE, sourceAttachment.getIncrementalMacChunkSize()); contentValues.put(CONTENT_DISPOSITION, sourceAttachment.getKey()); contentValues.put(NAME, sourceAttachment.getRelay()); contentValues.put(SIZE, sourceAttachment.getSize()); @@ -777,6 +780,7 @@ public class AttachmentTable extends DatabaseTable { values.put(CONTENT_LOCATION, attachment.getLocation()); values.put(DIGEST, attachment.getDigest()); values.put(MAC_DIGEST, attachment.getIncrementalDigest()); + values.put(INCREMENTAL_MAC_CHUNK_SIZE, attachment.getIncrementalMacChunkSize()); values.put(CONTENT_DISPOSITION, attachment.getKey()); values.put(NAME, attachment.getRelay()); values.put(SIZE, attachment.getSize()); @@ -1309,6 +1313,7 @@ public class AttachmentTable extends DatabaseTable { object.getString(NAME), null, null, + 0, object.getString(FAST_PREFLIGHT_ID), object.getInt(VOICE_NOTE) == 1, object.getInt(BORDERLESS) == 1, @@ -1357,6 +1362,7 @@ public class AttachmentTable extends DatabaseTable { cursor.getString(cursor.getColumnIndexOrThrow(NAME)), cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)), cursor.getBlob(cursor.getColumnIndexOrThrow(MAC_DIGEST)), + cursor.getInt(cursor.getColumnIndexOrThrow(INCREMENTAL_MAC_CHUNK_SIZE)), cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)), cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1, cursor.getInt(cursor.getColumnIndexOrThrow(BORDERLESS)) == 1, @@ -1429,6 +1435,7 @@ public class AttachmentTable extends DatabaseTable { contentValues.put(CONTENT_LOCATION, useTemplateUpload ? template.getLocation() : attachment.getLocation()); contentValues.put(DIGEST, useTemplateUpload ? template.getDigest() : attachment.getDigest()); contentValues.put(MAC_DIGEST, useTemplateUpload ? template.getIncrementalDigest() : attachment.getIncrementalDigest()); + contentValues.put(INCREMENTAL_MAC_CHUNK_SIZE, useTemplateUpload ? template.getIncrementalMacChunkSize() : attachment.getIncrementalMacChunkSize()); contentValues.put(CONTENT_DISPOSITION, useTemplateUpload ? template.getKey() : attachment.getKey()); contentValues.put(NAME, useTemplateUpload ? template.getRelay() : attachment.getRelay()); contentValues.put(FILE_NAME, StorageUtil.getCleanFileName(attachment.getFileName())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt index 27d1ea6385..c9f81ca6ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt @@ -50,6 +50,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD ${AttachmentTable.TABLE_NAME}.${AttachmentTable.NAME}, ${AttachmentTable.TABLE_NAME}.${AttachmentTable.UPLOAD_TIMESTAMP}, ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MAC_DIGEST}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.INCREMENTAL_MAC_CHUNK_SIZE}, ${MessageTable.TABLE_NAME}.${MessageTable.TYPE}, ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT}, ${MessageTable.TABLE_NAME}.${MessageTable.DATE_RECEIVED}, @@ -57,7 +58,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID}, ${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID}, ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} as $THREAD_RECIPIENT_ID - FROM + FROM ${AttachmentTable.TABLE_NAME} LEFT JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} LEFT JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.ID} = ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index cc8d6262fb..5d268b8208 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V203_PreKeyStaleTim import org.thoughtcrime.securesms.database.helpers.migration.V204_GroupForeignKeyMigration import org.thoughtcrime.securesms.database.helpers.migration.V205_DropPushTable import org.thoughtcrime.securesms.database.helpers.migration.V206_AddConversationCountIndex +import org.thoughtcrime.securesms.database.helpers.migration.V207_AddChunkSizeColumn /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. @@ -70,7 +71,7 @@ object SignalDatabaseMigrations { val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass) - const val DATABASE_VERSION = 206 + const val DATABASE_VERSION = 207 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -305,6 +306,10 @@ object SignalDatabaseMigrations { if (oldVersion < 206) { V206_AddConversationCountIndex.migrate(context, db, oldVersion, newVersion) } + + if (oldVersion < 207) { + V207_AddChunkSizeColumn.migrate(context, db, oldVersion, newVersion) + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V207_AddChunkSizeColumn.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V207_AddChunkSizeColumn.kt new file mode 100644 index 0000000000..bfe1e25104 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V207_AddChunkSizeColumn.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase + +/** + * New field migration. + */ +@Suppress("ClassName") +object V207_AddChunkSizeColumn : SignalDatabaseMigration { + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("ALTER TABLE part ADD COLUMN incremental_mac_chunk_size INTEGER DEFAULT 0") + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 62074097c8..6e5b9564d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -248,6 +248,7 @@ public final class AttachmentDownloadJob extends BaseJob { 0, 0, Optional.ofNullable(attachment.getDigest()), Optional.ofNullable(attachment.getIncrementalDigest()), + attachment.getIncrementalMacChunkSize(), Optional.ofNullable(attachment.getFileName()), attachment.isVoiceNote(), attachment.isBorderless(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java index 3eaf2370b8..69b8cb54f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java @@ -85,7 +85,7 @@ public final class AvatarGroupsV1DownloadJob extends BaseJob { attachment.deleteOnExit(); SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); - SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(0, new SignalServiceAttachmentRemoteId(avatarId), contentType, key, Optional.of(0), Optional.empty(), 0, 0, digest, Optional.empty(), fileName, false, false, false, Optional.empty(), Optional.empty(), System.currentTimeMillis()); + SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(0, new SignalServiceAttachmentRemoteId(avatarId), contentType, key, Optional.of(0), Optional.empty(), 0, 0, digest, Optional.empty(), 0, fileName, false, false, false, Optional.empty(), Optional.empty(), System.currentTimeMillis()); InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); AvatarHelper.setAvatar(context, record.get().getRecipientId(), inputStream); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index 80b1992637..4e69361f1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -284,6 +284,7 @@ public abstract class PushSendJob extends SendJob { height, Optional.ofNullable(attachment.getDigest()), Optional.ofNullable(attachment.getIncrementalDigest()), + attachment.getIncrementalMacChunkSize(), Optional.ofNullable(attachment.getFileName()), attachment.isVoiceNote(), attachment.isBorderless(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamLocalUriFetcher.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamLocalUriFetcher.java index 9d86652922..058d14a887 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamLocalUriFetcher.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamLocalUriFetcher.java @@ -7,7 +7,6 @@ import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.data.DataFetcher; import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidMacException; import org.signal.libsignal.protocol.InvalidMessageException; import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream; @@ -24,23 +23,25 @@ class AttachmentStreamLocalUriFetcher implements DataFetcher { private final byte[] key; private final Optional digest; private final Optional incrementalDigest; + private final int incrementalMacChunkSize; private final long plaintextLength; private InputStream is; - AttachmentStreamLocalUriFetcher(File attachment, long plaintextLength, byte[] key, Optional digest, Optional incrementalDigest) { - this.attachment = attachment; - this.plaintextLength = plaintextLength; - this.digest = digest; - this.incrementalDigest = incrementalDigest; - this.key = key; + AttachmentStreamLocalUriFetcher(File attachment, long plaintextLength, byte[] key, Optional digest, Optional incrementalDigest, int incrementalMacChunkSize) { + this.attachment = attachment; + this.plaintextLength = plaintextLength; + this.digest = digest; + this.incrementalDigest = incrementalDigest; + this.incrementalMacChunkSize = incrementalMacChunkSize; + this.key = key; } @Override public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { try { if (!digest.isPresent()) throw new InvalidMessageException("No attachment digest!"); - is = AttachmentCipherInputStream.createForAttachment(attachment, plaintextLength, key, digest.get(), incrementalDigest.orElse(null)); + is = AttachmentCipherInputStream.createForAttachment(attachment, plaintextLength, key, digest.get(), incrementalDigest.orElse(null), incrementalMacChunkSize); callback.onDataReady(is); } catch (IOException | InvalidMessageException e) { callback.onLoadFailed(e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamUriLoader.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamUriLoader.java index a8c3f8772d..e00caf7f1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamUriLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentStreamUriLoader.java @@ -20,7 +20,7 @@ public class AttachmentStreamUriLoader implements ModelLoader buildLoadData(@NonNull AttachmentModel attachmentModel, int width, int height, @NonNull Options options) { - return new LoadData<>(attachmentModel, new AttachmentStreamLocalUriFetcher(attachmentModel.attachment, attachmentModel.plaintextLength, attachmentModel.key, attachmentModel.digest, attachmentModel.incrementalDigest)); + return new LoadData<>(attachmentModel, new AttachmentStreamLocalUriFetcher(attachmentModel.attachment, attachmentModel.plaintextLength, attachmentModel.key, attachmentModel.digest, attachmentModel.incrementalDigest, attachmentModel.incrementalMacChunkSize)); } @Override @@ -46,19 +46,22 @@ public class AttachmentStreamUriLoader implements ModelLoader digest; public @NonNull Optional incrementalDigest; + public int incrementalMacChunkSize; public long plaintextLength; public AttachmentModel(@NonNull File attachment, @NonNull byte[] key, long plaintextLength, @NonNull Optional digest, - @NonNull Optional incrementalDigest) + @NonNull Optional incrementalDigest, + int incrementalMacChunkSize) { - this.attachment = attachment; - this.key = key; - this.digest = digest; - this.incrementalDigest = incrementalDigest; - this.plaintextLength = plaintextLength; + this.attachment = attachment; + this.key = key; + this.digest = digest; + this.incrementalDigest = incrementalDigest; + this.incrementalMacChunkSize = incrementalMacChunkSize; + this.plaintextLength = plaintextLength; } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt index 20ff6b1607..171dc85014 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt @@ -45,6 +45,7 @@ object ReleaseChannel { mediaHeight, Optional.empty(), Optional.empty(), + 0, Optional.of(media), false, false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java index 2bf8a36429..b8d75745fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java @@ -13,7 +13,6 @@ import androidx.media3.datasource.TransferListener; import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -65,7 +64,7 @@ class PartDataSource implements DataSource { final byte[] decode = Base64.decode(attachmentKey); final File transferFile = attachmentDatabase.getOrCreateTransferFile(attachment.getAttachmentId()); try { - this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.getSize(), decode, attachment.getDigest(), attachment.getIncrementalDigest()); + this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.getSize(), decode, attachment.getDigest(), attachment.getIncrementalDigest(), attachment.getIncrementalMacChunkSize()); long skipped = 0; while (skipped < dataSpec.position) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt index 757ce5893b..bcf5946565 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt @@ -243,6 +243,7 @@ class UploadDependencyGraphTest { attachment.relay, attachment.digest, attachment.incrementalDigest, + attachment.incrementalMacChunkSize, attachment.fastPreflightId, attachment.isVoiceNote, attachment.isBorderless, diff --git a/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt b/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt index bae5ac9493..1f5267336e 100644 --- a/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt +++ b/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt @@ -41,6 +41,7 @@ object FakeMessageRecords { relay: String = "", digest: ByteArray = byteArrayOf(), incrementalDigest: ByteArray = byteArrayOf(), + incrementalMacChunkSize: Int = 0, fastPreflightId: String = "", voiceNote: Boolean = false, borderless: Boolean = false, @@ -71,6 +72,7 @@ object FakeMessageRecords { relay, digest, incrementalDigest, + incrementalMacChunkSize, fastPreflightId, voiceNote, borderless, diff --git a/dependencies.gradle b/dependencies.gradle index a819aeeea3..4fb9bce332 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -15,7 +15,7 @@ dependencyResolutionManagement { version('exoplayer', '2.19.0') version('glide', '4.15.1') version('kotlin', '1.8.10') - version('libsignal-client', '0.32.0') + version('libsignal-client', '0.32.1') version('mp4parser', '1.9.39') version('android-gradle-plugin', '8.0.2') version('accompanist', '0.28.0') diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 25e7a98035..fb08b4efe9 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -5333,20 +5333,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 1c560d2de0..a68215bbdb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -159,7 +159,7 @@ public class SignalServiceMessageReceiver { if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!"); socket.retrieveAttachment(pointer.getCdnNumber(), pointer.getRemoteId(), destination, maxSizeBytes, listener); - return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().orElse(0), pointer.getKey(), pointer.getDigest().get(), null); + return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().orElse(0), pointer.getKey(), pointer.getDigest().get(), null, 0); } public InputStream retrieveSticker(byte[] packId, byte[] packKey, int stickerId) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index f410dfe29a..e3b2310694 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -20,7 +20,6 @@ import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.signal.libsignal.protocol.state.PreKeyBundle; import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; -import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream; import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.EnvelopeContent; @@ -833,6 +832,7 @@ public class SignalServiceMessageSender { attachment.getWidth(), attachment.getHeight(), Optional.of(attachmentIdAndDigest.second().getDigest()), Optional.of(attachmentIdAndDigest.second().getIncrementalDigest()), + attachmentIdAndDigest.second().getIncrementalMacChunkSize(), attachment.getFileName(), attachment.getVoiceNote(), attachment.isBorderless(), @@ -874,6 +874,7 @@ public class SignalServiceMessageSender { attachment.getHeight(), Optional.of(digest.getDigest()), Optional.ofNullable(digest.getIncrementalDigest()), + digest.getIncrementalMacChunkSize(), attachment.getFileName(), attachment.getVoiceNote(), attachment.isBorderless(), diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java index 7b559542f7..366a85a972 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java @@ -11,8 +11,6 @@ import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice; import org.signal.libsignal.protocol.incrementalmac.IncrementalMacInputStream; import org.signal.libsignal.protocol.kdf.HKDFv3; -import org.signal.libsignal.protocol.logging.Log; -import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; import org.whispersystems.signalservice.internal.util.ContentLengthInputStream; import org.whispersystems.signalservice.internal.util.Util; @@ -55,7 +53,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { private long totalRead; private byte[] overflowBuffer; - public static InputStream createForAttachment(File file, long plaintextLength, byte[] combinedKeyMaterial, byte[] digest, byte[] incrementalDigest) + public static InputStream createForAttachment(File file, long plaintextLength, byte[] combinedKeyMaterial, byte[] digest, byte[] incrementalDigest, int incrementalMacChunkSize) throws InvalidMessageException, IOException { try { @@ -73,19 +71,18 @@ public class AttachmentCipherInputStream extends FilterInputStream { final InputStream wrappedStream; - boolean hasIncrementalMac = incrementalDigest != null && incrementalDigest.length > 0; + final boolean hasIncrementalMac = incrementalDigest != null && incrementalDigest.length > 0 && incrementalMacChunkSize > 0; + if (!hasIncrementalMac) { try (FileInputStream macVerificationStream = new FileInputStream(file)) { verifyMac(macVerificationStream, file.length(), mac, digest); } wrappedStream = new FileInputStream(file); } else { - final int dataSize = Math.toIntExact(AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(plaintextLength))); - final ChunkSizeChoice sizeChoice = ChunkSizeChoice.inferChunkSize(dataSize); wrappedStream = new IncrementalMacInputStream( new FileInputStream(file), parts[1], - sizeChoice, + ChunkSizeChoice.everyNthByte(incrementalMacChunkSize), incrementalDigest); } InputStream inputStream = new AttachmentCipherInputStream(wrappedStream, parts[0], file.length() - BLOCK_SIZE - mac.getMacLength()); @@ -153,7 +150,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { int read; //noinspection StatementWithEmptyBody - while ((read = read(buffer)) == 0); + while ((read = read(buffer)) == 0) ; return (read == -1) ? -1 : ((int) buffer[0]) & 0xFF; } @@ -165,9 +162,13 @@ public class AttachmentCipherInputStream extends FilterInputStream { @Override public int read(byte[] buffer, int offset, int length) throws IOException { - if (totalRead != totalDataSize) return readIncremental(buffer, offset, length); - else if (!done) return readFinal(buffer, offset, length); - else return -1; + if (totalRead != totalDataSize) { + return readIncremental(buffer, offset, length); + } else if (!done) { + return readFinal(buffer, offset, length); + } else { + return -1; + } } @Override @@ -179,7 +180,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { public long skip(long byteCount) throws IOException { long skipped = 0L; while (skipped < byteCount) { - byte[] buf = new byte[Math.min(4096, (int)(byteCount-skipped))]; + byte[] buf = new byte[Math.min(4096, (int) (byteCount - skipped))]; int read = read(buf); skipped += read; @@ -190,8 +191,8 @@ public class AttachmentCipherInputStream extends FilterInputStream { private int readFinal(byte[] buffer, int offset, int length) throws IOException { try { - byte[] internal = new byte[buffer.length]; - int actualLength = Math.min(length, cipher.doFinal(internal, 0)); + byte[] internal = new byte[buffer.length]; + int actualLength = Math.min(length, cipher.doFinal(internal, 0)); System.arraycopy(internal, 0, buffer, offset, actualLength); done = true; @@ -222,11 +223,11 @@ public class AttachmentCipherInputStream extends FilterInputStream { } if (length + totalRead > totalDataSize) - length = (int)(totalDataSize - totalRead); + length = (int) (totalDataSize - totalRead); byte[] internalBuffer = new byte[length]; - int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize()); - totalRead += read; + int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize()); + totalRead += read; try { int outputLen = cipher.getOutputSize(read); @@ -256,9 +257,9 @@ public class AttachmentCipherInputStream extends FilterInputStream { throws InvalidMacException { try { - MessageDigest digest = MessageDigest.getInstance("SHA256"); - int remainingData = Util.toIntExact(length) - mac.getMacLength(); - byte[] buffer = new byte[4096]; + MessageDigest digest = MessageDigest.getInstance("SHA256"); + int remainingData = Util.toIntExact(length) - mac.getMacLength(); + byte[] buffer = new byte[4096]; while (remainingData > 0) { int read = inputStream.read(buffer, 0, Math.min(buffer.length, remainingData)); @@ -291,11 +292,14 @@ public class AttachmentCipherInputStream extends FilterInputStream { private void readFully(byte[] buffer) throws IOException { int offset = 0; - for (;;) { + for (; ; ) { int read = super.read(buffer, offset, buffer.length - offset); - if (read + offset < buffer.length) offset += read; - else return; + if (read + offset < buffer.length) { + offset += read; + } else { + return; + } } } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java index 352b0107d7..50df2140c8 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachmentPointer.java @@ -26,6 +26,7 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { private final Optional preview; private final Optional digest; private final Optional incrementalDigest; + private final int incrementalMacChunkSize; private final Optional fileName; private final boolean voiceNote; private final boolean borderless; @@ -46,6 +47,7 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { int height, Optional digest, Optional incrementalDigest, + int incrementalMacChunkSize, Optional fileName, boolean voiceNote, boolean borderless, @@ -55,22 +57,23 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { long uploadTimestamp) { super(contentType); - this.cdnNumber = cdnNumber; - this.remoteId = remoteId; - this.key = key; - this.size = size; - this.preview = preview; - this.width = width; - this.height = height; - this.digest = digest; - this.incrementalDigest = incrementalDigest; - this.fileName = fileName; - this.voiceNote = voiceNote; - this.borderless = borderless; - this.caption = caption; - this.blurHash = blurHash; - this.uploadTimestamp = uploadTimestamp; - this.gif = gif; + this.cdnNumber = cdnNumber; + this.remoteId = remoteId; + this.key = key; + this.size = size; + this.preview = preview; + this.width = width; + this.height = height; + this.incrementalMacChunkSize = incrementalMacChunkSize; + this.digest = digest; + this.incrementalDigest = incrementalDigest; + this.fileName = fileName; + this.voiceNote = voiceNote; + this.borderless = borderless; + this.caption = caption; + this.blurHash = blurHash; + this.uploadTimestamp = uploadTimestamp; + this.gif = gif; } public int getCdnNumber() { @@ -135,6 +138,10 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment { return height; } + public int getIncrementalMacChunkSize() { + return incrementalMacChunkSize; + } + public Optional getCaption() { return caption; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/AttachmentPointerUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/AttachmentPointerUtil.java index 28cabeed80..976c84fa38 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/AttachmentPointerUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/AttachmentPointerUtil.java @@ -28,6 +28,7 @@ public final class AttachmentPointerUtil { pointer.height != null ? pointer.height : 0, pointer.digest != null ? Optional.of(pointer.digest.toByteArray()) : Optional.empty(), pointer.incrementalDigest != null ? Optional.of(pointer.incrementalDigest.toByteArray()) : Optional.empty(), + pointer.incrementalMacChunkSize != null ? pointer.incrementalMacChunkSize : 0, pointer.fileName != null ? Optional.of(pointer.fileName) : Optional.empty(), ((pointer.flags != null ? pointer.flags : 0) & FlagUtil.toBinaryFlag(AttachmentPointer.Flags.VOICE_MESSAGE.getValue())) != 0, ((pointer.flags != null ? pointer.flags : 0) & FlagUtil.toBinaryFlag(AttachmentPointer.Flags.BORDERLESS.getValue())) != 0, diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/crypto/AttachmentDigest.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/crypto/AttachmentDigest.kt index cbe51e971b..debd703b0c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/crypto/AttachmentDigest.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/crypto/AttachmentDigest.kt @@ -5,4 +5,4 @@ package org.whispersystems.signalservice.internal.crypto -data class AttachmentDigest(val digest: ByteArray, val incrementalDigest: ByteArray?) +data class AttachmentDigest(val digest: ByteArray, val incrementalDigest: ByteArray?, val incrementalMacChunkSize: Int) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/AttachmentCipherOutputStreamFactory.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/AttachmentCipherOutputStreamFactory.kt index a86a91926a..e82d0aa775 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/AttachmentCipherOutputStreamFactory.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/AttachmentCipherOutputStreamFactory.kt @@ -27,13 +27,12 @@ class AttachmentCipherOutputStreamFactory(private val key: ByteArray, private va } @Throws(IOException::class) - fun createIncrementalFor(wrap: OutputStream?, length: Long, incrementalDigestOut: OutputStream?): DigestingOutputStream { + fun createIncrementalFor(wrap: OutputStream?, length: Long, sizeChoice: ChunkSizeChoice, incrementalDigestOut: OutputStream?): DigestingOutputStream { if (length > Int.MAX_VALUE) { throw IllegalArgumentException("Attachment length overflows int!") } val privateKey = key.sliceArray(AES_KEY_LENGTH until key.size) - val sizeChoice = ChunkSizeChoice.inferChunkSize(length.toInt()) val incrementalStream = IncrementalMacOutputStream(wrap, privateKey, sizeChoice, incrementalDigestOut) return createFor(incrementalStream) } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBody.kt index 4162f1d13f..a032b3cc7c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBody.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBody.kt @@ -4,6 +4,7 @@ import okhttp3.MediaType import okhttp3.RequestBody import okhttp3.internal.http.UnrepeatableRequestBody import okio.BufferedSink +import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice import org.signal.libsignal.protocol.logging.Log import org.whispersystems.signalservice.api.crypto.DigestingOutputStream import org.whispersystems.signalservice.api.crypto.SkippingOutputStream @@ -25,10 +26,7 @@ class DigestingRequestBody( private val cancelationSignal: CancelationSignal?, private val contentStart: Long ) : RequestBody(), UnrepeatableRequestBody { - lateinit var transmittedDigest: ByteArray - private set - var incrementalDigest: ByteArray? = null - private set + var attachmentDigest: AttachmentDigest? = null init { require(contentLength >= contentStart) @@ -44,8 +42,9 @@ class DigestingRequestBody( val digestStream = ByteArrayOutputStream() val inner = SkippingOutputStream(contentStart, sink.outputStream()) val isIncremental = outputStreamFactory is AttachmentCipherOutputStreamFactory + val sizeChoice: ChunkSizeChoice = ChunkSizeChoice.inferChunkSize(contentLength.toInt()) val outputStream: DigestingOutputStream = if (isIncremental) { - (outputStreamFactory as AttachmentCipherOutputStreamFactory).createIncrementalFor(inner, contentLength, digestStream) + (outputStreamFactory as AttachmentCipherOutputStreamFactory).createIncrementalFor(inner, contentLength, sizeChoice, digestStream) } else { outputStreamFactory.createFor(inner) } @@ -65,7 +64,7 @@ class DigestingRequestBody( outputStream.flush() - if (isIncremental) { + val incrementalDigest: ByteArray = if (isIncremental) { if (contentLength != total) { Log.w(TAG, "Content uploaded ${logMessage(total, contentLength)} bytes compared to expected!") } else { @@ -73,17 +72,18 @@ class DigestingRequestBody( } outputStream.close() digestStream.close() - incrementalDigest = digestStream.toByteArray() + digestStream.toByteArray() + } else { + ByteArray(0) } - transmittedDigest = outputStream.transmittedDigest + + attachmentDigest = AttachmentDigest(outputStream.transmittedDigest, incrementalDigest, sizeChoice.sizeInBytes) } override fun contentLength(): Long { return if (contentLength > 0) contentLength - contentStart else -1 } - fun getAttachmentDigest(): AttachmentDigest = AttachmentDigest(transmittedDigest, incrementalDigest) - private fun logMessage(actual: Long, expected: Long): String { val difference = actual - expected return if (difference > 0) { diff --git a/libsignal/service/src/main/protowire/SignalService.proto b/libsignal/service/src/main/protowire/SignalService.proto index f3386a72f5..de9b4ea8de 100644 --- a/libsignal/service/src/main/protowire/SignalService.proto +++ b/libsignal/service/src/main/protowire/SignalService.proto @@ -670,21 +670,22 @@ message AttachmentPointer { fixed64 cdnId = 1; string cdnKey = 15; } - optional string contentType = 2; - optional bytes key = 3; - optional uint32 size = 4; - optional bytes thumbnail = 5; - optional bytes digest = 6; - optional bytes incrementalDigest = 16; - optional string fileName = 7; - optional uint32 flags = 8; - optional uint32 width = 9; - optional uint32 height = 10; - optional string caption = 11; - optional string blurHash = 12; - optional uint64 uploadTimestamp = 13; - optional uint32 cdnNumber = 14; - // Next ID: 17 + optional string contentType = 2; + optional bytes key = 3; + optional uint32 size = 4; + optional bytes thumbnail = 5; + optional bytes digest = 6; + optional bytes incrementalDigest = 16; + optional uint32 incrementalMacChunkSize = 17; + optional string fileName = 7; + optional uint32 flags = 8; + optional uint32 width = 9; + optional uint32 height = 10; + optional string caption = 11; + optional string blurHash = 12; + optional uint64 uploadTimestamp = 13; + optional uint32 cdnNumber = 14; + // Next ID: 18 } message GroupContext { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherTest.java index a1b018d9c6..b4263ae349 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherTest.java @@ -3,6 +3,7 @@ package org.whispersystems.signalservice.api.crypto; import org.conscrypt.Conscrypt; import org.junit.Test; import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice; import org.signal.libsignal.protocol.incrementalmac.InvalidMacException; import org.signal.libsignal.protocol.kdf.HKDFv3; import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; @@ -33,12 +34,12 @@ public final class AttachmentCipherTest { @Test public void attachment_encryptDecrypt() throws IOException, InvalidMessageException { - byte[] key = Util.getSecretBytes(64); - byte[] plaintextInput = "Peter Parker".getBytes(); - EncryptResult encryptResult = encryptData(plaintextInput, key, false); - File cipherFile = writeToFile(encryptResult.ciphertext); - InputStream inputStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, encryptResult.incrementalDigest); - byte[] plaintextOutput = readInputStreamFully(inputStream); + byte[] key = Util.getSecretBytes(64); + byte[] plaintextInput = "Peter Parker".getBytes(); + EncryptResult encryptResult = encryptData(plaintextInput, key, false); + File cipherFile = writeToFile(encryptResult.ciphertext); + InputStream inputStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, encryptResult.incrementalDigest, encryptResult.chunkSizeChoice); + byte[] plaintextOutput = readInputStreamFully(inputStream); assertArrayEquals(plaintextInput, plaintextOutput); @@ -47,12 +48,12 @@ public final class AttachmentCipherTest { @Test public void attachment_encryptDecryptEmpty() throws IOException, InvalidMessageException { - byte[] key = Util.getSecretBytes(64); - byte[] plaintextInput = "".getBytes(); - EncryptResult encryptResult = encryptData(plaintextInput, key, true); - File cipherFile = writeToFile(encryptResult.ciphertext); - InputStream inputStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, encryptResult.incrementalDigest); - byte[] plaintextOutput = readInputStreamFully(inputStream); + byte[] key = Util.getSecretBytes(64); + byte[] plaintextInput = "".getBytes(); + EncryptResult encryptResult = encryptData(plaintextInput, key, true); + File cipherFile = writeToFile(encryptResult.ciphertext); + InputStream inputStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, encryptResult.incrementalDigest, encryptResult.chunkSizeChoice); + byte[] plaintextOutput = readInputStreamFully(inputStream); assertArrayEquals(plaintextInput, plaintextOutput); @@ -72,7 +73,7 @@ public final class AttachmentCipherTest { cipherFile = writeToFile(encryptResult.ciphertext); - AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, badKey, encryptResult.digest, null); + AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, badKey, encryptResult.digest, null, 0); } catch (InvalidMessageException e) { hitCorrectException = true; } finally { @@ -97,7 +98,7 @@ public final class AttachmentCipherTest { cipherFile = writeToFile(encryptResult.ciphertext); - AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, badDigest, null); + AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, badDigest, null, 0); } catch (InvalidMessageException e) { hitCorrectException = true; } finally { @@ -126,7 +127,7 @@ public final class AttachmentCipherTest { cipherFile = writeToFile(encryptResult.ciphertext); - InputStream decryptedStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, badDigest); + InputStream decryptedStream = AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, badDigest, encryptResult.chunkSizeChoice); byte[] plaintextOutput = readInputStreamFully(decryptedStream); fail(); } catch (InvalidMacException e) { @@ -153,11 +154,12 @@ public final class AttachmentCipherTest { plaintextInput[i] = (byte) 0x97; } - byte[] key = Util.getSecretBytes(64); - byte[] iv = Util.getSecretBytes(16); - ByteArrayInputStream inputStream = new ByteArrayInputStream(plaintextInput); - InputStream paddedInputStream = new PaddingInputStream(inputStream, length); - ByteArrayOutputStream destinationOutputStream = new ByteArrayOutputStream(); + byte[] key = Util.getSecretBytes(64); + byte[] iv = Util.getSecretBytes(16); + ByteArrayInputStream inputStream = new ByteArrayInputStream(plaintextInput); + InputStream paddedInputStream = new PaddingInputStream(inputStream, length); + ByteArrayOutputStream destinationOutputStream = new ByteArrayOutputStream(); + DigestingOutputStream encryptingOutputStream = new AttachmentCipherOutputStreamFactory(key, iv).createFor(destinationOutputStream); Util.copy(paddedInputStream, encryptingOutputStream); @@ -170,7 +172,7 @@ public final class AttachmentCipherTest { File cipherFile = writeToFile(encryptedData); - InputStream decryptedStream = AttachmentCipherInputStream.createForAttachment(cipherFile, length, key, digest, null); + InputStream decryptedStream = AttachmentCipherInputStream.createForAttachment(cipherFile, length, key, digest, null, 0); byte[] plaintextOutput = readInputStreamFully(decryptedStream); assertArrayEquals(plaintextInput, plaintextOutput); @@ -187,11 +189,12 @@ public final class AttachmentCipherTest { try { byte[] key = Util.getSecretBytes(64); byte[] plaintextInput = "Aunt May".getBytes(); + ChunkSizeChoice sizeChoice = ChunkSizeChoice.inferChunkSize(plaintextInput.length); EncryptResult encryptResult = encryptData(plaintextInput, key, true); cipherFile = writeToFile(encryptResult.ciphertext); - AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, null, encryptResult.incrementalDigest); + AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, null, encryptResult.incrementalDigest, encryptResult.chunkSizeChoice); } catch (InvalidMessageException e) { hitCorrectException = true; } finally { @@ -218,7 +221,7 @@ public final class AttachmentCipherTest { cipherFile = writeToFile(badMacCiphertext); - AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, null); + AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, null, encryptResult.chunkSizeChoice); fail(); } catch (InvalidMessageException e) { hitCorrectException = true; @@ -306,8 +309,9 @@ public final class AttachmentCipherTest { AttachmentCipherOutputStreamFactory factory = new AttachmentCipherOutputStreamFactory(keyMaterial, iv); DigestingOutputStream encryptStream; + final ChunkSizeChoice sizeChoice = ChunkSizeChoice.inferChunkSize(data.length); if (withIncremental) { - encryptStream = factory.createIncrementalFor(outputStream, data.length, incrementalDigestOut); + encryptStream = factory.createIncrementalFor(outputStream, data.length, sizeChoice, incrementalDigestOut); } else { encryptStream = factory.createFor(outputStream); } @@ -318,7 +322,7 @@ public final class AttachmentCipherTest { encryptStream.close(); incrementalDigestOut.close(); - return new EncryptResult(outputStream.toByteArray(), encryptStream.getTransmittedDigest(), incrementalDigestOut.toByteArray()); + return new EncryptResult(outputStream.toByteArray(), encryptStream.getTransmittedDigest(), incrementalDigestOut.toByteArray(), sizeChoice.getSizeInBytes()); } private static File writeToFile(byte[] data) throws IOException { @@ -343,11 +347,13 @@ public final class AttachmentCipherTest { final byte[] ciphertext; final byte[] digest; final byte[] incrementalDigest; + final int chunkSizeChoice; - private EncryptResult(byte[] ciphertext, byte[] digest, byte[] incrementalDigest) { - this.ciphertext = ciphertext; - this.digest = digest; - this.incrementalDigest = incrementalDigest; + private EncryptResult(byte[] ciphertext, byte[] digest, byte[] incrementalDigest, int chunkSizeChoice) { + this.ciphertext = ciphertext; + this.digest = digest; + this.incrementalDigest = incrementalDigest; + this.chunkSizeChoice = chunkSizeChoice; } } } diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java index edd5aa3064..0f3df75cbe 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java @@ -1,8 +1,8 @@ package org.whispersystems.signalservice.internal.push.http; import org.junit.Test; -import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream; import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil; +import org.whispersystems.signalservice.internal.crypto.AttachmentDigest; import org.whispersystems.signalservice.internal.util.Util; import java.io.ByteArrayInputStream; @@ -11,6 +11,8 @@ import okio.Buffer; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; public class DigestingRequestBodyTest { @@ -36,8 +38,14 @@ public class DigestingRequestBodyTest { fromMiddle.writeTo(buffer); } - assertArrayEquals(fromStart.getTransmittedDigest(), fromMiddle.getTransmittedDigest()); - assertArrayEquals(fromStart.getIncrementalDigest(), fromMiddle.getIncrementalDigest()); + final AttachmentDigest fullResult = fromStart.getAttachmentDigest(); + assertNotNull(fullResult); + + final AttachmentDigest partialResult = fromMiddle.getAttachmentDigest(); + assertNotNull(partialResult); + + assertArrayEquals(fullResult.getDigest(), partialResult.getDigest()); + assertArrayEquals(fullResult.getIncrementalDigest(), partialResult.getIncrementalDigest()); } @Test