Move Backups.proto to Wire.

main
Greyson Parrelli 2023-02-11 12:48:42 -05:00
rodzic 055ceba398
commit af6f16bdb6
6 zmienionych plików z 197 dodań i 169 usunięć

Wyświetl plik

@ -6,6 +6,8 @@ import org.signal.core.util.Conversions;
import org.signal.core.util.StreamUtil; import org.signal.core.util.StreamUtil;
import org.signal.libsignal.protocol.kdf.HKDF; import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.util.ByteUtil; import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.Header;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -45,21 +47,21 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
byte[] headerFrame = new byte[headerLength]; byte[] headerFrame = new byte[headerLength];
StreamUtil.readFully(in, headerFrame); StreamUtil.readFully(in, headerFrame);
BackupProtos.BackupFrame frame = BackupProtos.BackupFrame.parseFrom(headerFrame); BackupFrame frame = BackupFrame.ADAPTER.decode(headerFrame);
if (!frame.hasHeader()) { if (frame.header_ == null) {
throw new IOException("Backup stream does not start with header!"); throw new IOException("Backup stream does not start with header!");
} }
BackupProtos.Header header = frame.getHeader(); Header header = frame.header_;
this.iv = header.getIv().toByteArray(); this.iv = header.iv.toByteArray();
if (iv.length != 16) { if (iv.length != 16) {
throw new IOException("Invalid IV length!"); throw new IOException("Invalid IV length!");
} }
byte[] key = getBackupKey(passphrase, header.hasSalt() ? header.getSalt().toByteArray() : null); byte[] key = getBackupKey(passphrase, header.salt != null ? header.salt.toByteArray() : null);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64); byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32); byte[][] split = ByteUtil.split(derived, 32, 32);
@ -76,7 +78,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
} }
} }
BackupProtos.BackupFrame readFrame() throws IOException { BackupFrame readFrame() throws IOException {
return readFrame(in); return readFrame(in);
} }
@ -128,7 +130,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
} }
} }
private BackupProtos.BackupFrame readFrame(InputStream in) throws IOException { private BackupFrame readFrame(InputStream in) throws IOException {
try { try {
byte[] length = new byte[4]; byte[] length = new byte[4];
StreamUtil.readFully(in, length); StreamUtil.readFully(in, length);
@ -151,7 +153,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
byte[] plaintext = cipher.doFinal(frame, 0, frame.length - 10); byte[] plaintext = cipher.doFinal(frame, 0, frame.length - 10);
return BackupProtos.BackupFrame.parseFrom(plaintext); return BackupFrame.ADAPTER.decode(plaintext);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

Wyświetl plik

@ -2,7 +2,10 @@ package org.thoughtcrime.securesms.backup
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame import org.thoughtcrime.securesms.backup.proto.Attachment
import org.thoughtcrime.securesms.backup.proto.Avatar
import org.thoughtcrime.securesms.backup.proto.BackupFrame
import org.thoughtcrime.securesms.backup.proto.Sticker
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -23,11 +26,11 @@ object BackupVerifier {
var frame: BackupFrame = inputStream.readFrame() var frame: BackupFrame = inputStream.readFrame()
cipherStream.use { cipherStream.use {
while (!frame.end && !cancellationSignal.isCanceled) { while (frame.end != true && !cancellationSignal.isCanceled) {
val verified = when { val verified = when {
frame.hasAttachment() -> verifyAttachment(frame.attachment, inputStream) frame.attachment != null -> verifyAttachment(frame.attachment!!, inputStream)
frame.hasSticker() -> verifySticker(frame.sticker, inputStream) frame.sticker != null -> verifySticker(frame.sticker!!, inputStream)
frame.hasAvatar() -> verifyAvatar(frame.avatar, inputStream) frame.avatar != null -> verifyAvatar(frame.avatar!!, inputStream)
else -> true else -> true
} }
@ -48,9 +51,9 @@ object BackupVerifier {
return true return true
} }
private fun verifyAttachment(attachment: BackupProtos.Attachment, inputStream: BackupRecordInputStream): Boolean { private fun verifyAttachment(attachment: Attachment, inputStream: BackupRecordInputStream): Boolean {
try { try {
inputStream.readAttachmentTo(NullOutputStream, attachment.length) inputStream.readAttachmentTo(NullOutputStream, attachment.length ?: 0)
} catch (e: IOException) { } catch (e: IOException) {
Log.w(TAG, "Bad attachment id: ${attachment.attachmentId} len: ${attachment.length}", e) Log.w(TAG, "Bad attachment id: ${attachment.attachmentId} len: ${attachment.length}", e)
return false return false
@ -59,9 +62,9 @@ object BackupVerifier {
return true return true
} }
private fun verifySticker(sticker: BackupProtos.Sticker, inputStream: BackupRecordInputStream): Boolean { private fun verifySticker(sticker: Sticker, inputStream: BackupRecordInputStream): Boolean {
try { try {
inputStream.readAttachmentTo(NullOutputStream, sticker.length) inputStream.readAttachmentTo(NullOutputStream, sticker.length ?: 0)
} catch (e: IOException) { } catch (e: IOException) {
Log.w(TAG, "Bad sticker id: ${sticker.rowId} len: ${sticker.length}", e) Log.w(TAG, "Bad sticker id: ${sticker.rowId} len: ${sticker.length}", e)
return false return false
@ -69,9 +72,9 @@ object BackupVerifier {
return true return true
} }
private fun verifyAvatar(avatar: BackupProtos.Avatar, inputStream: BackupRecordInputStream): Boolean { private fun verifyAvatar(avatar: Avatar, inputStream: BackupRecordInputStream): Boolean {
try { try {
inputStream.readAttachmentTo(NullOutputStream, avatar.length) inputStream.readAttachmentTo(NullOutputStream, avatar.length ?: 0)
} catch (e: IOException) { } catch (e: IOException) {
Log.w(TAG, "Bad avatar id: ${avatar.recipientId} len: ${avatar.length}", e) Log.w(TAG, "Bad avatar id: ${avatar.recipientId} len: ${avatar.length}", e)
return false return false

Wyświetl plik

@ -12,7 +12,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import com.annimon.stream.function.Predicate; import com.annimon.stream.function.Predicate;
import com.google.protobuf.ByteString;
import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabase;
@ -26,6 +25,15 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDF; import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.util.ByteUtil; import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.proto.Header;
import org.thoughtcrime.securesms.backup.proto.KeyValue;
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
import org.thoughtcrime.securesms.backup.proto.Sticker;
import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
@ -80,6 +88,8 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import okio.ByteString;
public class FullBackupExporter extends FullBackupBase { public class FullBackupExporter extends FullBackupBase {
private static final String TAG = Log.tag(FullBackupExporter.class); private static final String TAG = Log.tag(FullBackupExporter.class);
@ -187,7 +197,7 @@ public class FullBackupExporter extends FullBackupBase {
stopwatch.split("table::" + table); stopwatch.split("table::" + table);
} }
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) { for (SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
throwIfCanceled(cancellationSignal); throwIfCanceled(cancellationSignal);
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount)); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(preference); outputStream.write(preference);
@ -287,7 +297,7 @@ public class FullBackupExporter extends FullBackupBase {
String statement = createStatementsByTable.get(table); String statement = createStatementsByTable.get(table);
if (statement != null) { if (statement != null) {
outputStream.write(BackupProtos.SqlStatement.newBuilder().setStatement(statement).build()); outputStream.write(new SqlStatement.Builder().statement(statement).build());
} else { } else {
throw new IOException("Failed to find a create statement for table: " + table); throw new IOException("Failed to find a create statement for table: " + table);
} }
@ -299,7 +309,7 @@ public class FullBackupExporter extends FullBackupBase {
String name = cursor.getString(1); String name = cursor.getString(1);
if (isTableAllowed(name)) { if (isTableAllowed(name)) {
outputStream.write(BackupProtos.SqlStatement.newBuilder().setStatement(sql).build()); outputStream.write(new SqlStatement.Builder().statement(sql).build());
} }
} }
} }
@ -393,8 +403,8 @@ public class FullBackupExporter extends FullBackupBase {
throwIfCanceled(cancellationSignal); throwIfCanceled(cancellationSignal);
if (predicate == null || predicate.test(cursor)) { if (predicate == null || predicate.test(cursor)) {
StringBuilder statement = new StringBuilder(template); StringBuilder statement = new StringBuilder(template);
BackupProtos.SqlStatement.Builder statementBuilder = BackupProtos.SqlStatement.newBuilder(); SqlStatement.Builder statementBuilder = new SqlStatement.Builder();
statement.append('('); statement.append('(');
@ -402,15 +412,15 @@ public class FullBackupExporter extends FullBackupBase {
statement.append('?'); statement.append('?');
if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) { if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) {
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setStringParamter(cursor.getString(i))); statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().stringParamter(cursor.getString(i)).build());
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_FLOAT) { } else if (cursor.getType(i) == Cursor.FIELD_TYPE_FLOAT) {
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setDoubleParameter(cursor.getDouble(i))); statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().doubleParameter(cursor.getDouble(i)).build());
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_INTEGER) { } else if (cursor.getType(i) == Cursor.FIELD_TYPE_INTEGER) {
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setIntegerParameter(cursor.getLong(i))); statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().integerParameter(cursor.getLong(i)).build());
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) { } else if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setBlobParameter(ByteString.copyFrom(cursor.getBlob(i)))); statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().blobParameter(new ByteString(cursor.getBlob(i))).build());
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_NULL) { } else if (cursor.getType(i) == Cursor.FIELD_TYPE_NULL) {
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setNullparameter(true)); statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().nullparameter(true).build());
} else { } else {
throw new AssertionError("unknown type?" + cursor.getType(i)); throw new AssertionError("unknown type?" + cursor.getType(i));
} }
@ -423,7 +433,7 @@ public class FullBackupExporter extends FullBackupBase {
statement.append(')'); statement.append(')');
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount)); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(statementBuilder.setStatement(statement.toString()).build()); outputStream.write(statementBuilder.statement(statement.toString()).build());
if (postProcess != null) { if (postProcess != null) {
count = postProcess.postProcess(cursor, count); count = postProcess.postProcess(cursor, count);
@ -539,29 +549,30 @@ public class FullBackupExporter extends FullBackupBase {
if (!dataSet.containsKey(key)) { if (!dataSet.containsKey(key)) {
continue; continue;
} }
BackupProtos.KeyValue.Builder builder = BackupProtos.KeyValue.newBuilder()
.setKey(key); KeyValue.Builder builder = new KeyValue.Builder()
.key(key);
Class<?> type = dataSet.getType(key); Class<?> type = dataSet.getType(key);
if (type == byte[].class) { if (type == byte[].class) {
byte[] data = dataSet.getBlob(key, null); byte[] data = dataSet.getBlob(key, null);
if (data != null) { if (data != null) {
builder.setBlobValue(ByteString.copyFrom(dataSet.getBlob(key, null))); builder.blobValue(new ByteString(dataSet.getBlob(key, null)));
} else { } else {
Log.w(TAG, "Skipping storing null blob for key: " + key); Log.w(TAG, "Skipping storing null blob for key: " + key);
} }
} else if (type == Boolean.class) { } else if (type == Boolean.class) {
builder.setBooleanValue(dataSet.getBoolean(key, false)); builder.booleanValue(dataSet.getBoolean(key, false));
} else if (type == Float.class) { } else if (type == Float.class) {
builder.setFloatValue(dataSet.getFloat(key, 0)); builder.floatValue(dataSet.getFloat(key, 0));
} else if (type == Integer.class) { } else if (type == Integer.class) {
builder.setIntegerValue(dataSet.getInteger(key, 0)); builder.integerValue(dataSet.getInteger(key, 0));
} else if (type == Long.class) { } else if (type == Long.class) {
builder.setLongValue(dataSet.getLong(key, 0)); builder.longValue(dataSet.getLong(key, 0));
} else if (type == String.class) { } else if (type == String.class) {
String data = dataSet.getString(key, null); String data = dataSet.getString(key, null);
if (data != null) { if (data != null) {
builder.setStringValue(dataSet.getString(key, null)); builder.stringValue(dataSet.getString(key, null));
} else { } else {
Log.w(TAG, "Skipping storing null string for key: " + key); Log.w(TAG, "Skipping storing null string for key: " + key);
} }
@ -631,10 +642,12 @@ public class FullBackupExporter extends FullBackupBase {
mac.init(new SecretKeySpec(macKey, "HmacSHA256")); mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
byte[] header = BackupProtos.BackupFrame.newBuilder().setHeader(BackupProtos.Header.newBuilder() byte[] header = new BackupFrame.Builder().header_(new Header.Builder()
.setIv(ByteString.copyFrom(iv)) .iv(new okio.ByteString(iv))
.setSalt(ByteString.copyFrom(salt))) .salt(new okio.ByteString(salt))
.build().toByteArray(); .build())
.build()
.encode();
outputStream.write(Conversions.intToByteArray(header.length)); outputStream.write(Conversions.intToByteArray(header.length));
outputStream.write(header); outputStream.write(header);
@ -643,26 +656,26 @@ public class FullBackupExporter extends FullBackupBase {
} }
} }
public void write(BackupProtos.SharedPreference preference) throws IOException { public void write(SharedPreference preference) throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build()); write(outputStream, new BackupFrame.Builder().preference(preference).build());
} }
public void write(BackupProtos.KeyValue keyValue) throws IOException { public void write(KeyValue keyValue) throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder().setKeyValue(keyValue).build()); write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build());
} }
public void write(BackupProtos.SqlStatement statement) throws IOException { public void write(SqlStatement statement) throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build()); write(outputStream, new BackupFrame.Builder().statement(statement).build());
} }
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException { public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
try { try {
write(outputStream, BackupProtos.BackupFrame.newBuilder() write(outputStream, new BackupFrame.Builder()
.setAvatar(BackupProtos.Avatar.newBuilder() .avatar(new Avatar.Builder()
.setRecipientId(avatarName) .recipientId(avatarName)
.setLength(Util.toIntExact(size)) .length(Util.toIntExact(size))
.build()) .build())
.build()); .build());
} catch (ArithmeticException e) { } catch (ArithmeticException e) {
Log.w(TAG, "Unable to write avatar to backup", e); Log.w(TAG, "Unable to write avatar to backup", e);
throw new InvalidBackupStreamException(); throw new InvalidBackupStreamException();
@ -675,13 +688,13 @@ public class FullBackupExporter extends FullBackupBase {
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException { public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
try { try {
write(outputStream, BackupProtos.BackupFrame.newBuilder() write(outputStream, new BackupFrame.Builder()
.setAttachment(BackupProtos.Attachment.newBuilder() .attachment(new Attachment.Builder()
.setRowId(attachmentId.getRowId()) .rowId(attachmentId.getRowId())
.setAttachmentId(attachmentId.getUniqueId()) .attachmentId(attachmentId.getUniqueId())
.setLength(Util.toIntExact(size)) .length(Util.toIntExact(size))
.build()) .build())
.build()); .build());
} catch (ArithmeticException e) { } catch (ArithmeticException e) {
Log.w(TAG, "Unable to write " + attachmentId + " to backup", e); Log.w(TAG, "Unable to write " + attachmentId + " to backup", e);
throw new InvalidBackupStreamException(); throw new InvalidBackupStreamException();
@ -694,12 +707,12 @@ public class FullBackupExporter extends FullBackupBase {
public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException { public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException {
try { try {
write(outputStream, BackupProtos.BackupFrame.newBuilder() write(outputStream, new BackupFrame.Builder()
.setSticker(BackupProtos.Sticker.newBuilder() .sticker(new Sticker.Builder()
.setRowId(rowId) .rowId(rowId)
.setLength(Util.toIntExact(size)) .length(Util.toIntExact(size))
.build()) .build())
.build()); .build());
} catch (ArithmeticException e) { } catch (ArithmeticException e) {
Log.w(TAG, "Unable to write sticker to backup", e); Log.w(TAG, "Unable to write sticker to backup", e);
throw new InvalidBackupStreamException(); throw new InvalidBackupStreamException();
@ -711,13 +724,13 @@ public class FullBackupExporter extends FullBackupBase {
} }
void writeDatabaseVersion(int version) throws IOException { void writeDatabaseVersion(int version) throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder() write(outputStream, new BackupFrame.Builder()
.setVersion(BackupProtos.DatabaseVersion.newBuilder().setVersion(version)) .version(new DatabaseVersion.Builder().version(version).build())
.build()); .build());
} }
void writeEnd() throws IOException { void writeEnd() throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder().setEnd(true).build()); write(outputStream, new BackupFrame.Builder().end(true).build());
} }
/** /**
@ -758,12 +771,12 @@ public class FullBackupExporter extends FullBackupBase {
} }
} }
private void write(@NonNull OutputStream out, @NonNull BackupProtos.BackupFrame frame) throws IOException { private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException {
try { try {
Conversions.intToByteArray(iv, 0, counter++); Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv)); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] frameCiphertext = cipher.doFinal(frame.toByteArray()); byte[] frameCiphertext = cipher.doFinal(frame.encode());
byte[] frameMac = mac.doFinal(frameCiphertext); byte[] frameMac = mac.doFinal(frameCiphertext);
byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10); byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10);

Wyświetl plik

@ -17,12 +17,14 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.SqlUtil; import org.signal.core.util.SqlUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.backup.BackupProtos.Attachment; import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame; import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion; import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference; import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement; import org.thoughtcrime.securesms.backup.proto.KeyValue;
import org.thoughtcrime.securesms.backup.BackupProtos.Sticker; import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
import org.thoughtcrime.securesms.backup.proto.Sticker;
import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.AttachmentTable;
@ -87,17 +89,17 @@ public class FullBackupImporter extends FullBackupBase {
BackupFrame frame; BackupFrame frame;
while (!(frame = inputStream.readFrame()).getEnd()) { while ((frame = inputStream.readFrame()).end != Boolean.TRUE) {
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0)); if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0));
count++; count++;
if (frame.hasVersion()) processVersion(db, frame.getVersion()); if (frame.version != null) processVersion(db, frame.version);
else if (frame.hasStatement()) tryProcessStatement(db, frame.getStatement()); else if (frame.statement != null) tryProcessStatement(db, frame.statement);
else if (frame.hasPreference()) processPreference(context, frame.getPreference()); else if (frame.preference != null) processPreference(context, frame.preference);
else if (frame.hasAttachment()) processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream); else if (frame.attachment != null) processAttachment(context, attachmentSecret, db, frame.attachment, inputStream);
else if (frame.hasSticker()) processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream); else if (frame.sticker != null) processSticker(context, attachmentSecret, db, frame.sticker, inputStream);
else if (frame.hasAvatar()) processAvatar(context, db, frame.getAvatar(), inputStream); else if (frame.avatar != null) processAvatar(context, db, frame.avatar, inputStream);
else if (frame.hasKeyValue()) processKeyValue(frame.getKeyValue()); else if (frame.keyValue != null) processKeyValue(frame.keyValue);
else count--; else count--;
} }
@ -120,11 +122,11 @@ public class FullBackupImporter extends FullBackupBase {
} }
private static void processVersion(@NonNull SQLiteDatabase db, DatabaseVersion version) throws IOException { private static void processVersion(@NonNull SQLiteDatabase db, DatabaseVersion version) throws IOException {
if (version.getVersion() > db.getVersion()) { if (version.version == null || version.version > db.getVersion()) {
throw new DatabaseDowngradeException(db.getVersion(), version.getVersion()); throw new DatabaseDowngradeException(db.getVersion(), version.version != null ? version.version : -1);
} }
db.setVersion(version.getVersion()); db.setVersion(version.version);
} }
private static void tryProcessStatement(@NonNull SQLiteDatabase db, SqlStatement statement) { private static void tryProcessStatement(@NonNull SQLiteDatabase db, SqlStatement statement) {
@ -132,9 +134,9 @@ public class FullBackupImporter extends FullBackupBase {
processStatement(db, statement); processStatement(db, statement);
} catch (SQLiteConstraintException e) { } catch (SQLiteConstraintException e) {
String tableName = "?"; String tableName = "?";
String statementString = statement.getStatement(); String statementString = statement.statement;
if (statementString.startsWith("INSERT INTO ")) { if (statementString != null && statementString.startsWith("INSERT INTO ")) {
int nameStart = "INSERT INTO ".length(); int nameStart = "INSERT INTO ".length();
int nameEnd = statementString.indexOf(" ", "INSERT INTO ".length()); int nameEnd = statementString.indexOf(" ", "INSERT INTO ".length());
@ -153,27 +155,32 @@ public class FullBackupImporter extends FullBackupBase {
} }
private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) { private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) {
boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchTable.FTS_TABLE_NAME + "_"); if (statement.statement == null) {
boolean isForEmojiSecretTable = statement.getStatement().contains(EmojiSearchTable.TABLE_NAME + "_"); Log.w(TAG, "Null statement!");
boolean isForSqliteSecretTable = statement.getStatement().toLowerCase().startsWith("create table sqlite_"); return;
}
boolean isForMmsFtsSecretTable = statement.statement.contains(SearchTable.FTS_TABLE_NAME + "_");
boolean isForEmojiSecretTable = statement.statement.contains(EmojiSearchTable.TABLE_NAME + "_");
boolean isForSqliteSecretTable = statement.statement.toLowerCase().startsWith("create table sqlite_");
if (isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) { if (isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) {
Log.i(TAG, "Ignoring import for statement: " + statement.getStatement()); Log.i(TAG, "Ignoring import for statement: " + statement.statement);
return; return;
} }
List<Object> parameters = new LinkedList<>(); List<Object> parameters = new LinkedList<>();
for (SqlStatement.SqlParameter parameter : statement.getParametersList()) { for (SqlStatement.SqlParameter parameter : statement.parameters) {
if (parameter.hasStringParamter()) parameters.add(parameter.getStringParamter()); if (parameter.stringParamter != null) parameters.add(parameter.stringParamter);
else if (parameter.hasDoubleParameter()) parameters.add(parameter.getDoubleParameter()); else if (parameter.doubleParameter != null) parameters.add(parameter.doubleParameter);
else if (parameter.hasIntegerParameter()) parameters.add(parameter.getIntegerParameter()); else if (parameter.integerParameter != null) parameters.add(parameter.integerParameter);
else if (parameter.hasBlobParameter()) parameters.add(parameter.getBlobParameter().toByteArray()); else if (parameter.blobParameter != null) parameters.add(parameter.blobParameter.toByteArray());
else if (parameter.hasNullparameter()) parameters.add(null); else if (parameter.nullparameter != null) parameters.add(null);
} }
if (parameters.size() > 0) db.execSQL(statement.getStatement(), parameters.toArray()); if (parameters.size() > 0) db.execSQL(statement.statement, parameters.toArray());
else db.execSQL(statement.getStatement()); else db.execSQL(statement.statement);
} }
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream) private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
@ -185,12 +192,12 @@ public class FullBackupImporter extends FullBackupBase {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
try { try {
inputStream.readAttachmentTo(output.second, attachment.getLength()); inputStream.readAttachmentTo(output.second, attachment.length);
contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath()); contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath());
contentValues.put(AttachmentTable.DATA_RANDOM, output.first); contentValues.put(AttachmentTable.DATA_RANDOM, output.first);
} catch (BackupRecordInputStream.BadMacException e) { } catch (BackupRecordInputStream.BadMacException e) {
Log.w(TAG, "Bad MAC for attachment " + attachment.getAttachmentId() + "! Can't restore it.", e); Log.w(TAG, "Bad MAC for attachment " + attachment.attachmentId + "! Can't restore it.", e);
dataFile.delete(); dataFile.delete();
contentValues.put(AttachmentTable.DATA, (String) null); contentValues.put(AttachmentTable.DATA, (String) null);
contentValues.put(AttachmentTable.DATA_RANDOM, (String) null); contentValues.put(AttachmentTable.DATA_RANDOM, (String) null);
@ -198,7 +205,7 @@ public class FullBackupImporter extends FullBackupBase {
db.update(AttachmentTable.TABLE_NAME, contentValues, db.update(AttachmentTable.TABLE_NAME, contentValues,
AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?", AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?",
new String[] {String.valueOf(attachment.getRowId()), String.valueOf(attachment.getAttachmentId())}); new String[] {String.valueOf(attachment.rowId), String.valueOf(attachment.attachmentId)});
} }
private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream) private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream)
@ -209,52 +216,57 @@ public class FullBackupImporter extends FullBackupBase {
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false); Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
inputStream.readAttachmentTo(output.second, sticker.getLength()); inputStream.readAttachmentTo(output.second, sticker.length);
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(StickerTable.FILE_PATH, dataFile.getAbsolutePath()); contentValues.put(StickerTable.FILE_PATH, dataFile.getAbsolutePath());
contentValues.put(StickerTable.FILE_LENGTH, sticker.getLength()); contentValues.put(StickerTable.FILE_LENGTH, sticker.length);
contentValues.put(StickerTable.FILE_RANDOM, output.first); contentValues.put(StickerTable.FILE_RANDOM, output.first);
db.update(StickerTable.TABLE_NAME, contentValues, db.update(StickerTable.TABLE_NAME, contentValues,
StickerTable._ID + " = ?", StickerTable._ID + " = ?",
new String[] {String.valueOf(sticker.getRowId())}); new String[] {String.valueOf(sticker.rowId)});
} }
private static void processAvatar(@NonNull Context context, @NonNull SQLiteDatabase db, @NonNull BackupProtos.Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException { private static void processAvatar(@NonNull Context context, @NonNull SQLiteDatabase db, @NonNull Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException {
if (avatar.hasRecipientId()) { if (avatar.recipientId != null) {
RecipientId recipientId = RecipientId.from(avatar.getRecipientId()); RecipientId recipientId = RecipientId.from(avatar.recipientId);
inputStream.readAttachmentTo(AvatarHelper.getOutputStream(context, recipientId, false), avatar.getLength()); inputStream.readAttachmentTo(AvatarHelper.getOutputStream(context, recipientId, false), avatar.length);
} else { } else {
if (avatar.hasName() && SqlUtil.tableExists(db, "recipient_preferences")) { if (avatar.name != null && SqlUtil.tableExists(db, "recipient_preferences")) {
Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar (legacy) so it can be fetched later."); Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar (legacy) so it can be fetched later.");
db.execSQL("UPDATE recipient_preferences SET signal_profile_avatar = NULL WHERE recipient_ids = ?", new String[] { avatar.getName() }); db.execSQL("UPDATE recipient_preferences SET signal_profile_avatar = NULL WHERE recipient_ids = ?", new String[] { avatar.name });
} else if (avatar.hasName() && SqlUtil.tableExists(db, "recipient")) { } else if (avatar.name != null && SqlUtil.tableExists(db, "recipient")) {
Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar so it can be fetched later."); Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar so it can be fetched later.");
db.execSQL("UPDATE recipient SET signal_profile_avatar = NULL WHERE phone = ?", new String[] { avatar.getName() }); db.execSQL("UPDATE recipient SET signal_profile_avatar = NULL WHERE phone = ?", new String[] { avatar.name });
} else { } else {
Log.w(TAG, "Avatar is missing a recipientId. Skipping avatar restore."); Log.w(TAG, "Avatar is missing a recipientId. Skipping avatar restore.");
} }
inputStream.readAttachmentTo(new ByteArrayOutputStream(), avatar.getLength()); inputStream.readAttachmentTo(new ByteArrayOutputStream(), avatar.length);
} }
} }
private static void processKeyValue(BackupProtos.KeyValue keyValue) { private static void processKeyValue(KeyValue keyValue) {
KeyValueDataSet dataSet = new KeyValueDataSet(); KeyValueDataSet dataSet = new KeyValueDataSet();
if (keyValue.hasBlobValue()) { if (keyValue.key == null) {
dataSet.putBlob(keyValue.getKey(), keyValue.getBlobValue().toByteArray()); Log.w(TAG, "Null preference key!");
} else if (keyValue.hasBooleanValue()) { return;
dataSet.putBoolean(keyValue.getKey(), keyValue.getBooleanValue()); }
} else if (keyValue.hasFloatValue()) {
dataSet.putFloat(keyValue.getKey(), keyValue.getFloatValue()); if (keyValue.blobValue != null) {
} else if (keyValue.hasIntegerValue()) { dataSet.putBlob(keyValue.key, keyValue.blobValue.toByteArray());
dataSet.putInteger(keyValue.getKey(), keyValue.getIntegerValue()); } else if (keyValue.booleanValue != null) {
} else if (keyValue.hasLongValue()) { dataSet.putBoolean(keyValue.key, keyValue.booleanValue);
dataSet.putLong(keyValue.getKey(), keyValue.getLongValue()); } else if (keyValue.floatValue != null) {
} else if (keyValue.hasStringValue()) { dataSet.putFloat(keyValue.key, keyValue.floatValue);
dataSet.putString(keyValue.getKey(), keyValue.getStringValue()); } else if (keyValue.integerValue != null) {
dataSet.putInteger(keyValue.key, keyValue.integerValue);
} else if (keyValue.longValue != null) {
dataSet.putLong(keyValue.key, keyValue.longValue);
} else if (keyValue.stringValue != null) {
dataSet.putString(keyValue.key, keyValue.stringValue);
} else { } else {
Log.i(TAG, "Unknown KeyValue backup value, skipping"); Log.i(TAG, "Unknown KeyValue backup value, skipping");
return; return;
@ -265,25 +277,25 @@ public class FullBackupImporter extends FullBackupBase {
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
private static void processPreference(@NonNull Context context, SharedPreference preference) { private static void processPreference(@NonNull Context context, SharedPreference preference) {
SharedPreferences preferences = context.getSharedPreferences(preference.getFile(), 0); SharedPreferences preferences = context.getSharedPreferences(preference.file_, 0);
// Identity keys were moved from shared prefs into SignalStore. Need to handle importing backups made before the migration. // Identity keys were moved from shared prefs into SignalStore. Need to handle importing backups made before the migration.
if ("SecureSMS-Preferences".equals(preference.getFile())) { if ("SecureSMS-Preferences".equals(preference.file_)) {
if ("pref_identity_public_v3".equals(preference.getKey()) && preference.hasValue()) { if ("pref_identity_public_v3".equals(preference.key) && preference.value_ != null) {
SignalStore.account().restoreLegacyIdentityPublicKeyFromBackup(preference.getValue()); SignalStore.account().restoreLegacyIdentityPublicKeyFromBackup(preference.value_);
} else if ("pref_identity_private_v3".equals(preference.getKey()) && preference.hasValue()) { } else if ("pref_identity_private_v3".equals(preference.key) && preference.value_ != null) {
SignalStore.account().restoreLegacyIdentityPrivateKeyFromBackup(preference.getValue()); SignalStore.account().restoreLegacyIdentityPrivateKeyFromBackup(preference.value_);
} }
return; return;
} }
if (preference.hasValue()) { if (preference.value_ != null) {
preferences.edit().putString(preference.getKey(), preference.getValue()).commit(); preferences.edit().putString(preference.key, preference.value_).commit();
} else if (preference.hasBooleanValue()) { } else if (preference.booleanValue != null) {
preferences.edit().putBoolean(preference.getKey(), preference.getBooleanValue()).commit(); preferences.edit().putBoolean(preference.key, preference.booleanValue).commit();
} else if (preference.hasIsStringSetValue() && preference.getIsStringSetValue()) { } else if (preference.isStringSetValue == Boolean.TRUE) {
preferences.edit().putStringSet(preference.getKey(), new HashSet<>(preference.getStringSetValueList())).commit(); preferences.edit().putStringSet(preference.key, new HashSet<>(preference.stringSetValue)).commit();
} }
} }

Wyświetl plik

@ -20,11 +20,10 @@ import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.BackupProtos; import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SettingsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
@ -253,39 +252,39 @@ public class TextSecurePreferences {
return count; return count;
} }
public static List<BackupProtos.SharedPreference> getPreferencesToSaveToBackup(@NonNull Context context) { public static List<SharedPreference> getPreferencesToSaveToBackup(@NonNull Context context) {
SharedPreferences preferences = getSharedPreferences(context); SharedPreferences preferences = getSharedPreferences(context);
List<BackupProtos.SharedPreference> backupProtos = new ArrayList<>(); List<SharedPreference> backupProtos = new ArrayList<>();
String defaultFile = context.getPackageName() + "_preferences"; String defaultFile = context.getPackageName() + "_preferences";
for (String booleanPreference : booleanPreferencesToBackup) { for (String booleanPreference : booleanPreferencesToBackup) {
if (preferences.contains(booleanPreference)) { if (preferences.contains(booleanPreference)) {
backupProtos.add(BackupProtos.SharedPreference.newBuilder() backupProtos.add(new SharedPreference.Builder()
.setFile(defaultFile) .file_(defaultFile)
.setKey(booleanPreference) .key(booleanPreference)
.setBooleanValue(preferences.getBoolean(booleanPreference, false)) .booleanValue(preferences.getBoolean(booleanPreference, false))
.build()); .build());
} }
} }
for (String stringPreference : stringPreferencesToBackup) { for (String stringPreference : stringPreferencesToBackup) {
if (preferences.contains(stringPreference)) { if (preferences.contains(stringPreference)) {
backupProtos.add(BackupProtos.SharedPreference.newBuilder() backupProtos.add(new SharedPreference.Builder()
.setFile(defaultFile) .file_(defaultFile)
.setKey(stringPreference) .key(stringPreference)
.setValue(preferences.getString(stringPreference, null)) .value_(preferences.getString(stringPreference, null))
.build()); .build());
} }
} }
for (String stringSetPreference : stringSetPreferencesToBackup) { for (String stringSetPreference : stringSetPreferencesToBackup) {
if (preferences.contains(stringSetPreference)) { if (preferences.contains(stringSetPreference)) {
backupProtos.add(BackupProtos.SharedPreference.newBuilder() backupProtos.add(new SharedPreference.Builder()
.setFile(defaultFile) .file_(defaultFile)
.setKey(stringSetPreference) .key(stringSetPreference)
.setIsStringSetValue(true) .isStringSetValue(true)
.addAllStringSetValue(preferences.getStringSet(stringSetPreference, Collections.emptySet())) .stringSetValue(new ArrayList<>(preferences.getStringSet(stringSetPreference, Collections.emptySet())))
.build()); .build());
} }
} }

Wyświetl plik

@ -8,8 +8,7 @@ syntax = "proto2";
package signal; package signal;
option java_package = "org.thoughtcrime.securesms.backup"; option java_package = "org.thoughtcrime.securesms.backup.proto";
option java_outer_classname = "BackupProtos";
message SqlStatement { message SqlStatement {
message SqlParameter { message SqlParameter {