]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
Add --ignore-stories flag to prevent receiving story messages
[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.CaptchaRequiredException;
6 import org.asamk.signal.manager.api.IncorrectPinException;
7 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
8 import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
9 import org.asamk.signal.manager.api.PinLockedException;
10 import org.asamk.signal.manager.config.ServiceConfig;
11 import org.asamk.signal.manager.storage.SignalAccount;
12 import org.asamk.signal.manager.util.KeyUtils;
13 import org.asamk.signal.manager.util.NumberVerificationUtils;
14 import org.signal.libsignal.protocol.IdentityKeyPair;
15 import org.signal.libsignal.protocol.InvalidKeyException;
16 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
20 import org.whispersystems.signalservice.api.push.ACI;
21 import org.whispersystems.signalservice.api.push.PNI;
22 import org.whispersystems.signalservice.api.push.ServiceIdType;
23 import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
24 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
25 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
26 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
27 import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
28
29 import java.io.IOException;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.concurrent.TimeUnit;
34
35 public class AccountHelper {
36
37 private final static Logger logger = LoggerFactory.getLogger(AccountHelper.class);
38
39 private final Context context;
40 private final SignalAccount account;
41 private final SignalDependencies dependencies;
42
43 private Callable unregisteredListener;
44
45 public AccountHelper(final Context context) {
46 this.account = context.getAccount();
47 this.dependencies = context.getDependencies();
48 this.context = context;
49 }
50
51 public void setUnregisteredListener(final Callable unregisteredListener) {
52 this.unregisteredListener = unregisteredListener;
53 }
54
55 public void checkAccountState() throws IOException {
56 if (account.getLastReceiveTimestamp() == 0) {
57 logger.info("The Signal protocol expects that incoming messages are regularly received.");
58 } else {
59 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
60 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
61 if (days > 7) {
62 logger.warn(
63 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
64 days);
65 }
66 }
67 try {
68 updateAccountAttributes();
69 context.getPreKeyHelper().refreshPreKeysIfNecessary();
70 if (account.getAci() == null || account.getPni() == null) {
71 checkWhoAmiI();
72 }
73 if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
74 context.getSyncHelper().requestSyncPniIdentity();
75 }
76 if (account.getPreviousStorageVersion() < 4
77 && account.isPrimaryDevice()
78 && account.getRegistrationLockPin() != null) {
79 migrateRegistrationPin();
80 }
81 } catch (DeprecatedVersionException e) {
82 logger.debug("Signal-Server returned deprecated version exception", e);
83 throw e;
84 } catch (AuthorizationFailedException e) {
85 account.setRegistered(false);
86 throw e;
87 }
88 }
89
90 public void checkWhoAmiI() throws IOException {
91 final var whoAmI = dependencies.getAccountManager().getWhoAmI();
92 final var number = whoAmI.getNumber();
93 final var aci = ACI.parseOrNull(whoAmI.getAci());
94 final var pni = PNI.parseOrNull(whoAmI.getPni());
95 if (number.equals(account.getNumber()) && aci.equals(account.getAci()) && pni.equals(account.getPni())) {
96 return;
97 }
98
99 updateSelfIdentifiers(number, aci, pni);
100 }
101
102 private void updateSelfIdentifiers(final String number, final ACI aci, final PNI pni) {
103 account.setNumber(number);
104 account.setAci(aci);
105 account.setPni(pni);
106 if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null && account.getPni() != null) {
107 account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
108 }
109 account.getRecipientTrustedResolver().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
110 // TODO check and update remote storage
111 context.getUnidentifiedAccessHelper().rotateSenderCertificates();
112 dependencies.resetAfterAddressChange();
113 context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
114 }
115
116 public void setPni(
117 final PNI updatedPni,
118 final IdentityKeyPair pniIdentityKeyPair,
119 final SignedPreKeyRecord pniSignedPreKey,
120 final int localPniRegistrationId
121 ) throws IOException {
122 account.setPni(updatedPni, pniIdentityKeyPair, pniSignedPreKey, localPniRegistrationId);
123 context.getPreKeyHelper().refreshPreKeysIfNecessary(ServiceIdType.PNI);
124 if (account.getPni() == null || !account.getPni().equals(updatedPni)) {
125 context.getGroupV2Helper().clearAuthCredentialCache();
126 }
127 }
128
129 public void startChangeNumber(
130 String newNumber, String captcha, boolean voiceVerification
131 ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
132 final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
133 NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
134 }
135
136 public void finishChangeNumber(
137 String newNumber, String verificationCode, String pin
138 ) throws IncorrectPinException, PinLockedException, IOException {
139 // TODO create new PNI identity key
140 final List<OutgoingPushMessage> deviceMessages = null;
141 final Map<String, SignedPreKeyEntity> devicePniSignedPreKeys = null;
142 final Map<String, Integer> pniRegistrationIds = null;
143 final var result = NumberVerificationUtils.verifyNumber(verificationCode,
144 pin,
145 context.getPinHelper(),
146 (verificationCode1, registrationLock) -> dependencies.getAccountManager()
147 .changeNumber(new ChangePhoneNumberRequest(newNumber,
148 verificationCode1,
149 registrationLock,
150 account.getPniIdentityKeyPair().getPublicKey(),
151 deviceMessages,
152 devicePniSignedPreKeys,
153 pniRegistrationIds)));
154 // TODO handle response
155 updateSelfIdentifiers(newNumber, account.getAci(), PNI.parseOrThrow(result.first().getPni()));
156 }
157
158 public void setDeviceName(String deviceName) {
159 final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
160 final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
161 account.setEncryptedDeviceName(encryptedDeviceName);
162 }
163
164 public void updateAccountAttributes() throws IOException {
165 dependencies.getAccountManager()
166 .setAccountAttributes(null,
167 account.getLocalRegistrationId(),
168 true,
169 null,
170 account.getRegistrationLock(),
171 account.getSelfUnidentifiedAccessKey(),
172 account.isUnrestrictedUnidentifiedAccess(),
173 ServiceConfig.capabilities,
174 account.isDiscoverableByPhoneNumber(),
175 account.getEncryptedDeviceName(),
176 account.getLocalPniRegistrationId());
177 }
178
179 public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
180 var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
181
182 try {
183 dependencies.getAccountManager()
184 .addDevice(deviceLinkInfo.deviceIdentifier(),
185 deviceLinkInfo.deviceKey(),
186 account.getAciIdentityKeyPair(),
187 account.getPniIdentityKeyPair(),
188 account.getProfileKey(),
189 verificationCode);
190 } catch (InvalidKeyException e) {
191 throw new InvalidDeviceLinkException("Invalid device link", e);
192 }
193 account.setMultiDevice(true);
194 }
195
196 public void removeLinkedDevices(int deviceId) throws IOException {
197 dependencies.getAccountManager().removeDevice(deviceId);
198 var devices = dependencies.getAccountManager().getDevices();
199 account.setMultiDevice(devices.size() > 1);
200 }
201
202 public void migrateRegistrationPin() throws IOException {
203 var masterKey = account.getOrCreatePinMasterKey();
204
205 context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
206 }
207
208 public void setRegistrationPin(String pin) throws IOException {
209 var masterKey = account.getOrCreatePinMasterKey();
210
211 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
212
213 account.setRegistrationLockPin(pin);
214 }
215
216 public void removeRegistrationPin() throws IOException {
217 // Remove KBS Pin
218 context.getPinHelper().removeRegistrationLockPin();
219
220 account.setRegistrationLockPin(null);
221 }
222
223 public void unregister() throws IOException {
224 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
225 // If this is the primary device, other users can't send messages to this number anymore.
226 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
227 dependencies.getAccountManager().setGcmId(Optional.empty());
228
229 account.setRegistered(false);
230 unregisteredListener.call();
231 }
232
233 public void deleteAccount() throws IOException {
234 try {
235 context.getPinHelper().removeRegistrationLockPin();
236 } catch (IOException e) {
237 logger.warn("Failed to remove registration lock pin");
238 }
239 account.setRegistrationLockPin(null);
240
241 dependencies.getAccountManager().deleteAccount();
242
243 account.setRegistered(false);
244 unregisteredListener.call();
245 }
246
247 public interface Callable {
248
249 void call();
250 }
251 }