Initial pre-alpha support for sender key.

This commit is contained in:
Greyson Parrelli
2021-05-14 14:03:35 -04:00
parent c54f016213
commit 57c0b8fd0f
124 changed files with 3668 additions and 444 deletions

View File

@@ -0,0 +1,43 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
public final class SenderKeyUtil {
private SenderKeyUtil() {}
/**
* Clears the state for a sender key session we created. It will naturally get re-created when it is next needed, rotating the key.
*/
public static void rotateOurKey(@NonNull Context context, @NonNull DistributionId distributionId) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
new SignalSenderKeyStore(context).deleteAllFor(Recipient.self().getId(), distributionId);
DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(distributionId);
}
}
/**
* Gets when the sender key session was created, or -1 if it doesn't exist.
*/
public static long getCreateTimeForOurKey(@NonNull Context context, @NonNull DistributionId distributionId) {
return DatabaseFactory.getSenderKeyDatabase(context).getCreatedTime(Recipient.self().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID, distributionId);
}
/**
* Deletes all stored state around session keys. Should only really be used when the user is re-registering.
*/
public static void clearAllState(@NonNull Context context) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
new SignalSenderKeyStore(context).deleteAll();
DatabaseFactory.getSenderKeySharedDatabase(context).deleteAll();
}
}
}

View File

@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.keyvalue.CertificateType;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@@ -32,6 +33,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -63,6 +65,22 @@ public class UnidentifiedAccessUtil {
return getAccessFor(context, recipients, true);
}
@WorkerThread
public static Map<RecipientId, Optional<UnidentifiedAccessPair>> getAccessMapFor(@NonNull Context context, @NonNull List<Recipient> recipients) {
List<Optional<UnidentifiedAccessPair>> accessList = getAccessFor(context, recipients, true);
Iterator<Recipient> recipientIterator = recipients.iterator();
Iterator<Optional<UnidentifiedAccessPair>> accessIterator = accessList.iterator();
Map<RecipientId, Optional<UnidentifiedAccessPair>> accessMap = new HashMap<>(recipients.size());
while (recipientIterator.hasNext()) {
accessMap.put(recipientIterator.next().getId(), accessIterator.next());
}
return accessMap;
}
@WorkerThread
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.groups.state.SenderKeyRecord;
import org.whispersystems.libsignal.state.IdentityKeyStore;
@@ -17,8 +18,11 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceProtocolStore;
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
import org.whispersystems.signalservice.api.push.DistributionId;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class SignalProtocolStoreImpl implements SignalServiceProtocolStore {
@@ -27,12 +31,14 @@ public class SignalProtocolStoreImpl implements SignalServiceProtocolStore {
private final SignedPreKeyStore signedPreKeyStore;
private final IdentityKeyStore identityKeyStore;
private final SignalServiceSessionStore sessionStore;
private final SignalSenderKeyStore senderKeyStore;
public SignalProtocolStoreImpl(Context context) {
this.preKeyStore = new TextSecurePreKeyStore(context);
this.signedPreKeyStore = new TextSecurePreKeyStore(context);
this.identityKeyStore = new TextSecureIdentityKeyStore(context);
this.sessionStore = new TextSecureSessionStore(context);
this.senderKeyStore = new SignalSenderKeyStore(context);
}
@Override
@@ -85,6 +91,11 @@ public class SignalProtocolStoreImpl implements SignalServiceProtocolStore {
return sessionStore.loadSession(axolotlAddress);
}
@Override
public List<SessionRecord> loadExistingSessions(List<SignalProtocolAddress> addresses) throws NoSessionException {
return sessionStore.loadExistingSessions(addresses);
}
@Override
public List<Integer> getSubDeviceSessions(String number) {
return sessionStore.getSubDeviceSessions(number);
@@ -142,11 +153,26 @@ public class SignalProtocolStoreImpl implements SignalServiceProtocolStore {
@Override
public void storeSenderKey(SignalProtocolAddress sender, UUID distributionId, SenderKeyRecord record) {
senderKeyStore.storeSenderKey(sender, distributionId, record);
}
@Override
public SenderKeyRecord loadSenderKey(SignalProtocolAddress sender, UUID distributionId) {
return null;
return senderKeyStore.loadSenderKey(sender, distributionId);
}
@Override
public Set<SignalProtocolAddress> getSenderKeySharedWith(DistributionId distributionId) {
return senderKeyStore.getSenderKeySharedWith(distributionId);
}
@Override
public void markSenderKeySharedWith(DistributionId distributionId, Collection<SignalProtocolAddress> addresses) {
senderKeyStore.markSenderKeySharedWith(distributionId, addresses);
}
@Override
public void clearSenderKeySharedWith(DistributionId distributionId, Collection<SignalProtocolAddress> addresses) {
senderKeyStore.clearSenderKeySharedWith(distributionId, addresses);
}
}

View File

@@ -0,0 +1,88 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.groups.state.SenderKeyRecord;
import org.whispersystems.signalservice.api.SignalSessionLock;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
/**
* An implementation of the storage interface used by the protocol layer to store sender keys. For
* more details around sender keys, see {@link org.thoughtcrime.securesms.database.SenderKeyDatabase}.
*/
public final class SignalSenderKeyStore implements SignalServiceSenderKeyStore {
private final Context context;
public SignalSenderKeyStore(@NonNull Context context) {
this.context = context;
}
@Override
public void storeSenderKey(@NonNull SignalProtocolAddress sender, @NonNull UUID distributionId, @NonNull SenderKeyRecord record) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
RecipientId recipientId = RecipientId.fromExternalPush(sender.getName());
DatabaseFactory.getSenderKeyDatabase(context).store(recipientId, sender.getDeviceId(), DistributionId.from(distributionId), record);
}
}
@Override
public @Nullable SenderKeyRecord loadSenderKey(@NonNull SignalProtocolAddress sender, @NonNull UUID distributionId) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
RecipientId recipientId = RecipientId.fromExternalPush(sender.getName());
return DatabaseFactory.getSenderKeyDatabase(context).load(recipientId, sender.getDeviceId(), DistributionId.from(distributionId));
}
}
@Override
public Set<SignalProtocolAddress> getSenderKeySharedWith(DistributionId distributionId) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
return DatabaseFactory.getSenderKeySharedDatabase(context).getSharedWith(distributionId);
}
}
@Override
public void markSenderKeySharedWith(DistributionId distributionId, Collection<SignalProtocolAddress> addresses) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
DatabaseFactory.getSenderKeySharedDatabase(context).markAsShared(distributionId, addresses);
}
}
@Override
public void clearSenderKeySharedWith(DistributionId distributionId, Collection<SignalProtocolAddress> addresses) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
DatabaseFactory.getSenderKeySharedDatabase(context).delete(distributionId, addresses);
}
}
/**
* Removes all sender key session state for all devices for the provided recipient-distributionId pair.
*/
public void deleteAllFor(@NonNull RecipientId recipientId, @NonNull DistributionId distributionId) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
DatabaseFactory.getSenderKeyDatabase(context).deleteAllFor(recipientId, distributionId);
}
}
/**
* Deletes all sender key session state.
*/
public void deleteAll() {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
DatabaseFactory.getSenderKeyDatabase(context).deleteAll();
}
}
}

