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