]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
Update self identifiers after whoAmI request
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / AccountHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.DeviceLinkInfo;
4 import org.asamk.signal.manager.SignalDependencies;
5 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
6 import org.asamk.signal.manager.config.ServiceConfig;
7 import org.asamk.signal.manager.storage.SignalAccount;
8 import org.asamk.signal.manager.util.KeyUtils;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.whispersystems.libsignal.InvalidKeyException;
12 import org.whispersystems.libsignal.util.guava.Optional;
13 import org.whispersystems.signalservice.api.push.ACI;
14 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
15 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
16
17 import java.io.IOException;
18 import java.util.concurrent.TimeUnit;
19
20 public class AccountHelper {
21
22 private final static Logger logger = LoggerFactory.getLogger(AccountHelper.class);
23
24 private final Context context;
25 private final SignalAccount account;
26 private final SignalDependencies dependencies;
27
28 private Callable unregisteredListener;
29
30 public AccountHelper(final Context context) {
31 this.account = context.getAccount();
32 this.dependencies = context.getDependencies();
33 this.context = context;
34 }
35
36 public void setUnregisteredListener(final Callable unregisteredListener) {
37 this.unregisteredListener = unregisteredListener;
38 }
39
40 public void checkAccountState() throws IOException {
41 if (account.getLastReceiveTimestamp() == 0) {
42 logger.info("The Signal protocol expects that incoming messages are regularly received.");
43 } else {
44 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
45 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
46 if (days > 7) {
47 logger.warn(
48 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
49 days);
50 }
51 }
52 try {
53 context.getPreKeyHelper().refreshPreKeysIfNecessary();
54 if (account.getAci() == null) {
55 checkWhoAmiI();
56 }
57 updateAccountAttributes();
58 } catch (AuthorizationFailedException e) {
59 account.setRegistered(false);
60 throw e;
61 }
62 }
63
64 public void checkWhoAmiI() throws IOException {
65 final var whoAmI = dependencies.getAccountManager().getWhoAmI();
66 final var number = whoAmI.getNumber();
67 final var aci = ACI.parseOrNull(whoAmI.getAci());
68 if (number.equals(account.getNumber()) && aci.equals(account.getAci())) {
69 return;
70 }
71
72 updateSelfIdentifiers(number, aci);
73 }
74
75 private void updateSelfIdentifiers(final String number, final ACI aci) {
76 account.setNumber(number);
77 account.setAci(aci);
78 account.getRecipientStore().resolveRecipientTrusted(account.getSelfRecipientAddress());
79 context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
80 // TODO check and update remote storage
81 context.getUnidentifiedAccessHelper().rotateSenderCertificates();
82 dependencies.getSignalWebSocket().forceNewWebSockets();
83 }
84
85 public void setDeviceName(String deviceName) {
86 final var privateKey = account.getIdentityKeyPair().getPrivateKey();
87 final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
88 account.setEncryptedDeviceName(encryptedDeviceName);
89 }
90
91 public void updateAccountAttributes() throws IOException {
92 dependencies.getAccountManager()
93 .setAccountAttributes(account.getEncryptedDeviceName(),
94 null,
95 account.getLocalRegistrationId(),
96 true,
97 null,
98 account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
99 account.getSelfUnidentifiedAccessKey(),
100 account.isUnrestrictedUnidentifiedAccess(),
101 ServiceConfig.capabilities,
102 account.isDiscoverableByPhoneNumber());
103 }
104
105 public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
106 var identityKeyPair = account.getIdentityKeyPair();
107 var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
108
109 try {
110 dependencies.getAccountManager()
111 .addDevice(deviceLinkInfo.deviceIdentifier(),
112 deviceLinkInfo.deviceKey(),
113 identityKeyPair,
114 Optional.of(account.getProfileKey().serialize()),
115 verificationCode);
116 } catch (InvalidKeyException e) {
117 throw new InvalidDeviceLinkException("Invalid device link", e);
118 }
119 account.setMultiDevice(true);
120 }
121
122 public void removeLinkedDevices(int deviceId) throws IOException {
123 dependencies.getAccountManager().removeDevice(deviceId);
124 var devices = dependencies.getAccountManager().getDevices();
125 account.setMultiDevice(devices.size() > 1);
126 }
127
128 public void setRegistrationPin(String pin) throws IOException {
129 final var masterKey = account.getPinMasterKey() != null
130 ? account.getPinMasterKey()
131 : KeyUtils.createMasterKey();
132
133 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
134
135 account.setRegistrationLockPin(pin, masterKey);
136 }
137
138 public void removeRegistrationPin() throws IOException {
139 // Remove KBS Pin
140 context.getPinHelper().removeRegistrationLockPin();
141
142 account.setRegistrationLockPin(null, null);
143 }
144
145 public void unregister() throws IOException {
146 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
147 // If this is the master device, other users can't send messages to this number anymore.
148 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
149 dependencies.getAccountManager().setGcmId(Optional.absent());
150
151 account.setRegistered(false);
152 unregisteredListener.call();
153 }
154
155 public void deleteAccount() throws IOException {
156 try {
157 context.getPinHelper().removeRegistrationLockPin();
158 } catch (IOException e) {
159 logger.warn("Failed to remove registration lock pin");
160 }
161 account.setRegistrationLockPin(null, null);
162
163 dependencies.getAccountManager().deleteAccount();
164
165 account.setRegistered(false);
166 unregisteredListener.call();
167 }
168
169 public interface Callable {
170
171 void call();
172 }
173 }