mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Support PNI prekeys.
This commit is contained in:
@@ -176,7 +176,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||||
.addNonBlocking(this::initializeFcmCheck)
|
.addNonBlocking(this::initializeFcmCheck)
|
||||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
.addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded)
|
||||||
.addNonBlocking(this::initializePeriodicTasks)
|
.addNonBlocking(this::initializePeriodicTasks)
|
||||||
.addNonBlocking(this::initializeCircumvention)
|
.addNonBlocking(this::initializeCircumvention)
|
||||||
.addNonBlocking(this::initializePendingMessages)
|
.addNonBlocking(this::initializePendingMessages)
|
||||||
@@ -352,12 +352,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeSignedPreKeyCheck() {
|
|
||||||
if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) {
|
|
||||||
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeExpiringMessageManager() {
|
private void initializeExpiringMessageManager() {
|
||||||
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||||
SignalStore.account().generateAciIdentityKey();
|
SignalStore.account().generateAciIdentityKey();
|
||||||
|
SignalStore.account().generatePniIdentityKey();
|
||||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -17,64 +17,66 @@
|
|||||||
|
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
|
||||||
import org.whispersystems.libsignal.util.Medium;
|
import org.whispersystems.libsignal.util.Medium;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class PreKeyUtil {
|
public class PreKeyUtil {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(PreKeyUtil.class);
|
private static final String TAG = Log.tag(PreKeyUtil.class);
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 100;
|
private static final int BATCH_SIZE = 100;
|
||||||
|
private static final long ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30);
|
||||||
|
|
||||||
|
public synchronized static @NonNull List<PreKeyRecord> generateAndStoreOneTimePreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
|
||||||
|
Log.i(TAG, "Generating one-time prekeys...");
|
||||||
|
|
||||||
public synchronized static List<PreKeyRecord> generatePreKeys(Context context) {
|
|
||||||
PreKeyStore preKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
|
||||||
List<PreKeyRecord> records = new LinkedList<>();
|
List<PreKeyRecord> records = new LinkedList<>();
|
||||||
int preKeyIdOffset = TextSecurePreferences.getNextPreKeyId(context);
|
int preKeyIdOffset = metadataStore.getNextOneTimePreKeyId();
|
||||||
|
|
||||||
for (int i=0;i<BATCH_SIZE;i++) {
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||||
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
|
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
|
||||||
|
|
||||||
preKeyStore.storePreKey(preKeyId, record);
|
protocolStore.storePreKey(preKeyId, record);
|
||||||
records.add(record);
|
records.add(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSecurePreferences.setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
metadataStore.setNextOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active) {
|
public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) {
|
||||||
|
Log.i(TAG, "Generating signed prekeys...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SignedPreKeyStore signedPreKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
|
||||||
int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context);
|
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
byte[] signature = Curve.calculateSignature(protocolStore.getIdentityKeyPair().getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
|
||||||
|
|
||||||
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
protocolStore.storeSignedPreKey(signedPreKeyId, record);
|
||||||
TextSecurePreferences.setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
|
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||||
|
|
||||||
if (active) {
|
if (setAsActive) {
|
||||||
TextSecurePreferences.setActiveSignedPreKeyId(context, signedPreKeyId);
|
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
@@ -83,12 +85,33 @@ public class PreKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
|
/**
|
||||||
TextSecurePreferences.setActiveSignedPreKeyId(context, id);
|
* Finds all of the signed prekeys that are older than the archive age, and archive all but the youngest of those.
|
||||||
}
|
*/
|
||||||
|
public synchronized static void cleanSignedPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
|
||||||
|
Log.i(TAG, "Cleaning signed prekeys...");
|
||||||
|
|
||||||
public static synchronized int getActiveSignedPreKeyId(Context context) {
|
int activeSignedPreKeyId = metadataStore.getActiveSignedPreKeyId();
|
||||||
return TextSecurePreferences.getActiveSignedPreKeyId(context);
|
if (activeSignedPreKeyId < 0) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
SignedPreKeyRecord currentRecord = protocolStore.loadSignedPreKey(activeSignedPreKeyId);
|
||||||
|
List<SignedPreKeyRecord> allRecords = protocolStore.loadSignedPreKeys();
|
||||||
|
|
||||||
|
allRecords.stream()
|
||||||
|
.filter(r -> r.getId() != currentRecord.getId())
|
||||||
|
.filter(r -> (now - r.getTimestamp()) > ARCHIVE_AGE)
|
||||||
|
.sorted(Comparator.comparingLong(SignedPreKeyRecord::getTimestamp).reversed())
|
||||||
|
.skip(1)
|
||||||
|
.forEach(record -> {
|
||||||
|
Log.i(TAG, "Removing signed prekey record: " + record.getId() + " with timestamp: " + record.getTimestamp());
|
||||||
|
protocolStore.removeSignedPreKey(record.getId());
|
||||||
|
});
|
||||||
|
} catch (InvalidKeyIdException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.thoughtcrime.securesms.crypto.storage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows storing various metadata around prekey state.
|
||||||
|
*/
|
||||||
|
interface PreKeyMetadataStore {
|
||||||
|
var nextSignedPreKeyId: Int
|
||||||
|
var activeSignedPreKeyId: Int
|
||||||
|
var isSignedPreKeyRegistered: Boolean
|
||||||
|
var signedPreKeyFailureCount: Int
|
||||||
|
var nextOneTimePreKeyId: Int
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.thoughtcrime.securesms.crypto.storage;
|
package org.thoughtcrime.securesms.crypto.storage;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
@@ -11,6 +9,7 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
|
|||||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -22,16 +21,16 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||||||
private static final Object LOCK = new Object();
|
private static final Object LOCK = new Object();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Context context;
|
private final AccountIdentifier accountId;
|
||||||
|
|
||||||
public TextSecurePreKeyStore(@NonNull Context context) {
|
public TextSecurePreKeyStore(@NonNull AccountIdentifier accountId) {
|
||||||
this.context = context;
|
this.accountId = accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
PreKeyRecord preKeyRecord = SignalDatabase.preKeys().getPreKey(preKeyId);
|
PreKeyRecord preKeyRecord = SignalDatabase.oneTimePreKeys().get(accountId, preKeyId);
|
||||||
|
|
||||||
if (preKeyRecord == null) throw new InvalidKeyIdException("No such key: " + preKeyId);
|
if (preKeyRecord == null) throw new InvalidKeyIdException("No such key: " + preKeyId);
|
||||||
else return preKeyRecord;
|
else return preKeyRecord;
|
||||||
@@ -41,7 +40,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||||||
@Override
|
@Override
|
||||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
SignedPreKeyRecord signedPreKeyRecord = SignalDatabase.signedPreKeys().getSignedPreKey(signedPreKeyId);
|
SignedPreKeyRecord signedPreKeyRecord = SignalDatabase.signedPreKeys().get(accountId, signedPreKeyId);
|
||||||
|
|
||||||
if (signedPreKeyRecord == null) throw new InvalidKeyIdException("No such signed prekey: " + signedPreKeyId);
|
if (signedPreKeyRecord == null) throw new InvalidKeyIdException("No such signed prekey: " + signedPreKeyId);
|
||||||
else return signedPreKeyRecord;
|
else return signedPreKeyRecord;
|
||||||
@@ -51,41 +50,41 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||||||
@Override
|
@Override
|
||||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
return SignalDatabase.signedPreKeys().getAllSignedPreKeys();
|
return SignalDatabase.signedPreKeys().getAll(accountId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
SignalDatabase.preKeys().insertPreKey(preKeyId, record);
|
SignalDatabase.oneTimePreKeys().insert(accountId, preKeyId, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
SignalDatabase.signedPreKeys().insertSignedPreKey(signedPreKeyId, record);
|
SignalDatabase.signedPreKeys().insert(accountId, signedPreKeyId, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsPreKey(int preKeyId) {
|
public boolean containsPreKey(int preKeyId) {
|
||||||
return SignalDatabase.preKeys().getPreKey(preKeyId) != null;
|
return SignalDatabase.oneTimePreKeys().get(accountId, preKeyId) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||||
return SignalDatabase.signedPreKeys().getSignedPreKey(signedPreKeyId) != null;
|
return SignalDatabase.signedPreKeys().get(accountId, signedPreKeyId) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePreKey(int preKeyId) {
|
public void removePreKey(int preKeyId) {
|
||||||
SignalDatabase.preKeys().removePreKey(preKeyId);
|
SignalDatabase.oneTimePreKeys().delete(accountId, preKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeSignedPreKey(int signedPreKeyId) {
|
public void removeSignedPreKey(int signedPreKeyId) {
|
||||||
SignalDatabase.signedPreKeys().removeSignedPreKey(signedPreKeyId);
|
SignalDatabase.signedPreKeys().delete(accountId, signedPreKeyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database;
|
|||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -17,6 +18,7 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.KeyValuePersistentStorage;
|
import org.thoughtcrime.securesms.keyvalue.KeyValuePersistentStorage;
|
||||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -60,6 +62,11 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean exists(Context context) {
|
||||||
|
return context.getDatabasePath(DATABASE_NAME).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
private KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||||
super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0,new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook());
|
super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0,new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook());
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class OneTimePreKeyDatabase extends Database {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(OneTimePreKeyDatabase.class);
|
|
||||||
|
|
||||||
public static final String TABLE_NAME = "one_time_prekeys";
|
|
||||||
private static final String ID = "_id";
|
|
||||||
public static final String KEY_ID = "key_id";
|
|
||||||
public static final String PUBLIC_KEY = "public_key";
|
|
||||||
public static final String PRIVATE_KEY = "private_key";
|
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
|
||||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
|
||||||
KEY_ID + " INTEGER UNIQUE, " +
|
|
||||||
PUBLIC_KEY + " TEXT NOT NULL, " +
|
|
||||||
PRIVATE_KEY + " TEXT NOT NULL);";
|
|
||||||
|
|
||||||
OneTimePreKeyDatabase(Context context, SignalDatabase databaseHelper) {
|
|
||||||
super(context, databaseHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable PreKeyRecord getPreKey(int keyId) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
|
|
||||||
new String[] {String.valueOf(keyId)},
|
|
||||||
null, null, null))
|
|
||||||
{
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
try {
|
|
||||||
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
|
||||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
|
||||||
|
|
||||||
return new PreKeyRecord(keyId, new ECKeyPair(publicKey, privateKey));
|
|
||||||
} catch (InvalidKeyException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertPreKey(int keyId, PreKeyRecord record) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(KEY_ID, keyId);
|
|
||||||
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
|
|
||||||
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
|
|
||||||
|
|
||||||
database.replace(TABLE_NAME, null, contentValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removePreKey(int keyId) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
database.delete(TABLE_NAME, KEY_ID + " = ?", new String[] {String.valueOf(keyId)});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.content.contentValuesOf
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
|
import org.thoughtcrime.securesms.util.SqlUtil
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyException
|
||||||
|
import org.whispersystems.libsignal.ecc.Curve
|
||||||
|
import org.whispersystems.libsignal.ecc.ECKeyPair
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class OneTimePreKeyDatabase internal constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper) {
|
||||||
|
companion object {
|
||||||
|
private val TAG = Log.tag(OneTimePreKeyDatabase::class.java)
|
||||||
|
|
||||||
|
const val TABLE_NAME = "one_time_prekeys"
|
||||||
|
const val ID = "_id"
|
||||||
|
const val ACCOUNT_ID = "account_id"
|
||||||
|
const val KEY_ID = "key_id"
|
||||||
|
const val PUBLIC_KEY = "public_key"
|
||||||
|
const val PRIVATE_KEY = "private_key"
|
||||||
|
const val CREATE_TABLE = """
|
||||||
|
CREATE TABLE $TABLE_NAME (
|
||||||
|
$ID INTEGER PRIMARY KEY,
|
||||||
|
$ACCOUNT_ID TEXT NOT NULL,
|
||||||
|
$KEY_ID INTEGER UNIQUE,
|
||||||
|
$PUBLIC_KEY TEXT NOT NULL,
|
||||||
|
$PRIVATE_KEY TEXT NOT NULL,
|
||||||
|
UNIQUE($ACCOUNT_ID, $KEY_ID)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(accountId: AccountIdentifier, keyId: Int): PreKeyRecord? {
|
||||||
|
readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(accountId, keyId), null, null, null).use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
try {
|
||||||
|
val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0)
|
||||||
|
val privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.requireNonNullString(PRIVATE_KEY)))
|
||||||
|
return PreKeyRecord(keyId, ECKeyPair(publicKey, privateKey))
|
||||||
|
} catch (e: InvalidKeyException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(accountId: AccountIdentifier, keyId: Int, record: PreKeyRecord) {
|
||||||
|
val contentValues = contentValuesOf(
|
||||||
|
ACCOUNT_ID to accountId.toString(),
|
||||||
|
KEY_ID to keyId,
|
||||||
|
PUBLIC_KEY to Base64.encodeBytes(record.keyPair.publicKey.serialize()),
|
||||||
|
PRIVATE_KEY to Base64.encodeBytes(record.keyPair.privateKey.serialize())
|
||||||
|
)
|
||||||
|
|
||||||
|
writableDatabase.replace(TABLE_NAME, null, contentValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(accountId: AccountIdentifier, keyId: Int) {
|
||||||
|
val database = databaseHelper.signalWritableDatabase
|
||||||
|
database.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(accountId, keyId))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -397,8 +397,8 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
|||||||
get() = instance!!.pendingRetryReceiptDatabase
|
get() = instance!!.pendingRetryReceiptDatabase
|
||||||
|
|
||||||
@get:JvmStatic
|
@get:JvmStatic
|
||||||
@get:JvmName("preKeys")
|
@get:JvmName("oneTimePreKeys")
|
||||||
val preKeys: OneTimePreKeyDatabase
|
val oneTimePreKeys: OneTimePreKeyDatabase
|
||||||
get() = instance!!.preKeyDatabase
|
get() = instance!!.preKeyDatabase
|
||||||
|
|
||||||
@get:Deprecated("This only exists to migrate from legacy storage. There shouldn't be any new usages.")
|
@get:Deprecated("This only exists to migrate from legacy storage. There shouldn't be any new usages.")
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
|
||||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SignedPreKeyDatabase extends Database {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(SignedPreKeyDatabase.class);
|
|
||||||
|
|
||||||
public static final String TABLE_NAME = "signed_prekeys";
|
|
||||||
|
|
||||||
private static final String ID = "_id";
|
|
||||||
public static final String KEY_ID = "key_id";
|
|
||||||
public static final String PUBLIC_KEY = "public_key";
|
|
||||||
public static final String PRIVATE_KEY = "private_key";
|
|
||||||
public static final String SIGNATURE = "signature";
|
|
||||||
public static final String TIMESTAMP = "timestamp";
|
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
|
||||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
|
||||||
KEY_ID + " INTEGER UNIQUE, " +
|
|
||||||
PUBLIC_KEY + " TEXT NOT NULL, " +
|
|
||||||
PRIVATE_KEY + " TEXT NOT NULL, " +
|
|
||||||
SIGNATURE + " TEXT NOT NULL, " +
|
|
||||||
TIMESTAMP + " INTEGER DEFAULT 0);";
|
|
||||||
|
|
||||||
SignedPreKeyDatabase(Context context, SignalDatabase databaseHelper) {
|
|
||||||
super(context, databaseHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable SignedPreKeyRecord getSignedPreKey(int keyId) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
|
|
||||||
new String[] {String.valueOf(keyId)},
|
|
||||||
null, null, null))
|
|
||||||
{
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
try {
|
|
||||||
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
|
||||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
|
||||||
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
|
|
||||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
|
||||||
|
|
||||||
return new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature);
|
|
||||||
} catch (InvalidKeyException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull List<SignedPreKeyRecord> getAllSignedPreKeys() {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
|
||||||
List<SignedPreKeyRecord> results = new LinkedList<>();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
try {
|
|
||||||
int keyId = cursor.getInt(cursor.getColumnIndexOrThrow(KEY_ID));
|
|
||||||
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
|
|
||||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
|
|
||||||
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
|
|
||||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
|
||||||
|
|
||||||
results.add(new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature));
|
|
||||||
} catch (InvalidKeyException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertSignedPreKey(int keyId, SignedPreKeyRecord record) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(KEY_ID, keyId);
|
|
||||||
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
|
|
||||||
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
|
|
||||||
contentValues.put(SIGNATURE, Base64.encodeBytes(record.getSignature()));
|
|
||||||
contentValues.put(TIMESTAMP, record.getTimestamp());
|
|
||||||
|
|
||||||
database.replace(TABLE_NAME, null, contentValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeSignedPreKey(int keyId) {
|
|
||||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
|
||||||
database.delete(TABLE_NAME, KEY_ID + " = ? AND " + SIGNATURE + " IS NOT NULL", new String[] {String.valueOf(keyId)});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.content.contentValuesOf
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
|
import org.thoughtcrime.securesms.util.SqlUtil
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyException
|
||||||
|
import org.whispersystems.libsignal.ecc.Curve
|
||||||
|
import org.whispersystems.libsignal.ecc.ECKeyPair
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
class SignedPreKeyDatabase internal constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper) {
|
||||||
|
companion object {
|
||||||
|
private val TAG = Log.tag(SignedPreKeyDatabase::class.java)
|
||||||
|
|
||||||
|
const val TABLE_NAME = "signed_prekeys"
|
||||||
|
const val ID = "_id"
|
||||||
|
const val ACCOUNT_ID = "account_id"
|
||||||
|
const val KEY_ID = "key_id"
|
||||||
|
const val PUBLIC_KEY = "public_key"
|
||||||
|
const val PRIVATE_KEY = "private_key"
|
||||||
|
const val SIGNATURE = "signature"
|
||||||
|
const val TIMESTAMP = "timestamp"
|
||||||
|
const val CREATE_TABLE = """
|
||||||
|
CREATE TABLE $TABLE_NAME (
|
||||||
|
$ID INTEGER PRIMARY KEY,
|
||||||
|
$ACCOUNT_ID TEXT NOT NULL,
|
||||||
|
$KEY_ID INTEGER UNIQUE,
|
||||||
|
$PUBLIC_KEY TEXT NOT NULL,
|
||||||
|
$PRIVATE_KEY TEXT NOT NULL,
|
||||||
|
$SIGNATURE TEXT NOT NULL,
|
||||||
|
$TIMESTAMP INTEGER DEFAULT 0,
|
||||||
|
UNIQUE($ACCOUNT_ID, $KEY_ID)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(accountId: AccountIdentifier, keyId: Int): SignedPreKeyRecord? {
|
||||||
|
readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(accountId, keyId), null, null, null).use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
try {
|
||||||
|
val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0)
|
||||||
|
val privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.requireNonNullString(PRIVATE_KEY)))
|
||||||
|
val signature = Base64.decode(cursor.requireNonNullString(SIGNATURE))
|
||||||
|
val timestamp = cursor.requireLong(TIMESTAMP)
|
||||||
|
return SignedPreKeyRecord(keyId, timestamp, ECKeyPair(publicKey, privateKey), signature)
|
||||||
|
} catch (e: InvalidKeyException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAll(accountId: AccountIdentifier): List<SignedPreKeyRecord> {
|
||||||
|
val results: MutableList<SignedPreKeyRecord> = LinkedList()
|
||||||
|
|
||||||
|
readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ?", SqlUtil.buildArgs(accountId), null, null, null).use { cursor ->
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
val keyId = cursor.requireInt(KEY_ID)
|
||||||
|
val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0)
|
||||||
|
val privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.requireNonNullString(PRIVATE_KEY)))
|
||||||
|
val signature = Base64.decode(cursor.requireNonNullString(SIGNATURE))
|
||||||
|
val timestamp = cursor.requireLong(TIMESTAMP)
|
||||||
|
results.add(SignedPreKeyRecord(keyId, timestamp, ECKeyPair(publicKey, privateKey), signature))
|
||||||
|
} catch (e: InvalidKeyException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(accountId: AccountIdentifier, keyId: Int, record: SignedPreKeyRecord) {
|
||||||
|
val contentValues = contentValuesOf(
|
||||||
|
ACCOUNT_ID to accountId.toString(),
|
||||||
|
KEY_ID to keyId,
|
||||||
|
PUBLIC_KEY to Base64.encodeBytes(record.keyPair.publicKey.serialize()),
|
||||||
|
PRIVATE_KEY to Base64.encodeBytes(record.keyPair.privateKey.serialize()),
|
||||||
|
SIGNATURE to Base64.encodeBytes(record.signature),
|
||||||
|
TIMESTAMP to record.timestamp
|
||||||
|
)
|
||||||
|
writableDatabase.replace(TABLE_NAME, null, contentValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(accountId: AccountIdentifier, keyId: Int) {
|
||||||
|
writableDatabase.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(accountId, keyId))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import org.signal.core.util.Conversions;
|
|||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@@ -94,7 +95,7 @@ public final class PreKeyMigrationHelper {
|
|||||||
reader.close();
|
reader.close();
|
||||||
|
|
||||||
Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId);
|
Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId);
|
||||||
TextSecurePreferences.setNextPreKeyId(context, index.nextPreKeyId);
|
SignalStore.account().aciPreKeys().setNextOneTimePreKeyId(index.nextPreKeyId);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
@@ -108,8 +109,8 @@ public final class PreKeyMigrationHelper {
|
|||||||
|
|
||||||
Log.i(TAG, "Setting next signed prekey id: " + index.nextSignedPreKeyId);
|
Log.i(TAG, "Setting next signed prekey id: " + index.nextSignedPreKeyId);
|
||||||
Log.i(TAG, "Setting active signed prekey id: " + index.activeSignedPreKeyId);
|
Log.i(TAG, "Setting active signed prekey id: " + index.activeSignedPreKeyId);
|
||||||
TextSecurePreferences.setNextSignedPreKeyId(context, index.nextSignedPreKeyId);
|
SignalStore.account().aciPreKeys().setNextSignedPreKeyId(index.nextSignedPreKeyId);
|
||||||
TextSecurePreferences.setActiveSignedPreKeyId(context, index.activeSignedPreKeyId);
|
SignalStore.account().aciPreKeys().setActiveSignedPreKeyId(index.activeSignedPreKeyId);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.database.helpers
|
package org.thoughtcrime.securesms.database.helpers
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -18,8 +19,10 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy
|
|||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper.entrySet
|
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper.entrySet
|
||||||
|
import org.thoughtcrime.securesms.database.KeyValueDatabase
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList
|
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList
|
||||||
|
import org.thoughtcrime.securesms.database.requireString
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.groups.GroupId
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob
|
||||||
@@ -39,6 +42,7 @@ import org.thoughtcrime.securesms.util.SqlUtil
|
|||||||
import org.thoughtcrime.securesms.util.Stopwatch
|
import org.thoughtcrime.securesms.util.Stopwatch
|
||||||
import org.thoughtcrime.securesms.util.Triple
|
import org.thoughtcrime.securesms.util.Triple
|
||||||
import org.thoughtcrime.securesms.util.Util
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import org.whispersystems.signalservice.api.push.DistributionId
|
import org.whispersystems.signalservice.api.push.DistributionId
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -185,11 +189,12 @@ object SignalDatabaseMigrations {
|
|||||||
private const val PNI_CLEANUP = 127
|
private const val PNI_CLEANUP = 127
|
||||||
private const val MESSAGE_RANGES = 128
|
private const val MESSAGE_RANGES = 128
|
||||||
private const val REACTION_TRIGGER_FIX = 129
|
private const val REACTION_TRIGGER_FIX = 129
|
||||||
|
private const val PNI_STORES = 130
|
||||||
|
|
||||||
const val DATABASE_VERSION = 129
|
const val DATABASE_VERSION = 130
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun migrate(context: Context, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
|
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL")
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL")
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.id)
|
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.id)
|
||||||
@@ -2285,6 +2290,80 @@ object SignalDatabaseMigrations {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < PNI_STORES) {
|
||||||
|
val localAci: ACI? = getLocalAci(context)
|
||||||
|
|
||||||
|
// One-Time Prekeys
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE one_time_prekeys_tmp (
|
||||||
|
_id INTEGER PRIMARY KEY,
|
||||||
|
account_id TEXT NOT NULL,
|
||||||
|
key_id INTEGER,
|
||||||
|
public_key TEXT NOT NULL,
|
||||||
|
private_key TEXT NOT NULL,
|
||||||
|
UNIQUE(account_id, key_id)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (localAci != null) {
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO one_time_prekeys_tmp (account_id, key_id, public_key, private_key)
|
||||||
|
SELECT
|
||||||
|
'$localAci' AS account_id,
|
||||||
|
one_time_prekeys.key_id,
|
||||||
|
one_time_prekeys.public_key,
|
||||||
|
one_time_prekeys.private_key
|
||||||
|
FROM one_time_prekeys
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No local ACI set. Not migrating any existing one-time prekeys.")
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execSQL("DROP TABLE one_time_prekeys")
|
||||||
|
db.execSQL("ALTER TABLE one_time_prekeys_tmp RENAME TO one_time_prekeys")
|
||||||
|
|
||||||
|
// Signed Prekeys
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE signed_prekeys_tmp (
|
||||||
|
_id INTEGER PRIMARY KEY,
|
||||||
|
account_id TEXT NOT NULL,
|
||||||
|
key_id INTEGER,
|
||||||
|
public_key TEXT NOT NULL,
|
||||||
|
private_key TEXT NOT NULL,
|
||||||
|
signature TEXT NOT NULL,
|
||||||
|
timestamp INTEGER DEFAULT 0,
|
||||||
|
UNIQUE(account_id, key_id)
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (localAci != null) {
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO signed_prekeys_tmp (account_id, key_id, public_key, private_key, signature, timestamp)
|
||||||
|
SELECT
|
||||||
|
'$localAci' AS account_id,
|
||||||
|
signed_prekeys.key_id,
|
||||||
|
signed_prekeys.public_key,
|
||||||
|
signed_prekeys.private_key,
|
||||||
|
signed_prekeys.signature,
|
||||||
|
signed_prekeys.timestamp
|
||||||
|
FROM signed_prekeys
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No local ACI set. Not migrating any existing signed prekeys.")
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execSQL("DROP TABLE signed_prekeys")
|
||||||
|
db.execSQL("ALTER TABLE signed_prekeys_tmp RENAME TO signed_prekeys")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -2294,6 +2373,9 @@ object SignalDatabaseMigrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Important: You can't change this method, or you risk breaking existing migrations. If you need to change this, make a new method.
|
||||||
|
*/
|
||||||
private fun migrateReaction(db: SQLiteDatabase, cursor: Cursor, isMms: Boolean) {
|
private fun migrateReaction(db: SQLiteDatabase, cursor: Cursor, isMms: Boolean) {
|
||||||
try {
|
try {
|
||||||
val messageId = CursorUtil.requireLong(cursor, "_id")
|
val messageId = CursorUtil.requireLong(cursor, "_id")
|
||||||
@@ -2314,4 +2396,22 @@ object SignalDatabaseMigrations {
|
|||||||
Log.w(TAG, "Failed to parse reaction!")
|
Log.w(TAG, "Failed to parse reaction!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Important: You can't change this method, or you risk breaking existing migrations. If you need to change this, make a new method.
|
||||||
|
*/
|
||||||
|
private fun getLocalAci(context: Application): ACI? {
|
||||||
|
if (KeyValueDatabase.exists(context)) {
|
||||||
|
val keyValueDatabase = KeyValueDatabase.getInstance(context).readableDatabase
|
||||||
|
keyValueDatabase.query("key_value", arrayOf("value"), "key = ?", SqlUtil.buildArgs("account.aci"), null, null, null).use { cursor ->
|
||||||
|
return if (cursor.moveToFirst()) {
|
||||||
|
ACI.parseOrNull(cursor.requireString("value"))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ACI.parseOrNull(PreferenceManager.getDefaultSharedPreferences(context).getString("pref_local_uuid", null))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,16 +278,27 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull SignalServiceDataStoreImpl provideProtocolStore() {
|
public @NonNull SignalServiceDataStoreImpl provideProtocolStore() {
|
||||||
|
ACI localAci = SignalStore.account().getAci();
|
||||||
|
PNI localPni = SignalStore.account().getPni();
|
||||||
|
|
||||||
|
if (localAci == null) {
|
||||||
|
throw new IllegalStateException("No ACI set!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localPni == null) {
|
||||||
|
throw new IllegalStateException("No PNI set!");
|
||||||
|
}
|
||||||
|
|
||||||
SignalBaseIdentityKeyStore baseIdentityStore = new SignalBaseIdentityKeyStore(context);
|
SignalBaseIdentityKeyStore baseIdentityStore = new SignalBaseIdentityKeyStore(context);
|
||||||
|
|
||||||
SignalServiceAccountDataStoreImpl aciStore = new SignalServiceAccountDataStoreImpl(context,
|
SignalServiceAccountDataStoreImpl aciStore = new SignalServiceAccountDataStoreImpl(context,
|
||||||
new TextSecurePreKeyStore(context),
|
new TextSecurePreKeyStore(localAci),
|
||||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getAciIdentityKey()),
|
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getAciIdentityKey()),
|
||||||
new TextSecureSessionStore(context),
|
new TextSecureSessionStore(context),
|
||||||
new SignalSenderKeyStore(context));
|
new SignalSenderKeyStore(context));
|
||||||
|
|
||||||
SignalServiceAccountDataStoreImpl pniStore = new SignalServiceAccountDataStoreImpl(context,
|
SignalServiceAccountDataStoreImpl pniStore = new SignalServiceAccountDataStoreImpl(context,
|
||||||
new TextSecurePreKeyStore(context),
|
new TextSecurePreKeyStore(localPni),
|
||||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getPniIdentityKey()),
|
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getPniIdentityKey()),
|
||||||
new TextSecureSessionStore(context),
|
new TextSecureSessionStore(context),
|
||||||
new SignalSenderKeyStore(context));
|
new SignalSenderKeyStore(context));
|
||||||
|
|||||||
@@ -4,37 +4,24 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated. Only exists for previously-enqueued jobs.
|
||||||
|
* Use {@link PreKeyUtil#cleanSignedPreKeys(SignalProtocolStore, PreKeyMetadataStore)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public class CleanPreKeysJob extends BaseJob {
|
public class CleanPreKeysJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "CleanPreKeysJob";
|
public static final String KEY = "CleanPreKeysJob";
|
||||||
|
|
||||||
private static final String TAG = Log.tag(CleanPreKeysJob.class);
|
private static final String TAG = Log.tag(CleanPreKeysJob.class);
|
||||||
|
|
||||||
private static final long ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30);
|
|
||||||
|
|
||||||
public CleanPreKeysJob() {
|
|
||||||
this(new Job.Parameters.Builder()
|
|
||||||
.setQueue("CleanPreKeysJob")
|
|
||||||
.setMaxAttempts(5)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private CleanPreKeysJob(@NonNull Job.Parameters parameters) {
|
private CleanPreKeysJob(@NonNull Job.Parameters parameters) {
|
||||||
super(parameters);
|
super(parameters);
|
||||||
}
|
}
|
||||||
@@ -50,47 +37,13 @@ public class CleanPreKeysJob extends BaseJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRun() throws IOException {
|
public void onRun() {
|
||||||
try {
|
PreKeyUtil.cleanSignedPreKeys(ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys());
|
||||||
Log.i(TAG, "Cleaning prekeys...");
|
PreKeyUtil.cleanSignedPreKeys(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys());
|
||||||
|
|
||||||
int activeSignedPreKeyId = PreKeyUtil.getActiveSignedPreKeyId(context);
|
|
||||||
SignedPreKeyStore signedPreKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
|
||||||
|
|
||||||
if (activeSignedPreKeyId < 0) return;
|
|
||||||
|
|
||||||
SignedPreKeyRecord currentRecord = signedPreKeyStore.loadSignedPreKey(activeSignedPreKeyId);
|
|
||||||
List<SignedPreKeyRecord> allRecords = signedPreKeyStore.loadSignedPreKeys();
|
|
||||||
LinkedList<SignedPreKeyRecord> oldRecords = removeRecordFrom(currentRecord, allRecords);
|
|
||||||
|
|
||||||
Collections.sort(oldRecords, new SignedPreKeySorter());
|
|
||||||
|
|
||||||
Log.i(TAG, "Active signed prekey: " + activeSignedPreKeyId);
|
|
||||||
Log.i(TAG, "Old signed prekey record count: " + oldRecords.size());
|
|
||||||
|
|
||||||
boolean foundAgedRecord = false;
|
|
||||||
|
|
||||||
for (SignedPreKeyRecord oldRecord : oldRecords) {
|
|
||||||
long archiveDuration = System.currentTimeMillis() - oldRecord.getTimestamp();
|
|
||||||
|
|
||||||
if (archiveDuration >= ARCHIVE_AGE) {
|
|
||||||
if (!foundAgedRecord) {
|
|
||||||
foundAgedRecord = true;
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Removing signed prekey record: " + oldRecord.getId() + " with timestamp: " + oldRecord.getTimestamp());
|
|
||||||
signedPreKeyStore.removeSignedPreKey(oldRecord.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InvalidKeyIdException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onShouldRetry(@NonNull Exception throwable) {
|
public boolean onShouldRetry(@NonNull Exception throwable) {
|
||||||
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
|
||||||
if (throwable instanceof PushNetworkException) return true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,30 +52,6 @@ public class CleanPreKeysJob extends BaseJob {
|
|||||||
Log.w(TAG, "Failed to execute clean signed prekeys task.");
|
Log.w(TAG, "Failed to execute clean signed prekeys task.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkedList<SignedPreKeyRecord> removeRecordFrom(SignedPreKeyRecord currentRecord,
|
|
||||||
List<SignedPreKeyRecord> records)
|
|
||||||
|
|
||||||
{
|
|
||||||
LinkedList<SignedPreKeyRecord> others = new LinkedList<>();
|
|
||||||
|
|
||||||
for (SignedPreKeyRecord record : records) {
|
|
||||||
if (record.getId() != currentRecord.getId()) {
|
|
||||||
others.add(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return others;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SignedPreKeySorter implements Comparator<SignedPreKeyRecord> {
|
|
||||||
@Override
|
|
||||||
public int compare(SignedPreKeyRecord lhs, SignedPreKeyRecord rhs) {
|
|
||||||
if (lhs.getTimestamp() > rhs.getTimestamp()) return -1;
|
|
||||||
else if (lhs.getTimestamp() < rhs.getTimestamp()) return 1;
|
|
||||||
else return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<CleanPreKeysJob> {
|
public static final class Factory implements Job.Factory<CleanPreKeysJob> {
|
||||||
@Override
|
@Override
|
||||||
public @NonNull CleanPreKeysJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
public @NonNull CleanPreKeysJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
|||||||
@@ -1,36 +1,41 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and uploads a new signed prekey for an identity if one hasn't been uploaded yet.
|
||||||
|
*/
|
||||||
public class CreateSignedPreKeyJob extends BaseJob {
|
public class CreateSignedPreKeyJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "CreateSignedPreKeyJob";
|
public static final String KEY = "CreateSignedPreKeyJob";
|
||||||
|
|
||||||
private static final String TAG = Log.tag(CreateSignedPreKeyJob.class);
|
private static final String TAG = Log.tag(CreateSignedPreKeyJob.class);
|
||||||
|
|
||||||
public CreateSignedPreKeyJob(Context context) {
|
private CreateSignedPreKeyJob() {
|
||||||
this(new Job.Parameters.Builder()
|
this(new Job.Parameters.Builder()
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setMaxInstancesForFactory(1)
|
||||||
.setQueue("CreateSignedPreKeyJob")
|
.setQueue("CreateSignedPreKeyJob")
|
||||||
.setMaxAttempts(25)
|
.setLifespan(TimeUnit.DAYS.toMillis(30))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +43,16 @@ public class CreateSignedPreKeyJob extends BaseJob {
|
|||||||
super(parameters);
|
super(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues an instance of this job if we not yet created + uploaded signed prekeys for one of our identities.
|
||||||
|
*/
|
||||||
|
public static void enqueueIfNeeded() {
|
||||||
|
if (!SignalStore.account().aciPreKeys().isSignedPreKeyRegistered() || !SignalStore.account().pniPreKeys().isSignedPreKeyRegistered()) {
|
||||||
|
Log.i(TAG, "Some signed prekeys aren't registered yet. Enqueuing a job.");
|
||||||
|
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Data serialize() {
|
public @NonNull Data serialize() {
|
||||||
return Data.EMPTY;
|
return Data.EMPTY;
|
||||||
@@ -50,22 +65,33 @@ public class CreateSignedPreKeyJob extends BaseJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRun() throws IOException {
|
public void onRun() throws IOException {
|
||||||
if (TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
|
||||||
Log.w(TAG, "Signed prekey already registered...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SignalStore.account().isRegistered()) {
|
if (!SignalStore.account().isRegistered()) {
|
||||||
Log.w(TAG, "Not yet registered...");
|
Log.w(TAG, "Not yet registered...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
createPreKeys(SignalStore.account().getAci(), ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys());
|
||||||
IdentityKeyPair identityKeyPair = SignalStore.account().getAciIdentityKey();
|
createPreKeys(SignalStore.account().getPni(), ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys());
|
||||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair, true);
|
}
|
||||||
|
|
||||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
private void createPreKeys(@Nullable AccountIdentifier accountId, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore)
|
||||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
throws IOException
|
||||||
|
{
|
||||||
|
if (accountId == null) {
|
||||||
|
warn(TAG, "AccountId not set!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataStore.isSignedPreKeyRegistered()) {
|
||||||
|
warn(TAG, "Signed prekey for " + (accountId.isAci() ? "ACI" : "PNI") + " already registered...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
|
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true);
|
||||||
|
|
||||||
|
accountManager.setSignedPreKey(accountId, signedPreKeyRecord);
|
||||||
|
metadataStore.setSignedPreKeyRegistered(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
|||||||
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
|
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.PinOptOutMigration;
|
import org.thoughtcrime.securesms.migrations.PinOptOutMigration;
|
||||||
import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob;
|
import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob;
|
||||||
|
import org.thoughtcrime.securesms.migrations.PniAccountInitializationMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.PniMigrationJob;
|
import org.thoughtcrime.securesms.migrations.PniMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
|
import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
|
||||||
import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
|
import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
|
||||||
@@ -197,6 +198,7 @@ public final class JobManagerFactories {
|
|||||||
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
||||||
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
|
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
|
||||||
put(PinReminderMigrationJob.KEY, new PinReminderMigrationJob.Factory());
|
put(PinReminderMigrationJob.KEY, new PinReminderMigrationJob.Factory());
|
||||||
|
put(PniAccountInitializationMigrationJob.KEY, new PniAccountInitializationMigrationJob.Factory());
|
||||||
put(PniMigrationJob.KEY, new PniMigrationJob.Factory());
|
put(PniMigrationJob.KEY, new PniMigrationJob.Factory());
|
||||||
put(ProfileMigrationJob.KEY, new ProfileMigrationJob.Factory());
|
put(ProfileMigrationJob.KEY, new ProfileMigrationJob.Factory());
|
||||||
put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory());
|
put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory());
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public abstract class PushSendJob extends SendJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void onSend() throws Exception {
|
protected final void onSend() throws Exception {
|
||||||
if (TextSecurePreferences.getSignedPreKeyFailureCount(context) > 5) {
|
if (SignalStore.account().aciPreKeys().getSignedPreKeyFailureCount() > 5) {
|
||||||
ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob());
|
ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob());
|
||||||
throw new TextSecureExpiredException("Too many signed prekey rotation failures");
|
throw new TextSecureExpiredException("Too many signed prekey rotation failures");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
@@ -22,6 +26,11 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that our prekeys are up to date for both our ACI and PNI identities.
|
||||||
|
* Specifically, if we have less than {@link #PREKEY_MINIMUM} one-time prekeys, we will generate and upload
|
||||||
|
* a new batch of one-time prekeys, as well as a new signed prekey.
|
||||||
|
*/
|
||||||
public class RefreshPreKeysJob extends BaseJob {
|
public class RefreshPreKeysJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "RefreshPreKeysJob";
|
public static final String KEY = "RefreshPreKeysJob";
|
||||||
@@ -72,34 +81,61 @@ public class RefreshPreKeysJob extends BaseJob {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
ACI aci = SignalStore.account().getAci();
|
||||||
|
SignalProtocolStore aciProtocolStore = ApplicationDependencies.getProtocolStore().aci();
|
||||||
|
PreKeyMetadataStore aciPreKeyStore = SignalStore.account().aciPreKeys();
|
||||||
|
|
||||||
|
PNI pni = SignalStore.account().getPni();
|
||||||
|
SignalProtocolStore pniProtocolStore = ApplicationDependencies.getProtocolStore().pni();
|
||||||
|
PreKeyMetadataStore pniPreKeyStore = SignalStore.account().pniPreKeys();
|
||||||
|
|
||||||
int availableKeys = accountManager.getPreKeysCount();
|
if (refreshKeys(aci, aciProtocolStore, aciPreKeyStore)) {
|
||||||
|
PreKeyUtil.cleanSignedPreKeys(aciProtocolStore, aciPreKeyStore);
|
||||||
Log.i(TAG, "Available keys: " + availableKeys);
|
}
|
||||||
|
|
||||||
if (availableKeys >= PREKEY_MINIMUM && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
if (refreshKeys(pni, pniProtocolStore, pniPreKeyStore)) {
|
||||||
Log.i(TAG, "Available keys sufficient.");
|
PreKeyUtil.cleanSignedPreKeys(pniProtocolStore, pniPreKeyStore);
|
||||||
SignalStore.misc().setLastPrekeyRefreshTime(System.currentTimeMillis());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context);
|
|
||||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
|
||||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
|
|
||||||
|
|
||||||
Log.i(TAG, "Registering new prekeys...");
|
|
||||||
|
|
||||||
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKeyRecord, preKeyRecords);
|
|
||||||
|
|
||||||
PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
|
|
||||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new CleanPreKeysJob());
|
|
||||||
SignalStore.misc().setLastPrekeyRefreshTime(System.currentTimeMillis());
|
SignalStore.misc().setLastPrekeyRefreshTime(System.currentTimeMillis());
|
||||||
Log.i(TAG, "Successfully refreshed prekeys.");
|
Log.i(TAG, "Successfully refreshed prekeys.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if we need to clean prekeys, otherwise false.
|
||||||
|
*/
|
||||||
|
private boolean refreshKeys(@Nullable AccountIdentifier accountId, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) throws IOException {
|
||||||
|
if (accountId == null) {
|
||||||
|
throw new IOException("Unset identifier!");
|
||||||
|
}
|
||||||
|
|
||||||
|
String logPrefix = "[" + (accountId.isAci() ? "ACI" : "PNI") + "] ";
|
||||||
|
|
||||||
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
|
|
||||||
|
int availableKeys = accountManager.getPreKeysCount(accountId);
|
||||||
|
log(TAG, logPrefix + "Available keys: " + availableKeys);
|
||||||
|
|
||||||
|
if (availableKeys >= PREKEY_MINIMUM && metadataStore.isSignedPreKeyRegistered()) {
|
||||||
|
log(TAG, logPrefix + "Available keys sufficient.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
|
||||||
|
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, false);
|
||||||
|
IdentityKeyPair identityKey = protocolStore.getIdentityKeyPair();
|
||||||
|
|
||||||
|
log(TAG, logPrefix + "Registering new prekeys...");
|
||||||
|
|
||||||
|
accountManager.setPreKeys(accountId, identityKey.getPublicKey(), signedPreKeyRecord, preKeyRecords);
|
||||||
|
|
||||||
|
metadataStore.setActiveSignedPreKeyId(signedPreKeyRecord.getId());
|
||||||
|
metadataStore.setSignedPreKeyRegistered(true);
|
||||||
|
|
||||||
|
log(TAG, logPrefix + "Need to clean prekeys.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onShouldRetry(@NonNull Exception exception) {
|
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||||
if (exception instanceof NonSuccessfulResponseCodeException) return false;
|
if (exception instanceof NonSuccessfulResponseCodeException) return false;
|
||||||
|
|||||||
@@ -4,21 +4,28 @@ package org.thoughtcrime.securesms.jobs;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the creation + upload of new signed prekeys for both the ACI and PNI identities.
|
||||||
|
*/
|
||||||
public class RotateSignedPreKeyJob extends BaseJob {
|
public class RotateSignedPreKeyJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "RotateSignedPreKeyJob";
|
public static final String KEY = "RotateSignedPreKeyJob";
|
||||||
@@ -53,17 +60,34 @@ public class RotateSignedPreKeyJob extends BaseJob {
|
|||||||
public void onRun() throws Exception {
|
public void onRun() throws Exception {
|
||||||
Log.i(TAG, "Rotating signed prekey...");
|
Log.i(TAG, "Rotating signed prekey...");
|
||||||
|
|
||||||
|
ACI aci = SignalStore.account().getAci();
|
||||||
|
PNI pni = SignalStore.account().getPni();
|
||||||
|
|
||||||
|
if (aci == null) {
|
||||||
|
Log.w(TAG, "ACI is unset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pni == null) {
|
||||||
|
Log.w(TAG, "PNI is unset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate(aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys());
|
||||||
|
rotate(pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotate(@NonNull AccountIdentifier accountId, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, false);
|
||||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
|
|
||||||
|
|
||||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
accountManager.setSignedPreKey(accountId, signedPreKeyRecord);
|
||||||
|
|
||||||
PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
|
metadataStore.setActiveSignedPreKeyId(signedPreKeyRecord.getId());
|
||||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
metadataStore.setSignedPreKeyRegistered(true);
|
||||||
TextSecurePreferences.setSignedPreKeyFailureCount(context, 0);
|
metadataStore.setSignedPreKeyFailureCount(0);
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new CleanPreKeysJob());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -73,7 +97,11 @@ public class RotateSignedPreKeyJob extends BaseJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure() {
|
||||||
TextSecurePreferences.setSignedPreKeyFailureCount(context, TextSecurePreferences.getSignedPreKeyFailureCount(context) + 1);
|
PreKeyMetadataStore aciStore = SignalStore.account().aciPreKeys();
|
||||||
|
PreKeyMetadataStore pniStore = SignalStore.account().pniPreKeys();
|
||||||
|
|
||||||
|
aciStore.setSignedPreKeyFailureCount(aciStore.getSignedPreKeyFailureCount() + 1);
|
||||||
|
pniStore.setSignedPreKeyFailureCount(pniStore.getSignedPreKeyFailureCount() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<RotateSignedPreKeyJob> {
|
public static final class Factory implements Job.Factory<RotateSignedPreKeyJob> {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher
|
import org.thoughtcrime.securesms.crypto.MasterCipher
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
||||||
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.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
@@ -18,9 +19,11 @@ import org.thoughtcrime.securesms.util.Util
|
|||||||
import org.whispersystems.libsignal.IdentityKey
|
import org.whispersystems.libsignal.IdentityKey
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair
|
import org.whispersystems.libsignal.IdentityKeyPair
|
||||||
import org.whispersystems.libsignal.ecc.Curve
|
import org.whispersystems.libsignal.ecc.Curve
|
||||||
|
import org.whispersystems.libsignal.util.Medium
|
||||||
import org.whispersystems.signalservice.api.push.ACI
|
import org.whispersystems.signalservice.api.push.ACI
|
||||||
import org.whispersystems.signalservice.api.push.PNI
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
internal class AccountValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
internal class AccountValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||||
|
|
||||||
@@ -35,10 +38,22 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
private const val KEY_FCM_TOKEN_LAST_SET_TIME = "account.fcm_token_last_set_time"
|
private const val KEY_FCM_TOKEN_LAST_SET_TIME = "account.fcm_token_last_set_time"
|
||||||
private const val KEY_DEVICE_NAME = "account.device_name"
|
private const val KEY_DEVICE_NAME = "account.device_name"
|
||||||
private const val KEY_DEVICE_ID = "account.device_id"
|
private const val KEY_DEVICE_ID = "account.device_id"
|
||||||
|
|
||||||
private const val KEY_ACI_IDENTITY_PUBLIC_KEY = "account.aci_identity_public_key"
|
private const val KEY_ACI_IDENTITY_PUBLIC_KEY = "account.aci_identity_public_key"
|
||||||
private const val KEY_ACI_IDENTITY_PRIVATE_KEY = "account.aci_identity_private_key"
|
private const val KEY_ACI_IDENTITY_PRIVATE_KEY = "account.aci_identity_private_key"
|
||||||
|
private const val KEY_ACI_SIGNED_PREKEY_REGISTERED = "account.aci_signed_prekey_registered"
|
||||||
|
private const val KEY_ACI_NEXT_SIGNED_PREKEY_ID = "account.aci_next_signed_prekey_id"
|
||||||
|
private const val KEY_ACI_ACTIVE_SIGNED_PREKEY_ID = "account.aci_active_signed_prekey_id"
|
||||||
|
private const val KEY_ACI_SIGNED_PREKEY_FAILURE_COUNT = "account.aci_signed_prekey_failure_count"
|
||||||
|
private const val KEY_ACI_NEXT_ONE_TIME_PREKEY_ID = "account.aci_next_one_time_prekey_id"
|
||||||
|
|
||||||
private const val KEY_PNI_IDENTITY_PUBLIC_KEY = "account.pni_identity_public_key"
|
private const val KEY_PNI_IDENTITY_PUBLIC_KEY = "account.pni_identity_public_key"
|
||||||
private const val KEY_PNI_IDENTITY_PRIVATE_KEY = "account.pni_identity_private_key"
|
private const val KEY_PNI_IDENTITY_PRIVATE_KEY = "account.pni_identity_private_key"
|
||||||
|
private const val KEY_PNI_SIGNED_PREKEY_REGISTERED = "account.pni_signed_prekey_registered"
|
||||||
|
private const val KEY_PNI_NEXT_SIGNED_PREKEY_ID = "account.pni_next_signed_prekey_id"
|
||||||
|
private const val KEY_PNI_ACTIVE_SIGNED_PREKEY_ID = "account.pni_active_signed_prekey_id"
|
||||||
|
private const val KEY_PNI_SIGNED_PREKEY_FAILURE_COUNT = "account.pni_signed_prekey_failure_count"
|
||||||
|
private const val KEY_PNI_NEXT_ONE_TIME_PREKEY_ID = "account.pni_next_one_time_prekey_id"
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
const val KEY_E164 = "account.e164"
|
const val KEY_E164 = "account.e164"
|
||||||
@@ -102,9 +117,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A randomly-generated value that represents this registration instance. Helps the server know if you reinstalled. */
|
/** A randomly-generated value that represents this registration instance. Helps the server know if you reinstalled. */
|
||||||
var registrationId: Int
|
var registrationId: Int by integerValue(KEY_REGISTRATION_ID, 0)
|
||||||
get() = getInteger(KEY_REGISTRATION_ID, 0)
|
|
||||||
set(value) = putInteger(KEY_REGISTRATION_ID, value)
|
|
||||||
|
|
||||||
/** The identity key pair for the ACI identity. */
|
/** The identity key pair for the ACI identity. */
|
||||||
val aciIdentityKey: IdentityKeyPair
|
val aciIdentityKey: IdentityKeyPair
|
||||||
@@ -147,8 +160,8 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
val key: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
val key: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
store
|
store
|
||||||
.beginWrite()
|
.beginWrite()
|
||||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
.putBlob(KEY_PNI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
||||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
.putBlob(KEY_PNI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,11 +187,27 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, Base64.decode(base64))
|
putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, Base64.decode(base64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:JvmName("aciPreKeys")
|
||||||
|
val aciPreKeys: PreKeyMetadataStore = object : PreKeyMetadataStore {
|
||||||
|
override var nextSignedPreKeyId: Int by integerValue(KEY_ACI_NEXT_SIGNED_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
|
||||||
|
override var activeSignedPreKeyId: Int by integerValue(KEY_ACI_ACTIVE_SIGNED_PREKEY_ID, -1)
|
||||||
|
override var isSignedPreKeyRegistered: Boolean by booleanValue(KEY_ACI_SIGNED_PREKEY_REGISTERED, false)
|
||||||
|
override var signedPreKeyFailureCount: Int by integerValue(KEY_ACI_SIGNED_PREKEY_FAILURE_COUNT, 0)
|
||||||
|
override var nextOneTimePreKeyId: Int by integerValue(KEY_ACI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:JvmName("pniPreKeys")
|
||||||
|
val pniPreKeys: PreKeyMetadataStore = object : PreKeyMetadataStore {
|
||||||
|
override var nextSignedPreKeyId: Int by integerValue(KEY_PNI_NEXT_SIGNED_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
|
||||||
|
override var activeSignedPreKeyId: Int by integerValue(KEY_PNI_ACTIVE_SIGNED_PREKEY_ID, -1)
|
||||||
|
override var isSignedPreKeyRegistered: Boolean by booleanValue(KEY_PNI_SIGNED_PREKEY_REGISTERED, false)
|
||||||
|
override var signedPreKeyFailureCount: Int by integerValue(KEY_PNI_SIGNED_PREKEY_FAILURE_COUNT, 0)
|
||||||
|
override var nextOneTimePreKeyId: Int by integerValue(KEY_PNI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
/** Indicates whether the user has the ability to receive FCM messages. Largely coupled to whether they have Play Service. */
|
/** Indicates whether the user has the ability to receive FCM messages. Largely coupled to whether they have Play Service. */
|
||||||
var fcmEnabled: Boolean
|
@get:JvmName("isFcmEnabled")
|
||||||
@JvmName("isFcmEnabled")
|
var fcmEnabled: Boolean by booleanValue(KEY_FCM_ENABLED, false)
|
||||||
get() = getBoolean(KEY_FCM_ENABLED, false)
|
|
||||||
set(value) = putBoolean(KEY_FCM_ENABLED, value)
|
|
||||||
|
|
||||||
/** The FCM token, which allows the server to send us FCM messages. */
|
/** The FCM token, which allows the server to send us FCM messages. */
|
||||||
var fcmToken: String?
|
var fcmToken: String?
|
||||||
@@ -249,6 +278,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
ApplicationDependencies.getGroupsV2Authorization().clear()
|
ApplicationDependencies.getGroupsV2Authorization().clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Do not alter. If you need to migrate more stuff, create a new method. */
|
||||||
private fun migrateFromSharedPrefsV1(context: Context) {
|
private fun migrateFromSharedPrefsV1(context: Context) {
|
||||||
Log.i(TAG, "[V1] Migrating account values from shared prefs.")
|
Log.i(TAG, "[V1] Migrating account values from shared prefs.")
|
||||||
|
|
||||||
@@ -263,23 +293,24 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
putLong(KEY_FCM_TOKEN_LAST_SET_TIME, TextSecurePreferences.getLongPreference(context, "pref_gcm_registration_id_last_set_time", 0))
|
putLong(KEY_FCM_TOKEN_LAST_SET_TIME, TextSecurePreferences.getLongPreference(context, "pref_gcm_registration_id_last_set_time", 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Do not alter. If you need to migrate more stuff, create a new method. */
|
||||||
private fun migrateFromSharedPrefsV2(context: Context) {
|
private fun migrateFromSharedPrefsV2(context: Context) {
|
||||||
Log.i(TAG, "[V2] Migrating account values from shared prefs.")
|
Log.i(TAG, "[V2] Migrating account values from shared prefs.")
|
||||||
|
|
||||||
val masterSecretPrefs: SharedPreferences = context.getSharedPreferences("SecureSMS-Preferences", 0)
|
val masterSecretPrefs: SharedPreferences = context.getSharedPreferences("SecureSMS-Preferences", 0)
|
||||||
val defaultPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val defaultPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
val storeWriter: KeyValueStore.Writer = store.beginWrite()
|
||||||
|
|
||||||
if (masterSecretPrefs.hasStringData("pref_identity_public_v3")) {
|
if (masterSecretPrefs.hasStringData("pref_identity_public_v3")) {
|
||||||
Log.i(TAG, "Migrating modern identity key.")
|
Log.i(TAG, "Migrating modern identity key.")
|
||||||
|
|
||||||
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_v3", null)!!)
|
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_v3", null)!!)
|
||||||
val identityPrivate = Base64.decode(masterSecretPrefs.getString("pref_identity_private_v3", null)!!)
|
val identityPrivate = Base64.decode(masterSecretPrefs.getString("pref_identity_private_v3", null)!!)
|
||||||
|
|
||||||
store
|
storeWriter
|
||||||
.beginWrite()
|
|
||||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||||
.commit()
|
|
||||||
} else if (masterSecretPrefs.hasStringData("pref_identity_public_curve25519")) {
|
} else if (masterSecretPrefs.hasStringData("pref_identity_public_curve25519")) {
|
||||||
Log.i(TAG, "Migrating legacy identity key.")
|
Log.i(TAG, "Migrating legacy identity key.")
|
||||||
|
|
||||||
@@ -287,15 +318,21 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_curve25519", null)!!)
|
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_curve25519", null)!!)
|
||||||
val identityPrivate = masterCipher.decryptKey(Base64.decode(masterSecretPrefs.getString("pref_identity_private_curve25519", null)!!)).serialize()
|
val identityPrivate = masterCipher.decryptKey(Base64.decode(masterSecretPrefs.getString("pref_identity_private_curve25519", null)!!)).serialize()
|
||||||
|
|
||||||
store
|
storeWriter
|
||||||
.beginWrite()
|
|
||||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||||
.commit()
|
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "No pre-existing identity key! No migration.")
|
Log.w(TAG, "No pre-existing identity key! No migration.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeWriter
|
||||||
|
.putInteger(KEY_ACI_NEXT_SIGNED_PREKEY_ID, defaultPrefs.getInt("pref_next_signed_pre_key_id", SecureRandom().nextInt(Medium.MAX_VALUE)))
|
||||||
|
.putInteger(KEY_ACI_ACTIVE_SIGNED_PREKEY_ID, defaultPrefs.getInt("pref_active_signed_pre_key_id", -1))
|
||||||
|
.putInteger(KEY_ACI_NEXT_ONE_TIME_PREKEY_ID, defaultPrefs.getInt("pref_next_pre_key_id", SecureRandom().nextInt(Medium.MAX_VALUE)))
|
||||||
|
.putInteger(KEY_ACI_SIGNED_PREKEY_FAILURE_COUNT, defaultPrefs.getInt("pref_signed_prekey_failure_count", 0))
|
||||||
|
.putBoolean(KEY_ACI_SIGNED_PREKEY_REGISTERED, defaultPrefs.getBoolean("pref_signed_prekey_registered", false))
|
||||||
|
.commit()
|
||||||
|
|
||||||
masterSecretPrefs
|
masterSecretPrefs
|
||||||
.edit()
|
.edit()
|
||||||
.remove("pref_identity_public_v3")
|
.remove("pref_identity_public_v3")
|
||||||
@@ -308,6 +345,11 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||||||
.edit()
|
.edit()
|
||||||
.remove("pref_local_uuid")
|
.remove("pref_local_uuid")
|
||||||
.remove("pref_identity_public_v3")
|
.remove("pref_identity_public_v3")
|
||||||
|
.remove("pref_next_signed_pre_key_id")
|
||||||
|
.remove("pref_active_signed_pre_key_id")
|
||||||
|
.remove("pref_signed_prekey_failure_count")
|
||||||
|
.remove("pref_signed_prekey_registered")
|
||||||
|
.remove("pref_next_pre_key_id")
|
||||||
.remove("pref_gcm_password")
|
.remove("pref_gcm_password")
|
||||||
.remove("pref_gcm_registered")
|
.remove("pref_gcm_registered")
|
||||||
.remove("pref_local_registration_id")
|
.remove("pref_local_registration_id")
|
||||||
|
|||||||
@@ -3,102 +3,102 @@ package org.thoughtcrime.securesms.keyvalue
|
|||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
internal fun SignalStoreValues.longValue(key: String, default: Long): SignalStoreValueDelegate<Long> {
|
internal fun SignalStoreValues.longValue(key: String, default: Long): SignalStoreValueDelegate<Long> {
|
||||||
return LongValue(key, default)
|
return LongValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun SignalStoreValues.booleanValue(key: String, default: Boolean): SignalStoreValueDelegate<Boolean> {
|
internal fun SignalStoreValues.booleanValue(key: String, default: Boolean): SignalStoreValueDelegate<Boolean> {
|
||||||
return BooleanValue(key, default)
|
return BooleanValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T : String?> SignalStoreValues.stringValue(key: String, default: T): SignalStoreValueDelegate<T> {
|
internal fun <T : String?> SignalStoreValues.stringValue(key: String, default: T): SignalStoreValueDelegate<T> {
|
||||||
return StringValue(key, default)
|
return StringValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun SignalStoreValues.integerValue(key: String, default: Int): SignalStoreValueDelegate<Int> {
|
internal fun SignalStoreValues.integerValue(key: String, default: Int): SignalStoreValueDelegate<Int> {
|
||||||
return IntValue(key, default)
|
return IntValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun SignalStoreValues.floatValue(key: String, default: Float): SignalStoreValueDelegate<Float> {
|
internal fun SignalStoreValues.floatValue(key: String, default: Float): SignalStoreValueDelegate<Float> {
|
||||||
return FloatValue(key, default)
|
return FloatValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun SignalStoreValues.blobValue(key: String, default: ByteArray): SignalStoreValueDelegate<ByteArray> {
|
internal fun SignalStoreValues.blobValue(key: String, default: ByteArray): SignalStoreValueDelegate<ByteArray> {
|
||||||
return BlobValue(key, default)
|
return BlobValue(key, default, this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kotlin delegate that serves as a base for all other value types. This allows us to only expose this sealed
|
* Kotlin delegate that serves as a base for all other value types. This allows us to only expose this sealed
|
||||||
* class to callers and protect the individual implementations as private behind the various extension functions.
|
* class to callers and protect the individual implementations as private behind the various extension functions.
|
||||||
*/
|
*/
|
||||||
sealed class SignalStoreValueDelegate<T> {
|
sealed class SignalStoreValueDelegate<T>(private val store: KeyValueStore) {
|
||||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
return getValue(thisRef as SignalStoreValues)
|
return getValue(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
setValue(thisRef as SignalStoreValues, value)
|
setValue(store, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract fun getValue(values: SignalStoreValues): T
|
internal abstract fun getValue(values: KeyValueStore): T
|
||||||
internal abstract fun setValue(values: SignalStoreValues, value: T)
|
internal abstract fun setValue(values: KeyValueStore, value: T)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LongValue(private val key: String, private val default: Long) : SignalStoreValueDelegate<Long>() {
|
private class LongValue(private val key: String, private val default: Long, store: KeyValueStore) : SignalStoreValueDelegate<Long>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): Long {
|
override fun getValue(values: KeyValueStore): Long {
|
||||||
return values.getLong(key, default)
|
return values.getLong(key, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: Long) {
|
override fun setValue(values: KeyValueStore, value: Long) {
|
||||||
values.putLong(key, value)
|
values.beginWrite().putLong(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BooleanValue(private val key: String, private val default: Boolean) : SignalStoreValueDelegate<Boolean>() {
|
private class BooleanValue(private val key: String, private val default: Boolean, store: KeyValueStore) : SignalStoreValueDelegate<Boolean>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): Boolean {
|
override fun getValue(values: KeyValueStore): Boolean {
|
||||||
return values.getBoolean(key, default)
|
return values.getBoolean(key, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: Boolean) {
|
override fun setValue(values: KeyValueStore, value: Boolean) {
|
||||||
values.putBoolean(key, value)
|
values.beginWrite().putBoolean(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StringValue<T : String?>(private val key: String, private val default: T) : SignalStoreValueDelegate<T>() {
|
private class StringValue<T : String?>(private val key: String, private val default: T, store: KeyValueStore) : SignalStoreValueDelegate<T>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): T {
|
override fun getValue(values: KeyValueStore): T {
|
||||||
return values.getString(key, default) as T
|
return values.getString(key, default) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: T) {
|
override fun setValue(values: KeyValueStore, value: T) {
|
||||||
values.putString(key, value)
|
values.beginWrite().putString(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IntValue(private val key: String, private val default: Int) : SignalStoreValueDelegate<Int>() {
|
private class IntValue(private val key: String, private val default: Int, store: KeyValueStore) : SignalStoreValueDelegate<Int>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): Int {
|
override fun getValue(values: KeyValueStore): Int {
|
||||||
return values.getInteger(key, default)
|
return values.getInteger(key, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: Int) {
|
override fun setValue(values: KeyValueStore, value: Int) {
|
||||||
values.putInteger(key, value)
|
values.beginWrite().putInteger(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FloatValue(private val key: String, private val default: Float) : SignalStoreValueDelegate<Float>() {
|
private class FloatValue(private val key: String, private val default: Float, store: KeyValueStore) : SignalStoreValueDelegate<Float>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): Float {
|
override fun getValue(values: KeyValueStore): Float {
|
||||||
return values.getFloat(key, default)
|
return values.getFloat(key, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: Float) {
|
override fun setValue(values: KeyValueStore, value: Float) {
|
||||||
values.putFloat(key, value)
|
values.beginWrite().putFloat(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BlobValue(private val key: String, private val default: ByteArray) : SignalStoreValueDelegate<ByteArray>() {
|
private class BlobValue(private val key: String, private val default: ByteArray, store: KeyValueStore) : SignalStoreValueDelegate<ByteArray>(store) {
|
||||||
override fun getValue(values: SignalStoreValues): ByteArray {
|
override fun getValue(values: KeyValueStore): ByteArray {
|
||||||
return values.getBlob(key, default)
|
return values.getBlob(key, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(values: SignalStoreValues, value: ByteArray) {
|
override fun setValue(values: KeyValueStore, value: ByteArray) {
|
||||||
values.putBlob(key, value)
|
values.beginWrite().putBlob(key, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,9 +97,10 @@ public class ApplicationMigrations {
|
|||||||
static final int FIX_EMOJI_QUALITY = 53;
|
static final int FIX_EMOJI_QUALITY = 53;
|
||||||
static final int CHANGE_NUMBER_CAPABILITY_4 = 54;
|
static final int CHANGE_NUMBER_CAPABILITY_4 = 54;
|
||||||
static final int KBS_MIGRATION = 55;
|
static final int KBS_MIGRATION = 55;
|
||||||
|
static final int PNI_IDENTITY = 56;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int CURRENT_VERSION = 55;
|
public static final int CURRENT_VERSION = 56;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||||
@@ -421,6 +422,10 @@ public class ApplicationMigrations {
|
|||||||
jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob());
|
jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastSeenVersion < Version.PNI_IDENTITY) {
|
||||||
|
jobs.put(Version.PNI_IDENTITY, new PniAccountInitializationMigrationJob());
|
||||||
|
}
|
||||||
|
|
||||||
return jobs;
|
return jobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastSeenVersion < SIGNED_PREKEY_VERSION) {
|
if (lastSeenVersion < SIGNED_PREKEY_VERSION) {
|
||||||
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(context));
|
CreateSignedPreKeyJob.enqueueIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) {
|
if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) {
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.thoughtcrime.securesms.migrations;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
|
import org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes various aspects of the PNI identity. Notably:
|
||||||
|
* - Creates an identity key
|
||||||
|
* - Creates and uploads one-time prekeys
|
||||||
|
* - Creates and uploads signed prekeys
|
||||||
|
*/
|
||||||
|
public class PniAccountInitializationMigrationJob extends MigrationJob {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(PniAccountInitializationMigrationJob.class);
|
||||||
|
|
||||||
|
public static final String KEY = "PniAccountInitializationMigrationJob";
|
||||||
|
|
||||||
|
PniAccountInitializationMigrationJob() {
|
||||||
|
this(new Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PniAccountInitializationMigrationJob(@NonNull Parameters parameters) {
|
||||||
|
super(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUiBlocking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performMigration() throws IOException {
|
||||||
|
PNI pni = SignalStore.account().getPni();
|
||||||
|
|
||||||
|
if (!Recipient.self().isRegistered() || pni == null) {
|
||||||
|
Log.w(TAG, "Not yet registered! No need to perform this migration.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalStore.account().generatePniIdentityKey();
|
||||||
|
|
||||||
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
|
SignalProtocolStore protocolStore = ApplicationDependencies.getProtocolStore().pni();
|
||||||
|
PreKeyMetadataStore metadataStore = SignalStore.account().pniPreKeys();
|
||||||
|
|
||||||
|
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true);
|
||||||
|
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
|
||||||
|
|
||||||
|
accountManager.setPreKeys(pni, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys);
|
||||||
|
metadataStore.setSignedPreKeyRegistered(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean shouldRetry(@NonNull Exception e) {
|
||||||
|
return e instanceof IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements Job.Factory<PniAccountInitializationMigrationJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull PniAccountInitializationMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
return new PniAccountInitializationMigrationJob(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,13 @@ import androidx.annotation.WorkerThread;
|
|||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl;
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
@@ -30,14 +32,15 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository.VerifyAcc
|
|||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.libsignal.util.KeyHelper;
|
import org.whispersystems.libsignal.util.KeyHelper;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
|
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||||
import org.whispersystems.signalservice.api.push.PNI;
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
@@ -74,7 +77,7 @@ public final class RegistrationRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull ProfileKey getProfileKey(@NonNull String e164) {
|
public @NonNull ProfileKey getProfileKey(@NonNull String e164) {
|
||||||
ProfileKey profileKey = findExistingProfileKey(context, e164);
|
ProfileKey profileKey = findExistingProfileKey(e164);
|
||||||
|
|
||||||
if (profileKey == null) {
|
if (profileKey == null) {
|
||||||
profileKey = ProfileKeyUtil.createNew();
|
profileKey = ProfileKeyUtil.createNew();
|
||||||
@@ -127,19 +130,22 @@ public final class RegistrationRepository {
|
|||||||
@Nullable KbsPinData kbsData)
|
@Nullable KbsPinData kbsData)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
SessionUtil.archiveAllSessions();
|
|
||||||
SenderKeyUtil.clearAllState(context);
|
|
||||||
|
|
||||||
ACI aci = ACI.parseOrThrow(response.getUuid());
|
ACI aci = ACI.parseOrThrow(response.getUuid());
|
||||||
PNI pni = PNI.parseOrThrow(response.getPni());
|
PNI pni = PNI.parseOrThrow(response.getPni());
|
||||||
boolean hasPin = response.isStorageCapable();
|
boolean hasPin = response.isStorageCapable();
|
||||||
|
|
||||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
SignalStore.account().setAci(aci);
|
||||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(context);
|
SignalStore.account().setPni(pni);
|
||||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(context, identityKey, true);
|
|
||||||
|
|
||||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createAuthenticated(context, aci, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
|
SessionUtil.archiveAllSessions();
|
||||||
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
|
SenderKeyUtil.clearAllState(context);
|
||||||
|
|
||||||
|
SignalServiceAccountManager accountManager = AccountManagerFactory.createAuthenticated(context, aci, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
|
||||||
|
SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci();
|
||||||
|
SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni();
|
||||||
|
|
||||||
|
generateAndRegisterPreKeys(aci, accountManager, aciProtocolStore, SignalStore.account().aciPreKeys());
|
||||||
|
generateAndRegisterPreKeys(pni, accountManager, pniProtocolStore, SignalStore.account().pniPreKeys());
|
||||||
|
|
||||||
if (registrationData.isFcm()) {
|
if (registrationData.isFcm()) {
|
||||||
accountManager.setGcmId(Optional.fromNullable(registrationData.getFcmToken()));
|
accountManager.setGcmId(Optional.fromNullable(registrationData.getFcmToken()));
|
||||||
@@ -151,35 +157,50 @@ public final class RegistrationRepository {
|
|||||||
recipientDatabase.setProfileSharing(selfId, true);
|
recipientDatabase.setProfileSharing(selfId, true);
|
||||||
recipientDatabase.markRegisteredOrThrow(selfId, aci);
|
recipientDatabase.markRegisteredOrThrow(selfId, aci);
|
||||||
recipientDatabase.setPni(selfId, pni);
|
recipientDatabase.setPni(selfId, pni);
|
||||||
|
|
||||||
SignalStore.account().setE164(registrationData.getE164());
|
|
||||||
SignalStore.account().setAci(aci);
|
|
||||||
SignalStore.account().setPni(pni);
|
|
||||||
recipientDatabase.setProfileKey(selfId, registrationData.getProfileKey());
|
recipientDatabase.setProfileKey(selfId, registrationData.getProfileKey());
|
||||||
|
|
||||||
ApplicationDependencies.getRecipientCache().clearSelf();
|
ApplicationDependencies.getRecipientCache().clearSelf();
|
||||||
|
|
||||||
|
SignalStore.account().setE164(registrationData.getE164());
|
||||||
SignalStore.account().setFcmToken(registrationData.getFcmToken());
|
SignalStore.account().setFcmToken(registrationData.getFcmToken());
|
||||||
SignalStore.account().setFcmEnabled(registrationData.isFcm());
|
SignalStore.account().setFcmEnabled(registrationData.isFcm());
|
||||||
|
|
||||||
ApplicationDependencies.getProtocolStore().aci().identities()
|
long now = System.currentTimeMillis();
|
||||||
.saveIdentityWithoutSideEffects(selfId,
|
saveOwnIdentityKey(selfId, aciProtocolStore, now);
|
||||||
identityKey.getPublicKey(),
|
saveOwnIdentityKey(selfId, pniProtocolStore, now);
|
||||||
IdentityDatabase.VerifiedStatus.VERIFIED,
|
|
||||||
true,
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
true);
|
|
||||||
|
|
||||||
SignalStore.account().setServicePassword(registrationData.getPassword());
|
SignalStore.account().setServicePassword(registrationData.getPassword());
|
||||||
SignalStore.account().setRegistered(true);
|
SignalStore.account().setRegistered(true);
|
||||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
|
||||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||||
|
|
||||||
PinState.onRegistration(context, kbsData, pin, hasPin);
|
PinState.onRegistration(context, kbsData, pin, hasPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateAndRegisterPreKeys(@NonNull AccountIdentifier accountId,
|
||||||
|
@NonNull SignalServiceAccountManager accountManager,
|
||||||
|
@NonNull SignalProtocolStore protocolStore,
|
||||||
|
@NonNull PreKeyMetadataStore metadataStore)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true);
|
||||||
|
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
|
||||||
|
|
||||||
|
accountManager.setPreKeys(accountId, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys);
|
||||||
|
metadataStore.setSignedPreKeyRegistered(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveOwnIdentityKey(@NonNull RecipientId selfId, @NonNull SignalServiceAccountDataStoreImpl protocolStore, long now) {
|
||||||
|
protocolStore.identities().saveIdentityWithoutSideEffects(selfId,
|
||||||
|
protocolStore.getIdentityKeyPair().getPublicKey(),
|
||||||
|
IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||||
|
true,
|
||||||
|
now,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private static @Nullable ProfileKey findExistingProfileKey(@NonNull Context context, @NonNull String e164number) {
|
private static @Nullable ProfileKey findExistingProfileKey(@NonNull String e164number) {
|
||||||
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
|
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
|
||||||
Optional<RecipientId> recipient = recipientDatabase.getByE164(e164number);
|
Optional<RecipientId> recipient = recipientDatabase.getByE164(e164number);
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,10 @@ public class TextSecurePreferences {
|
|||||||
private static final String SHOW_INVITE_REMINDER_PREF = "pref_show_invite_reminder";
|
private static final String SHOW_INVITE_REMINDER_PREF = "pref_show_invite_reminder";
|
||||||
public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size";
|
public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size";
|
||||||
|
|
||||||
private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
|
|
||||||
private static final String WIFI_SMS_PREF = "pref_wifi_sms";
|
private static final String WIFI_SMS_PREF = "pref_wifi_sms";
|
||||||
|
|
||||||
private static final String RATING_LATER_PREF = "pref_rating_later";
|
private static final String RATING_LATER_PREF = "pref_rating_later";
|
||||||
private static final String RATING_ENABLED_PREF = "pref_rating_enabled";
|
private static final String RATING_ENABLED_PREF = "pref_rating_enabled";
|
||||||
private static final String SIGNED_PREKEY_FAILURE_COUNT_PREF = "pref_signed_prekey_failure_count";
|
|
||||||
|
|
||||||
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
|
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
|
||||||
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
|
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
|
||||||
@@ -127,10 +125,6 @@ public class TextSecurePreferences {
|
|||||||
public static final String CALL_RINGTONE_PREF = "pref_call_ringtone";
|
public static final String CALL_RINGTONE_PREF = "pref_call_ringtone";
|
||||||
public static final String CALL_VIBRATE_PREF = "pref_call_vibrate";
|
public static final String CALL_VIBRATE_PREF = "pref_call_vibrate";
|
||||||
|
|
||||||
private static final String NEXT_PRE_KEY_ID = "pref_next_pre_key_id";
|
|
||||||
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
|
|
||||||
private static final String NEXT_SIGNED_PRE_KEY_ID = "pref_next_signed_pre_key_id";
|
|
||||||
|
|
||||||
public static final String BACKUP = "pref_backup";
|
public static final String BACKUP = "pref_backup";
|
||||||
public static final String BACKUP_ENABLED = "pref_backup_enabled";
|
public static final String BACKUP_ENABLED = "pref_backup_enabled";
|
||||||
private static final String BACKUP_PASSPHRASE = "pref_backup_passphrase";
|
private static final String BACKUP_PASSPHRASE = "pref_backup_passphrase";
|
||||||
@@ -400,30 +394,6 @@ public class TextSecurePreferences {
|
|||||||
return getLongPreference(context, BACKUP_TIME, -1);
|
return getLongPreference(context, BACKUP_TIME, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getNextPreKeyId(@NonNull Context context) {
|
|
||||||
return getIntegerPreference(context, NEXT_PRE_KEY_ID, new SecureRandom().nextInt(Medium.MAX_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setNextPreKeyId(@NonNull Context context, int value) {
|
|
||||||
setIntegerPrefrence(context, NEXT_PRE_KEY_ID, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getNextSignedPreKeyId(@NonNull Context context) {
|
|
||||||
return getIntegerPreference(context, NEXT_SIGNED_PRE_KEY_ID, new SecureRandom().nextInt(Medium.MAX_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setNextSignedPreKeyId(@NonNull Context context, int value) {
|
|
||||||
setIntegerPrefrence(context, NEXT_SIGNED_PRE_KEY_ID, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getActiveSignedPreKeyId(@NonNull Context context) {
|
|
||||||
return getIntegerPreference(context, ACTIVE_SIGNED_PRE_KEY_ID, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setActiveSignedPreKeyId(@NonNull Context context, int value) {
|
|
||||||
setIntegerPrefrence(context, ACTIVE_SIGNED_PRE_KEY_ID, value);;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
|
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
|
||||||
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
|
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
|
||||||
EventBus.getDefault().post(new SqlCipherMigrationConstraintObserver.SqlCipherNeedsMigrationEvent());
|
EventBus.getDefault().post(new SqlCipherMigrationConstraintObserver.SqlCipherNeedsMigrationEvent());
|
||||||
@@ -563,14 +533,6 @@ public class TextSecurePreferences {
|
|||||||
return getBooleanPreference(context, MULTI_DEVICE_PROVISIONED_PREF, false);
|
return getBooleanPreference(context, MULTI_DEVICE_PROVISIONED_PREF, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setSignedPreKeyFailureCount(Context context, int value) {
|
|
||||||
setIntegerPrefrence(context, SIGNED_PREKEY_FAILURE_COUNT_PREF, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSignedPreKeyFailureCount(Context context) {
|
|
||||||
return getIntegerPreference(context, SIGNED_PREKEY_FAILURE_COUNT_PREF, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
|
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
|
||||||
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
|
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
|
||||||
@@ -611,14 +573,6 @@ public class TextSecurePreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSignedPreKeyRegistered(Context context) {
|
|
||||||
return getBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setSignedPreKeyRegistered(Context context, boolean value) {
|
|
||||||
setBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static boolean isInThreadNotifications(Context context) {
|
public static boolean isInThreadNotifications(Context context) {
|
||||||
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
|
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
|
||||||
|
|||||||
@@ -416,18 +416,18 @@ public class SignalServiceAccountManager {
|
|||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void setPreKeys(IdentityKey identityKey, SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
public void setPreKeys(AccountIdentifier accountId, IdentityKey identityKey, SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
this.pushServiceSocket.registerPreKeys(identityKey, signedPreKey, oneTimePreKeys);
|
this.pushServiceSocket.registerPreKeys(accountId, identityKey, signedPreKey, oneTimePreKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The server's count of currently available (eg. unused) prekeys for this user.
|
* @return The server's count of currently available (eg. unused) prekeys for this user.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public int getPreKeysCount() throws IOException {
|
public int getPreKeysCount(AccountIdentifier accountId) throws IOException {
|
||||||
return this.pushServiceSocket.getAvailablePreKeys();
|
return this.pushServiceSocket.getAvailablePreKeys(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -436,16 +436,16 @@ public class SignalServiceAccountManager {
|
|||||||
* @param signedPreKey The client's new signed prekey.
|
* @param signedPreKey The client's new signed prekey.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void setSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
public void setSignedPreKey(AccountIdentifier accountId, SignedPreKeyRecord signedPreKey) throws IOException {
|
||||||
this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey);
|
this.pushServiceSocket.setCurrentSignedPreKey(accountId, signedPreKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The server's view of the client's current signed prekey.
|
* @return The server's view of the client's current signed prekey.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public SignedPreKeyEntity getSignedPreKey() throws IOException {
|
public SignedPreKeyEntity getSignedPreKey(AccountIdentifier accountId) throws IOException {
|
||||||
return this.pushServiceSocket.getCurrentSignedPreKey();
|
return this.pushServiceSocket.getCurrentSignedPreKey(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ public final class ACI extends AccountIdentifier {
|
|||||||
return this.equals(UNKNOWN);
|
return this.equals(UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAci() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return uuid.hashCode();
|
return uuid.hashCode();
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ public abstract class AccountIdentifier {
|
|||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract boolean isAci();
|
||||||
|
|
||||||
|
public final boolean isPni() {
|
||||||
|
return !isAci();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return uuid.toString();
|
return uuid.toString();
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ public final class PNI extends AccountIdentifier {
|
|||||||
super(uuid);
|
super(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAci() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return uuid.hashCode();
|
return uuid.hashCode();
|
||||||
|
|||||||
@@ -193,10 +193,10 @@ public class PushServiceSocket {
|
|||||||
private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number";
|
private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number";
|
||||||
private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s";
|
private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s";
|
||||||
|
|
||||||
private static final String PREKEY_METADATA_PATH = "/v2/keys/";
|
private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s";
|
||||||
private static final String PREKEY_PATH = "/v2/keys/%s";
|
private static final String PREKEY_PATH = "/v2/keys/%s?identity=%s";
|
||||||
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s";
|
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s";
|
||||||
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed";
|
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed?identity=%s";
|
||||||
|
|
||||||
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
|
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
|
||||||
private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s";
|
private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s";
|
||||||
@@ -563,7 +563,8 @@ public class PushServiceSocket {
|
|||||||
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
|
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerPreKeys(IdentityKey identityKey,
|
public void registerPreKeys(AccountIdentifier accountId,
|
||||||
|
IdentityKey identityKey,
|
||||||
SignedPreKeyRecord signedPreKey,
|
SignedPreKeyRecord signedPreKey,
|
||||||
List<PreKeyRecord> records)
|
List<PreKeyRecord> records)
|
||||||
throws IOException
|
throws IOException
|
||||||
@@ -578,15 +579,17 @@ public class PushServiceSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||||
signedPreKey.getKeyPair().getPublicKey(),
|
signedPreKey.getKeyPair().getPublicKey(),
|
||||||
signedPreKey.getSignature());
|
signedPreKey.getSignature());
|
||||||
|
|
||||||
String response = makeServiceRequest(String.format(PREKEY_PATH, ""), "PUT",
|
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, "", accountId.isAci() ? "aci" : "pni"),
|
||||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
"PUT",
|
||||||
|
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAvailablePreKeys() throws IOException {
|
public int getAvailablePreKeys(AccountIdentifier accountId) throws IOException {
|
||||||
String responseText = makeServiceRequest(PREKEY_METADATA_PATH, "GET", null);
|
String path = String.format(PREKEY_METADATA_PATH, accountId.isAci() ? "aci" : "pni");
|
||||||
|
String responseText = makeServiceRequest(path, "GET", null);
|
||||||
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
|
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
|
||||||
|
|
||||||
return preKeyStatus.getCount();
|
return preKeyStatus.getCount();
|
||||||
@@ -675,9 +678,10 @@ public class PushServiceSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
|
public SignedPreKeyEntity getCurrentSignedPreKey(AccountIdentifier accountId) throws IOException {
|
||||||
try {
|
try {
|
||||||
String responseText = makeServiceRequest(SIGNED_PREKEY_PATH, "GET", null);
|
String path = String.format(SIGNED_PREKEY_PATH, accountId.isAci() ? "aci" : "pni");
|
||||||
|
String responseText = makeServiceRequest(path, "GET", null);
|
||||||
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
|
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
@@ -685,11 +689,12 @@ public class PushServiceSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
public void setCurrentSignedPreKey(AccountIdentifier accountId, SignedPreKeyRecord signedPreKey) throws IOException {
|
||||||
|
String path = String.format(SIGNED_PREKEY_PATH, accountId.isAci() ? "aci" : "pni");
|
||||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||||
signedPreKey.getKeyPair().getPublicKey(),
|
signedPreKey.getKeyPair().getPublicKey(),
|
||||||
signedPreKey.getSignature());
|
signedPreKey.getSignature());
|
||||||
makeServiceRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
|
makeServiceRequest(path, "PUT", JsonUtil.toJson(signedPreKeyEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
|
public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
|
||||||
|
|||||||
Reference in New Issue
Block a user