Add support for kyber prekeys.

This commit is contained in:
Greyson Parrelli
2023-05-22 14:03:31 -07:00
committed by Cody Henthorne
parent 15c248184f
commit e2ef8e2ef9
24 changed files with 669 additions and 208 deletions

View File

@@ -31,6 +31,7 @@ import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceIdType
@@ -244,10 +245,19 @@ class ChangeNumberRepository(
SignalStore.account().setPniIdentityKeyAfterChangeNumber(pniIdentityKeyPair)
val signedPreKey = pniProtocolStore.loadSignedPreKey(pniSignedPreyKeyId)
val oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(pniProtocolStore, pniMetadataStore)
val oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimeEcPreKeys(pniProtocolStore, pniMetadataStore)
pniMetadataStore.activeSignedPreKeyId = signedPreKey.id
accountManager.setPreKeys(ServiceIdType.PNI, pniProtocolStore.identityKeyPair.publicKey, signedPreKey, oneTimePreKeys)
accountManager.setPreKeys(
PreKeyUpload(
serviceIdType = ServiceIdType.PNI,
identityKey = pniProtocolStore.identityKeyPair.publicKey,
signedPreKey = signedPreKey,
oneTimeEcPreKeys = oneTimePreKeys,
lastResortKyberPreKey = null,
oneTimeKyberPreKeys = null
)
)
pniMetadataStore.isSignedPreKeyRegistered = true
pniProtocolStore.identities().saveIdentityWithoutSideEffects(

View File

@@ -25,11 +25,15 @@ import org.signal.libsignal.protocol.InvalidKeyIdException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.signal.libsignal.protocol.kem.KEMKeyPair;
import org.signal.libsignal.protocol.kem.KEMKeyType;
import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignalProtocolStore;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.Medium;
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import java.util.Comparator;
import java.util.LinkedList;
@@ -44,11 +48,11 @@ public class PreKeyUtil {
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 @NonNull List<PreKeyRecord> generateAndStoreOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
Log.i(TAG, "Generating one-time EC prekeys...");
List<PreKeyRecord> records = new LinkedList<>();
int preKeyIdOffset = metadataStore.getNextOneTimePreKeyId();
int preKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId();
for (int i = 0; i < BATCH_SIZE; i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
@@ -59,7 +63,27 @@ public class PreKeyUtil {
records.add(record);
}
metadataStore.setNextOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
metadataStore.setNextEcOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
return records;
}
public synchronized static @NonNull List<KyberPreKeyRecord> generateAndStoreOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
Log.i(TAG, "Generating one-time kyber prekeys...");
List<KyberPreKeyRecord> records = new LinkedList<>();
int preKeyIdOffset = metadataStore.getNextKyberPreKeyId();
for (int i = 0; i < BATCH_SIZE; i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
KyberPreKeyRecord record = generateKyberPreKey(preKeyId, protocolStore.getIdentityKeyPair().getPrivateKey());
protocolStore.storeKyberPreKey(preKeyId, record);
records.add(record);
}
metadataStore.setNextKyberPreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
return records;
}
@@ -94,6 +118,31 @@ public class PreKeyUtil {
}
}
public synchronized static @NonNull KyberPreKeyRecord generateAndStoreLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
return generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey());
}
public synchronized static @NonNull KyberPreKeyRecord generateAndStoreLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore,
@NonNull PreKeyMetadataStore metadataStore,
@NonNull ECPrivateKey privateKey)
{
int id = metadataStore.getNextKyberPreKeyId();
KyberPreKeyRecord record = generateKyberPreKey(id, privateKey);
protocolStore.storeKyberPreKey(id, record);
metadataStore.setNextKyberPreKeyId((id + 1) % Medium.MAX_VALUE);
return record;
}
public synchronized static @NonNull KyberPreKeyRecord generateKyberPreKey(int id, @NonNull ECPrivateKey privateKey) {
KEMKeyPair keyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024);
byte[] signature = privateKey.calculateSignature(keyPair.getPublicKey().serialize());
return new KyberPreKeyRecord(id, System.currentTimeMillis(), keyPair, signature);
}
/**
* Finds all of the signed prekeys that are older than the archive age, and archive all but the youngest of those.
*/
@@ -123,4 +172,34 @@ public class PreKeyUtil {
Log.w(TAG, e);
}
}
/**
* 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 cleanLastResortKyberPreKeys(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
Log.i(TAG, "Cleaning kyber prekeys...");
int activeLastResortKeyId = metadataStore.getLastResortKyberPreKeyId();
if (activeLastResortKeyId < 0) {
return;
}
try {
long now = System.currentTimeMillis();
KyberPreKeyRecord currentRecord = protocolStore.loadKyberPreKey(activeLastResortKeyId);
List<KyberPreKeyRecord> allRecords = protocolStore.loadLastResortKyberPreKeys();
allRecords.stream()
.filter(r -> r.getId() != currentRecord.getId())
.filter(r -> (now - r.getTimestamp()) > ARCHIVE_AGE)
.sorted(Comparator.comparingLong(KyberPreKeyRecord::getTimestamp).reversed())
.skip(1)
.forEach(record -> {
Log.i(TAG, "Removing kyber prekey record: " + record.getId() + " with timestamp: " + record.getTimestamp());
protocolStore.removeKyberPreKey(record.getId());
});
} catch (InvalidKeyIdException e) {
Log.w(TAG, e);
}
}
}

View File

@@ -8,5 +8,8 @@ interface PreKeyMetadataStore {
var activeSignedPreKeyId: Int
var isSignedPreKeyRegistered: Boolean
var lastSignedPreKeyRotationTime: Long
var nextOneTimePreKeyId: Int
var nextEcOneTimePreKeyId: Int
var nextKyberPreKeyId: Int
var lastResortKyberPreKeyId: Int
var lastResortKyberPreKeyRotationTime: Long
}

View File

@@ -10,13 +10,14 @@ import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.KyberPreKeyStore
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.SignalServiceKyberPreKeyStore
import org.whispersystems.signalservice.api.push.ServiceId
import kotlin.jvm.Throws
/**
* An implementation of the [KyberPreKeyStore] that stores entries in [org.thoughtcrime.securesms.database.KyberPreKeyTable].
*/
class SignalKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreKeyStore {
class SignalKyberPreKeyStore(private val selfServiceId: ServiceId) : SignalServiceKyberPreKeyStore {
@Throws(InvalidKeyIdException::class)
override fun loadKyberPreKey(kyberPreKeyId: Int): KyberPreKeyRecord {
@@ -31,8 +32,22 @@ class SignalKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreKey
}
}
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> {
ReentrantSessionLock.INSTANCE.acquire().use {
return SignalDatabase.kyberPreKeys.getAllLastResort(selfServiceId).map { it.record }
}
}
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord) {
error("This method is only used in tests")
ReentrantSessionLock.INSTANCE.acquire().use {
return SignalDatabase.kyberPreKeys.insert(selfServiceId, kyberPreKeyId, record, false)
}
}
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) {
ReentrantSessionLock.INSTANCE.acquire().use {
return SignalDatabase.kyberPreKeys.insert(selfServiceId, kyberPreKeyId, kyberPreKeyRecord, true)
}
}
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean {
@@ -46,4 +61,10 @@ class SignalKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreKey
SignalDatabase.kyberPreKeys.deleteIfNotLastResort(selfServiceId, kyberPreKeyId)
}
}
override fun removeKyberPreKey(kyberPreKeyId: Int) {
ReentrantSessionLock.INSTANCE.acquire().use {
SignalDatabase.kyberPreKeys.delete(selfServiceId, kyberPreKeyId)
}
}
}

