Support PNI prekeys.

This commit is contained in:
Greyson Parrelli
2022-02-01 14:09:04 -05:00
parent db534cd376
commit e8ad1e8ed1
32 changed files with 808 additions and 532 deletions

View File

@@ -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);
}
}
}

View File

@@ -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
}

View File

@@ -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);
}
}