]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java
Announce support for gift badges if linked device
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / ProvisioningManagerImpl.java
1 /*
2 Copyright (C) 2015-2022 AsamK and contributors
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 package org.asamk.signal.manager;
18
19 import org.asamk.signal.manager.api.UserAlreadyExistsException;
20 import org.asamk.signal.manager.config.ServiceConfig;
21 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
22 import org.asamk.signal.manager.storage.SignalAccount;
23 import org.asamk.signal.manager.storage.accounts.AccountsStore;
24 import org.asamk.signal.manager.util.KeyUtils;
25 import org.signal.libsignal.protocol.IdentityKeyPair;
26 import org.signal.libsignal.protocol.util.KeyHelper;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
30 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
31 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
32 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
33 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
34 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
35 import org.whispersystems.signalservice.internal.push.ConfirmCodeMessage;
36 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
37
38 import java.io.IOException;
39 import java.net.URI;
40 import java.nio.channels.OverlappingFileLockException;
41 import java.util.concurrent.TimeoutException;
42 import java.util.function.Consumer;
43
44 import static org.asamk.signal.manager.config.ServiceConfig.getCapabilities;
45
46 class ProvisioningManagerImpl implements ProvisioningManager {
47
48 private final static Logger logger = LoggerFactory.getLogger(ProvisioningManagerImpl.class);
49
50 private final PathConfig pathConfig;
51 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
52 private final String userAgent;
53 private final Consumer<Manager> newManagerListener;
54 private final AccountsStore accountsStore;
55
56 private final SignalServiceAccountManager accountManager;
57 private final IdentityKeyPair tempIdentityKey;
58 private final int registrationId;
59 private final int pniRegistrationId;
60 private final String password;
61
62 ProvisioningManagerImpl(
63 PathConfig pathConfig,
64 ServiceEnvironmentConfig serviceEnvironmentConfig,
65 String userAgent,
66 final Consumer<Manager> newManagerListener,
67 final AccountsStore accountsStore
68 ) {
69 this.pathConfig = pathConfig;
70 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
71 this.userAgent = userAgent;
72 this.newManagerListener = newManagerListener;
73 this.accountsStore = accountsStore;
74
75 tempIdentityKey = KeyUtils.generateIdentityKeyPair();
76 registrationId = KeyHelper.generateRegistrationId(false);
77 pniRegistrationId = KeyHelper.generateRegistrationId(false);
78 password = KeyUtils.createPassword();
79 GroupsV2Operations groupsV2Operations;
80 try {
81 groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()),
82 ServiceConfig.GROUP_MAX_SIZE);
83 } catch (Throwable ignored) {
84 groupsV2Operations = null;
85 }
86 accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
87 new DynamicCredentialsProvider(null, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
88 userAgent,
89 groupsV2Operations,
90 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
91 }
92
93 @Override
94 public URI getDeviceLinkUri() throws TimeoutException, IOException {
95 var deviceUuid = accountManager.getNewDeviceUuid();
96
97 return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
98 }
99
100 @Override
101 public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
102 var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
103 var number = ret.getNumber();
104 var aci = ret.getAci();
105 var pni = ret.getPni();
106
107 logger.info("Received link information from {}, linking in progress ...", number);
108
109 var accountPath = accountsStore.getPathByAci(aci);
110 if (accountPath == null) {
111 accountPath = accountsStore.getPathByNumber(number);
112 }
113 if (accountPath != null
114 && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)
115 && !canRelinkExistingAccount(accountPath)) {
116 throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
117 }
118 if (accountPath == null) {
119 accountPath = accountsStore.addAccount(number, aci);
120 } else {
121 accountsStore.updateAccount(accountPath, number, aci);
122 }
123
124 var encryptedDeviceName = deviceName == null
125 ? null
126 : DeviceNameUtil.encryptDeviceName(deviceName, ret.getAciIdentity().getPrivateKey());
127
128 logger.debug("Finishing new device registration");
129 var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
130 new ConfirmCodeMessage(false,
131 true,
132 registrationId,
133 pniRegistrationId,
134 encryptedDeviceName,
135 getCapabilities(false)));
136
137 // Create new account with the synced identity
138 var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
139
140 SignalAccount account = null;
141 try {
142 account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
143 accountPath,
144 number,
145 serviceEnvironmentConfig.getType(),
146 aci,
147 pni,
148 password,
149 encryptedDeviceName,
150 deviceId,
151 ret.getAciIdentity(),
152 ret.getPniIdentity(),
153 registrationId,
154 pniRegistrationId,
155 profileKey,
156 Settings.DEFAULT);
157 account.getConfigurationStore().setReadReceipts(ret.isReadReceipts());
158
159 ManagerImpl m = null;
160 try {
161 m = new ManagerImpl(account,
162 pathConfig,
163 new AccountFileUpdaterImpl(accountsStore, accountPath),
164 serviceEnvironmentConfig,
165 userAgent);
166 account = null;
167
168 logger.debug("Refreshing pre keys");
169 try {
170 m.refreshPreKeys();
171 } catch (Exception e) {
172 logger.error("Failed to refresh pre keys.", e);
173 }
174
175 logger.debug("Requesting sync data");
176 try {
177 m.requestAllSyncData();
178 } catch (Exception e) {
179 logger.error(
180 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.",
181 e);
182 }
183
184 if (newManagerListener != null) {
185 newManagerListener.accept(m);
186 m = null;
187 }
188 return number;
189 } finally {
190 if (m != null) {
191 m.close();
192 }
193 }
194 } finally {
195 if (account != null) {
196 account.close();
197 }
198 }
199 }
200
201 private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
202 final SignalAccount signalAccount;
203 try {
204 signalAccount = SignalAccount.load(pathConfig.dataPath(), accountPath, false, Settings.DEFAULT);
205 } catch (IOException e) {
206 logger.debug("Account in use or failed to load.", e);
207 return false;
208 } catch (OverlappingFileLockException e) {
209 logger.debug("Account in use.", e);
210 return false;
211 }
212
213 try (signalAccount) {
214 if (signalAccount.isPrimaryDevice()) {
215 logger.debug("Account is a primary device.");
216 return false;
217 }
218 if (signalAccount.isRegistered()
219 && signalAccount.getServiceEnvironment() != null
220 && signalAccount.getServiceEnvironment() != serviceEnvironmentConfig.getType()) {
221 logger.debug("Account is registered in another environment: {}.",
222 signalAccount.getServiceEnvironment());
223 return false;
224 }
225
226 final var m = new ManagerImpl(signalAccount,
227 pathConfig,
228 new AccountFileUpdaterImpl(accountsStore, accountPath),
229 serviceEnvironmentConfig,
230 userAgent);
231 try (m) {
232 m.checkAccountState();
233 } catch (AuthorizationFailedException ignored) {
234 return true;
235 }
236
237 logger.debug("Account is still successfully linked.");
238 return false;
239 }
240 }
241 }