mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Refactor identity key store
This commit is contained in:
parent
afb22deada
commit
8a0c6cae15
19 changed files with 717 additions and 563 deletions
|
@ -34,9 +34,10 @@ import org.asamk.signal.manager.storage.contacts.ContactInfo;
|
|||
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
|
||||
import org.asamk.signal.manager.storage.identities.IdentityInfo;
|
||||
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
|
||||
import org.asamk.signal.manager.storage.profiles.SignalProfile;
|
||||
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.stickers.Sticker;
|
||||
import org.asamk.signal.manager.util.AttachmentUtils;
|
||||
import org.asamk.signal.manager.util.IOUtils;
|
||||
|
@ -234,8 +235,6 @@ public class Manager implements Closeable {
|
|||
clientZkProfileOperations,
|
||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||
|
||||
this.account.setResolver(this::resolveSignalServiceAddress);
|
||||
|
||||
this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
|
||||
account.getProfileStore()::getProfileKey,
|
||||
this::getRecipientProfile,
|
||||
|
@ -1223,17 +1222,7 @@ public class Manager implements Closeable {
|
|||
|
||||
private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException {
|
||||
var messageSender = createMessageSender();
|
||||
try {
|
||||
messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
|
||||
} catch (UntrustedIdentityException e) {
|
||||
if (e.getIdentityKey() != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
|
||||
e.getIdentityKey(),
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
|
||||
}
|
||||
|
||||
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
|
||||
|
@ -1303,22 +1292,8 @@ public class Manager implements Closeable {
|
|||
unidentifiedAccessHelper.getAccessFor(recipients),
|
||||
isRecipientUpdate,
|
||||
message);
|
||||
for (var r : result) {
|
||||
if (r.getIdentityFailure() != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(r.getAddress(),
|
||||
r.getIdentityFailure().getIdentityKey(),
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
}
|
||||
return new Pair<>(timestamp, result);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
if (e.getIdentityKey() != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
|
||||
e.getIdentityKey(),
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
return new Pair<>(timestamp, List.of());
|
||||
}
|
||||
} else {
|
||||
|
@ -1388,12 +1363,6 @@ public class Manager implements Closeable {
|
|||
false,
|
||||
System.currentTimeMillis() - startTime);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
if (e.getIdentityKey() != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
|
||||
e.getIdentityKey(),
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
|
||||
}
|
||||
}
|
||||
|
@ -1406,12 +1375,6 @@ public class Manager implements Closeable {
|
|||
try {
|
||||
return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
if (e.getIdentityKey() != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
|
||||
e.getIdentityKey(),
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
||||
}
|
||||
}
|
||||
|
@ -1424,15 +1387,7 @@ public class Manager implements Closeable {
|
|||
return cipher.decrypt(envelope);
|
||||
} catch (ProtocolUntrustedIdentityException e) {
|
||||
if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
|
||||
var identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
|
||||
final var untrustedIdentity = identityException.getUntrustedIdentity();
|
||||
if (untrustedIdentity != null) {
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(resolveSignalServiceAddress(identityException.getName()),
|
||||
untrustedIdentity,
|
||||
TrustLevel.UNTRUSTED);
|
||||
}
|
||||
throw identityException;
|
||||
throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
|
||||
}
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
@ -2004,8 +1959,8 @@ public class Manager implements Closeable {
|
|||
}
|
||||
if (c.getVerified().isPresent()) {
|
||||
final var verifiedMessage = c.getVerified().get();
|
||||
account.getSignalProtocolStore()
|
||||
.setIdentityTrustLevel(verifiedMessage.getDestination(),
|
||||
account.getIdentityKeyStore()
|
||||
.setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
|
||||
verifiedMessage.getIdentityKey(),
|
||||
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
|
||||
}
|
||||
|
@ -2040,8 +1995,8 @@ public class Manager implements Closeable {
|
|||
}
|
||||
if (syncMessage.getVerified().isPresent()) {
|
||||
final var verifiedMessage = syncMessage.getVerified().get();
|
||||
account.getSignalProtocolStore()
|
||||
.setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()),
|
||||
account.getIdentityKeyStore()
|
||||
.setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
|
||||
verifiedMessage.getIdentityKey(),
|
||||
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
|
||||
}
|
||||
|
@ -2283,7 +2238,8 @@ public class Manager implements Closeable {
|
|||
var out = new DeviceContactsOutputStream(fos);
|
||||
for (var record : account.getContactStore().getContacts()) {
|
||||
VerifiedMessage verifiedMessage = null;
|
||||
var currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
|
||||
var currentIdentity = account.getIdentityKeyStore()
|
||||
.getIdentity(resolveRecipientTrusted(record.getAddress()));
|
||||
if (currentIdentity != null) {
|
||||
verifiedMessage = new VerifiedMessage(record.getAddress(),
|
||||
currentIdentity.getIdentityKey(),
|
||||
|
@ -2395,11 +2351,12 @@ public class Manager implements Closeable {
|
|||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities() {
|
||||
return account.getSignalProtocolStore().getIdentities();
|
||||
return account.getIdentityKeyStore().getIdentities();
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
|
||||
return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
|
||||
final var identity = account.getIdentityKeyStore().getIdentity(canonicalizeAndResolveRecipient(number));
|
||||
return identity == null ? List.of() : List.of(identity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2409,8 +2366,10 @@ public class Manager implements Closeable {
|
|||
* @param fingerprint Fingerprint
|
||||
*/
|
||||
public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
|
||||
var address = canonicalizeAndResolveSignalServiceAddress(name);
|
||||
return trustIdentity(address, (identityKey) -> Arrays.equals(identityKey.serialize(), fingerprint));
|
||||
var recipientId = canonicalizeAndResolveRecipient(name);
|
||||
return trustIdentity(recipientId,
|
||||
identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
|
||||
TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2420,47 +2379,11 @@ public class Manager implements Closeable {
|
|||
* @param safetyNumber Safety number
|
||||
*/
|
||||
public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
|
||||
var address = canonicalizeAndResolveSignalServiceAddress(name);
|
||||
return trustIdentity(address, (identityKey) -> safetyNumber.equals(computeSafetyNumber(address, identityKey)));
|
||||
}
|
||||
|
||||
private boolean trustIdentity(SignalServiceAddress address, Function<IdentityKey, Boolean> verifier) {
|
||||
var ids = account.getSignalProtocolStore().getIdentities(address);
|
||||
if (ids == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IdentityInfo foundIdentity = null;
|
||||
|
||||
for (var id : ids) {
|
||||
if (verifier.apply(id.getIdentityKey())) {
|
||||
foundIdentity = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIdentity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
account.getSignalProtocolStore()
|
||||
.setIdentityTrustLevel(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
|
||||
try {
|
||||
sendVerifiedMessage(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
logger.warn("Failed to send verification sync message: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Successfully trusted the new identity, now remove all other identities for that number
|
||||
for (var id : ids) {
|
||||
if (id == foundIdentity) {
|
||||
continue;
|
||||
}
|
||||
account.getSignalProtocolStore().removeIdentity(address, id.getIdentityKey());
|
||||
}
|
||||
|
||||
account.save();
|
||||
return true;
|
||||
var recipientId = canonicalizeAndResolveRecipient(name);
|
||||
var address = account.getRecipientStore().resolveServiceAddress(recipientId);
|
||||
return trustIdentity(recipientId,
|
||||
identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
|
||||
TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2468,24 +2391,31 @@ public class Manager implements Closeable {
|
|||
*
|
||||
* @param name username of the identity
|
||||
*/
|
||||
public boolean trustIdentityAllKeys(String name) {
|
||||
var address = resolveSignalServiceAddress(name);
|
||||
var ids = account.getSignalProtocolStore().getIdentities(address);
|
||||
if (ids == null) {
|
||||
public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
|
||||
var recipientId = canonicalizeAndResolveRecipient(name);
|
||||
return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
|
||||
}
|
||||
|
||||
private boolean trustIdentity(
|
||||
RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
|
||||
) {
|
||||
var identity = account.getIdentityKeyStore().getIdentity(recipientId);
|
||||
if (identity == null) {
|
||||
return false;
|
||||
}
|
||||
for (var id : ids) {
|
||||
if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
|
||||
account.getSignalProtocolStore()
|
||||
.setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
|
||||
try {
|
||||
sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
logger.warn("Failed to send verification sync message: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!verifier.apply(identity.getIdentityKey())) {
|
||||
return false;
|
||||
}
|
||||
account.save();
|
||||
|
||||
account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
|
||||
try {
|
||||
var address = account.getRecipientStore().resolveServiceAddress(recipientId);
|
||||
sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
logger.warn("Failed to send verification sync message: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2499,6 +2429,7 @@ public class Manager implements Closeable {
|
|||
theirIdentityKey);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
|
||||
var canonicalizedNumber = UuidUtil.isUuid(identifier)
|
||||
? identifier
|
||||
|
@ -2506,12 +2437,14 @@ public class Manager implements Closeable {
|
|||
return resolveSignalServiceAddress(canonicalizedNumber);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
|
||||
var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
|
||||
|
||||
return resolveSignalServiceAddress(address);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
|
||||
if (address.matches(account.getSelfAddress())) {
|
||||
return account.getSelfAddress();
|
||||
|
@ -2520,6 +2453,27 @@ public class Manager implements Closeable {
|
|||
return account.getRecipientStore().resolveServiceAddress(address);
|
||||
}
|
||||
|
||||
public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
|
||||
return account.getRecipientStore().resolveServiceAddress(recipientId);
|
||||
}
|
||||
|
||||
public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
|
||||
var canonicalizedNumber = UuidUtil.isUuid(identifier)
|
||||
? identifier
|
||||
: PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
|
||||
var address = Utils.getSignalServiceAddressFromIdentifier(canonicalizedNumber);
|
||||
|
||||
return resolveRecipient(address);
|
||||
}
|
||||
|
||||
public RecipientId resolveRecipient(SignalServiceAddress address) {
|
||||
return account.getRecipientStore().resolveRecipientUntrusted(address);
|
||||
}
|
||||
|
||||
private RecipientId resolveRecipientTrusted(SignalServiceAddress address) {
|
||||
return account.getRecipientStore().resolveRecipient(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
close(true);
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider
|
|||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class RegistrationManager implements Closeable {
|
||||
|
@ -164,10 +165,10 @@ public class RegistrationManager implements Closeable {
|
|||
account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
|
||||
account.setRegistrationLockPin(pin);
|
||||
account.getSessionStore().archiveAllSessions();
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(account.getSelfAddress(),
|
||||
account.getIdentityKeyPair().getPublicKey(),
|
||||
TrustLevel.TRUSTED_VERIFIED);
|
||||
final var recipientId = account.getRecipientStore().resolveRecipient(account.getSelfAddress());
|
||||
final var publicKey = account.getIdentityKeyPair().getPublicKey();
|
||||
account.getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
|
||||
account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
|
||||
Manager m = null;
|
||||
try {
|
||||
|
|
|
@ -14,12 +14,13 @@ import org.asamk.signal.manager.storage.contacts.ContactInfo;
|
|||
import org.asamk.signal.manager.storage.contacts.JsonContactsStore;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
||||
import org.asamk.signal.manager.storage.groups.JsonGroupStore;
|
||||
import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
|
||||
import org.asamk.signal.manager.storage.messageCache.MessageCache;
|
||||
import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
|
||||
import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
|
||||
import org.asamk.signal.manager.storage.profiles.ProfileStore;
|
||||
import org.asamk.signal.manager.storage.protocol.JsonSignalProtocolStore;
|
||||
import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver;
|
||||
import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
|
||||
import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
|
||||
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientStore;
|
||||
|
@ -81,10 +82,11 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
private boolean registered = false;
|
||||
|
||||
private JsonSignalProtocolStore signalProtocolStore;
|
||||
private SignalProtocolStore signalProtocolStore;
|
||||
private PreKeyStore preKeyStore;
|
||||
private SignedPreKeyStore signedPreKeyStore;
|
||||
private SessionStore sessionStore;
|
||||
private IdentityKeyStore identityKeyStore;
|
||||
private JsonGroupStore groupStore;
|
||||
private JsonContactsStore contactStore;
|
||||
private RecipientStore recipientStore;
|
||||
|
@ -141,11 +143,14 @@ public class SignalAccount implements Closeable {
|
|||
account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
|
||||
account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
|
||||
account.recipientStore::resolveRecipient);
|
||||
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey,
|
||||
registrationId,
|
||||
account.preKeyStore,
|
||||
account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
|
||||
account.recipientStore::resolveRecipient,
|
||||
identityKey,
|
||||
registrationId);
|
||||
account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
|
||||
account.signedPreKeyStore,
|
||||
account.sessionStore);
|
||||
account.sessionStore,
|
||||
account.identityKeyStore);
|
||||
account.profileStore = new ProfileStore();
|
||||
account.stickerStore = new StickerStore();
|
||||
|
||||
|
@ -190,11 +195,14 @@ public class SignalAccount implements Closeable {
|
|||
account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
|
||||
account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
|
||||
account.recipientStore::resolveRecipient);
|
||||
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey,
|
||||
registrationId,
|
||||
account.preKeyStore,
|
||||
account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
|
||||
account.recipientStore::resolveRecipient,
|
||||
identityKey,
|
||||
registrationId);
|
||||
account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
|
||||
account.signedPreKeyStore,
|
||||
account.sessionStore);
|
||||
account.sessionStore,
|
||||
account.identityKeyStore);
|
||||
account.profileStore = new ProfileStore();
|
||||
account.stickerStore = new StickerStore();
|
||||
|
||||
|
@ -235,6 +243,7 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
identityKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
}
|
||||
|
||||
public static File getFileName(File dataPath, String username) {
|
||||
|
@ -261,6 +270,10 @@ public class SignalAccount implements Closeable {
|
|||
return new File(getUserPath(dataPath, username), "signed-pre-keys");
|
||||
}
|
||||
|
||||
private static File getIdentitiesPath(File dataPath, String username) {
|
||||
return new File(getUserPath(dataPath, username), "identities");
|
||||
}
|
||||
|
||||
private static File getSessionsPath(File dataPath, String username) {
|
||||
return new File(getUserPath(dataPath, username), "sessions");
|
||||
}
|
||||
|
@ -299,6 +312,17 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
username = Utils.getNotNullNode(rootNode, "username").asText();
|
||||
password = Utils.getNotNullNode(rootNode, "password").asText();
|
||||
int registrationId = 0;
|
||||
if (rootNode.hasNonNull("registrationId")) {
|
||||
registrationId = rootNode.get("registrationId").asInt();
|
||||
}
|
||||
IdentityKeyPair identityKeyPair = null;
|
||||
if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
|
||||
final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
|
||||
final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
|
||||
identityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
|
||||
}
|
||||
|
||||
if (rootNode.hasNonNull("registrationLockPin")) {
|
||||
registrationLockPin = rootNode.get("registrationLockPin").asText();
|
||||
}
|
||||
|
@ -338,13 +362,15 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
|
||||
JsonSignalProtocolStore.class);
|
||||
var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
|
||||
? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
|
||||
LegacyJsonSignalProtocolStore.class)
|
||||
: null;
|
||||
|
||||
preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
|
||||
if (signalProtocolStore.getLegacyPreKeyStore() != null) {
|
||||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
|
||||
logger.debug("Migrating legacy pre key store.");
|
||||
for (var entry : signalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
|
||||
for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
|
||||
try {
|
||||
preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
|
||||
} catch (IOException e) {
|
||||
|
@ -352,12 +378,11 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
}
|
||||
}
|
||||
signalProtocolStore.setPreKeyStore(preKeyStore);
|
||||
|
||||
signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
|
||||
if (signalProtocolStore.getLegacySignedPreKeyStore() != null) {
|
||||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
|
||||
logger.debug("Migrating legacy signed pre key store.");
|
||||
for (var entry : signalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
|
||||
for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
|
||||
try {
|
||||
signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
|
||||
} catch (IOException e) {
|
||||
|
@ -365,12 +390,11 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
}
|
||||
}
|
||||
signalProtocolStore.setSignedPreKeyStore(signedPreKeyStore);
|
||||
|
||||
sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient);
|
||||
if (signalProtocolStore.getLegacySessionStore() != null) {
|
||||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
|
||||
logger.debug("Migrating legacy session store.");
|
||||
for (var session : signalProtocolStore.getLegacySessionStore().getSessions()) {
|
||||
for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
|
||||
try {
|
||||
sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
|
||||
session.deviceId), new SessionRecord(session.sessionRecord));
|
||||
|
@ -379,7 +403,27 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
}
|
||||
}
|
||||
signalProtocolStore.setSessionStore(sessionStore);
|
||||
|
||||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
|
||||
identityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
|
||||
registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
|
||||
}
|
||||
identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
|
||||
recipientStore::resolveRecipient,
|
||||
identityKeyPair,
|
||||
registrationId);
|
||||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
|
||||
logger.debug("Migrating identity session store.");
|
||||
for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
|
||||
RecipientId recipientId = recipientStore.resolveRecipient(identity.getAddress());
|
||||
identityKeyStore.saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
|
||||
identityKeyStore.setIdentityTrustLevel(recipientId,
|
||||
identity.getIdentityKey(),
|
||||
identity.getTrustLevel());
|
||||
}
|
||||
}
|
||||
|
||||
signalProtocolStore = new SignalProtocolStore(preKeyStore, signedPreKeyStore, sessionStore, identityKeyStore);
|
||||
|
||||
registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
|
||||
var groupStoreNode = rootNode.get("groupStore");
|
||||
|
@ -431,10 +475,6 @@ public class SignalAccount implements Closeable {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
for (var identity : signalProtocolStore.getIdentities()) {
|
||||
identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
messageCache = new MessageCache(getMessageCachePath(dataPath, username));
|
||||
|
@ -475,6 +515,13 @@ public class SignalAccount implements Closeable {
|
|||
.put("deviceId", deviceId)
|
||||
.put("isMultiDevice", isMultiDevice)
|
||||
.put("password", password)
|
||||
.put("registrationId", identityKeyStore.getLocalRegistrationId())
|
||||
.put("identityPrivateKey",
|
||||
Base64.getEncoder()
|
||||
.encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
|
||||
.put("identityKey",
|
||||
Base64.getEncoder()
|
||||
.encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
|
||||
.put("registrationLockPin", registrationLockPin)
|
||||
.put("pinMasterKey",
|
||||
pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
|
||||
|
@ -484,7 +531,6 @@ public class SignalAccount implements Closeable {
|
|||
.put("nextSignedPreKeyId", nextSignedPreKeyId)
|
||||
.put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
|
||||
.put("registered", registered)
|
||||
.putPOJO("axolotlStore", signalProtocolStore)
|
||||
.putPOJO("groupStore", groupStore)
|
||||
.putPOJO("contactStore", contactStore)
|
||||
.putPOJO("profileStore", profileStore)
|
||||
|
@ -517,10 +563,6 @@ public class SignalAccount implements Closeable {
|
|||
return new Pair<>(fileChannel, lock);
|
||||
}
|
||||
|
||||
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||
signalProtocolStore.setResolver(resolver);
|
||||
}
|
||||
|
||||
public void addPreKeys(List<PreKeyRecord> records) {
|
||||
for (var record : records) {
|
||||
if (preKeyIdOffset != record.getId()) {
|
||||
|
@ -543,7 +585,7 @@ public class SignalAccount implements Closeable {
|
|||
save();
|
||||
}
|
||||
|
||||
public JsonSignalProtocolStore getSignalProtocolStore() {
|
||||
public SignalProtocolStore getSignalProtocolStore() {
|
||||
return signalProtocolStore;
|
||||
}
|
||||
|
||||
|
@ -551,6 +593,10 @@ public class SignalAccount implements Closeable {
|
|||
return sessionStore;
|
||||
}
|
||||
|
||||
public IdentityKeyStore getIdentityKeyStore() {
|
||||
return identityKeyStore;
|
||||
}
|
||||
|
||||
public JsonGroupStore getGroupStore() {
|
||||
return groupStore;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.asamk.signal.manager.storage.identities;
|
||||
|
||||
import org.asamk.signal.manager.TrustLevel;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class IdentityInfo {
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final IdentityKey identityKey;
|
||||
private final TrustLevel trustLevel;
|
||||
private final Date added;
|
||||
|
||||
IdentityInfo(
|
||||
final RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel, Date added
|
||||
) {
|
||||
this.recipientId = recipientId;
|
||||
this.identityKey = identityKey;
|
||||
this.trustLevel = trustLevel;
|
||||
this.added = added;
|
||||
}
|
||||
|
||||
public RecipientId getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return this.identityKey;
|
||||
}
|
||||
|
||||
public TrustLevel getTrustLevel() {
|
||||
return this.trustLevel;
|
||||
}
|
||||
|
||||
boolean isTrusted() {
|
||||
return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
|
||||
}
|
||||
|
||||
public Date getDateAdded() {
|
||||
return this.added;
|
||||
}
|
||||
|
||||
public byte[] getFingerprint() {
|
||||
return identityKey.getPublicKey().serialize();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
package org.asamk.signal.manager.storage.identities;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.asamk.signal.manager.TrustLevel;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.asamk.signal.manager.util.IOUtils;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IdentityKeyStore implements org.whispersystems.libsignal.state.IdentityKeyStore {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class);
|
||||
private final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
|
||||
|
||||
private final Map<RecipientId, IdentityInfo> cachedIdentities = new HashMap<>();
|
||||
|
||||
private final File identitiesPath;
|
||||
|
||||
private final RecipientResolver resolver;
|
||||
private final IdentityKeyPair identityKeyPair;
|
||||
private final int localRegistrationId;
|
||||
|
||||
public IdentityKeyStore(
|
||||
final File identitiesPath,
|
||||
final RecipientResolver resolver,
|
||||
final IdentityKeyPair identityKeyPair,
|
||||
final int localRegistrationId
|
||||
) {
|
||||
this.identitiesPath = identitiesPath;
|
||||
this.resolver = resolver;
|
||||
this.identityKeyPair = identityKeyPair;
|
||||
this.localRegistrationId = localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
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) {
|
||||
synchronized (cachedIdentities) {
|
||||
final var identityInfo = loadIdentityLocked(recipientId);
|
||||
if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
// Identity already exists, not updating the trust level
|
||||
return false;
|
||||
}
|
||||
|
||||
final var trustLevel = identityInfo == null ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
|
||||
final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, added);
|
||||
storeIdentityLocked(recipientId, newIdentityInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setIdentityTrustLevel(
|
||||
RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel
|
||||
) {
|
||||
synchronized (cachedIdentities) {
|
||||
final var identityInfo = loadIdentityLocked(recipientId);
|
||||
if (identityInfo == null || !identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
// Identity not found, not updating the trust level
|
||||
return false;
|
||||
}
|
||||
|
||||
final var newIdentityInfo = new IdentityInfo(recipientId,
|
||||
identityKey,
|
||||
trustLevel,
|
||||
identityInfo.getDateAdded());
|
||||
storeIdentityLocked(recipientId, newIdentityInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
var recipientId = resolveRecipient(address.getName());
|
||||
|
||||
synchronized (cachedIdentities) {
|
||||
final var identityInfo = loadIdentityLocked(recipientId);
|
||||
if (identityInfo == null) {
|
||||
// Identity not found
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
||||
if (!identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
// Identity found, but different
|
||||
return false;
|
||||
}
|
||||
|
||||
return identityInfo.isTrusted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
var recipientId = resolveRecipient(address.getName());
|
||||
|
||||
synchronized (cachedIdentities) {
|
||||
var identity = loadIdentityLocked(recipientId);
|
||||
return identity == null ? null : identity.getIdentityKey();
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityInfo getIdentity(RecipientId recipientId) {
|
||||
synchronized (cachedIdentities) {
|
||||
return loadIdentityLocked(recipientId);
|
||||
}
|
||||
}
|
||||
|
||||
final Pattern identityFileNamePattern = Pattern.compile("([0-9]+)");
|
||||
|
||||
public List<IdentityInfo> getIdentities() {
|
||||
final var files = identitiesPath.listFiles();
|
||||
if (files == null) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(files)
|
||||
.filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
|
||||
.map(f -> RecipientId.of(Integer.parseInt(f.getName())))
|
||||
.map(this::loadIdentityLocked)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void mergeRecipients(final RecipientId recipientId, final RecipientId toBeMergedRecipientId) {
|
||||
synchronized (cachedIdentities) {
|
||||
deleteIdentityLocked(toBeMergedRecipientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param identifier can be either a serialized uuid or a e164 phone number
|
||||
*/
|
||||
private RecipientId resolveRecipient(String identifier) {
|
||||
return resolver.resolveRecipient(Utils.getSignalServiceAddressFromIdentifier(identifier));
|
||||
}
|
||||
|
||||
private File getIdentityFile(final RecipientId recipientId) {
|
||||
try {
|
||||
IOUtils.createPrivateDirectories(identitiesPath);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Failed to create identities path", e);
|
||||
}
|
||||
return new File(identitiesPath, String.valueOf(recipientId.getId()));
|
||||
}
|
||||
|
||||
private IdentityInfo loadIdentityLocked(final RecipientId recipientId) {
|
||||
{
|
||||
final var session = cachedIdentities.get(recipientId);
|
||||
if (session != null) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
final var file = getIdentityFile(recipientId);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
try (var inputStream = new FileInputStream(file)) {
|
||||
var storage = objectMapper.readValue(inputStream, IdentityStorage.class);
|
||||
|
||||
var id = new IdentityKey(Base64.getDecoder().decode(storage.getIdentityKey()));
|
||||
var trustLevel = TrustLevel.fromInt(storage.getTrustLevel());
|
||||
var added = new Date(storage.getAddedTimestamp());
|
||||
|
||||
final var identityInfo = new IdentityInfo(recipientId, id, trustLevel, added);
|
||||
cachedIdentities.put(recipientId, identityInfo);
|
||||
return identityInfo;
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
logger.warn("Failed to load identity key: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void storeIdentityLocked(final RecipientId recipientId, final IdentityInfo identityInfo) {
|
||||
cachedIdentities.put(recipientId, identityInfo);
|
||||
|
||||
var storage = new IdentityStorage(Base64.getEncoder().encodeToString(identityInfo.getIdentityKey().serialize()),
|
||||
identityInfo.getTrustLevel().ordinal(),
|
||||
identityInfo.getDateAdded().getTime());
|
||||
|
||||
final var file = getIdentityFile(recipientId);
|
||||
// Write to memory first to prevent corrupting the file in case of serialization errors
|
||||
try (var inMemoryOutput = new ByteArrayOutputStream()) {
|
||||
objectMapper.writeValue(inMemoryOutput, storage);
|
||||
|
||||
var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());
|
||||
try (var outputStream = new FileOutputStream(file)) {
|
||||
input.transferTo(outputStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error saving identity file: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteIdentityLocked(final RecipientId recipientId) {
|
||||
cachedIdentities.remove(recipientId);
|
||||
|
||||
final var file = getIdentityFile(recipientId);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Files.delete(file.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to delete identity file {}: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IdentityStorage {
|
||||
|
||||
private String identityKey;
|
||||
private int trustLevel;
|
||||
private long addedTimestamp;
|
||||
|
||||
// For deserialization
|
||||
private IdentityStorage() {
|
||||
}
|
||||
|
||||
private IdentityStorage(final String identityKey, final int trustLevel, final long addedTimestamp) {
|
||||
this.identityKey = identityKey;
|
||||
this.trustLevel = trustLevel;
|
||||
this.addedTimestamp = addedTimestamp;
|
||||
}
|
||||
|
||||
public String getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public int getTrustLevel() {
|
||||
return trustLevel;
|
||||
}
|
||||
|
||||
public long getAddedTimestamp() {
|
||||
return addedTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,277 +0,0 @@
|
|||
package org.asamk.signal.manager.storage.protocol;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import org.asamk.signal.manager.TrustLevel;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(JsonIdentityKeyStore.class);
|
||||
|
||||
private final List<IdentityInfo> identities = new ArrayList<>();
|
||||
|
||||
private final IdentityKeyPair identityKeyPair;
|
||||
private final int localRegistrationId;
|
||||
|
||||
private SignalServiceAddressResolver resolver;
|
||||
|
||||
public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) {
|
||||
this.identityKeyPair = identityKeyPair;
|
||||
this.localRegistrationId = localRegistrationId;
|
||||
}
|
||||
|
||||
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
private SignalServiceAddress resolveSignalServiceAddress(String identifier) {
|
||||
if (resolver != null) {
|
||||
return resolver.resolveSignalServiceAddress(identifier);
|
||||
} else {
|
||||
return Utils.getSignalServiceAddressFromIdentifier(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identityKeyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return saveIdentity(resolveSignalServiceAddress(address.getName()),
|
||||
identityKey,
|
||||
TrustLevel.TRUSTED_UNVERIFIED,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given identityKey for the user name and sets the trustLevel and added timestamp.
|
||||
* If the identityKey already exists, the trustLevel and added timestamp are NOT updated.
|
||||
*
|
||||
* @param serviceAddress User address, i.e. phone number and/or uuid
|
||||
* @param identityKey The user's public key
|
||||
* @param trustLevel Level of trust: untrusted, trusted, trusted and verified
|
||||
* @param added Added timestamp, if null and the key is newly added, the current time is used.
|
||||
*/
|
||||
public boolean saveIdentity(
|
||||
SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added
|
||||
) {
|
||||
for (var id : identities) {
|
||||
if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) {
|
||||
id.address = serviceAddress;
|
||||
}
|
||||
// Identity already exists, not updating the trust level
|
||||
return true;
|
||||
}
|
||||
|
||||
identities.add(new IdentityInfo(serviceAddress, identityKey, trustLevel, added != null ? added : new Date()));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update trustLevel for the given identityKey for the user name.
|
||||
*
|
||||
* @param serviceAddress User address, i.e. phone number and/or uuid
|
||||
* @param identityKey The user's public key
|
||||
* @param trustLevel Level of trust: untrusted, trusted, trusted and verified
|
||||
*/
|
||||
public void setIdentityTrustLevel(
|
||||
SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel
|
||||
) {
|
||||
for (var id : identities) {
|
||||
if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) {
|
||||
id.address = serviceAddress;
|
||||
}
|
||||
id.trustLevel = trustLevel;
|
||||
return;
|
||||
}
|
||||
|
||||
identities.add(new IdentityInfo(serviceAddress, identityKey, trustLevel, new Date()));
|
||||
}
|
||||
|
||||
public void removeIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey) {
|
||||
identities.removeIf(id -> id.address.matches(serviceAddress) && id.identityKey.equals(identityKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
||||
var serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||
var trustOnFirstUse = true;
|
||||
|
||||
for (var id : identities) {
|
||||
if (!id.address.matches(serviceAddress)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id.identityKey.equals(identityKey)) {
|
||||
return id.isTrusted();
|
||||
} else {
|
||||
trustOnFirstUse = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trustOnFirstUse) {
|
||||
saveIdentity(resolveSignalServiceAddress(address.getName()), identityKey, TrustLevel.UNTRUSTED, null);
|
||||
}
|
||||
|
||||
return trustOnFirstUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
var serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||
var identity = getIdentity(serviceAddress);
|
||||
return identity == null ? null : identity.getIdentityKey();
|
||||
}
|
||||
|
||||
public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
|
||||
long maxDate = 0;
|
||||
IdentityInfo maxIdentity = null;
|
||||
for (var id : this.identities) {
|
||||
if (!id.address.matches(serviceAddress)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var time = id.getDateAdded().getTime();
|
||||
if (maxIdentity == null || maxDate <= time) {
|
||||
maxDate = time;
|
||||
maxIdentity = id;
|
||||
}
|
||||
}
|
||||
return maxIdentity;
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities() {
|
||||
// TODO deep copy
|
||||
return identities;
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities(SignalServiceAddress serviceAddress) {
|
||||
var identities = new ArrayList<IdentityInfo>();
|
||||
for (var identity : this.identities) {
|
||||
if (identity.address.matches(serviceAddress)) {
|
||||
identities.add(identity);
|
||||
}
|
||||
}
|
||||
return identities;
|
||||
}
|
||||
|
||||
public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<JsonIdentityKeyStore> {
|
||||
|
||||
@Override
|
||||
public JsonIdentityKeyStore deserialize(
|
||||
JsonParser jsonParser, DeserializationContext deserializationContext
|
||||
) throws IOException {
|
||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||
|
||||
var localRegistrationId = node.get("registrationId").asInt();
|
||||
var identityKeyPair = new IdentityKeyPair(Base64.getDecoder().decode(node.get("identityKey").asText()));
|
||||
|
||||
var keyStore = new JsonIdentityKeyStore(identityKeyPair, localRegistrationId);
|
||||
|
||||
var trustedKeysNode = node.get("trustedKeys");
|
||||
if (trustedKeysNode.isArray()) {
|
||||
for (var trustedKey : trustedKeysNode) {
|
||||
var trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null;
|
||||
|
||||
if (UuidUtil.isUuid(trustedKeyName)) {
|
||||
// Ignore identities that were incorrectly created with UUIDs as name
|
||||
continue;
|
||||
}
|
||||
|
||||
var uuid = trustedKey.hasNonNull("uuid")
|
||||
? UuidUtil.parseOrNull(trustedKey.get("uuid").asText())
|
||||
: null;
|
||||
final var serviceAddress = uuid == null
|
||||
? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
|
||||
: new SignalServiceAddress(uuid, trustedKeyName);
|
||||
try {
|
||||
var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);
|
||||
var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
|
||||
"trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED;
|
||||
var added = trustedKey.hasNonNull("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp")
|
||||
.asLong()) : new Date();
|
||||
keyStore.saveIdentity(serviceAddress, id, trustLevel, added);
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JsonIdentityKeyStoreSerializer extends JsonSerializer<JsonIdentityKeyStore> {
|
||||
|
||||
@Override
|
||||
public void serialize(
|
||||
JsonIdentityKeyStore jsonIdentityKeyStore, JsonGenerator json, SerializerProvider serializerProvider
|
||||
) throws IOException {
|
||||
json.writeStartObject();
|
||||
json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId());
|
||||
json.writeStringField("identityKey",
|
||||
Base64.getEncoder().encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().serialize()));
|
||||
json.writeStringField("identityPrivateKey",
|
||||
Base64.getEncoder()
|
||||
.encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()));
|
||||
json.writeStringField("identityPublicKey",
|
||||
Base64.getEncoder()
|
||||
.encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().getPublicKey().serialize()));
|
||||
json.writeArrayFieldStart("trustedKeys");
|
||||
for (var trustedKey : jsonIdentityKeyStore.identities) {
|
||||
json.writeStartObject();
|
||||
if (trustedKey.getAddress().getNumber().isPresent()) {
|
||||
json.writeStringField("name", trustedKey.getAddress().getNumber().get());
|
||||
}
|
||||
if (trustedKey.getAddress().getUuid().isPresent()) {
|
||||
json.writeStringField("uuid", trustedKey.getAddress().getUuid().get().toString());
|
||||
}
|
||||
json.writeStringField("identityKey",
|
||||
Base64.getEncoder().encodeToString(trustedKey.identityKey.serialize()));
|
||||
json.writeNumberField("trustLevel", trustedKey.trustLevel.ordinal());
|
||||
json.writeNumberField("addedTimestamp", trustedKey.added.getTime());
|
||||
json.writeEndObject();
|
||||
}
|
||||
json.writeEndArray();
|
||||
json.writeEndObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,14 +6,14 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
public class IdentityInfo {
|
||||
public class LegacyIdentityInfo {
|
||||
|
||||
SignalServiceAddress address;
|
||||
IdentityKey identityKey;
|
||||
TrustLevel trustLevel;
|
||||
Date added;
|
||||
|
||||
IdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
||||
LegacyIdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
||||
this.address = address;
|
||||
this.identityKey = identityKey;
|
||||
this.trustLevel = trustLevel;
|
|
@ -0,0 +1,120 @@
|
|||
package org.asamk.signal.manager.storage.protocol;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.asamk.signal.manager.TrustLevel;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LegacyJsonIdentityKeyStore {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(LegacyJsonIdentityKeyStore.class);
|
||||
|
||||
private final List<LegacyIdentityInfo> identities;
|
||||
private final IdentityKeyPair identityKeyPair;
|
||||
private final int localRegistrationId;
|
||||
|
||||
private LegacyJsonIdentityKeyStore(
|
||||
final List<LegacyIdentityInfo> identities, IdentityKeyPair identityKeyPair, int localRegistrationId
|
||||
) {
|
||||
this.identities = identities;
|
||||
this.identityKeyPair = identityKeyPair;
|
||||
this.localRegistrationId = localRegistrationId;
|
||||
}
|
||||
|
||||
public List<LegacyIdentityInfo> getIdentities() {
|
||||
return identities.stream()
|
||||
.map(LegacyIdentityInfo::getAddress)
|
||||
.collect(Collectors.toSet())
|
||||
.stream()
|
||||
.map(this::getIdentity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identityKeyPair;
|
||||
}
|
||||
|
||||
public int getLocalRegistrationId() {
|
||||
return localRegistrationId;
|
||||
}
|
||||
|
||||
private LegacyIdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
|
||||
long maxDate = 0;
|
||||
LegacyIdentityInfo maxIdentity = null;
|
||||
for (var id : this.identities) {
|
||||
if (!id.address.matches(serviceAddress)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var time = id.getDateAdded().getTime();
|
||||
if (maxIdentity == null || maxDate <= time) {
|
||||
maxDate = time;
|
||||
maxIdentity = id;
|
||||
}
|
||||
}
|
||||
return maxIdentity;
|
||||
}
|
||||
|
||||
public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<LegacyJsonIdentityKeyStore> {
|
||||
|
||||
@Override
|
||||
public LegacyJsonIdentityKeyStore deserialize(
|
||||
JsonParser jsonParser, DeserializationContext deserializationContext
|
||||
) throws IOException {
|
||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||
|
||||
var localRegistrationId = node.get("registrationId").asInt();
|
||||
var identityKeyPair = new IdentityKeyPair(Base64.getDecoder().decode(node.get("identityKey").asText()));
|
||||
|
||||
var identities = new ArrayList<LegacyIdentityInfo>();
|
||||
|
||||
var trustedKeysNode = node.get("trustedKeys");
|
||||
if (trustedKeysNode.isArray()) {
|
||||
for (var trustedKey : trustedKeysNode) {
|
||||
var trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null;
|
||||
|
||||
if (UuidUtil.isUuid(trustedKeyName)) {
|
||||
// Ignore identities that were incorrectly created with UUIDs as name
|
||||
continue;
|
||||
}
|
||||
|
||||
var uuid = trustedKey.hasNonNull("uuid")
|
||||
? UuidUtil.parseOrNull(trustedKey.get("uuid").asText())
|
||||
: null;
|
||||
final var serviceAddress = uuid == null
|
||||
? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
|
||||
: new SignalServiceAddress(uuid, trustedKeyName);
|
||||
try {
|
||||
var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);
|
||||
var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
|
||||
"trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED;
|
||||
var added = trustedKey.hasNonNull("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp")
|
||||
.asLong()) : new Date();
|
||||
identities.add(new LegacyIdentityInfo(serviceAddress, id, trustLevel, added));
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LegacyJsonIdentityKeyStore(identities, identityKeyPair, localRegistrationId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,13 +16,13 @@ import java.util.List;
|
|||
|
||||
public class LegacyJsonSessionStore {
|
||||
|
||||
private final List<SessionInfo> sessions;
|
||||
private final List<LegacySessionInfo> sessions;
|
||||
|
||||
private LegacyJsonSessionStore(final List<SessionInfo> sessions) {
|
||||
private LegacyJsonSessionStore(final List<LegacySessionInfo> sessions) {
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
public List<SessionInfo> getSessions() {
|
||||
public List<LegacySessionInfo> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class LegacyJsonSessionStore {
|
|||
) throws IOException {
|
||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||
|
||||
var sessions = new ArrayList<SessionInfo>();
|
||||
var sessions = new ArrayList<LegacySessionInfo>();
|
||||
|
||||
if (node.isArray()) {
|
||||
for (var session : node) {
|
||||
|
@ -50,7 +50,7 @@ public class LegacyJsonSessionStore {
|
|||
: new SignalServiceAddress(uuid, sessionName);
|
||||
final var deviceId = session.get("deviceId").asInt();
|
||||
final var record = Base64.getDecoder().decode(session.get("record").asText());
|
||||
var sessionInfo = new SessionInfo(serviceAddress, deviceId, record);
|
||||
var sessionInfo = new LegacySessionInfo(serviceAddress, deviceId, record);
|
||||
sessions.add(sessionInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.asamk.signal.manager.storage.protocol;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
public class LegacyJsonSignalProtocolStore {
|
||||
|
||||
@JsonProperty("preKeys")
|
||||
@JsonDeserialize(using = LegacyJsonPreKeyStore.JsonPreKeyStoreDeserializer.class)
|
||||
private LegacyJsonPreKeyStore legacyPreKeyStore;
|
||||
|
||||
@JsonProperty("sessionStore")
|
||||
@JsonDeserialize(using = LegacyJsonSessionStore.JsonSessionStoreDeserializer.class)
|
||||
private LegacyJsonSessionStore legacySessionStore;
|
||||
|
||||
@JsonProperty("signedPreKeyStore")
|
||||
@JsonDeserialize(using = LegacyJsonSignedPreKeyStore.JsonSignedPreKeyStoreDeserializer.class)
|
||||
private LegacyJsonSignedPreKeyStore legacySignedPreKeyStore;
|
||||
|
||||
@JsonProperty("identityKeyStore")
|
||||
@JsonDeserialize(using = LegacyJsonIdentityKeyStore.JsonIdentityKeyStoreDeserializer.class)
|
||||
private LegacyJsonIdentityKeyStore legacyIdentityKeyStore;
|
||||
|
||||
private LegacyJsonSignalProtocolStore() {
|
||||
}
|
||||
|
||||
public LegacyJsonPreKeyStore getLegacyPreKeyStore() {
|
||||
return legacyPreKeyStore;
|
||||
}
|
||||
|
||||
public LegacyJsonSignedPreKeyStore getLegacySignedPreKeyStore() {
|
||||
return legacySignedPreKeyStore;
|
||||
}
|
||||
|
||||
public LegacyJsonSessionStore getLegacySessionStore() {
|
||||
return legacySessionStore;
|
||||
}
|
||||
|
||||
public LegacyJsonIdentityKeyStore getLegacyIdentityKeyStore() {
|
||||
return legacyIdentityKeyStore;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package org.asamk.signal.manager.storage.protocol;
|
|||
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public class SessionInfo {
|
||||
public class LegacySessionInfo {
|
||||
|
||||
public SignalServiceAddress address;
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class SessionInfo {
|
|||
|
||||
public byte[] sessionRecord;
|
||||
|
||||
public SessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) {
|
||||
LegacySessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) {
|
||||
this.address = address;
|
||||
this.deviceId = deviceId;
|
||||
this.sessionRecord = sessionRecord;
|
|
@ -1,15 +1,10 @@
|
|||
package org.asamk.signal.manager.storage.protocol;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.asamk.signal.manager.TrustLevel;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
|
@ -17,76 +12,26 @@ 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.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(value = {"sessionStore", "preKeys", "signedPreKeyStore"}, allowSetters = true)
|
||||
public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
|
||||
public class SignalProtocolStore implements SignalServiceProtocolStore {
|
||||
|
||||
@JsonProperty("preKeys")
|
||||
@JsonDeserialize(using = LegacyJsonPreKeyStore.JsonPreKeyStoreDeserializer.class)
|
||||
private LegacyJsonPreKeyStore legacyPreKeyStore;
|
||||
private final PreKeyStore preKeyStore;
|
||||
private final SignedPreKeyStore signedPreKeyStore;
|
||||
private final SignalServiceSessionStore sessionStore;
|
||||
private final IdentityKeyStore identityKeyStore;
|
||||
|
||||
@JsonProperty("sessionStore")
|
||||
@JsonDeserialize(using = LegacyJsonSessionStore.JsonSessionStoreDeserializer.class)
|
||||
private LegacyJsonSessionStore legacySessionStore;
|
||||
|
||||
@JsonProperty("signedPreKeyStore")
|
||||
@JsonDeserialize(using = LegacyJsonSignedPreKeyStore.JsonSignedPreKeyStoreDeserializer.class)
|
||||
private LegacyJsonSignedPreKeyStore legacySignedPreKeyStore;
|
||||
|
||||
@JsonProperty("identityKeyStore")
|
||||
@JsonDeserialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreDeserializer.class)
|
||||
@JsonSerialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreSerializer.class)
|
||||
private JsonIdentityKeyStore identityKeyStore;
|
||||
|
||||
private PreKeyStore preKeyStore;
|
||||
private SignedPreKeyStore signedPreKeyStore;
|
||||
private SignalServiceSessionStore sessionStore;
|
||||
|
||||
public JsonSignalProtocolStore() {
|
||||
}
|
||||
|
||||
public JsonSignalProtocolStore(
|
||||
IdentityKeyPair identityKeyPair,
|
||||
int registrationId,
|
||||
PreKeyStore preKeyStore,
|
||||
SignedPreKeyStore signedPreKeyStore,
|
||||
SignalServiceSessionStore sessionStore
|
||||
public SignalProtocolStore(
|
||||
final PreKeyStore preKeyStore,
|
||||
final SignedPreKeyStore signedPreKeyStore,
|
||||
final SignalServiceSessionStore sessionStore,
|
||||
final IdentityKeyStore identityKeyStore
|
||||
) {
|
||||
this.preKeyStore = preKeyStore;
|
||||
this.signedPreKeyStore = signedPreKeyStore;
|
||||
this.sessionStore = sessionStore;
|
||||
this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId);
|
||||
}
|
||||
|
||||
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||
identityKeyStore.setResolver(resolver);
|
||||
}
|
||||
|
||||
public void setPreKeyStore(final PreKeyStore preKeyStore) {
|
||||
this.preKeyStore = preKeyStore;
|
||||
}
|
||||
|
||||
public void setSignedPreKeyStore(final SignedPreKeyStore signedPreKeyStore) {
|
||||
this.signedPreKeyStore = signedPreKeyStore;
|
||||
}
|
||||
|
||||
public void setSessionStore(final SignalServiceSessionStore sessionStore) {
|
||||
this.sessionStore = sessionStore;
|
||||
}
|
||||
|
||||
public LegacyJsonPreKeyStore getLegacyPreKeyStore() {
|
||||
return legacyPreKeyStore;
|
||||
}
|
||||
|
||||
public LegacyJsonSignedPreKeyStore getLegacySignedPreKeyStore() {
|
||||
return legacySignedPreKeyStore;
|
||||
}
|
||||
|
||||
public LegacyJsonSessionStore getLegacySessionStore() {
|
||||
return legacySessionStore;
|
||||
this.identityKeyStore = identityKeyStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -104,28 +49,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
|
|||
return identityKeyStore.saveIdentity(address, identityKey);
|
||||
}
|
||||
|
||||
public void saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||
identityKeyStore.saveIdentity(serviceAddress, identityKey, trustLevel, null);
|
||||
}
|
||||
|
||||
public void setIdentityTrustLevel(
|
||||
SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel
|
||||
) {
|
||||
identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel);
|
||||
}
|
||||
|
||||
public void removeIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey) {
|
||||
identityKeyStore.removeIdentity(serviceAddress, identityKey);
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities() {
|
||||
return identityKeyStore.getIdentities();
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentities(SignalServiceAddress serviceAddress) {
|
||||
return identityKeyStore.getIdentities(serviceAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
||||
|
@ -136,10 +59,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
|
|||
return identityKeyStore.getIdentity(address);
|
||||
}
|
||||
|
||||
public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
|
||||
return identityKeyStore.getIdentity(serviceAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||
return preKeyStore.loadPreKey(preKeyId);
|
|
@ -1,13 +0,0 @@
|
|||
package org.asamk.signal.manager.storage.protocol;
|
||||
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public interface SignalServiceAddressResolver {
|
||||
|
||||
/**
|
||||
* Get a SignalServiceAddress with number and/or uuid from an identifier name.
|
||||
*
|
||||
* @param identifier can be either a serialized uuid or a e164 phone number
|
||||
*/
|
||||
SignalServiceAddress resolveSignalServiceAddress(String identifier);
|
||||
}
|
|
@ -229,14 +229,15 @@ public class RecipientStore {
|
|||
}
|
||||
|
||||
private void save() {
|
||||
var storage = new Storage(recipients.entrySet()
|
||||
.stream()
|
||||
.map(pair -> new Storage.Recipient(pair.getKey().getId(),
|
||||
pair.getValue().getNumber().orNull(),
|
||||
pair.getValue().getUuid().transform(UUID::toString).orNull()))
|
||||
.collect(Collectors.toList()), lastId);
|
||||
|
||||
// Write to memory first to prevent corrupting the file in case of serialization errors
|
||||
try (var inMemoryOutput = new ByteArrayOutputStream()) {
|
||||
var storage = new Storage(recipients.entrySet()
|
||||
.stream()
|
||||
.map(pair -> new Storage.Recipient(pair.getKey().getId(),
|
||||
pair.getValue().getNumber().orNull(),
|
||||
pair.getValue().getUuid().transform(UUID::toString).orNull()))
|
||||
.collect(Collectors.toList()), lastId);
|
||||
objectMapper.writeValue(inMemoryOutput, storage);
|
||||
|
||||
var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());
|
||||
|
|
|
@ -199,7 +199,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private File getSessionPath(Key key) {
|
||||
private File getSessionFile(Key key) {
|
||||
try {
|
||||
IOUtils.createPrivateDirectories(sessionsPath);
|
||||
} catch (IOException e) {
|
||||
|
@ -216,7 +216,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
}
|
||||
|
||||
final var file = getSessionPath(key);
|
||||
final var file = getSessionFile(key);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
private void storeSessionLocked(final Key key, final SessionRecord session) {
|
||||
cachedSessions.put(key, session);
|
||||
|
||||
final var file = getSessionPath(key);
|
||||
final var file = getSessionFile(key);
|
||||
try {
|
||||
try (var outputStream = new FileOutputStream(file)) {
|
||||
outputStream.write(session.serialize());
|
||||
|
@ -263,7 +263,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
private void deleteSessionLocked(final Key key) {
|
||||
cachedSessions.remove(key);
|
||||
|
||||
final var file = getSessionPath(key);
|
||||
final var file = getSessionFile(key);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.whispersystems.libsignal.IdentityKey;
|
|||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.Medium;
|
||||
|
@ -23,6 +24,17 @@ public class KeyUtils {
|
|||
private KeyUtils() {
|
||||
}
|
||||
|
||||
public static IdentityKeyPair getIdentityKeyPair(byte[] publicKeyBytes, byte[] privateKeyBytes) {
|
||||
try {
|
||||
IdentityKey publicKey = new IdentityKey(publicKeyBytes);
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(privateKeyBytes);
|
||||
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static IdentityKeyPair generateIdentityKeyPair() {
|
||||
var djbKeyPair = Curve.generateKeyPair();
|
||||
var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue