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