]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
Add workaround for #1045
[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 dependencies.getSignalWebSocket().forceNewWebSockets();
114 context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
115 }
116
117 public void setPni(
118 final PNI updatedPni,
119 final IdentityKeyPair pniIdentityKeyPair,
120 final SignedPreKeyRecord pniSignedPreKey,
121 final int localPniRegistrationId
122 ) throws IOException {
123 account.setPni(updatedPni, pniIdentityKeyPair, pniSignedPreKey, localPniRegistrationId);
124 context.getPreKeyHelper().refreshPreKeysIfNecessary(ServiceIdType.PNI);
125 if (account.getPni() == null || !account.getPni().equals(updatedPni)) {
126 context.getGroupV2Helper().clearAuthCredentialCache();
127 }
128 }
129
130 public void startChangeNumber(
131 String newNumber, String captcha, boolean voiceVerification
132 ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
133 final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
134 NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
135 }
136
137 public void finishChangeNumber(
138 String newNumber, String verificationCode, String pin
139 ) throws IncorrectPinException, PinLockedException, IOException {
140 // TODO create new PNI identity key
141 final List<OutgoingPushMessage> deviceMessages = null;
142 final Map<String, SignedPreKeyEntity> devicePniSignedPreKeys = null;
143 final Map<String, Integer> pniRegistrationIds = null;
144 final var result = NumberVerificationUtils.verifyNumber(verificationCode,
145 pin,
146 context.getPinHelper(),
147 (verificationCode1, registrationLock) -> dependencies.getAccountManager()
148 .changeNumber(new ChangePhoneNumberRequest(newNumber,
149 verificationCode1,
150 registrationLock,
151 account.getPniIdentityKeyPair().getPublicKey(),
152 deviceMessages,
153 devicePniSignedPreKeys,
154 pniRegistrationIds)));
155 // TODO handle response
156 updateSelfIdentifiers(newNumber, account.getAci(), PNI.parseOrThrow(result.first().getPni()));
157 }
158
159 public void setDeviceName(String deviceName) {
160 final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
161 final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
162 account.setEncryptedDeviceName(encryptedDeviceName);
163 }
164
165 public void updateAccountAttributes() throws IOException {
166 dependencies.getAccountManager()
167 .setAccountAttributes(null,
168 account.getLocalRegistrationId(),
169 true,
170 null,
171 account.getRegistrationLock(),
172 account.getSelfUnidentifiedAccessKey(),
173 account.isUnrestrictedUnidentifiedAccess(),
174 ServiceConfig.capabilities,
175 account.isDiscoverableByPhoneNumber(),
176 account.getEncryptedDeviceName(),
177 account.getLocalPniRegistrationId());
178 }
179
180 public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
181 var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
182
183 try {
184 dependencies.getAccountManager()
185 .addDevice(deviceLinkInfo.deviceIdentifier(),
186 deviceLinkInfo.deviceKey(),
187 account.getAciIdentityKeyPair(),
188 account.getPniIdentityKeyPair(),
189 account.getProfileKey(),
190 verificationCode);
191 } catch (InvalidKeyException e) {
192 throw new InvalidDeviceLinkException("Invalid device link", e);
193 }
194 account.setMultiDevice(true);
195 }
196
197 public void removeLinkedDevices(int deviceId) throws IOException {
198 dependencies.getAccountManager().removeDevice(deviceId);
199 var devices = dependencies.getAccountManager().getDevices();
200 account.setMultiDevice(devices.size() > 1);
201 }
202
203 public void migrateRegistrationPin() throws IOException {
204 var masterKey = account.getOrCreatePinMasterKey();
205
206 context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
207 }
208
209 public void setRegistrationPin(String pin) throws IOException {
210 var masterKey = account.getOrCreatePinMasterKey();
211
212 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
213
214 account.setRegistrationLockPin(pin);
215 }
216
217 public void removeRegistrationPin() throws IOException {
218 // Remove KBS Pin
219 context.getPinHelper().removeRegistrationLockPin();
220
221 account.setRegistrationLockPin(null);
222 }
223
224 public void unregister() throws IOException {
225 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
226 // If this is the primary device, other users can't send messages to this number anymore.
227 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
228 dependencies.getAccountManager().setGcmId(Optional.empty());
229
230 account.setRegistered(false);
231 unregisteredListener.call();
232 }
233
234 public void deleteAccount() throws IOException {
235 try {
236 context.getPinHelper().removeRegistrationLockPin();
237 } catch (IOException e) {
238 logger.warn("Failed to remove registration lock pin");
239 }
240 account.setRegistrationLockPin(null);
241
242 dependencies.getAccountManager().deleteAccount();
243
244 account.setRegistered(false);
245 unregisteredListener.call();
246 }
247
248 public interface Callable {
249
250 void call();
251 }
252 }