Support PNI prekeys.
This commit is contained in:
parent
db534cd376
commit
e8ad1e8ed1
32 changed files with 808 additions and 532 deletions
|
@ -176,7 +176,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||
.addNonBlocking(this::initializeFcmCheck)
|
||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||
.addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded)
|
||||
.addNonBlocking(this::initializePeriodicTasks)
|
||||
.addNonBlocking(this::initializeCircumvention)
|
||||
.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() {
|
||||
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
|||
|
||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||
SignalStore.account().generateAciIdentityKey();
|
||||
SignalStore.account().generatePniIdentityKey();
|
||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||
|
||||
return null;
|
||||
|
|
|
@ -17,64 +17,66 @@
|
|||
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||
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.SignedPreKeyStore;
|
||||
import org.whispersystems.libsignal.util.Medium;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PreKeyUtil {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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<>();
|
||||
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;
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
|
||||
|
||||
preKeyStore.storePreKey(preKeyId, record);
|
||||
protocolStore.storePreKey(preKeyId, 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
SignedPreKeyStore signedPreKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
||||
int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context);
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||
int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(protocolStore.getIdentityKeyPair().getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||
|
||||
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
TextSecurePreferences.setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||
protocolStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||
|
||||
if (active) {
|
||||
TextSecurePreferences.setActiveSignedPreKeyId(context, signedPreKeyId);
|
||||
if (setAsActive) {
|
||||
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
|
||||
}
|
||||
|
||||
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) {
|
||||
return TextSecurePreferences.getActiveSignedPreKeyId(context);
|
||||
}
|
||||
int activeSignedPreKeyId = metadataStore.getActiveSignedPreKeyId();
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -22,16 +21,16 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||
private static final Object LOCK = new Object();
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
private final AccountIdentifier accountId;
|
||||
|
||||
public TextSecurePreKeyStore(@NonNull Context context) {
|
||||
this.context = context;
|
||||
public TextSecurePreKeyStore(@NonNull AccountIdentifier accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
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);
|
||||
else return preKeyRecord;
|
||||
|
@ -41,7 +40,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||
@Override
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||
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);
|
||||
else return signedPreKeyRecord;
|
||||
|
@ -51,41 +50,41 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
|||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
synchronized (LOCK) {
|
||||
return SignalDatabase.signedPreKeys().getAllSignedPreKeys();
|
||||
return SignalDatabase.signedPreKeys().getAll(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||
synchronized (LOCK) {
|
||||
SignalDatabase.preKeys().insertPreKey(preKeyId, record);
|
||||
SignalDatabase.oneTimePreKeys().insert(accountId, preKeyId, record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||
synchronized (LOCK) {
|
||||
SignalDatabase.signedPreKeys().insertSignedPreKey(signedPreKeyId, record);
|
||||
SignalDatabase.signedPreKeys().insert(accountId, signedPreKeyId, record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int preKeyId) {
|
||||
return SignalDatabase.preKeys().getPreKey(preKeyId) != null;
|
||||
return SignalDatabase.oneTimePreKeys().get(accountId, preKeyId) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||
return SignalDatabase.signedPreKeys().getSignedPreKey(signedPreKeyId) != null;
|
||||
return SignalDatabase.signedPreKeys().get(accountId, signedPreKeyId) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePreKey(int preKeyId) {
|
||||
SignalDatabase.preKeys().removePreKey(preKeyId);
|
||||
SignalDatabase.oneTimePreKeys().delete(accountId, preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
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.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -17,6 +18,7 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
|||
import org.thoughtcrime.securesms.keyvalue.KeyValuePersistentStorage;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -60,6 +62,11 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
|
|||
return instance;
|
||||
}
|
||||
|
||||
public static boolean exists(Context context) {
|
||||
return context.getDatabasePath(DATABASE_NAME).exists();
|
||||
}
|
||||
|
||||
|
||||
private KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||
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:JvmStatic
|
||||
@get:JvmName("preKeys")
|
||||
val preKeys: OneTimePreKeyDatabase
|
||||
@get:JvmName("oneTimePreKeys")
|
||||
val oneTimePreKeys: OneTimePreKeyDatabase
|
||||
get() = instance!!.preKeyDatabase
|
||||
|
||||
@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.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -94,7 +95,7 @@ public final class PreKeyMigrationHelper {
|
|||
reader.close();
|
||||
|
||||
Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId);
|
||||
TextSecurePreferences.setNextPreKeyId(context, index.nextPreKeyId);
|
||||
SignalStore.account().aciPreKeys().setNextOneTimePreKeyId(index.nextPreKeyId);
|
||||
} catch (IOException 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 active signed prekey id: " + index.activeSignedPreKeyId);
|
||||
TextSecurePreferences.setNextSignedPreKeyId(context, index.nextSignedPreKeyId);
|
||||
TextSecurePreferences.setActiveSignedPreKeyId(context, index.activeSignedPreKeyId);
|
||||
SignalStore.account().aciPreKeys().setNextSignedPreKeyId(index.nextSignedPreKeyId);
|
||||
SignalStore.account().aciPreKeys().setActiveSignedPreKeyId(index.activeSignedPreKeyId);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.database.helpers
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.content.ContentValues
|
||||
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.ChatColors
|
||||
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.model.databaseprotos.ReactionList
|
||||
import org.thoughtcrime.securesms.database.requireString
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
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.Triple
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
|
@ -185,11 +189,12 @@ object SignalDatabaseMigrations {
|
|||
private const val PNI_CLEANUP = 127
|
||||
private const val MESSAGE_RANGES = 128
|
||||
private const val REACTION_TRIGGER_FIX = 129
|
||||
private const val PNI_STORES = 130
|
||||
|
||||
const val DATABASE_VERSION = 129
|
||||
const val DATABASE_VERSION = 130
|
||||
|
||||
@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) {
|
||||
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)
|
||||
|
@ -2285,6 +2290,80 @@ object SignalDatabaseMigrations {
|
|||
""".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
|
||||
|
@ -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) {
|
||||
try {
|
||||
val messageId = CursorUtil.requireLong(cursor, "_id")
|
||||
|
@ -2314,4 +2396,22 @@ object SignalDatabaseMigrations {
|
|||
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
|
||||
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);
|
||||
|
||||
SignalServiceAccountDataStoreImpl aciStore = new SignalServiceAccountDataStoreImpl(context,
|
||||
new TextSecurePreKeyStore(context),
|
||||
new TextSecurePreKeyStore(localAci),
|
||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getAciIdentityKey()),
|
||||
new TextSecureSessionStore(context),
|
||||
new SignalSenderKeyStore(context));
|
||||
|
||||
SignalServiceAccountDataStoreImpl pniStore = new SignalServiceAccountDataStoreImpl(context,
|
||||
new TextSecurePreKeyStore(context),
|
||||
new TextSecurePreKeyStore(localPni),
|
||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getPniIdentityKey()),
|
||||
new TextSecureSessionStore(context),
|
||||
new SignalSenderKeyStore(context));
|
||||
|
|
|
@ -4,37 +4,24 @@ 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.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
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;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
|
||||
/**
|
||||
* Deprecated. Only exists for previously-enqueued jobs.
|
||||
* Use {@link PreKeyUtil#cleanSignedPreKeys(SignalProtocolStore, PreKeyMetadataStore)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class CleanPreKeysJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "CleanPreKeysJob";
|
||||
|
||||
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) {
|
||||
super(parameters);
|
||||
}
|
||||
|
@ -50,47 +37,13 @@ public class CleanPreKeysJob extends BaseJob {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
try {
|
||||
Log.i(TAG, "Cleaning prekeys...");
|
||||
|
||||
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);
|
||||
}
|
||||
public void onRun() {
|
||||
PreKeyUtil.cleanSignedPreKeys(ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys());
|
||||
PreKeyUtil.cleanSignedPreKeys(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception throwable) {
|
||||
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
||||
if (throwable instanceof PushNetworkException) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -99,30 +52,6 @@ public class CleanPreKeysJob extends BaseJob {
|
|||
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> {
|
||||
@Override
|
||||
public @NonNull CleanPreKeysJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
|
|
|
@ -1,36 +1,41 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
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.keyvalue.SignalStore;
|
||||
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.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
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 static final String KEY = "CreateSignedPreKeyJob";
|
||||
|
||||
private static final String TAG = Log.tag(CreateSignedPreKeyJob.class);
|
||||
|
||||
public CreateSignedPreKeyJob(Context context) {
|
||||
private CreateSignedPreKeyJob() {
|
||||
this(new Job.Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setMaxInstancesForFactory(1)
|
||||
.setQueue("CreateSignedPreKeyJob")
|
||||
.setMaxAttempts(25)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(30))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@ -38,6 +43,16 @@ public class CreateSignedPreKeyJob extends BaseJob {
|
|||
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
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
|
@ -50,22 +65,33 @@ public class CreateSignedPreKeyJob extends BaseJob {
|
|||
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
if (TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
||||
Log.w(TAG, "Signed prekey already registered...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SignalStore.account().isRegistered()) {
|
||||
Log.w(TAG, "Not yet registered...");
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
IdentityKeyPair identityKeyPair = SignalStore.account().getAciIdentityKey();
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair, true);
|
||||
createPreKeys(SignalStore.account().getAci(), ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys());
|
||||
createPreKeys(SignalStore.account().getPni(), ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys());
|
||||
}
|
||||
|
||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
private void createPreKeys(@Nullable AccountIdentifier accountId, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore)
|
||||
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
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
|||
import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.PinOptOutMigration;
|
||||
import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.PniAccountInitializationMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.PniMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
|
||||
|
@ -197,6 +198,7 @@ public final class JobManagerFactories {
|
|||
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
||||
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
|
||||
put(PinReminderMigrationJob.KEY, new PinReminderMigrationJob.Factory());
|
||||
put(PniAccountInitializationMigrationJob.KEY, new PniAccountInitializationMigrationJob.Factory());
|
||||
put(PniMigrationJob.KEY, new PniMigrationJob.Factory());
|
||||
put(ProfileMigrationJob.KEY, new ProfileMigrationJob.Factory());
|
||||
put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory());
|
||||
|
|
|
@ -100,7 +100,7 @@ public abstract class PushSendJob extends SendJob {
|
|||
|
||||
@Override
|
||||
protected final void onSend() throws Exception {
|
||||
if (TextSecurePreferences.getSignedPreKeyFailureCount(context) > 5) {
|
||||
if (SignalStore.account().aciPreKeys().getSignedPreKeyFailureCount() > 5) {
|
||||
ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob());
|
||||
throw new TextSecureExpiredException("Too many signed prekey rotation failures");
|
||||
}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
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.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
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.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.PushNetworkException;
|
||||
|
||||
|
@ -22,6 +26,11 @@ import java.io.IOException;
|
|||
import java.util.List;
|
||||
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 static final String KEY = "RefreshPreKeysJob";
|
||||
|
@ -72,34 +81,61 @@ public class RefreshPreKeysJob extends BaseJob {
|
|||
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();
|
||||
|
||||
Log.i(TAG, "Available keys: " + availableKeys);
|
||||
|
||||
if (availableKeys >= PREKEY_MINIMUM && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
||||
Log.i(TAG, "Available keys sufficient.");
|
||||
SignalStore.misc().setLastPrekeyRefreshTime(System.currentTimeMillis());
|
||||
return;
|
||||
if (refreshKeys(aci, aciProtocolStore, aciPreKeyStore)) {
|
||||
PreKeyUtil.cleanSignedPreKeys(aciProtocolStore, aciPreKeyStore);
|
||||
}
|
||||
|
||||
if (refreshKeys(pni, pniProtocolStore, pniPreKeyStore)) {
|
||||
PreKeyUtil.cleanSignedPreKeys(pniProtocolStore, pniPreKeyStore);
|
||||
}
|
||||
|
||||
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());
|
||||
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
|
||||
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||
if (exception instanceof NonSuccessfulResponseCodeException) return false;
|
||||
|
|
|
@ -4,21 +4,28 @@ package org.thoughtcrime.securesms.jobs;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
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.keyvalue.SignalStore;
|
||||
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.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 java.io.IOException;
|
||||
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 static final String KEY = "RotateSignedPreKeyJob";
|
||||
|
@ -53,17 +60,34 @@ public class RotateSignedPreKeyJob extends BaseJob {
|
|||
public void onRun() throws Exception {
|
||||
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();
|
||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, false);
|
||||
|
||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
||||
accountManager.setSignedPreKey(accountId, signedPreKeyRecord);
|
||||
|
||||
PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
TextSecurePreferences.setSignedPreKeyFailureCount(context, 0);
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new CleanPreKeysJob());
|
||||
metadataStore.setActiveSignedPreKeyId(signedPreKeyRecord.getId());
|
||||
metadataStore.setSignedPreKeyRegistered(true);
|
||||
metadataStore.setSignedPreKeyFailureCount(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,7 +97,11 @@ public class RotateSignedPreKeyJob extends BaseJob {
|
|||
|
||||
@Override
|
||||
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> {
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
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.IdentityKeyPair
|
||||
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.PNI
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.security.SecureRandom
|
||||
|
||||
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_DEVICE_NAME = "account.device_name"
|
||||
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_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_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
|
||||
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. */
|
||||
var registrationId: Int
|
||||
get() = getInteger(KEY_REGISTRATION_ID, 0)
|
||||
set(value) = putInteger(KEY_REGISTRATION_ID, value)
|
||||
var registrationId: Int by integerValue(KEY_REGISTRATION_ID, 0)
|
||||
|
||||
/** The identity key pair for the ACI identity. */
|
||||
val aciIdentityKey: IdentityKeyPair
|
||||
|
@ -147,8 +160,8 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||
val key: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
||||
.putBlob(KEY_PNI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
||||
.putBlob(KEY_PNI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
@ -174,11 +187,27 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||
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. */
|
||||
var fcmEnabled: Boolean
|
||||
@JvmName("isFcmEnabled")
|
||||
get() = getBoolean(KEY_FCM_ENABLED, false)
|
||||
set(value) = putBoolean(KEY_FCM_ENABLED, value)
|
||||
@get:JvmName("isFcmEnabled")
|
||||
var fcmEnabled: Boolean by booleanValue(KEY_FCM_ENABLED, false)
|
||||
|
||||
/** The FCM token, which allows the server to send us FCM messages. */
|
||||
var fcmToken: String?
|
||||
|
@ -249,6 +278,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||
ApplicationDependencies.getGroupsV2Authorization().clear()
|
||||
}
|
||||
|
||||
/** Do not alter. If you need to migrate more stuff, create a new method. */
|
||||
private fun migrateFromSharedPrefsV1(context: Context) {
|
||||
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))
|
||||
}
|
||||
|
||||
/** Do not alter. If you need to migrate more stuff, create a new method. */
|
||||
private fun migrateFromSharedPrefsV2(context: Context) {
|
||||
Log.i(TAG, "[V2] Migrating account values from shared prefs.")
|
||||
|
||||
val masterSecretPrefs: SharedPreferences = context.getSharedPreferences("SecureSMS-Preferences", 0)
|
||||
val defaultPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val storeWriter: KeyValueStore.Writer = store.beginWrite()
|
||||
|
||||
if (masterSecretPrefs.hasStringData("pref_identity_public_v3")) {
|
||||
Log.i(TAG, "Migrating modern identity key.")
|
||||
|
||||
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_v3", null)!!)
|
||||
val identityPrivate = Base64.decode(masterSecretPrefs.getString("pref_identity_private_v3", null)!!)
|
||||
|
||||
store
|
||||
.beginWrite()
|
||||
storeWriter
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||
.commit()
|
||||
} else if (masterSecretPrefs.hasStringData("pref_identity_public_curve25519")) {
|
||||
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 identityPrivate = masterCipher.decryptKey(Base64.decode(masterSecretPrefs.getString("pref_identity_private_curve25519", null)!!)).serialize()
|
||||
|
||||
store
|
||||
.beginWrite()
|
||||
storeWriter
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||
.commit()
|
||||
} else {
|
||||
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
|
||||
.edit()
|
||||
.remove("pref_identity_public_v3")
|
||||
|
@ -308,6 +345,11 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
|||
.edit()
|
||||
.remove("pref_local_uuid")
|
||||
.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_registered")
|
||||
.remove("pref_local_registration_id")
|
||||
|
|
|
@ -3,102 +3,102 @@ package org.thoughtcrime.securesms.keyvalue
|
|||
import kotlin.reflect.KProperty
|
||||
|
||||
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> {
|
||||
return BooleanValue(key, default)
|
||||
return BooleanValue(key, default, this.store)
|
||||
}
|
||||
|
||||
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> {
|
||||
return IntValue(key, default)
|
||||
return IntValue(key, default, this.store)
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
* 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 {
|
||||
return getValue(thisRef as SignalStoreValues)
|
||||
return getValue(store)
|
||||
}
|
||||
|
||||
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 setValue(values: SignalStoreValues, value: T)
|
||||
internal abstract fun getValue(values: KeyValueStore): T
|
||||
internal abstract fun setValue(values: KeyValueStore, value: T)
|
||||
}
|
||||
|
||||
private class LongValue(private val key: String, private val default: Long) : SignalStoreValueDelegate<Long>() {
|
||||
override fun getValue(values: SignalStoreValues): Long {
|
||||
private class LongValue(private val key: String, private val default: Long, store: KeyValueStore) : SignalStoreValueDelegate<Long>(store) {
|
||||
override fun getValue(values: KeyValueStore): Long {
|
||||
return values.getLong(key, default)
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: Long) {
|
||||
values.putLong(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: Long) {
|
||||
values.beginWrite().putLong(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private class BooleanValue(private val key: String, private val default: Boolean) : SignalStoreValueDelegate<Boolean>() {
|
||||
override fun getValue(values: SignalStoreValues): Boolean {
|
||||
private class BooleanValue(private val key: String, private val default: Boolean, store: KeyValueStore) : SignalStoreValueDelegate<Boolean>(store) {
|
||||
override fun getValue(values: KeyValueStore): Boolean {
|
||||
return values.getBoolean(key, default)
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: Boolean) {
|
||||
values.putBoolean(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: Boolean) {
|
||||
values.beginWrite().putBoolean(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private class StringValue<T : String?>(private val key: String, private val default: T) : SignalStoreValueDelegate<T>() {
|
||||
override fun getValue(values: SignalStoreValues): T {
|
||||
private class StringValue<T : String?>(private val key: String, private val default: T, store: KeyValueStore) : SignalStoreValueDelegate<T>(store) {
|
||||
override fun getValue(values: KeyValueStore): T {
|
||||
return values.getString(key, default) as T
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: T) {
|
||||
values.putString(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: T) {
|
||||
values.beginWrite().putString(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private class IntValue(private val key: String, private val default: Int) : SignalStoreValueDelegate<Int>() {
|
||||
override fun getValue(values: SignalStoreValues): Int {
|
||||
private class IntValue(private val key: String, private val default: Int, store: KeyValueStore) : SignalStoreValueDelegate<Int>(store) {
|
||||
override fun getValue(values: KeyValueStore): Int {
|
||||
return values.getInteger(key, default)
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: Int) {
|
||||
values.putInteger(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: Int) {
|
||||
values.beginWrite().putInteger(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private class FloatValue(private val key: String, private val default: Float) : SignalStoreValueDelegate<Float>() {
|
||||
override fun getValue(values: SignalStoreValues): Float {
|
||||
private class FloatValue(private val key: String, private val default: Float, store: KeyValueStore) : SignalStoreValueDelegate<Float>(store) {
|
||||
override fun getValue(values: KeyValueStore): Float {
|
||||
return values.getFloat(key, default)
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: Float) {
|
||||
values.putFloat(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: Float) {
|
||||
values.beginWrite().putFloat(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private class BlobValue(private val key: String, private val default: ByteArray) : SignalStoreValueDelegate<ByteArray>() {
|
||||
override fun getValue(values: SignalStoreValues): ByteArray {
|
||||
private class BlobValue(private val key: String, private val default: ByteArray, store: KeyValueStore) : SignalStoreValueDelegate<ByteArray>(store) {
|
||||
override fun getValue(values: KeyValueStore): ByteArray {
|
||||
return values.getBlob(key, default)
|
||||
}
|
||||
|
||||
override fun setValue(values: SignalStoreValues, value: ByteArray) {
|
||||
values.putBlob(key, value)
|
||||
override fun setValue(values: KeyValueStore, value: ByteArray) {
|
||||
values.beginWrite().putBlob(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,9 +97,10 @@ public class ApplicationMigrations {
|
|||
static final int FIX_EMOJI_QUALITY = 53;
|
||||
static final int CHANGE_NUMBER_CAPABILITY_4 = 54;
|
||||
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
|
||||
|
@ -421,6 +422,10 @@ public class ApplicationMigrations {
|
|||
jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.PNI_IDENTITY) {
|
||||
jobs.put(Version.PNI_IDENTITY, new PniAccountInitializationMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||
}
|
||||
|
||||
if (lastSeenVersion < SIGNED_PREKEY_VERSION) {
|
||||
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(context));
|
||||
CreateSignedPreKeyJob.enqueueIfNeeded();
|
||||
}
|
||||
|
||||
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.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||
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.RecipientDatabase;
|
||||
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.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.KeyHelper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
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.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
@ -74,7 +77,7 @@ public final class RegistrationRepository {
|
|||
}
|
||||
|
||||
public @NonNull ProfileKey getProfileKey(@NonNull String e164) {
|
||||
ProfileKey profileKey = findExistingProfileKey(context, e164);
|
||||
ProfileKey profileKey = findExistingProfileKey(e164);
|
||||
|
||||
if (profileKey == null) {
|
||||
profileKey = ProfileKeyUtil.createNew();
|
||||
|
@ -127,19 +130,22 @@ public final class RegistrationRepository {
|
|||
@Nullable KbsPinData kbsData)
|
||||
throws IOException
|
||||
{
|
||||
SessionUtil.archiveAllSessions();
|
||||
SenderKeyUtil.clearAllState(context);
|
||||
|
||||
ACI aci = ACI.parseOrThrow(response.getUuid());
|
||||
PNI pni = PNI.parseOrThrow(response.getPni());
|
||||
boolean hasPin = response.isStorageCapable();
|
||||
|
||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(context);
|
||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(context, identityKey, true);
|
||||
SignalStore.account().setAci(aci);
|
||||
SignalStore.account().setPni(pni);
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createAuthenticated(context, aci, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
|
||||
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
|
||||
SessionUtil.archiveAllSessions();
|
||||
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()) {
|
||||
accountManager.setGcmId(Optional.fromNullable(registrationData.getFcmToken()));
|
||||
|
@ -151,35 +157,50 @@ public final class RegistrationRepository {
|
|||
recipientDatabase.setProfileSharing(selfId, true);
|
||||
recipientDatabase.markRegisteredOrThrow(selfId, aci);
|
||||
recipientDatabase.setPni(selfId, pni);
|
||||
|
||||
SignalStore.account().setE164(registrationData.getE164());
|
||||
SignalStore.account().setAci(aci);
|
||||
SignalStore.account().setPni(pni);
|
||||
recipientDatabase.setProfileKey(selfId, registrationData.getProfileKey());
|
||||
|
||||
ApplicationDependencies.getRecipientCache().clearSelf();
|
||||
|
||||
SignalStore.account().setE164(registrationData.getE164());
|
||||
SignalStore.account().setFcmToken(registrationData.getFcmToken());
|
||||
SignalStore.account().setFcmEnabled(registrationData.isFcm());
|
||||
|
||||
ApplicationDependencies.getProtocolStore().aci().identities()
|
||||
.saveIdentityWithoutSideEffects(selfId,
|
||||
identityKey.getPublicKey(),
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||
true,
|
||||
System.currentTimeMillis(),
|
||||
true);
|
||||
long now = System.currentTimeMillis();
|
||||
saveOwnIdentityKey(selfId, aciProtocolStore, now);
|
||||
saveOwnIdentityKey(selfId, pniProtocolStore, now);
|
||||
|
||||
SignalStore.account().setServicePassword(registrationData.getPassword());
|
||||
SignalStore.account().setRegistered(true);
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||
|
||||
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
|
||||
private static @Nullable ProfileKey findExistingProfileKey(@NonNull Context context, @NonNull String e164number) {
|
||||
private static @Nullable ProfileKey findExistingProfileKey(@NonNull String e164number) {
|
||||
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
|
||||
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";
|
||||
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 RATING_LATER_PREF = "pref_rating_later";
|
||||
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 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_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_ENABLED = "pref_backup_enabled";
|
||||
private static final String BACKUP_PASSPHRASE = "pref_backup_passphrase";
|
||||
|
@ -400,30 +394,6 @@ public class TextSecurePreferences {
|
|||
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) {
|
||||
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
|
||||
EventBus.getDefault().post(new SqlCipherMigrationConstraintObserver.SqlCipherNeedsMigrationEvent());
|
||||
|
@ -563,14 +533,6 @@ public class TextSecurePreferences {
|
|||
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
|
||||
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
|
||||
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
|
||||
public static boolean isInThreadNotifications(Context context) {
|
||||
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
|
||||
|
|
|
@ -416,18 +416,18 @@ public class SignalServiceAccountManager {
|
|||
*
|
||||
* @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
|
||||
{
|
||||
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.
|
||||
* @throws IOException
|
||||
*/
|
||||
public int getPreKeysCount() throws IOException {
|
||||
return this.pushServiceSocket.getAvailablePreKeys();
|
||||
public int getPreKeysCount(AccountIdentifier accountId) throws IOException {
|
||||
return this.pushServiceSocket.getAvailablePreKeys(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -436,16 +436,16 @@ public class SignalServiceAccountManager {
|
|||
* @param signedPreKey The client's new signed prekey.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
||||
this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey);
|
||||
public void setSignedPreKey(AccountIdentifier accountId, SignedPreKeyRecord signedPreKey) throws IOException {
|
||||
this.pushServiceSocket.setCurrentSignedPreKey(accountId, signedPreKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server's view of the client's current signed prekey.
|
||||
* @throws IOException
|
||||
*/
|
||||
public SignedPreKeyEntity getSignedPreKey() throws IOException {
|
||||
return this.pushServiceSocket.getCurrentSignedPreKey();
|
||||
public SignedPreKeyEntity getSignedPreKey(AccountIdentifier accountId) throws IOException {
|
||||
return this.pushServiceSocket.getCurrentSignedPreKey(accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,6 +83,11 @@ public final class ACI extends AccountIdentifier {
|
|||
return this.equals(UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAci() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return uuid.hashCode();
|
||||
|
|
|
@ -17,6 +17,12 @@ public abstract class AccountIdentifier {
|
|||
return uuid;
|
||||
}
|
||||
|
||||
public abstract boolean isAci();
|
||||
|
||||
public final boolean isPni() {
|
||||
return !isAci();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uuid.toString();
|
||||
|
|
|
@ -27,6 +27,11 @@ public final class PNI extends AccountIdentifier {
|
|||
super(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAci() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int 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 IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s";
|
||||
|
||||
private static final String PREKEY_METADATA_PATH = "/v2/keys/";
|
||||
private static final String PREKEY_PATH = "/v2/keys/%s";
|
||||
private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%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 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_MESSAGE_PATH = "/v1/provisioning/%s";
|
||||
|
@ -563,7 +563,8 @@ public class PushServiceSocket {
|
|||
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
|
||||
}
|
||||
|
||||
public void registerPreKeys(IdentityKey identityKey,
|
||||
public void registerPreKeys(AccountIdentifier accountId,
|
||||
IdentityKey identityKey,
|
||||
SignedPreKeyRecord signedPreKey,
|
||||
List<PreKeyRecord> records)
|
||||
throws IOException
|
||||
|
@ -578,15 +579,17 @@ public class PushServiceSocket {
|
|||
}
|
||||
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
|
||||
String response = makeServiceRequest(String.format(PREKEY_PATH, ""), "PUT",
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, "", accountId.isAci() ? "aci" : "pni"),
|
||||
"PUT",
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
}
|
||||
|
||||
public int getAvailablePreKeys() throws IOException {
|
||||
String responseText = makeServiceRequest(PREKEY_METADATA_PATH, "GET", null);
|
||||
public int getAvailablePreKeys(AccountIdentifier accountId) throws IOException {
|
||||
String path = String.format(PREKEY_METADATA_PATH, accountId.isAci() ? "aci" : "pni");
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
|
||||
|
||||
return preKeyStatus.getCount();
|
||||
|
@ -675,9 +678,10 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
|
||||
public SignedPreKeyEntity getCurrentSignedPreKey(AccountIdentifier accountId) throws IOException {
|
||||
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);
|
||||
} catch (NotFoundException 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(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
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)
|
||||
|
|
Loading…
Add table
Reference in a new issue