Refactor identity key store

This commit is contained in:
AsamK 2022-06-09 15:37:21 +02:00
parent c8cd36bde8
commit 26620f3137
9 changed files with 99 additions and 61 deletions

View file

@ -1049,7 +1049,7 @@ class ManagerImpl implements Manager {
IdentityInfo identity; IdentityInfo identity;
try { try {
identity = account.getIdentityKeyStore() identity = account.getIdentityKeyStore()
.getIdentity(context.getRecipientHelper().resolveRecipient(recipient)); .getIdentityInfo(context.getRecipientHelper().resolveRecipient(recipient));
} catch (UnregisteredRecipientException e) { } catch (UnregisteredRecipientException e) {
identity = null; identity = null;
} }

View file

@ -16,7 +16,6 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.function.Function; import java.util.function.Function;
import static org.asamk.signal.manager.config.ServiceConfig.capabilities; import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
@ -85,7 +84,7 @@ public class IdentityHelper {
private boolean trustIdentity( private boolean trustIdentity(
RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
) { ) {
var identity = account.getIdentityKeyStore().getIdentity(recipientId); var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
if (identity == null) { if (identity == null) {
return false; return false;
} }
@ -110,7 +109,7 @@ public class IdentityHelper {
) { ) {
final var identityKey = identityFailure.getIdentityKey(); final var identityKey = identityFailure.getIdentityKey();
if (identityKey != null) { if (identityKey != null) {
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date()); account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
} else { } else {
// Retrieve profile to get the current identity key from the server // Retrieve profile to get the current identity key from the server
context.getProfileHelper().refreshRecipientProfile(recipientId); context.getProfileHelper().refreshRecipientProfile(recipientId);

View file

@ -35,7 +35,6 @@ import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -354,7 +353,7 @@ public final class ProfileHelper {
try { try {
logger.trace("Storing identity"); logger.trace("Storing identity");
final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey())); final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()));
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date()); account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
} catch (InvalidKeyException ignored) { } catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}", logger.warn("Got invalid identity key in profile for {}",
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier()); context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier());

View file

@ -477,7 +477,7 @@ public class SendHelper {
continue; continue;
} }
final var identity = account.getIdentityKeyStore().getIdentity(recipientId); final var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
if (identity == null || !identity.getTrustLevel().isTrusted()) { if (identity == null || !identity.getTrustLevel().isTrusted()) {
continue; continue;
} }

View file

@ -24,7 +24,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -127,7 +126,7 @@ public class StorageHelper {
try { try {
logger.trace("Storing identity key {}", recipientId); logger.trace("Storing identity key {}", recipientId);
final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get()); final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date()); account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState()); final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState());
if (trustLevel != null) { if (trustLevel != null) {

View file

@ -128,7 +128,7 @@ public class SyncHelper {
final var contact = contactPair.second(); final var contact = contactPair.second();
final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId); final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
var currentIdentity = account.getIdentityKeyStore().getIdentity(recipientId); var currentIdentity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
VerifiedMessage verifiedMessage = null; VerifiedMessage verifiedMessage = null;
if (currentIdentity != null) { if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(address, verifiedMessage = new VerifiedMessage(address,

View file

@ -15,6 +15,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.groups.GroupStore; import org.asamk.signal.manager.storage.groups.GroupStore;
import org.asamk.signal.manager.storage.identities.IdentityKeyStore; import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.storage.messageCache.MessageCache; import org.asamk.signal.manager.storage.messageCache.MessageCache;
import org.asamk.signal.manager.storage.prekeys.PreKeyStore; import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
@ -81,7 +82,6 @@ import java.security.SecureRandom;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Base64; import java.util.Base64;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -137,6 +137,7 @@ public class SignalAccount implements Closeable {
private SignedPreKeyStore pniSignedPreKeyStore; private SignedPreKeyStore pniSignedPreKeyStore;
private SessionStore sessionStore; private SessionStore sessionStore;
private IdentityKeyStore identityKeyStore; private IdentityKeyStore identityKeyStore;
private SignalIdentityKeyStore aciIdentityKeyStore;
private SenderKeyStore senderKeyStore; private SenderKeyStore senderKeyStore;
private GroupStore groupStore; private GroupStore groupStore;
private GroupStore.Storage groupStoreStorage; private GroupStore.Storage groupStoreStorage;
@ -1016,7 +1017,7 @@ public class SignalAccount implements Closeable {
() -> signalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(), () -> signalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
getAciSignedPreKeyStore(), getAciSignedPreKeyStore(),
getSessionStore(), getSessionStore(),
getIdentityKeyStore(), getAciIdentityKeyStore(),
getSenderKeyStore(), getSenderKeyStore(),
this::isMultiDevice)); this::isMultiDevice));
} }
@ -1050,11 +1051,17 @@ public class SignalAccount implements Closeable {
return getOrCreate(() -> identityKeyStore, return getOrCreate(() -> identityKeyStore,
() -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath), () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
getRecipientResolver(), getRecipientResolver(),
aciIdentityKeyPair,
localRegistrationId,
trustNewIdentity)); trustNewIdentity));
} }
public SignalIdentityKeyStore getAciIdentityKeyStore() {
return getOrCreate(() -> aciIdentityKeyStore,
() -> aciIdentityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
() -> aciIdentityKeyPair,
localRegistrationId,
getIdentityKeyStore()));
}
public GroupStore getGroupStore() { public GroupStore getGroupStore() {
return groupStore; return groupStore;
} }
@ -1390,7 +1397,7 @@ public class SignalAccount implements Closeable {
getSenderKeyStore().deleteAll(); getSenderKeyStore().deleteAll();
final var recipientId = getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress()); final var recipientId = getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
final var publicKey = getAciIdentityKeyPair().getPublicKey(); final var publicKey = getAciIdentityKeyPair().getPublicKey();
getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date()); getIdentityKeyStore().saveIdentity(recipientId, publicKey);
getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED); getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
} }

