1 package org
.asamk
.signal
.manager
.helper
;
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
.PinLockedException
;
9 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
10 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
11 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
12 import org
.asamk
.signal
.manager
.util
.NumberVerificationUtils
;
13 import org
.slf4j
.Logger
;
14 import org
.slf4j
.LoggerFactory
;
15 import org
.whispersystems
.libsignal
.InvalidKeyException
;
16 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
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
;
22 import java
.io
.IOException
;
23 import java
.util
.concurrent
.TimeUnit
;
25 public class AccountHelper
{
27 private final static Logger logger
= LoggerFactory
.getLogger(AccountHelper
.class);
29 private final Context context
;
30 private final SignalAccount account
;
31 private final SignalDependencies dependencies
;
33 private Callable unregisteredListener
;
35 public AccountHelper(final Context context
) {
36 this.account
= context
.getAccount();
37 this.dependencies
= context
.getDependencies();
38 this.context
= context
;
41 public void setUnregisteredListener(final Callable unregisteredListener
) {
42 this.unregisteredListener
= unregisteredListener
;
45 public void checkAccountState() throws IOException
{
46 if (account
.getLastReceiveTimestamp() == 0) {
47 logger
.info("The Signal protocol expects that incoming messages are regularly received.");
49 var diffInMilliseconds
= System
.currentTimeMillis() - account
.getLastReceiveTimestamp();
50 long days
= TimeUnit
.DAYS
.convert(diffInMilliseconds
, TimeUnit
.MILLISECONDS
);
53 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
58 context
.getPreKeyHelper().refreshPreKeysIfNecessary();
59 if (account
.getAci() == null || account
.getPni() == null) {
62 if (!account
.isMasterDevice() && account
.getPniIdentityKeyPair() == null) {
63 context
.getSyncHelper().requestSyncPniIdentity();
65 updateAccountAttributes();
66 } catch (AuthorizationFailedException e
) {
67 account
.setRegistered(false);
72 public void checkWhoAmiI() throws IOException
{
73 final var whoAmI
= dependencies
.getAccountManager().getWhoAmI();
74 final var number
= whoAmI
.getNumber();
75 final var aci
= ACI
.parseOrNull(whoAmI
.getAci());
76 final var pni
= PNI
.parseOrNull(whoAmI
.getPni());
77 if (number
.equals(account
.getNumber()) && aci
.equals(account
.getAci()) && pni
.equals(account
.getPni())) {
81 updateSelfIdentifiers(number
, aci
, pni
);
84 private void updateSelfIdentifiers(final String number
, final ACI aci
, final PNI pni
) {
85 account
.setNumber(number
);
88 account
.getRecipientStore().resolveSelfRecipientTrusted(account
.getSelfRecipientAddress());
89 // TODO check and update remote storage
90 context
.getUnidentifiedAccessHelper().rotateSenderCertificates();
91 dependencies
.resetAfterAddressChange();
92 dependencies
.getSignalWebSocket().forceNewWebSockets();
93 context
.getAccountFileUpdater().updateAccountIdentifiers(account
.getNumber(), account
.getAci());
96 public void startChangeNumber(
97 String newNumber
, String captcha
, boolean voiceVerification
98 ) throws IOException
, CaptchaRequiredException
{
99 final var accountManager
= dependencies
.createUnauthenticatedAccountManager(newNumber
, account
.getPassword());
100 NumberVerificationUtils
.requestVerificationCode(accountManager
, captcha
, voiceVerification
);
103 public void finishChangeNumber(
104 String newNumber
, String verificationCode
, String pin
105 ) throws IncorrectPinException
, PinLockedException
, IOException
{
106 final var result
= NumberVerificationUtils
.verifyNumber(verificationCode
,
108 context
.getPinHelper(),
109 (verificationCode1
, registrationLock
) -> dependencies
.getAccountManager()
110 .changeNumber(verificationCode1
, newNumber
, registrationLock
));
111 // TODO handle response
112 updateSelfIdentifiers(newNumber
, account
.getAci(), PNI
.parseOrThrow(result
.first().getPni()));
115 public void setDeviceName(String deviceName
) {
116 final var privateKey
= account
.getAciIdentityKeyPair().getPrivateKey();
117 final var encryptedDeviceName
= DeviceNameUtil
.encryptDeviceName(deviceName
, privateKey
);
118 account
.setEncryptedDeviceName(encryptedDeviceName
);
121 public void updateAccountAttributes() throws IOException
{
122 dependencies
.getAccountManager()
123 .setAccountAttributes(null,
124 account
.getLocalRegistrationId(),
127 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
128 account
.getSelfUnidentifiedAccessKey(),
129 account
.isUnrestrictedUnidentifiedAccess(),
130 ServiceConfig
.capabilities
,
131 account
.isDiscoverableByPhoneNumber(),
132 account
.getEncryptedDeviceName());
135 public void addDevice(DeviceLinkInfo deviceLinkInfo
) throws IOException
, InvalidDeviceLinkException
{
136 var verificationCode
= dependencies
.getAccountManager().getNewDeviceVerificationCode();
139 dependencies
.getAccountManager()
140 .addDevice(deviceLinkInfo
.deviceIdentifier(),
141 deviceLinkInfo
.deviceKey(),
142 account
.getAciIdentityKeyPair(),
143 account
.getPniIdentityKeyPair(),
144 account
.getProfileKey(),
146 } catch (InvalidKeyException e
) {
147 throw new InvalidDeviceLinkException("Invalid device link", e
);
149 account
.setMultiDevice(true);
152 public void removeLinkedDevices(int deviceId
) throws IOException
{
153 dependencies
.getAccountManager().removeDevice(deviceId
);
154 var devices
= dependencies
.getAccountManager().getDevices();
155 account
.setMultiDevice(devices
.size() > 1);
158 public void setRegistrationPin(String pin
) throws IOException
{
159 final var masterKey
= account
.getPinMasterKey() != null
160 ? account
.getPinMasterKey()
161 : KeyUtils
.createMasterKey();
163 context
.getPinHelper().setRegistrationLockPin(pin
, masterKey
);
165 account
.setRegistrationLockPin(pin
, masterKey
);
168 public void removeRegistrationPin() throws IOException
{
170 context
.getPinHelper().removeRegistrationLockPin();
172 account
.setRegistrationLockPin(null, null);
175 public void unregister() throws IOException
{
176 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
177 // If this is the master device, other users can't send messages to this number anymore.
178 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
179 dependencies
.getAccountManager().setGcmId(Optional
.absent());
181 account
.setRegistered(false);
182 unregisteredListener
.call();
185 public void deleteAccount() throws IOException
{
187 context
.getPinHelper().removeRegistrationLockPin();
188 } catch (IOException e
) {
189 logger
.warn("Failed to remove registration lock pin");
191 account
.setRegistrationLockPin(null, null);
193 dependencies
.getAccountManager().deleteAccount();
195 account
.setRegistered(false);
196 unregisteredListener
.call();
199 public interface Callable
{