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