View file

@ -7,9 +7,8 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver; import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.IOUtils;
import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,7 +31,7 @@ import java.util.regex.Pattern;
import io.reactivex.rxjava3.subjects.PublishSubject; import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject; import io.reactivex.rxjava3.subjects.Subject;
public class IdentityKeyStore implements org.signal.libsignal.protocol.state.IdentityKeyStore { public class IdentityKeyStore {
private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class); private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class);
private final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper(); private final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
@ -42,24 +41,16 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
private final File identitiesPath; private final File identitiesPath;
private final RecipientResolver resolver; private final RecipientResolver resolver;
private final IdentityKeyPair identityKeyPair;
private final int localRegistrationId;
private final TrustNewIdentity trustNewIdentity; private final TrustNewIdentity trustNewIdentity;
private final PublishSubject<RecipientId> identityChanges = PublishSubject.create(); private final PublishSubject<RecipientId> identityChanges = PublishSubject.create();
private boolean isRetryingDecryption = false; private boolean isRetryingDecryption = false;
public IdentityKeyStore( public IdentityKeyStore(
final File identitiesPath, final File identitiesPath, final RecipientResolver resolver, final TrustNewIdentity trustNewIdentity
final RecipientResolver resolver,
final IdentityKeyPair identityKeyPair,
final int localRegistrationId,
final TrustNewIdentity trustNewIdentity
) { ) {
this.identitiesPath = identitiesPath; this.identitiesPath = identitiesPath;
this.resolver = resolver; this.resolver = resolver;
this.identityKeyPair = identityKeyPair;
this.localRegistrationId = localRegistrationId;
this.trustNewIdentity = trustNewIdentity; this.trustNewIdentity = trustNewIdentity;
} }
@ -67,21 +58,8 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
return identityChanges; return identityChanges;
} }
@Override public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey) {
public IdentityKeyPair getIdentityKeyPair() { return saveIdentity(recipientId, identityKey, null);
return identityKeyPair;
}
@Override
public int getLocalRegistrationId() {
return localRegistrationId;
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
final var recipientId = resolveRecipient(address.getName());
return saveIdentity(recipientId, identityKey, new Date());
} }
public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey, Date added) { public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey, Date added) {
@ -100,7 +78,10 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && identityInfo == null trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && identityInfo == null
) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED; ) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
logger.debug("Storing new identity for recipient {} with trust {}", recipientId, trustLevel); logger.debug("Storing new identity for recipient {} with trust {}", recipientId, trustLevel);
final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, added); final var newIdentityInfo = new IdentityInfo(recipientId,
identityKey,
trustLevel,
added == null ? new Date() : added);
storeIdentityLocked(recipientId, newIdentityInfo); storeIdentityLocked(recipientId, newIdentityInfo);
identityChanges.onNext(recipientId); identityChanges.onNext(recipientId);
return true; return true;
@ -137,26 +118,23 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
} }
} }
@Override public boolean isTrustedIdentity(RecipientId recipientId, IdentityKey identityKey, Direction direction) {
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
if (trustNewIdentity == TrustNewIdentity.ALWAYS) { if (trustNewIdentity == TrustNewIdentity.ALWAYS) {
return true; return true;
} }
var recipientId = resolveRecipient(address.getName());
synchronized (cachedIdentities) { synchronized (cachedIdentities) {
// TODO implement possibility for different handling of incoming/outgoing trust decisions // TODO implement possibility for different handling of incoming/outgoing trust decisions
var identityInfo = loadIdentityLocked(recipientId); var identityInfo = loadIdentityLocked(recipientId);
if (identityInfo == null) { if (identityInfo == null) {
logger.debug("Initial identity found for {}, saving.", recipientId); logger.debug("Initial identity found for {}, saving.", recipientId);
saveIdentity(address, identityKey); saveIdentity(recipientId, identityKey);
identityInfo = loadIdentityLocked(recipientId); identityInfo = loadIdentityLocked(recipientId);
} else if (!identityInfo.getIdentityKey().equals(identityKey)) { } else if (!identityInfo.getIdentityKey().equals(identityKey)) {
// Identity found, but different // Identity found, but different
if (direction == Direction.SENDING) { if (direction == Direction.SENDING) {
logger.debug("Changed identity found for {}, saving.", recipientId); logger.debug("Changed identity found for {}, saving.", recipientId);
saveIdentity(address, identityKey); saveIdentity(recipientId, identityKey);
identityInfo = loadIdentityLocked(recipientId); identityInfo = loadIdentityLocked(recipientId);
} else { } else {
logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, false); logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, false);
@ -170,17 +148,14 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
} }
} }
@Override public IdentityKey getIdentity(RecipientId recipientId) {
public IdentityKey getIdentity(SignalProtocolAddress address) {
var recipientId = resolveRecipient(address.getName());
synchronized (cachedIdentities) { synchronized (cachedIdentities) {
var identity = loadIdentityLocked(recipientId); var identity = loadIdentityLocked(recipientId);
return identity == null ? null : identity.getIdentityKey(); return identity == null ? null : identity.getIdentityKey();
} }
} }
public IdentityInfo getIdentity(RecipientId recipientId) { public IdentityInfo getIdentityInfo(RecipientId recipientId) {
synchronized (cachedIdentities) { synchronized (cachedIdentities) {
return loadIdentityLocked(recipientId); return loadIdentityLocked(recipientId);
} }
@ -214,13 +189,6 @@ public class IdentityKeyStore implements org.signal.libsignal.protocol.state.Ide
} }
} }
/**
* @param identifier can be either a serialized uuid or a e164 phone number
*/
private RecipientId resolveRecipient(String identifier) {
return resolver.resolveRecipient(identifier);
}
private File getIdentityFile(final RecipientId recipientId) { private File getIdentityFile(final RecipientId recipientId) {
try { try {
IOUtils.createPrivateDirectories(identitiesPath); IOUtils.createPrivateDirectories(identitiesPath);

View file

@ -0,0 +1,66 @@
package org.asamk.signal.manager.storage.identities;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import java.util.function.Supplier;
public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.state.IdentityKeyStore {
private final RecipientResolver resolver;
private final Supplier<IdentityKeyPair> identityKeyPairSupplier;
private final int localRegistrationId;
private final IdentityKeyStore identityKeyStore;
public SignalIdentityKeyStore(
final RecipientResolver resolver,
final Supplier<IdentityKeyPair> identityKeyPairSupplier,
final int localRegistrationId,
final IdentityKeyStore identityKeyStore
) {
this.resolver = resolver;
this.identityKeyPairSupplier = identityKeyPairSupplier;
this.localRegistrationId = localRegistrationId;
this.identityKeyStore = identityKeyStore;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return identityKeyPairSupplier.get();
}
@Override
public int getLocalRegistrationId() {
return localRegistrationId;
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
final var recipientId = resolveRecipient(address.getName());
return identityKeyStore.saveIdentity(recipientId, identityKey);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
var recipientId = resolveRecipient(address.getName());
return identityKeyStore.isTrustedIdentity(recipientId, identityKey, direction);
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
var recipientId = resolveRecipient(address.getName());
return identityKeyStore.getIdentity(recipientId);
}
/**
* @param identifier can be either a serialized uuid or an e164 phone number
*/
private RecipientId resolveRecipient(String identifier) {
return resolver.resolveRecipient(identifier);
}
}