import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.JsonGroupStore;
+import org.asamk.signal.manager.storage.messageCache.MessageCache;
import org.asamk.signal.manager.storage.profiles.ProfileStore;
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
import org.asamk.signal.manager.storage.protocol.JsonSignalProtocolStore;
import org.asamk.signal.manager.storage.stickers.StickerStore;
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
import org.asamk.signal.manager.storage.threads.ThreadInfo;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.Utils;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.Pair;
+import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
+import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.util.Base64;
public class SignalAccount implements Closeable {
- final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
+ private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
private final ObjectMapper jsonProcessor = new ObjectMapper();
private final FileChannel fileChannel;
private boolean isMultiDevice = false;
private String password;
private String registrationLockPin;
+ private MasterKey pinMasterKey;
private String signalingKey;
private ProfileKey profileKey;
private int preKeyIdOffset;
private ProfileStore profileStore;
private StickerStore stickerStore;
+ private MessageCache messageCache;
+
private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
this.fileChannel = fileChannel;
this.lock = lock;
try {
SignalAccount account = new SignalAccount(pair.first(), pair.second());
account.load(dataPath);
+ account.migrateLegacyConfigs();
+
return account;
} catch (Throwable e) {
pair.second().close();
account.recipientStore = new RecipientStore();
account.profileStore = new ProfileStore();
account.stickerStore = new StickerStore();
+
+ account.messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+
account.registered = false;
return account;
account.recipientStore = new RecipientStore();
account.profileStore = new ProfileStore();
account.stickerStore = new StickerStore();
+
+ account.messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+
account.registered = true;
account.isMultiDevice = true;
return account;
}
+ public void migrateLegacyConfigs() {
+ if (getProfileKey() == null && isRegistered()) {
+ // Old config file, creating new profile key
+ setProfileKey(KeyUtils.createProfileKey());
+ save();
+ }
+ // Store profile keys only in profile store
+ for (ContactInfo contact : getContactStore().getContacts()) {
+ String profileKeyString = contact.profileKey;
+ if (profileKeyString == null) {
+ continue;
+ }
+ final ProfileKey profileKey;
+ try {
+ profileKey = new ProfileKey(Base64.decode(profileKeyString));
+ } catch (InvalidInputException | IOException e) {
+ continue;
+ }
+ contact.profileKey = null;
+ getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
+ }
+ // Ensure our profile key is stored in profile store
+ getProfileStore().storeProfileKey(getSelfAddress(), getProfileKey());
+ }
+
public static File getFileName(File dataPath, String username) {
return new File(dataPath, username);
}
deviceId = node.asInt();
}
if (rootNode.has("isMultiDevice")) {
- isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
+ isMultiDevice = Utils.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
}
- username = Util.getNotNullNode(rootNode, "username").asText();
- password = Util.getNotNullNode(rootNode, "password").asText();
+ username = Utils.getNotNullNode(rootNode, "username").asText();
+ password = Utils.getNotNullNode(rootNode, "password").asText();
JsonNode pinNode = rootNode.get("registrationLockPin");
registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
+ JsonNode pinMasterKeyNode = rootNode.get("pinMasterKey");
+ pinMasterKey = pinMasterKeyNode == null || pinMasterKeyNode.isNull()
+ ? null
+ : new MasterKey(Base64.decode(pinMasterKeyNode.asText()));
if (rootNode.has("signalingKey")) {
- signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
+ signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText();
}
if (rootNode.has("preKeyIdOffset")) {
- preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
+ preKeyIdOffset = Utils.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
} else {
preKeyIdOffset = 0;
}
if (rootNode.has("nextSignedPreKeyId")) {
- nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
+ nextSignedPreKeyId = Utils.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
} else {
nextSignedPreKeyId = 0;
}
if (rootNode.has("profileKey")) {
try {
- profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText()));
+ profileKey = new ProfileKey(Base64.decode(Utils.getNotNullNode(rootNode, "profileKey").asText()));
} catch (InvalidInputException e) {
throw new IOException(
"Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
}
}
- signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"),
+ signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
JsonSignalProtocolStore.class);
- registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
+ registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
JsonNode groupStoreNode = rootNode.get("groupStore");
if (groupStoreNode != null) {
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
stickerStore = new StickerStore();
}
+ messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+
JsonNode threadStoreNode = rootNode.get("threadStore");
if (threadStoreNode != null) {
LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode,
.put("isMultiDevice", isMultiDevice)
.put("password", password)
.put("registrationLockPin", registrationLockPin)
+ .put("pinMasterKey", pinMasterKey == null ? null : Base64.encodeBytes(pinMasterKey.serialize()))
.put("signalingKey", signalingKey)
.put("preKeyIdOffset", preKeyIdOffset)
.put("nextSignedPreKeyId", nextSignedPreKeyId)
return stickerStore;
}
+ public MessageCache getMessageCache() {
+ return messageCache;
+ }
+
public String getUsername() {
return username;
}
return deviceId;
}
+ public void setDeviceId(final int deviceId) {
+ this.deviceId = deviceId;
+ }
+
public String getPassword() {
return password;
}
return registrationLockPin;
}
- public String getRegistrationLock() {
- return null; // TODO implement KBS
- }
-
public void setRegistrationLockPin(final String registrationLockPin) {
this.registrationLockPin = registrationLockPin;
}
+ public MasterKey getPinMasterKey() {
+ return pinMasterKey;
+ }
+
+ public void setPinMasterKey(final MasterKey pinMasterKey) {
+ this.pinMasterKey = pinMasterKey;
+ }
+
public String getSignalingKey() {
return signalingKey;
}
this.profileKey = profileKey;
}
+ public byte[] getSelfUnidentifiedAccessKey() {
+ return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
+ }
+
public int getPreKeyIdOffset() {
return preKeyIdOffset;
}
isMultiDevice = multiDevice;
}
+ public boolean isUnrestrictedUnidentifiedAccess() {
+ // TODO make configurable
+ return false;
+ }
+
+ public boolean isDiscoverableByPhoneNumber() {
+ // TODO make configurable
+ return true;
+ }
+
@Override
public void close() throws IOException {
+ save();
synchronized (fileChannel) {
try {
lock.close();