mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Initial pre-alpha support for sender key.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user