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