Refactor identity key store

This commit is contained in:
AsamK 2021-04-18 18:26:12 +02:00
parent afb22deada
commit 8a0c6cae15
19 changed files with 717 additions and 563 deletions

View file

@ -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.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV1; 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.identities.IdentityInfo;
import org.asamk.signal.manager.storage.messageCache.CachedMessage; import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.profiles.SignalProfile; 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.storage.stickers.Sticker;
import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.IOUtils;
@ -234,8 +235,6 @@ public class Manager implements Closeable {
clientZkProfileOperations, clientZkProfileOperations,
ServiceConfig.AUTOMATIC_NETWORK_RETRY); ServiceConfig.AUTOMATIC_NETWORK_RETRY);
this.account.setResolver(this::resolveSignalServiceAddress);
this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
account.getProfileStore()::getProfileKey, account.getProfileStore()::getProfileKey,
this::getRecipientProfile, this::getRecipientProfile,
@ -1223,17 +1222,7 @@ public class Manager implements Closeable {
private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException {
var messageSender = createMessageSender(); var messageSender = createMessageSender();
try {
messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync()); messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
} catch (UntrustedIdentityException e) {
if (e.getIdentityKey() != null) {
account.getSignalProtocolStore()
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
}
throw e;
}
} }
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException { private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
@ -1303,22 +1292,8 @@ public class Manager implements Closeable {
unidentifiedAccessHelper.getAccessFor(recipients), unidentifiedAccessHelper.getAccessFor(recipients),
isRecipientUpdate, isRecipientUpdate,
message); message);
for (var r : result) {
if (r.getIdentityFailure() != null) {
account.getSignalProtocolStore()
.saveIdentity(r.getAddress(),
r.getIdentityFailure().getIdentityKey(),
TrustLevel.UNTRUSTED);
}
}
return new Pair<>(timestamp, result); return new Pair<>(timestamp, result);
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
if (e.getIdentityKey() != null) {
account.getSignalProtocolStore()
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
}
return new Pair<>(timestamp, List.of()); return new Pair<>(timestamp, List.of());
} }
} else { } else {
@ -1388,12 +1363,6 @@ public class Manager implements Closeable {
false, false,
System.currentTimeMillis() - startTime); System.currentTimeMillis() - startTime);
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
if (e.getIdentityKey() != null) {
account.getSignalProtocolStore()
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
}
return SendMessageResult.identityFailure(recipient, e.getIdentityKey()); return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
} }
} }
@ -1406,12 +1375,6 @@ public class Manager implements Closeable {
try { try {
return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message); return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message);
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
if (e.getIdentityKey() != null) {
account.getSignalProtocolStore()
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
}
return SendMessageResult.identityFailure(address, e.getIdentityKey()); return SendMessageResult.identityFailure(address, e.getIdentityKey());
} }
} }
@ -1424,15 +1387,7 @@ public class Manager implements Closeable {
return cipher.decrypt(envelope); return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) { } catch (ProtocolUntrustedIdentityException e) {
if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) { if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
var identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause(); throw (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 new AssertionError(e); throw new AssertionError(e);
} }
@ -2004,8 +1959,8 @@ public class Manager implements Closeable {
} }
if (c.getVerified().isPresent()) { if (c.getVerified().isPresent()) {
final var verifiedMessage = c.getVerified().get(); final var verifiedMessage = c.getVerified().get();
account.getSignalProtocolStore() account.getIdentityKeyStore()
.setIdentityTrustLevel(verifiedMessage.getDestination(), .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(), verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
} }
@ -2040,8 +1995,8 @@ public class Manager implements Closeable {
} }
if (syncMessage.getVerified().isPresent()) { if (syncMessage.getVerified().isPresent()) {
final var verifiedMessage = syncMessage.getVerified().get(); final var verifiedMessage = syncMessage.getVerified().get();
account.getSignalProtocolStore() account.getIdentityKeyStore()
.setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(), verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
} }
@ -2283,7 +2238,8 @@ public class Manager implements Closeable {
var out = new DeviceContactsOutputStream(fos); var out = new DeviceContactsOutputStream(fos);
for (var record : account.getContactStore().getContacts()) { for (var record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null; VerifiedMessage verifiedMessage = null;
var currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress()); var currentIdentity = account.getIdentityKeyStore()
.getIdentity(resolveRecipientTrusted(record.getAddress()));
if (currentIdentity != null) { if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(record.getAddress(), verifiedMessage = new VerifiedMessage(record.getAddress(),
currentIdentity.getIdentityKey(), currentIdentity.getIdentityKey(),
@ -2395,11 +2351,12 @@ public class Manager implements Closeable {
} }
public List<IdentityInfo> getIdentities() { public List<IdentityInfo> getIdentities() {
return account.getSignalProtocolStore().getIdentities(); return account.getIdentityKeyStore().getIdentities();
} }
public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException { 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 * @param fingerprint Fingerprint
*/ */
public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException { public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
var address = canonicalizeAndResolveSignalServiceAddress(name); var recipientId = canonicalizeAndResolveRecipient(name);
return trustIdentity(address, (identityKey) -> Arrays.equals(identityKey.serialize(), fingerprint)); 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 * @param safetyNumber Safety number
*/ */
public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException { public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
var address = canonicalizeAndResolveSignalServiceAddress(name); var recipientId = canonicalizeAndResolveRecipient(name);
return trustIdentity(address, (identityKey) -> safetyNumber.equals(computeSafetyNumber(address, identityKey))); var address = account.getRecipientStore().resolveServiceAddress(recipientId);
} return trustIdentity(recipientId,
identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
private boolean trustIdentity(SignalServiceAddress address, Function<IdentityKey, Boolean> verifier) { TrustLevel.TRUSTED_VERIFIED);
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;
} }
/** /**
@ -2468,24 +2391,31 @@ public class Manager implements Closeable {
* *
* @param name username of the identity * @param name username of the identity
*/ */
public boolean trustIdentityAllKeys(String name) { public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
var address = resolveSignalServiceAddress(name); var recipientId = canonicalizeAndResolveRecipient(name);
var ids = account.getSignalProtocolStore().getIdentities(address); return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
if (ids == null) { }
private boolean trustIdentity(
RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
) {
var identity = account.getIdentityKeyStore().getIdentity(recipientId);
if (identity == null) {
return false; return false;
} }
for (var id : ids) {
if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { if (!verifier.apply(identity.getIdentityKey())) {
account.getSignalProtocolStore() return false;
.setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); }
account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
try { try {
sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); var address = account.getRecipientStore().resolveServiceAddress(recipientId);
sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
} catch (IOException | UntrustedIdentityException e) { } catch (IOException | UntrustedIdentityException e) {
logger.warn("Failed to send verification sync message: {}", e.getMessage()); logger.warn("Failed to send verification sync message: {}", e.getMessage());
} }
}
}
account.save();
return true; return true;
} }
@ -2499,6 +2429,7 @@ public class Manager implements Closeable {
theirIdentityKey); theirIdentityKey);
} }
@Deprecated
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
var canonicalizedNumber = UuidUtil.isUuid(identifier) var canonicalizedNumber = UuidUtil.isUuid(identifier)
? identifier ? identifier
@ -2506,12 +2437,14 @@ public class Manager implements Closeable {
return resolveSignalServiceAddress(canonicalizedNumber); return resolveSignalServiceAddress(canonicalizedNumber);
} }
@Deprecated
public SignalServiceAddress resolveSignalServiceAddress(String identifier) { public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
var address = Utils.getSignalServiceAddressFromIdentifier(identifier); var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
return resolveSignalServiceAddress(address); return resolveSignalServiceAddress(address);
} }
@Deprecated
public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) { public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
if (address.matches(account.getSelfAddress())) { if (address.matches(account.getSelfAddress())) {
return account.getSelfAddress(); return account.getSelfAddress();
@ -2520,6 +2453,27 @@ public class Manager implements Closeable {
return account.getRecipientStore().resolveServiceAddress(address); 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 @Override
public void close() throws IOException { public void close() throws IOException {
close(true); close(true);

View file

@ -40,6 +40,7 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
public class RegistrationManager implements Closeable { public class RegistrationManager implements Closeable {
@ -164,10 +165,10 @@ public class RegistrationManager implements Closeable {
account.setUuid(UuidUtil.parseOrNull(response.getUuid())); account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
account.setRegistrationLockPin(pin); account.setRegistrationLockPin(pin);
account.getSessionStore().archiveAllSessions(); account.getSessionStore().archiveAllSessions();
account.getSignalProtocolStore() final var recipientId = account.getRecipientStore().resolveRecipient(account.getSelfAddress());
.saveIdentity(account.getSelfAddress(), final var publicKey = account.getIdentityKeyPair().getPublicKey();
account.getIdentityKeyPair().getPublicKey(), account.getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
TrustLevel.TRUSTED_VERIFIED); account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
Manager m = null; Manager m = null;
try { try {

View file

@ -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.contacts.JsonContactsStore;
import org.asamk.signal.manager.storage.groups.GroupInfoV1; import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.JsonGroupStore; 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.messageCache.MessageCache;
import org.asamk.signal.manager.storage.prekeys.PreKeyStore; import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore; import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
import org.asamk.signal.manager.storage.profiles.ProfileStore; import org.asamk.signal.manager.storage.profiles.ProfileStore;
import org.asamk.signal.manager.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver; import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore; import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientStore; import org.asamk.signal.manager.storage.recipients.RecipientStore;
@ -81,10 +82,11 @@ public class SignalAccount implements Closeable {
private boolean registered = false; private boolean registered = false;
private JsonSignalProtocolStore signalProtocolStore; private SignalProtocolStore signalProtocolStore;
private PreKeyStore preKeyStore; private PreKeyStore preKeyStore;
private SignedPreKeyStore signedPreKeyStore; private SignedPreKeyStore signedPreKeyStore;
private SessionStore sessionStore; private SessionStore sessionStore;
private IdentityKeyStore identityKeyStore;
private JsonGroupStore groupStore; private JsonGroupStore groupStore;
private JsonContactsStore contactStore; private JsonContactsStore contactStore;
private RecipientStore recipientStore; private RecipientStore recipientStore;
@ -141,11 +143,14 @@ public class SignalAccount implements Closeable {
account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username)); account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
account.sessionStore = new SessionStore(getSessionsPath(dataPath, username), account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
account.recipientStore::resolveRecipient); account.recipientStore::resolveRecipient);
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
registrationId, account.recipientStore::resolveRecipient,
account.preKeyStore, identityKey,
registrationId);
account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
account.signedPreKeyStore, account.signedPreKeyStore,
account.sessionStore); account.sessionStore,
account.identityKeyStore);
account.profileStore = new ProfileStore(); account.profileStore = new ProfileStore();
account.stickerStore = new StickerStore(); account.stickerStore = new StickerStore();
@ -190,11 +195,14 @@ public class SignalAccount implements Closeable {
account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username)); account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
account.sessionStore = new SessionStore(getSessionsPath(dataPath, username), account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
account.recipientStore::resolveRecipient); account.recipientStore::resolveRecipient);
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
registrationId, account.recipientStore::resolveRecipient,
account.preKeyStore, identityKey,
registrationId);
account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
account.signedPreKeyStore, account.signedPreKeyStore,
account.sessionStore); account.sessionStore,
account.identityKeyStore);
account.profileStore = new ProfileStore(); account.profileStore = new ProfileStore();
account.stickerStore = new StickerStore(); account.stickerStore = new StickerStore();
@ -235,6 +243,7 @@ public class SignalAccount implements Closeable {
private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) { private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId); sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId);
identityKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
} }
public static File getFileName(File dataPath, String username) { 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"); 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) { private static File getSessionsPath(File dataPath, String username) {
return new File(getUserPath(dataPath, username), "sessions"); return new File(getUserPath(dataPath, username), "sessions");
} }
@ -299,6 +312,17 @@ public class SignalAccount implements Closeable {
} }
username = Utils.getNotNullNode(rootNode, "username").asText(); username = Utils.getNotNullNode(rootNode, "username").asText();
password = Utils.getNotNullNode(rootNode, "password").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")) { if (rootNode.hasNonNull("registrationLockPin")) {
registrationLockPin = rootNode.get("registrationLockPin").asText(); registrationLockPin = rootNode.get("registrationLockPin").asText();
} }
@ -338,13 +362,15 @@ public class SignalAccount implements Closeable {
} }
} }
signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"), var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
JsonSignalProtocolStore.class); ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
LegacyJsonSignalProtocolStore.class)
: null;
preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username)); preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
if (signalProtocolStore.getLegacyPreKeyStore() != null) { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
logger.debug("Migrating legacy pre key store."); logger.debug("Migrating legacy pre key store.");
for (var entry : signalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) { for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
try { try {
preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue())); preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
} catch (IOException e) { } catch (IOException e) {
@ -352,12 +378,11 @@ public class SignalAccount implements Closeable {
} }
} }
} }
signalProtocolStore.setPreKeyStore(preKeyStore);
signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username)); signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
if (signalProtocolStore.getLegacySignedPreKeyStore() != null) { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
logger.debug("Migrating legacy signed pre key store."); logger.debug("Migrating legacy signed pre key store.");
for (var entry : signalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) { for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
try { try {
signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue())); signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
} catch (IOException e) { } catch (IOException e) {
@ -365,12 +390,11 @@ public class SignalAccount implements Closeable {
} }
} }
} }
signalProtocolStore.setSignedPreKeyStore(signedPreKeyStore);
sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient); sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient);
if (signalProtocolStore.getLegacySessionStore() != null) { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
logger.debug("Migrating legacy session store."); logger.debug("Migrating legacy session store.");
for (var session : signalProtocolStore.getLegacySessionStore().getSessions()) { for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
try { try {
sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(), sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
session.deviceId), new SessionRecord(session.sessionRecord)); 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(); registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
var groupStoreNode = rootNode.get("groupStore"); var groupStoreNode = rootNode.get("groupStore");
@ -431,10 +475,6 @@ public class SignalAccount implements Closeable {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }
for (var identity : signalProtocolStore.getIdentities()) {
identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress()));
}
} }
messageCache = new MessageCache(getMessageCachePath(dataPath, username)); messageCache = new MessageCache(getMessageCachePath(dataPath, username));
@ -475,6 +515,13 @@ public class SignalAccount implements Closeable {
.put("deviceId", deviceId) .put("deviceId", deviceId)
.put("isMultiDevice", isMultiDevice) .put("isMultiDevice", isMultiDevice)
.put("password", password) .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("registrationLockPin", registrationLockPin)
.put("pinMasterKey", .put("pinMasterKey",
pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize())) pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
@ -484,7 +531,6 @@ public class SignalAccount implements Closeable {
.put("nextSignedPreKeyId", nextSignedPreKeyId) .put("nextSignedPreKeyId", nextSignedPreKeyId)
.put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize())) .put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
.put("registered", registered) .put("registered", registered)
.putPOJO("axolotlStore", signalProtocolStore)
.putPOJO("groupStore", groupStore) .putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore) .putPOJO("contactStore", contactStore)
.putPOJO("profileStore", profileStore) .putPOJO("profileStore", profileStore)
@ -517,10 +563,6 @@ public class SignalAccount implements Closeable {
return new Pair<>(fileChannel, lock); return new Pair<>(fileChannel, lock);
} }
public void setResolver(final SignalServiceAddressResolver resolver) {
signalProtocolStore.setResolver(resolver);
}
public void addPreKeys(List<PreKeyRecord> records) { public void addPreKeys(List<PreKeyRecord> records) {
for (var record : records) { for (var record : records) {
if (preKeyIdOffset != record.getId()) { if (preKeyIdOffset != record.getId()) {
@ -543,7 +585,7 @@ public class SignalAccount implements Closeable {
save(); save();
} }
public JsonSignalProtocolStore getSignalProtocolStore() { public SignalProtocolStore getSignalProtocolStore() {
return signalProtocolStore; return signalProtocolStore;
} }
@ -551,6 +593,10 @@ public class SignalAccount implements Closeable {
return sessionStore; return sessionStore;
} }
public IdentityKeyStore getIdentityKeyStore() {
return identityKeyStore;
}
public JsonGroupStore getGroupStore() { public JsonGroupStore getGroupStore() {
return groupStore; return groupStore;
} }

View file

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

View file

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

View file

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

View file

@ -6,14 +6,14 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Date; import java.util.Date;
public class IdentityInfo { public class LegacyIdentityInfo {
SignalServiceAddress address; SignalServiceAddress address;
IdentityKey identityKey; IdentityKey identityKey;
TrustLevel trustLevel; TrustLevel trustLevel;
Date added; Date added;
IdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) { LegacyIdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
this.address = address; this.address = address;
this.identityKey = identityKey; this.identityKey = identityKey;
this.trustLevel = trustLevel; this.trustLevel = trustLevel;

View file

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

View file

@ -16,13 +16,13 @@ import java.util.List;
public class LegacyJsonSessionStore { 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; this.sessions = sessions;
} }
public List<SessionInfo> getSessions() { public List<LegacySessionInfo> getSessions() {
return sessions; return sessions;
} }
@ -34,7 +34,7 @@ public class LegacyJsonSessionStore {
) throws IOException { ) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser); JsonNode node = jsonParser.getCodec().readTree(jsonParser);
var sessions = new ArrayList<SessionInfo>(); var sessions = new ArrayList<LegacySessionInfo>();
if (node.isArray()) { if (node.isArray()) {
for (var session : node) { for (var session : node) {
@ -50,7 +50,7 @@ public class LegacyJsonSessionStore {
: new SignalServiceAddress(uuid, sessionName); : new SignalServiceAddress(uuid, sessionName);
final var deviceId = session.get("deviceId").asInt(); final var deviceId = session.get("deviceId").asInt();
final var record = Base64.getDecoder().decode(session.get("record").asText()); 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); sessions.add(sessionInfo);
} }
} }

View file

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

View file

@ -2,7 +2,7 @@ package org.asamk.signal.manager.storage.protocol;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
public class SessionInfo { public class LegacySessionInfo {
public SignalServiceAddress address; public SignalServiceAddress address;
@ -10,7 +10,7 @@ public class SessionInfo {
public byte[] sessionRecord; 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.address = address;
this.deviceId = deviceId; this.deviceId = deviceId;
this.sessionRecord = sessionRecord; this.sessionRecord = sessionRecord;

View file

@ -1,15 +1,10 @@
package org.asamk.signal.manager.storage.protocol; 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.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.state.IdentityKeyStore;
import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.PreKeyStore; import org.whispersystems.libsignal.state.PreKeyStore;
import org.whispersystems.libsignal.state.SessionRecord; 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.libsignal.state.SignedPreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceProtocolStore; import org.whispersystems.signalservice.api.SignalServiceProtocolStore;
import org.whispersystems.signalservice.api.SignalServiceSessionStore; import org.whispersystems.signalservice.api.SignalServiceSessionStore;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.List; import java.util.List;
@JsonIgnoreProperties(value = {"sessionStore", "preKeys", "signedPreKeyStore"}, allowSetters = true) public class SignalProtocolStore implements SignalServiceProtocolStore {
public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
@JsonProperty("preKeys") private final PreKeyStore preKeyStore;
@JsonDeserialize(using = LegacyJsonPreKeyStore.JsonPreKeyStoreDeserializer.class) private final SignedPreKeyStore signedPreKeyStore;
private LegacyJsonPreKeyStore legacyPreKeyStore; private final SignalServiceSessionStore sessionStore;
private final IdentityKeyStore identityKeyStore;
@JsonProperty("sessionStore") public SignalProtocolStore(
@JsonDeserialize(using = LegacyJsonSessionStore.JsonSessionStoreDeserializer.class) final PreKeyStore preKeyStore,
private LegacyJsonSessionStore legacySessionStore; final SignedPreKeyStore signedPreKeyStore,
final SignalServiceSessionStore sessionStore,
@JsonProperty("signedPreKeyStore") final IdentityKeyStore identityKeyStore
@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
) { ) {
this.preKeyStore = preKeyStore; this.preKeyStore = preKeyStore;
this.signedPreKeyStore = signedPreKeyStore; this.signedPreKeyStore = signedPreKeyStore;
this.sessionStore = sessionStore; this.sessionStore = sessionStore;
this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId); this.identityKeyStore = identityKeyStore;
}
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;
} }
@Override @Override
@ -104,28 +49,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
return identityKeyStore.saveIdentity(address, identityKey); 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 @Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
return identityKeyStore.isTrustedIdentity(address, identityKey, direction); return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
@ -136,10 +59,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
return identityKeyStore.getIdentity(address); return identityKeyStore.getIdentity(address);
} }
public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
return identityKeyStore.getIdentity(serviceAddress);
}
@Override @Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
return preKeyStore.loadPreKey(preKeyId); return preKeyStore.loadPreKey(preKeyId);

View file

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

View file

@ -229,14 +229,15 @@ public class RecipientStore {
} }
private void save() { private void save() {
// 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() var storage = new Storage(recipients.entrySet()
.stream() .stream()
.map(pair -> new Storage.Recipient(pair.getKey().getId(), .map(pair -> new Storage.Recipient(pair.getKey().getId(),
pair.getValue().getNumber().orNull(), pair.getValue().getNumber().orNull(),
pair.getValue().getUuid().transform(UUID::toString).orNull())) pair.getValue().getUuid().transform(UUID::toString).orNull()))
.collect(Collectors.toList()), lastId); .collect(Collectors.toList()), lastId);
// Write to memory first to prevent corrupting the file in case of serialization errors
try (var inMemoryOutput = new ByteArrayOutputStream()) {
objectMapper.writeValue(inMemoryOutput, storage); objectMapper.writeValue(inMemoryOutput, storage);
var input = new ByteArrayInputStream(inMemoryOutput.toByteArray()); var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());

View file

@ -199,7 +199,7 @@ public class SessionStore implements SignalServiceSessionStore {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private File getSessionPath(Key key) { private File getSessionFile(Key key) {
try { try {
IOUtils.createPrivateDirectories(sessionsPath); IOUtils.createPrivateDirectories(sessionsPath);
} catch (IOException e) { } 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()) { if (!file.exists()) {
return null; return null;
} }
@ -233,7 +233,7 @@ public class SessionStore implements SignalServiceSessionStore {
private void storeSessionLocked(final Key key, final SessionRecord session) { private void storeSessionLocked(final Key key, final SessionRecord session) {
cachedSessions.put(key, session); cachedSessions.put(key, session);
final var file = getSessionPath(key); final var file = getSessionFile(key);
try { try {
try (var outputStream = new FileOutputStream(file)) { try (var outputStream = new FileOutputStream(file)) {
outputStream.write(session.serialize()); outputStream.write(session.serialize());
@ -263,7 +263,7 @@ public class SessionStore implements SignalServiceSessionStore {
private void deleteSessionLocked(final Key key) { private void deleteSessionLocked(final Key key) {
cachedSessions.remove(key); cachedSessions.remove(key);
final var file = getSessionPath(key); final var file = getSessionFile(key);
if (!file.exists()) { if (!file.exists()) {
return; return;
} }

View file

@ -6,6 +6,7 @@ import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPrivateKey;
import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.Medium; import org.whispersystems.libsignal.util.Medium;
@ -23,6 +24,17 @@ public class KeyUtils {
private 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() { public static IdentityKeyPair generateIdentityKeyPair() {
var djbKeyPair = Curve.generateKeyPair(); var djbKeyPair = Curve.generateKeyPair();
var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());

View file

@ -8,11 +8,12 @@ import org.asamk.signal.PlainTextWriterImpl;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.storage.protocol.IdentityInfo; import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.util.Hex; import org.asamk.signal.util.Hex;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.util.List; import java.util.List;
@ -22,9 +23,10 @@ public class ListIdentitiesCommand implements LocalCommand {
private final static Logger logger = LoggerFactory.getLogger(ListIdentitiesCommand.class); private final static Logger logger = LoggerFactory.getLogger(ListIdentitiesCommand.class);
private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) { private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) {
var digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey())); final SignalServiceAddress address = m.resolveSignalServiceAddress(theirId.getRecipientId());
var digits = Util.formatSafetyNumber(m.computeSafetyNumber(address, theirId.getIdentityKey()));
writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}", writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
theirId.getAddress().getNumber().orNull(), address.getNumber().orNull(),
theirId.getTrustLevel(), theirId.getTrustLevel(),
theirId.getDateAdded(), theirId.getDateAdded(),
Hex.toString(theirId.getFingerprint()), Hex.toString(theirId.getFingerprint()),

View file

@ -29,7 +29,12 @@ public class TrustCommand implements LocalCommand {
public void handleCommand(final Namespace ns, final Manager m) throws CommandException { public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
var number = ns.getString("number"); var number = ns.getString("number");
if (ns.getBoolean("trust_all_known_keys")) { if (ns.getBoolean("trust_all_known_keys")) {
var res = m.trustIdentityAllKeys(number); boolean res;
try {
res = m.trustIdentityAllKeys(number);
} catch (InvalidNumberException e) {
throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
}
if (!res) { if (!res) {
throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct."); throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct.");
} }

View file

@ -8,6 +8,7 @@ import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.util.ErrorUtils; import org.asamk.signal.util.ErrorUtils;
import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -144,7 +145,11 @@ public class DbusSignalImpl implements Signal {
@Override @Override
public long sendMessageReaction( public long sendMessageReaction(
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final String recipient final String emoji,
final boolean remove,
final String targetAuthor,
final long targetSentTimestamp,
final String recipient
) { ) {
var recipients = new ArrayList<String>(1); var recipients = new ArrayList<String>(1);
recipients.add(recipient); recipients.add(recipient);
@ -153,7 +158,11 @@ public class DbusSignalImpl implements Signal {
@Override @Override
public long sendMessageReaction( public long sendMessageReaction(
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final List<String> recipients final String emoji,
final boolean remove,
final String targetAuthor,
final long targetSentTimestamp,
final List<String> recipients
) { ) {
try { try {
final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients); final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
@ -210,10 +219,18 @@ public class DbusSignalImpl implements Signal {
@Override @Override
public long sendGroupMessageReaction( public long sendGroupMessageReaction(
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final byte[] groupId final String emoji,
final boolean remove,
final String targetAuthor,
final long targetSentTimestamp,
final byte[] groupId
) { ) {
try { try {
final var results = m.sendGroupMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, GroupId.unknownVersion(groupId)); final var results = m.sendGroupMessageReaction(emoji,
remove,
targetAuthor,
targetSentTimestamp,
GroupId.unknownVersion(groupId));
checkSendMessageResults(results.first(), results.second()); checkSendMessageResults(results.first(), results.second());
return results.first(); return results.first();
} catch (IOException e) { } catch (IOException e) {
@ -366,8 +383,11 @@ public class DbusSignalImpl implements Signal {
// all numbers the system knows // all numbers the system knows
@Override @Override
public List<String> listNumbers() { public List<String> listNumbers() {
return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()), return Stream.concat(m.getIdentities()
m.getContacts().stream().map(c -> c.number)) .stream()
.map(IdentityInfo::getRecipientId)
.map(m::resolveSignalServiceAddress)
.map(a -> a.getNumber().orNull()), m.getContacts().stream().map(c -> c.number))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -385,7 +405,8 @@ public class DbusSignalImpl implements Signal {
} }
// Try profiles if no contact name was found // Try profiles if no contact name was found
for (var identity : m.getIdentities()) { for (var identity : m.getIdentities()) {
final var address = identity.getAddress(); final var recipientId = identity.getRecipientId();
final var address = m.resolveSignalServiceAddress(recipientId);
var number = address.getNumber().orNull(); var number = address.getNumber().orNull();
if (number != null) { if (number != null) {
var profile = m.getRecipientProfile(address); var profile = m.getRecipientProfile(address);