mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 16:19:33 +01:00
Support PNI prekeys.
This commit is contained in:
@@ -17,64 +17,66 @@
|
||||
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
import org.whispersystems.libsignal.ecc.ECKeyPair;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||
import org.whispersystems.libsignal.util.Medium;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PreKeyUtil {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(PreKeyUtil.class);
|
||||
|
||||
private static final int BATCH_SIZE = 100;
|
||||
private static final int BATCH_SIZE = 100;
|
||||
private static final long ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30);
|
||||
|
||||
public synchronized static @NonNull List<PreKeyRecord> generateAndStoreOneTimePreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
|
||||
Log.i(TAG, "Generating one-time prekeys...");
|
||||
|
||||
public synchronized static List<PreKeyRecord> generatePreKeys(Context context) {
|
||||
PreKeyStore preKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
||||
List<PreKeyRecord> records = new LinkedList<>();
|
||||
int preKeyIdOffset = TextSecurePreferences.getNextPreKeyId(context);
|
||||
int preKeyIdOffset = metadataStore.getNextOneTimePreKeyId();
|
||||
|
||||
for (int i=0;i<BATCH_SIZE;i++) {
|
||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
|
||||
|
||||
preKeyStore.storePreKey(preKeyId, record);
|
||||
protocolStore.storePreKey(preKeyId, record);
|
||||
records.add(record);
|
||||
}
|
||||
|
||||
TextSecurePreferences.setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
||||
metadataStore.setNextOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public synchronized static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active) {
|
||||
public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) {
|
||||
Log.i(TAG, "Generating signed prekeys...");
|
||||
|
||||
try {
|
||||
SignedPreKeyStore signedPreKeyStore = ApplicationDependencies.getProtocolStore().aci();
|
||||
int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context);
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||
int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(protocolStore.getIdentityKeyPair().getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
|
||||
|
||||
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
TextSecurePreferences.setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||
protocolStore.storeSignedPreKey(signedPreKeyId, record);
|
||||
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
|
||||
|
||||
if (active) {
|
||||
TextSecurePreferences.setActiveSignedPreKeyId(context, signedPreKeyId);
|
||||
if (setAsActive) {
|
||||
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
|
||||
}
|
||||
|
||||
return record;
|
||||
@@ -83,12 +85,33 @@ public class PreKeyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
|
||||
TextSecurePreferences.setActiveSignedPreKeyId(context, id);
|
||||
}
|
||||
/**
|
||||
* Finds all of the signed prekeys that are older than the archive age, and archive all but the youngest of those.
|
||||
*/
|
||||
public synchronized static void cleanSignedPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
|
||||
Log.i(TAG, "Cleaning signed prekeys...");
|
||||
|
||||
public static synchronized int getActiveSignedPreKeyId(Context context) {
|
||||
return TextSecurePreferences.getActiveSignedPreKeyId(context);
|
||||
}
|
||||
int activeSignedPreKeyId = metadataStore.getActiveSignedPreKeyId();
|
||||
if (activeSignedPreKeyId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
SignedPreKeyRecord currentRecord = protocolStore.loadSignedPreKey(activeSignedPreKeyId);
|
||||
List<SignedPreKeyRecord> allRecords = protocolStore.loadSignedPreKeys();
|
||||
|
||||
allRecords.stream()
|
||||
.filter(r -> r.getId() != currentRecord.getId())
|
||||
.filter(r -> (now - r.getTimestamp()) > ARCHIVE_AGE)
|
||||
.sorted(Comparator.comparingLong(SignedPreKeyRecord::getTimestamp).reversed())
|
||||
.skip(1)
|
||||
.forEach(record -> {
|
||||
Log.i(TAG, "Removing signed prekey record: " + record.getId() + " with timestamp: " + record.getTimestamp());
|
||||
protocolStore.removeSignedPreKey(record.getId());
|
||||
});
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage
|
||||
|
||||
/**
|
||||
* Allows storing various metadata around prekey state.
|
||||
*/
|
||||
interface PreKeyMetadataStore {
|
||||
var nextSignedPreKeyId: Int
|
||||
var activeSignedPreKeyId: Int
|
||||
var isSignedPreKeyRegistered: Boolean
|
||||
var signedPreKeyFailureCount: Int
|
||||
var nextOneTimePreKeyId: Int
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -11,6 +9,7 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -22,16 +21,16 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
private final AccountIdentifier accountId;
|
||||
|
||||
public TextSecurePreKeyStore(@NonNull Context context) {
|
||||
this.context = context;
|
||||
public TextSecurePreKeyStore(@NonNull AccountIdentifier accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
synchronized (LOCK) {
|
||||
PreKeyRecord preKeyRecord = SignalDatabase.preKeys().getPreKey(preKeyId);
|
||||
PreKeyRecord preKeyRecord = SignalDatabase.oneTimePreKeys().get(accountId, preKeyId);
|
||||
|
||||
if (preKeyRecord == null) throw new InvalidKeyIdException("No such key: " + preKeyId);
|
||||
else return preKeyRecord;
|
||||
@@ -41,7 +40,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||
@Override
|
||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||
synchronized (LOCK) {
|
||||
SignedPreKeyRecord signedPreKeyRecord = SignalDatabase.signedPreKeys().getSignedPreKey(signedPreKeyId);
|
||||
SignedPreKeyRecord signedPreKeyRecord = SignalDatabase.signedPreKeys().get(accountId, signedPreKeyId);
|
||||
|
||||
if (signedPreKeyRecord == null) throw new InvalidKeyIdException("No such signed prekey: " + signedPreKeyId);
|
||||
else return signedPreKeyRecord;
|
||||
@@ -51,41 +50,41 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
synchronized (LOCK) {
|
||||
return SignalDatabase.signedPreKeys().getAllSignedPreKeys();
|
||||
return SignalDatabase.signedPreKeys().getAll(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||
synchronized (LOCK) {
|
||||
SignalDatabase.preKeys().insertPreKey(preKeyId, record);
|
||||
SignalDatabase.oneTimePreKeys().insert(accountId, preKeyId, record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||
synchronized (LOCK) {
|
||||
SignalDatabase.signedPreKeys().insertSignedPreKey(signedPreKeyId, record);
|
||||
SignalDatabase.signedPreKeys().insert(accountId, signedPreKeyId, record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int preKeyId) {
|
||||
return SignalDatabase.preKeys().getPreKey(preKeyId) != null;
|
||||
return SignalDatabase.oneTimePreKeys().get(accountId, preKeyId) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||
return SignalDatabase.signedPreKeys().getSignedPreKey(signedPreKeyId) != null;
|
||||
return SignalDatabase.signedPreKeys().get(accountId, signedPreKeyId) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePreKey(int preKeyId) {
|
||||
SignalDatabase.preKeys().removePreKey(preKeyId);
|
||||
SignalDatabase.oneTimePreKeys().delete(accountId, preKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSignedPreKey(int signedPreKeyId) {
|
||||
SignalDatabase.signedPreKeys().removeSignedPreKey(signedPreKeyId);
|
||||
SignalDatabase.signedPreKeys().delete(accountId, signedPreKeyId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user