View File

@@ -181,11 +181,21 @@ public class SignalServiceAccountDataStoreImpl implements SignalServiceAccountDa
return kyberPreKeyStore.loadKyberPreKeys();
}
@Override
public @NonNull List<KyberPreKeyRecord> loadLastResortKyberPreKeys() {
return kyberPreKeyStore.loadLastResortKyberPreKeys();
}
@Override
public void storeKyberPreKey(int kyberPreKeyId, KyberPreKeyRecord record) {
kyberPreKeyStore.storeKyberPreKey(kyberPreKeyId, record);
}
@Override
public void storeLastResortKyberPreKey(int kyberPreKeyId, @NonNull KyberPreKeyRecord kyberPreKeyRecord) {
kyberPreKeyStore.storeKyberPreKey(kyberPreKeyId, kyberPreKeyRecord);
}
@Override
public boolean containsKyberPreKey(int kyberPreKeyId) {
return kyberPreKeyStore.containsKyberPreKey(kyberPreKeyId);
@@ -196,6 +206,11 @@ public class SignalServiceAccountDataStoreImpl implements SignalServiceAccountDa
kyberPreKeyStore.markKyberPreKeyUsed(kyberPreKeyId);
}
@Override
public void removeKyberPreKey(int kyberPreKeyId) {
kyberPreKeyStore.removeKyberPreKey(kyberPreKeyId);
}
@Override
public void storeSenderKey(SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) {
senderKeyStore.storeSenderKey(sender, distributionId, record);

View File

@@ -9,6 +9,7 @@ import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireNonNullBlob
import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceId
@@ -71,6 +72,20 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab
}
}
fun getAllLastResort(serviceId: ServiceId): List<KyberPreKey> {
return readableDatabase
.select(LAST_RESORT, SERIALIZED)
.from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY")
.where("$ACCOUNT_ID = ? AND $LAST_RESORT = ?", serviceId, 1)
.run()
.readToList { cursor ->
KyberPreKey(
record = KyberPreKeyRecord(cursor.requireNonNullBlob(SERIALIZED)),
lastResort = cursor.requireBoolean(LAST_RESORT)
)
}
}
fun contains(serviceId: ServiceId, keyId: Int): Boolean {
return readableDatabase
.exists("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY")
@@ -78,14 +93,15 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab
.run()
}
fun insert(serviceId: ServiceId, keyId: Int, record: KyberPreKeyRecord) {
fun insert(serviceId: ServiceId, keyId: Int, record: KyberPreKeyRecord, lastResort: Boolean) {
writableDatabase
.insertInto(TABLE_NAME)
.values(
ACCOUNT_ID to serviceId.toString(),
KEY_ID to keyId,
TIMESTAMP to record.timestamp,
SERIALIZED to record.serialize()
SERIALIZED to record.serialize(),
LAST_RESORT to lastResort.toInt()
)
.run(SQLiteDatabase.CONFLICT_REPLACE)
}

View File

@@ -95,7 +95,7 @@ public final class PreKeyMigrationHelper {
reader.close();
Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId);
SignalStore.account().aciPreKeys().setNextOneTimePreKeyId(index.nextPreKeyId);
SignalStore.account().aciPreKeys().setNextEcOneTimePreKeyId(index.nextPreKeyId);
} catch (IOException e) {
Log.w(TAG, e);
}

View File

@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.jobs
import androidx.annotation.VisibleForTesting
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SignalProtocolStore
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.crypto.PreKeyUtil
@@ -10,10 +12,13 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceIdType
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
@@ -24,7 +29,10 @@ import kotlin.time.DurationUnit
* attempt to make the state valid.
*
* It will rotate/create signed prekeys for both ACI and PNI identities, as well as ensure that the user
* has a sufficient number of one-time prekeys available on the service.
* has a sufficient number of one-time EC prekeys available on the service.
*
* It will also rotate/create last-resort kyber prekeys for both ACI and PNI identities, as well as ensure
* that the user has a sufficient number of one-time kyber prekeys available on the service.
*/
class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(parameters) {
@@ -33,14 +41,14 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
private val TAG = Log.tag(PreKeysSyncJob::class.java)
/** The minimum number of one-time prekeys we want to the service to have. If we have less than this, refill. */
/** The minimum number of one-time prekeys we want to the service to have. If we have less than this, refill. Applies to both EC and kyber prekeys. */
private const val ONE_TIME_PREKEY_MINIMUM = 10
/** How often we want to rotate signed prekeys. */
/** How often we want to rotate signed prekeys and last-resort kyber prekeys. */
@JvmField
val REFRESH_INTERVAL = 2.days.inWholeMilliseconds
/** If signed prekeys are older than this, we will require rotation before sending messages. */
/** If signed prekeys or last-resort kyber keys are older than this, we will require rotation before sending messages. */
@JvmField
val MAXIMUM_ALLOWED_SIGNED_PREKEY_AGE = 14.days.inWholeMilliseconds
@@ -57,11 +65,14 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
@JvmStatic
fun enqueueIfNeeded() {
if (!SignalStore.account().aciPreKeys.isSignedPreKeyRegistered || !SignalStore.account().pniPreKeys.isSignedPreKeyRegistered) {
Log.i(TAG, "Some signed prekeys aren't registered yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.isSignedPreKeyRegistered} PNI: ${SignalStore.account().pniPreKeys.isSignedPreKeyRegistered}")
Log.i(TAG, "Some signed/last-resort prekeys aren't registered yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.isSignedPreKeyRegistered} PNI: ${SignalStore.account().pniPreKeys.isSignedPreKeyRegistered}")
ApplicationDependencies.getJobManager().add(PreKeysSyncJob())
} else if (SignalStore.account().aciPreKeys.activeSignedPreKeyId < 0 || SignalStore.account().pniPreKeys.activeSignedPreKeyId < 0) {
Log.i(TAG, "Some signed prekeys aren't active yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.activeSignedPreKeyId >= 0} PNI: ${SignalStore.account().pniPreKeys.activeSignedPreKeyId >= 0}")
ApplicationDependencies.getJobManager().add(PreKeysSyncJob())
} else if (SignalStore.account().aciPreKeys.lastResortKyberPreKeyId < 0 || SignalStore.account().pniPreKeys.lastResortKyberPreKeyId < 0) {
Log.i(TAG, "Some last-resort kyber prekeys aren't active yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.lastResortKyberPreKeyId >= 0} PNI: ${SignalStore.account().pniPreKeys.lastResortKyberPreKeyId >= 0}")
ApplicationDependencies.getJobManager().add(PreKeysSyncJob())
} else {
val timeSinceLastFullRefresh = System.currentTimeMillis() - SignalStore.misc().lastFullPrekeyRefreshTime
@@ -101,47 +112,101 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
SignalStore.misc().lastFullPrekeyRefreshTime = System.currentTimeMillis()
}
private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore) {
private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore) {
if (serviceId == null) {
warn(TAG, serviceIdType, "AccountId not set!")
return
}
val accountManager = ApplicationDependencies.getSignalServiceAccountManager()
val availablePreKeyCounts: OneTimePreKeyCounts = accountManager.getPreKeyCounts(serviceIdType)
val signedPreKeyRegistered = metadataStore.isSignedPreKeyRegistered && metadataStore.activeSignedPreKeyId >= 0
val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastSignedPreKeyRotationTime
val signedPreKeyToUpload: SignedPreKeyRecord? = signedPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore)
val activeSignedPreKeyRecord: SignedPreKeyRecord = if (!signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL) {
log(serviceIdType, "Rotating signed prekey. SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
val signedPreKeyRecord: SignedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore)
accountManager.setSignedPreKey(serviceIdType, signedPreKeyRecord)
metadataStore.activeSignedPreKeyId = signedPreKeyRecord.id
metadataStore.isSignedPreKeyRegistered = true
metadataStore.lastSignedPreKeyRotationTime = System.currentTimeMillis()
signedPreKeyRecord
val oneTimeEcPreKeysToUpload: List<PreKeyRecord>? = if (availablePreKeyCounts.ecCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is less than our threshold. Need more.")
PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore)
} else {
log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
protocolStore.loadSignedPreKey(metadataStore.activeSignedPreKeyId)
log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is enough.")
null
}
val availableOneTimePreKeys = accountManager.getPreKeysCount(serviceIdType)
val lastResortKyberPreKeyToUpload: KyberPreKeyRecord? = lastResortKyberPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore)
if (availableOneTimePreKeys < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are $availableOneTimePreKeys one-time prekeys available, which is not sufficient. Uploading more.")
val preKeyRecords = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore)
val identityKey = protocolStore.identityKeyPair
accountManager.setPreKeys(serviceIdType, identityKey.publicKey, activeSignedPreKeyRecord, preKeyRecords)
val oneTimeKyberPreKeysToUpload: List<KyberPreKeyRecord>? = if (availablePreKeyCounts.kyberCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is less than our threshold. Need more.")
PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(protocolStore, metadataStore)
} else {
log(serviceIdType, "There are $availableOneTimePreKeys one-time prekeys available, which is sufficient. No need to upload.")
log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is enough.")
null
}
if (signedPreKeyToUpload != null || oneTimeEcPreKeysToUpload != null || lastResortKyberPreKeyToUpload != null || oneTimeKyberPreKeysToUpload != null) {
log(serviceIdType, "Something to upload. SignedPreKey: ${signedPreKeyToUpload != null}, OneTimeEcPreKeys: ${oneTimeEcPreKeysToUpload != null}, LastResortKyberPreKey: ${lastResortKyberPreKeyToUpload != null}, OneTimeKyberPreKeys: ${oneTimeKyberPreKeysToUpload != null}")
accountManager.setPreKeys(
PreKeyUpload(
serviceIdType = serviceIdType,
identityKey = protocolStore.identityKeyPair.publicKey,
signedPreKey = signedPreKeyToUpload,
oneTimeEcPreKeys = oneTimeEcPreKeysToUpload,
lastResortKyberPreKey = lastResortKyberPreKeyToUpload,
oneTimeKyberPreKeys = oneTimeKyberPreKeysToUpload
)
)
if (signedPreKeyToUpload != null) {
log(serviceIdType, "Successfully uploaded signed prekey.")
metadataStore.activeSignedPreKeyId = signedPreKeyToUpload.id
metadataStore.isSignedPreKeyRegistered = true
metadataStore.lastSignedPreKeyRotationTime = System.currentTimeMillis()
}
if (oneTimeEcPreKeysToUpload != null) {
log(serviceIdType, "Successfully uploaded one-time EC prekeys.")
}
if (lastResortKyberPreKeyToUpload != null) {
log(serviceIdType, "Successfully uploaded last-resort kyber prekey.")
metadataStore.lastResortKyberPreKeyId = lastResortKyberPreKeyToUpload.id
metadataStore.lastResortKyberPreKeyRotationTime = System.currentTimeMillis()
}
if (oneTimeKyberPreKeysToUpload != null) {
log(serviceIdType, "Successfully uploaded one-time kyber prekeys.")
}
} else {
log(serviceIdType, "No prekeys to upload.")
}
log(serviceIdType, "Cleaning prekeys...")
PreKeyUtil.cleanSignedPreKeys(protocolStore, metadataStore)
PreKeyUtil.cleanLastResortKyberPreKeys(protocolStore, metadataStore)
}
private fun signedPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore): SignedPreKeyRecord? {
val signedPreKeyRegistered = metadataStore.isSignedPreKeyRegistered && metadataStore.activeSignedPreKeyId >= 0
val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastSignedPreKeyRotationTime
return if (!signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL) {
log(serviceIdType, "Rotating signed prekey. SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore)
} else {
log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
null
}
}
private fun lastResortKyberPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore): KyberPreKeyRecord? {
val lastResortRegistered = metadataStore.lastResortKyberPreKeyId >= 0
val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastResortKyberPreKeyRotationTime
return if (!lastResortRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL) {
log(serviceIdType, "Rotating last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore)
} else {
log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
null
}
}
override fun onShouldRetry(e: Exception): Boolean {

View File

@@ -49,6 +49,9 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
private const val KEY_ACI_ACTIVE_SIGNED_PREKEY_ID = "account.aci_active_signed_prekey_id"
private const val KEY_ACI_LAST_SIGNED_PREKEY_ROTATION_TIME = "account.aci_last_signed_prekey_rotation_time"
private const val KEY_ACI_NEXT_ONE_TIME_PREKEY_ID = "account.aci_next_one_time_prekey_id"
private const val KEY_ACI_NEXT_KYBER_PREKEY_ID = "account.aci_next_kyber_prekey_id"
private const val KEY_ACI_LAST_RESORT_KYBER_PREKEY_ID = "account.aci_last_resort_kyber_prekey_id"
private const val KEY_ACI_LAST_RESORT_KYBER_PREKEY_ROTATION_TIME = "account.aci_last_resort_kyber_prekey_rotation_time"
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"
@@ -57,6 +60,9 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
private const val KEY_PNI_ACTIVE_SIGNED_PREKEY_ID = "account.pni_active_signed_prekey_id"
private const val KEY_PNI_LAST_SIGNED_PREKEY_ROTATION_TIME = "account.pni_last_signed_prekey_rotation_time"
private const val KEY_PNI_NEXT_ONE_TIME_PREKEY_ID = "account.pni_next_one_time_prekey_id"
private const val KEY_PNI_NEXT_KYBER_PREKEY_ID = "account.pni_next_kyber_prekey_id"
private const val KEY_PNI_LAST_RESORT_KYBER_PREKEY_ID = "account.pni_last_resort_kyber_prekey_id"
private const val KEY_PNI_LAST_RESORT_KYBER_PREKEY_ROTATION_TIME = "account.pni_last_resort_kyber_prekey_rotation_time"
@VisibleForTesting
const val KEY_E164 = "account.e164"
@@ -258,7 +264,10 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
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 lastSignedPreKeyRotationTime: Long by longValue(KEY_ACI_LAST_SIGNED_PREKEY_ROTATION_TIME, System.currentTimeMillis() - PreKeysSyncJob.REFRESH_INTERVAL)
override var nextOneTimePreKeyId: Int by integerValue(KEY_ACI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var nextEcOneTimePreKeyId: Int by integerValue(KEY_ACI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var nextKyberPreKeyId: Int by integerValue(KEY_ACI_NEXT_KYBER_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var lastResortKyberPreKeyId: Int by integerValue(KEY_ACI_LAST_RESORT_KYBER_PREKEY_ID, -1)
override var lastResortKyberPreKeyRotationTime: Long by longValue(KEY_ACI_LAST_RESORT_KYBER_PREKEY_ROTATION_TIME, 0)
}
@get:JvmName("pniPreKeys")
@@ -267,7 +276,10 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
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 lastSignedPreKeyRotationTime: Long by longValue(KEY_PNI_LAST_SIGNED_PREKEY_ROTATION_TIME, System.currentTimeMillis() - PreKeysSyncJob.REFRESH_INTERVAL)
override var nextOneTimePreKeyId: Int by integerValue(KEY_PNI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var nextEcOneTimePreKeyId: Int by integerValue(KEY_PNI_NEXT_ONE_TIME_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var nextKyberPreKeyId: Int by integerValue(KEY_PNI_NEXT_KYBER_PREKEY_ID, SecureRandom().nextInt(Medium.MAX_VALUE))
override var lastResortKyberPreKeyId: Int by integerValue(KEY_PNI_LAST_RESORT_KYBER_PREKEY_ID, -1)
override var lastResortKyberPreKeyRotationTime: Long by longValue(KEY_PNI_LAST_RESORT_KYBER_PREKEY_ROTATION_TIME, 0)
}
/** Indicates whether the user has the ability to receive FCM messages. Largely coupled to whether they have Play Service. */

View File

@@ -7,16 +7,16 @@ package org.thoughtcrime.securesms.messages.protocol
import org.signal.libsignal.protocol.InvalidKeyIdException
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.KyberPreKeyStore
import org.thoughtcrime.securesms.database.KyberPreKeyTable.KyberPreKey
import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.SignalServiceKyberPreKeyStore
import org.whispersystems.signalservice.api.push.ServiceId
/**
* An in-memory kyber prekey store that is intended to be used temporarily while decrypting messages.
*/
class BufferedKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreKeyStore {
class BufferedKyberPreKeyStore(private val selfServiceId: ServiceId) : SignalServiceKyberPreKeyStore {
/** Our in-memory cache of kyber prekeys. */
val store: MutableMap<Int, KyberPreKey> = mutableMapOf()
@@ -46,8 +46,16 @@ class BufferedKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreK
}
}
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> {
error("Not expected in this flow")
}
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord) {
error("This method is only used in tests")
error("Not expected in this flow")
}
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) {
error("Not expected in this flow")
}
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean {
@@ -67,6 +75,10 @@ class BufferedKyberPreKeyStore(private val selfServiceId: ServiceId) : KyberPreK
removedIfNotLastResort += kyberPreKeyId
}
override fun removeKyberPreKey(kyberPreKeyId: Int) {
error("Not expected in this flow")
}
fun flushToDisk(persistentStore: SignalServiceAccountDataStore) {
for (id in removedIfNotLastResort) {
persistentStore.markKyberPreKeyUsed(id)

View File

@@ -129,6 +129,10 @@ class BufferedSignalServiceAccountDataStore(selfServiceId: ServiceId) : SignalSe
kyberPreKeyStore.storeKyberPreKey(kyberPreKeyId, record)
}
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) {
kyberPreKeyStore.storeKyberPreKey(kyberPreKeyId, kyberPreKeyRecord)
}
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean {
return kyberPreKeyStore.containsKyberPreKey(kyberPreKeyId)
}
@@ -137,6 +141,14 @@ class BufferedSignalServiceAccountDataStore(selfServiceId: ServiceId) : SignalSe
return kyberPreKeyStore.markKyberPreKeyUsed(kyberPreKeyId)
}
override fun removeKyberPreKey(kyberPreKeyId: Int) {
kyberPreKeyStore.removeKyberPreKey(kyberPreKeyId)
}
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> {
return kyberPreKeyStore.loadLastResortKyberPreKeys()
}
override fun storeSenderKey(sender: SignalProtocolAddress, distributionId: UUID, record: SenderKeyRecord) {
senderKeyStore.storeSenderKey(sender, distributionId, record)
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.PreKeyUpload;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
@@ -76,9 +77,9 @@ public class PniAccountInitializationMigrationJob extends MigrationJob {
if (!metadataStore.isSignedPreKeyRegistered()) {
Log.i(TAG, "Uploading signed prekey for PNI.");
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore);
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore);
accountManager.setPreKeys(ServiceIdType.PNI, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys);
accountManager.setPreKeys(new PreKeyUpload(ServiceIdType.PNI, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys, null, null));
metadataStore.setActiveSignedPreKeyId(signedPreKey.getId());
metadataStore.setSignedPreKeyRegistered(true);
} else {

View File

@@ -9,6 +9,7 @@ import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationManagerCompat;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignalProtocolStore;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
@@ -36,7 +37,9 @@ import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.PreKeyUpload;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
@@ -185,14 +188,21 @@ public final class RegistrationRepository {
private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType,
@NonNull SignalServiceAccountManager accountManager,
@NonNull SignalProtocolStore protocolStore,
@NonNull SignalServiceAccountDataStore protocolStore,
@NonNull PreKeyMetadataStore metadataStore)
throws IOException
{
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore);
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore);
List<PreKeyRecord> oneTimeEcPreKeys = PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore);
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore);
List<KyberPreKeyRecord> oneTimeKyberPreKeys = PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(protocolStore, metadataStore);
accountManager.setPreKeys(serviceIdType, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys);
accountManager.setPreKeys(new PreKeyUpload(serviceIdType,
protocolStore.getIdentityKeyPair().getPublicKey(),
signedPreKey,
oneTimeEcPreKeys,
lastResortKyberPreKey,
oneTimeKyberPreKeys));
metadataStore.setActiveSignedPreKeyId(signedPreKey.getId());
metadataStore.setSignedPreKeyRegistered(true);
}