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