Add support for kyber prekeys.
This commit is contained in:
parent
15c248184f
commit
e2ef8e2ef9
24 changed files with 669 additions and 208 deletions
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ import org.signal.libsignal.protocol.state.SignalProtocolStore;
|
|||
* And extension of the normal protocol store interface that has additional methods that are needed
|
||||
* in the service layer, but not the protocol layer.
|
||||
*/
|
||||
public interface SignalServiceAccountDataStore extends SignalProtocolStore, SignalServiceSessionStore, SignalServiceSenderKeyStore {
|
||||
public interface SignalServiceAccountDataStore extends SignalProtocolStore,
|
||||
SignalServiceSessionStore,
|
||||
SignalServiceSenderKeyStore,
|
||||
SignalServiceKyberPreKeyStore {
|
||||
/**
|
||||
* @return True if the user has linked devices, otherwise false.
|
||||
*/
|
||||
|
|
|
@ -9,17 +9,16 @@ package org.whispersystems.signalservice.api;
|
|||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||
|
@ -36,7 +35,6 @@ import org.whispersystems.signalservice.api.push.ACI;
|
|||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
|
@ -59,6 +57,7 @@ import org.whispersystems.signalservice.internal.push.AuthCredentials;
|
|||
import org.whispersystems.signalservice.internal.push.BackupAuthCheckRequest;
|
||||
import org.whispersystems.signalservice.internal.push.BackupAuthCheckResponse;
|
||||
import org.whispersystems.signalservice.internal.push.CdsiAuthResponse;
|
||||
import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts;
|
||||
import org.whispersystems.signalservice.internal.push.ProfileAvatarData;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
|
||||
|
@ -80,7 +79,6 @@ import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper
|
|||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -98,7 +96,6 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -351,23 +348,19 @@ public class SignalServiceAccountManager {
|
|||
* Register an identity key, signed prekey, and list of one time prekeys
|
||||
* with the server.
|
||||
*
|
||||
* @param identityKey The client's long-term identity keypair.
|
||||
* @param signedPreKey The client's signed prekey.
|
||||
* @param oneTimePreKeys The client's list of one-time prekeys.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setPreKeys(ServiceIdType serviceIdType, IdentityKey identityKey, SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
||||
public void setPreKeys(PreKeyUpload preKeyUpload)
|
||||
throws IOException
|
||||
{
|
||||
this.pushServiceSocket.registerPreKeys(serviceIdType, identityKey, signedPreKey, oneTimePreKeys);
|
||||
this.pushServiceSocket.registerPreKeys(preKeyUpload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server's count of currently available (eg. unused) prekeys for this user.
|
||||
* @throws IOException
|
||||
*/
|
||||
public int getPreKeysCount(ServiceIdType serviceIdType) throws IOException {
|
||||
public OneTimePreKeyCounts getPreKeyCounts(ServiceIdType serviceIdType) throws IOException {
|
||||
return this.pushServiceSocket.getAvailablePreKeys(serviceIdType);
|
||||
}
|
||||
|
||||
|
@ -381,14 +374,6 @@ public class SignalServiceAccountManager {
|
|||
this.pushServiceSocket.setCurrentSignedPreKey(serviceIdType, signedPreKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The server's view of the client's current signed prekey.
|
||||
* @throws IOException
|
||||
*/
|
||||
public SignedPreKeyEntity getSignedPreKey(ServiceIdType serviceIdType) throws IOException {
|
||||
return this.pushServiceSocket.getCurrentSignedPreKey(serviceIdType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the identifier corresponds to a registered user, otherwise false.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.whispersystems.signalservice.api
|
||||
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyStore
|
||||
|
||||
/**
|
||||
* And extension of the normal protocol sender key store interface that has additional methods that are
|
||||
* needed in the service layer, but not the protocol layer.
|
||||
*/
|
||||
interface SignalServiceKyberPreKeyStore : KyberPreKeyStore {
|
||||
|
||||
/**
|
||||
* Identical to [storeKyberPreKey] but indicates that this is a last-resort key rather than a one-time key.
|
||||
*/
|
||||
fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord)
|
||||
|
||||
/**
|
||||
* Retrieves all last-resort kyber prekeys.
|
||||
*/
|
||||
fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord>
|
||||
|
||||
/**
|
||||
* Unconditionally remove the specified key from the store.
|
||||
*/
|
||||
fun removeKyberPreKey(kyberPreKeyId: Int)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.account
|
||||
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType
|
||||
|
||||
/**
|
||||
* Represents a bundle of prekeys you want to upload.
|
||||
*
|
||||
* If a field is nullable, not setting it will simply leave that field alone on the service.
|
||||
*/
|
||||
data class PreKeyUpload(
|
||||
val serviceIdType: ServiceIdType,
|
||||
val identityKey: IdentityKey,
|
||||
val signedPreKey: SignedPreKeyRecord?,
|
||||
val oneTimeEcPreKeys: List<PreKeyRecord>?,
|
||||
val lastResortKyberPreKey: KyberPreKeyRecord?,
|
||||
val oneTimeKyberPreKeys: List<KyberPreKeyRecord>?
|
||||
)
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.signal.libsignal.protocol.kem.KEMPublicKey;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class KyberPreKeyEntity {
|
||||
|
||||
@JsonProperty
|
||||
private int keyId;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = KEMPublicKeySerializer.class)
|
||||
@JsonDeserialize(using = KEMPublicKeyDeserializer.class)
|
||||
private KEMPublicKey publicKey;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] signature;
|
||||
|
||||
public KyberPreKeyEntity() {}
|
||||
|
||||
public KyberPreKeyEntity(int keyId, KEMPublicKey publicKey, byte[] signature) {
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public int getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public KEMPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
private static class KEMPublicKeySerializer extends JsonSerializer<KEMPublicKey> {
|
||||
@Override
|
||||
public void serialize(KEMPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class KEMPublicKeyDeserializer extends JsonDeserializer<KEMPublicKey> {
|
||||
@Override
|
||||
public KEMPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return new KEMPublicKey(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeBytesWithoutPadding(value));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Base64.decodeWithoutPadding(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class OneTimePreKeyCounts {
|
||||
|
||||
@JsonProperty("count")
|
||||
private int ecCount;
|
||||
|
||||
@JsonProperty("pqCount")
|
||||
private int kyberCount;
|
||||
|
||||
public OneTimePreKeyCounts() {}
|
||||
|
||||
public OneTimePreKeyCounts(int ecCount, int kyberCount) {
|
||||
this.ecCount = ecCount;
|
||||
this.kyberCount = kyberCount;
|
||||
}
|
||||
|
||||
public int getEcCount() {
|
||||
return ecCount;
|
||||
}
|
||||
|
||||
public int getKyberCount() {
|
||||
return kyberCount;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* <p>
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
|
@ -13,16 +13,19 @@ import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
|
|||
public class PreKeyResponseItem {
|
||||
|
||||
@JsonProperty
|
||||
public int deviceId;
|
||||
public int deviceId;
|
||||
|
||||
@JsonProperty
|
||||
public int registrationId;
|
||||
public int registrationId;
|
||||
|
||||
@JsonProperty
|
||||
public SignedPreKeyEntity signedPreKey;
|
||||
|
||||
@JsonProperty
|
||||
public PreKeyEntity preKey;
|
||||
public PreKeyEntity preKey;
|
||||
|
||||
@JsonProperty("pqPreKey")
|
||||
public KyberPreKeyEntity kyberPreKey;
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
|
@ -40,4 +43,8 @@ public class PreKeyResponseItem {
|
|||
return preKey;
|
||||
}
|
||||
|
||||
public KyberPreKeyEntity getKyberPreKey() {
|
||||
return kyberPreKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,23 +12,37 @@ import java.util.List;
|
|||
|
||||
public class PreKeyState {
|
||||
|
||||
@JsonProperty
|
||||
@JsonProperty("identityKey")
|
||||
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
|
||||
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
|
||||
private IdentityKey identityKey;
|
||||
|
||||
@JsonProperty
|
||||
private List<PreKeyEntity> preKeys;
|
||||
@JsonProperty("preKeys")
|
||||
private List<PreKeyEntity> oneTimeEcPreKeys;
|
||||
|
||||
@JsonProperty
|
||||
@JsonProperty("signedPreKey")
|
||||
private SignedPreKeyEntity signedPreKey;
|
||||
|
||||
@JsonProperty("pqLastResortPreKey")
|
||||
private KyberPreKeyEntity lastResortKyberKey;
|
||||
|
||||
@JsonProperty("pqPreKeys")
|
||||
private List<KyberPreKeyEntity> oneTimeKyberKeys;
|
||||
|
||||
public PreKeyState() {}
|
||||
|
||||
public PreKeyState(List<PreKeyEntity> preKeys, SignedPreKeyEntity signedPreKey, IdentityKey identityKey) {
|
||||
this.preKeys = preKeys;
|
||||
this.signedPreKey = signedPreKey;
|
||||
this.identityKey = identityKey;
|
||||
public PreKeyState(
|
||||
IdentityKey identityKey,
|
||||
SignedPreKeyEntity signedPreKey,
|
||||
List<PreKeyEntity> oneTimeEcPreKeys,
|
||||
KyberPreKeyEntity lastResortKyberPreKey,
|
||||
List<KyberPreKeyEntity> oneTimeKyberPreKeys
|
||||
) {
|
||||
this.identityKey = identityKey;
|
||||
this.signedPreKey = signedPreKey;
|
||||
this.oneTimeEcPreKeys = oneTimeEcPreKeys;
|
||||
this.lastResortKyberKey = lastResortKyberPreKey;
|
||||
this.oneTimeKyberKeys = oneTimeKyberPreKeys;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
|
@ -36,7 +50,7 @@ public class PreKeyState {
|
|||
}
|
||||
|
||||
public List<PreKeyEntity> getPreKeys() {
|
||||
return preKeys;
|
||||
return oneTimeEcPreKeys;
|
||||
}
|
||||
|
||||
public SignedPreKeyEntity getSignedPreKey() {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class PreKeyStatus {
|
||||
|
||||
@JsonProperty
|
||||
private int count;
|
||||
|
||||
public PreKeyStatus() {}
|
||||
|
||||
public PreKeyStatus(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
|
@ -11,12 +11,11 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.MessageLite;
|
||||
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.protocol.kem.KEMPublicKey;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.protocol.state.PreKeyBundle;
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
|
@ -41,6 +40,7 @@ import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
|||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||
|
@ -165,6 +165,7 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -215,8 +216,8 @@ public class PushServiceSocket {
|
|||
private static final String REQUEST_ACCOUNT_DATA_PATH = "/v2/accounts/data_report";
|
||||
|
||||
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 PREKEY_PATH = "/v2/keys?identity=%s";
|
||||
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s?pq=true";
|
||||
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed?identity=%s";
|
||||
|
||||
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
|
||||
|
@ -612,67 +613,113 @@ public class PushServiceSocket {
|
|||
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
|
||||
}
|
||||
|
||||
public void registerPreKeys(ServiceIdType serviceIdType,
|
||||
IdentityKey identityKey,
|
||||
SignedPreKeyRecord signedPreKey,
|
||||
List<PreKeyRecord> records)
|
||||
public void registerPreKeys(PreKeyUpload preKeyUpload)
|
||||
throws IOException
|
||||
{
|
||||
List<PreKeyEntity> entities = new LinkedList<>();
|
||||
SignedPreKeyEntity signedPreKey = null;
|
||||
List<PreKeyEntity> oneTimeEcPreKeys = null;
|
||||
KyberPreKeyEntity lastResortKyberPreKey = null;
|
||||
List<KyberPreKeyEntity> oneTimeKyberPreKeys = null;
|
||||
|
||||
try {
|
||||
for (PreKeyRecord record : records) {
|
||||
PreKeyEntity entity = new PreKeyEntity(record.getId(),
|
||||
record.getKeyPair().getPublicKey());
|
||||
|
||||
entities.add(entity);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError("unexpected invalid key", e);
|
||||
if (preKeyUpload.getSignedPreKey() != null) {
|
||||
signedPreKey = new SignedPreKeyEntity(preKeyUpload.getSignedPreKey().getId(),
|
||||
preKeyUpload.getSignedPreKey().getKeyPair().getPublicKey(),
|
||||
preKeyUpload.getSignedPreKey().getSignature());
|
||||
}
|
||||
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
if (preKeyUpload.getOneTimeEcPreKeys() != null) {
|
||||
oneTimeEcPreKeys = preKeyUpload
|
||||
.getOneTimeEcPreKeys()
|
||||
.stream()
|
||||
.map(it -> {
|
||||
try {
|
||||
return new PreKeyEntity(it.getId(), it.getKeyPair().getPublicKey());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError("unexpected invalid key", e);
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, "", serviceIdType.queryParam()),
|
||||
if (preKeyUpload.getLastResortKyberPreKey() != null) {
|
||||
lastResortKyberPreKey = new KyberPreKeyEntity(preKeyUpload.getLastResortKyberPreKey().getId(),
|
||||
preKeyUpload.getLastResortKyberPreKey().getKeyPair().getPublicKey(),
|
||||
preKeyUpload.getLastResortKyberPreKey().getSignature());
|
||||
}
|
||||
|
||||
if (preKeyUpload.getOneTimeKyberPreKeys() != null) {
|
||||
oneTimeKyberPreKeys = preKeyUpload
|
||||
.getOneTimeKyberPreKeys()
|
||||
.stream()
|
||||
.map(it -> new KyberPreKeyEntity(it.getId(), it.getKeyPair().getPublicKey(), it.getSignature()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, preKeyUpload.getServiceIdType().queryParam()),
|
||||
"PUT",
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
JsonUtil.toJson(new PreKeyState(preKeyUpload.getIdentityKey(),
|
||||
signedPreKey,
|
||||
oneTimeEcPreKeys,
|
||||
lastResortKyberPreKey,
|
||||
oneTimeKyberPreKeys)));
|
||||
}
|
||||
|
||||
public int getAvailablePreKeys(ServiceIdType serviceIdType) throws IOException {
|
||||
String path = String.format(PREKEY_METADATA_PATH, serviceIdType.queryParam());
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
|
||||
public OneTimePreKeyCounts getAvailablePreKeys(ServiceIdType serviceIdType) throws IOException {
|
||||
String path = String.format(PREKEY_METADATA_PATH, serviceIdType.queryParam());
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
OneTimePreKeyCounts preKeyStatus = JsonUtil.fromJson(responseText, OneTimePreKeyCounts.class);
|
||||
|
||||
return preKeyStatus.getCount();
|
||||
return preKeyStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves prekeys. If the specified device is the primary (i.e. deviceId 1), it will retrieve prekeys
|
||||
* for all devices. If it is not a primary, it will only contain the prekeys for that specific device.
|
||||
*/
|
||||
public List<PreKeyBundle> getPreKeys(SignalServiceAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
int deviceIdInteger)
|
||||
int deviceId)
|
||||
throws IOException
|
||||
{
|
||||
return getPreKeysBySpecifier(destination, unidentifiedAccess, deviceId == 1 ? "*" : String.valueOf(deviceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a prekey for a specific device.
|
||||
*/
|
||||
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
|
||||
List<PreKeyBundle> bundles = getPreKeysBySpecifier(destination, Optional.empty(), String.valueOf(deviceId));
|
||||
|
||||
if (bundles.size() > 0) {
|
||||
return bundles.get(0);
|
||||
} else {
|
||||
throw new IOException("No prekeys available!");
|
||||
}
|
||||
}
|
||||
|
||||
private List<PreKeyBundle> getPreKeysBySpecifier(SignalServiceAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
String deviceSpecifier)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
String deviceId = String.valueOf(deviceIdInteger);
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), deviceSpecifier);
|
||||
|
||||
if (deviceId.equals("1"))
|
||||
deviceId = "*";
|
||||
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), deviceId);
|
||||
|
||||
Log.d(TAG, "Fetching prekeys for " + destination.getIdentifier() + "." + deviceId + ", i.e. GET " + path);
|
||||
Log.d(TAG, "Fetching prekeys for " + destination.getIdentifier() + "." + deviceSpecifier + ", i.e. GET " + path);
|
||||
|
||||
String responseText = makeServiceRequest(path, "GET", null, NO_HEADERS, NO_HANDLER, unidentifiedAccess);
|
||||
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||
List<PreKeyBundle> bundles = new LinkedList<>();
|
||||
|
||||
for (PreKeyResponseItem device : response.getDevices()) {
|
||||
ECPublicKey preKey = null;
|
||||
ECPublicKey signedPreKey = null;
|
||||
byte[] signedPreKeySignature = null;
|
||||
int preKeyId = -1;
|
||||
int signedPreKeyId = -1;
|
||||
ECPublicKey preKey = null;
|
||||
ECPublicKey signedPreKey = null;
|
||||
byte[] signedPreKeySignature = null;
|
||||
int preKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
|
||||
int signedPreKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
|
||||
int kyberPreKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
|
||||
KEMPublicKey kyberPreKey = null;
|
||||
byte[] kyberPreKeySignature = null;
|
||||
|
||||
if (device.getSignedPreKey() != null) {
|
||||
signedPreKey = device.getSignedPreKey().getPublicKey();
|
||||
|
@ -685,9 +732,23 @@ public class PushServiceSocket {
|
|||
preKey = device.getPreKey().getPublicKey();
|
||||
}
|
||||
|
||||
bundles.add(new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId,
|
||||
preKey, signedPreKeyId, signedPreKey, signedPreKeySignature,
|
||||
response.getIdentityKey()));
|
||||
if (device.getKyberPreKey() != null) {
|
||||
kyberPreKey = device.getKyberPreKey().getPublicKey();
|
||||
kyberPreKeyId = device.getKyberPreKey().getKeyId();
|
||||
kyberPreKeySignature = device.getKyberPreKey().getSignature();
|
||||
}
|
||||
|
||||
bundles.add(new PreKeyBundle(device.getRegistrationId(),
|
||||
device.getDeviceId(),
|
||||
preKeyId,
|
||||
preKey,
|
||||
signedPreKeyId,
|
||||
signedPreKey,
|
||||
signedPreKeySignature,
|
||||
response.getIdentityKey(),
|
||||
kyberPreKeyId,
|
||||
kyberPreKey,
|
||||
kyberPreKeySignature));
|
||||
}
|
||||
|
||||
return bundles;
|
||||
|
@ -696,52 +757,6 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
|
||||
try {
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), String.valueOf(deviceId));
|
||||
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||
|
||||
if (response.getDevices() == null || response.getDevices().size() < 1)
|
||||
throw new IOException("Empty prekey list");
|
||||
|
||||
PreKeyResponseItem device = response.getDevices().get(0);
|
||||
ECPublicKey preKey = null;
|
||||
ECPublicKey signedPreKey = null;
|
||||
byte[] signedPreKeySignature = null;
|
||||
int preKeyId = -1;
|
||||
int signedPreKeyId = -1;
|
||||
|
||||
if (device.getPreKey() != null) {
|
||||
preKeyId = device.getPreKey().getKeyId();
|
||||
preKey = device.getPreKey().getPublicKey();
|
||||
}
|
||||
|
||||
if (device.getSignedPreKey() != null) {
|
||||
signedPreKeyId = device.getSignedPreKey().getKeyId();
|
||||
signedPreKey = device.getSignedPreKey().getPublicKey();
|
||||
signedPreKeySignature = device.getSignedPreKey().getSignature();
|
||||
}
|
||||
|
||||
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
|
||||
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(destination.getIdentifier(), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
public SignedPreKeyEntity getCurrentSignedPreKey(ServiceIdType serviceIdType) throws IOException {
|
||||
try {
|
||||
String path = String.format(SIGNED_PREKEY_PATH, serviceIdType.queryParam());
|
||||
String responseText = makeServiceRequest(path, "GET", null);
|
||||
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
|
||||
} catch (NotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord signedPreKey) throws IOException {
|
||||
String path = String.format(SIGNED_PREKEY_PATH, serviceIdType.queryParam());
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.signal.libsignal.protocol.IdentityKeyPair
|
|||
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord
|
||||
import org.signal.libsignal.protocol.state.IdentityKeyStore
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SessionRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
|
@ -19,10 +20,11 @@ class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
|
|||
|
||||
private val identityKey: IdentityKeyPair = IdentityKeyPair.generate()
|
||||
private val identities: MutableMap<SignalProtocolAddress, IdentityKey> = mutableMapOf()
|
||||
private val oneTimePreKeys: MutableMap<Int, PreKeyRecord> = mutableMapOf()
|
||||
private val oneTimeEcPreKeys: MutableMap<Int, PreKeyRecord> = mutableMapOf()
|
||||
private val signedPreKeys: MutableMap<Int, SignedPreKeyRecord> = mutableMapOf()
|
||||
private var sessions: MutableMap<SignalProtocolAddress, SessionRecord> = mutableMapOf()
|
||||
private val senderKeys: MutableMap<SenderKeyLocator, SenderKeyRecord> = mutableMapOf()
|
||||
private val kyberPreKeys: MutableMap<Int, KyberPreKeyRecord> = mutableMapOf()
|
||||
|
||||
override fun getIdentityKeyPair(): IdentityKeyPair {
|
||||
return identityKey
|
||||
|
@ -47,19 +49,19 @@ class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
|
|||
}
|
||||
|
||||
override fun loadPreKey(preKeyId: Int): PreKeyRecord {
|
||||
return oneTimePreKeys[preKeyId]!!
|
||||
return oneTimeEcPreKeys[preKeyId]!!
|
||||
}
|
||||
|
||||
override fun storePreKey(preKeyId: Int, record: PreKeyRecord) {
|
||||
oneTimePreKeys[preKeyId] = record
|
||||
oneTimeEcPreKeys[preKeyId] = record
|
||||
}
|
||||
|
||||
override fun containsPreKey(preKeyId: Int): Boolean {
|
||||
return oneTimePreKeys.containsKey(preKeyId)
|
||||
return oneTimeEcPreKeys.containsKey(preKeyId)
|
||||
}
|
||||
|
||||
override fun removePreKey(preKeyId: Int) {
|
||||
oneTimePreKeys.remove(preKeyId)
|
||||
oneTimeEcPreKeys.remove(preKeyId)
|
||||
}
|
||||
|
||||
override fun loadSession(address: SignalProtocolAddress): SessionRecord {
|
||||
|
@ -120,6 +122,38 @@ class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
|
|||
return senderKeys[SenderKeyLocator(sender, distributionId)]!!
|
||||
}
|
||||
|
||||
override fun loadKyberPreKey(kyberPreKeyId: Int): KyberPreKeyRecord {
|
||||
return kyberPreKeys[kyberPreKeyId]!!
|
||||
}
|
||||
|
||||
override fun loadKyberPreKeys(): List<KyberPreKeyRecord> {
|
||||
return kyberPreKeys.values.toList()
|
||||
}
|
||||
|
||||
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord?) {
|
||||
error("Not used")
|
||||
}
|
||||
|
||||
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean {
|
||||
return kyberPreKeys.containsKey(kyberPreKeyId)
|
||||
}
|
||||
|
||||
override fun markKyberPreKeyUsed(kyberPreKeyId: Int) {
|
||||
kyberPreKeys.remove(kyberPreKeyId)
|
||||
}
|
||||
|
||||
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) {
|
||||
error("Not used")
|
||||
}
|
||||
|
||||
override fun removeKyberPreKey(kyberPreKeyId: Int) {
|
||||
error("Not used")
|
||||
}
|
||||
|
||||
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> {
|
||||
error("Not used")
|
||||
}
|
||||
|
||||
override fun archiveSession(address: SignalProtocolAddress) {
|
||||
sessions[address]!!.archiveCurrentState()
|
||||
}
|
||||
|
@ -137,11 +171,11 @@ class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
|
|||
}
|
||||
|
||||
override fun markSenderKeySharedWith(distributionId: DistributionId, addresses: Collection<SignalProtocolAddress>) {
|
||||
// Not used
|
||||
// Called, but not needed
|
||||
}
|
||||
|
||||
override fun clearSenderKeySharedWith(addresses: Collection<SignalProtocolAddress>) {
|
||||
// Not used
|
||||
// Called, but not needed
|
||||
}
|
||||
|
||||
override fun isMultiDevice(): Boolean {
|
||||
|
|
Loading…
Add table
Reference in a new issue