View File

@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.SenderKeySharedDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.IdentityUtil;
@@ -73,6 +74,7 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
identityDatabase.saveIdentity(recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
IdentityUtil.markIdentityUpdate(context, recipientId);
SessionUtil.archiveSiblingSessions(context, address);
DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId);
return true;
}

View File

@@ -8,8 +8,10 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.SessionDatabase;
import org.thoughtcrime.securesms.database.SessionDatabase.RecipientDevice;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.state.SessionRecord;
@@ -18,6 +20,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class TextSecureSessionStore implements SignalServiceSessionStore {
@@ -44,6 +47,25 @@ public class TextSecureSessionStore implements SignalServiceSessionStore {
}
}
@Override
public List<SessionRecord> loadExistingSessions(List<SignalProtocolAddress> addresses) throws NoSessionException {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
List<RecipientDevice> ids = addresses.stream()
.map(address -> new RecipientDevice(RecipientId.fromExternalPush(address.getName()), address.getDeviceId()))
.collect(Collectors.toList());
List<SessionRecord> sessionRecords = DatabaseFactory.getSessionDatabase(context).load(ids);
if (sessionRecords.size() != addresses.size()) {
String message = "Mismatch! Asked for " + addresses.size() + " sessions, but only found " + sessionRecords.size() + "!";
Log.w(TAG, message);
throw new NoSessionException(message);
}
return sessionRecords;
}
}
@Override
public void storeSession(@NonNull SignalProtocolAddress address, @NonNull SessionRecord record) {
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {