]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java
7516355570a53da8271d6c731092003076c34873
[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.storage.identities.TrustNewIdentity;
25 import org.asamk.signal.manager.util.KeyUtils;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.whispersystems.libsignal.IdentityKeyPair;
29 import org.whispersystems.libsignal.util.KeyHelper;
30 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
31 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
32 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
33 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
34 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
35 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
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 String password;
58
59 ProvisioningManagerImpl(
60 PathConfig pathConfig,
61 ServiceEnvironmentConfig serviceEnvironmentConfig,
62 String userAgent,
63 final Consumer<Manager> newManagerListener,
64 final AccountsStore accountsStore
65 ) {
66 this.pathConfig = pathConfig;
67 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
68 this.userAgent = userAgent;
69 this.newManagerListener = newManagerListener;
70 this.accountsStore = accountsStore;
71
72 tempIdentityKey = KeyUtils.generateIdentityKeyPair();
73 registrationId = KeyHelper.generateRegistrationId(false);
74 password = KeyUtils.createPassword();
75 GroupsV2Operations groupsV2Operations;
76 try {
77 groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
78 } catch (Throwable ignored) {
79 groupsV2Operations = null;
80 }
81 accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
82 new DynamicCredentialsProvider(null, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
83 userAgent,
84 groupsV2Operations,
85 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
86 }
87
88 @Override
89 public URI getDeviceLinkUri() throws TimeoutException, IOException {
90 var deviceUuid = accountManager.getNewDeviceUuid();
91
92 return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
93 }
94
95 @Override
96 public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
97 var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
98 var number = ret.getNumber();
99 var aci = ret.getAci();
100 var pni = ret.getPni();
101
102 logger.info("Received link information from {}, linking in progress ...", number);
103
104 var accountPath = accountsStore.getPathByAci(aci);
105 if (accountPath == null) {
106 accountPath = accountsStore.getPathByNumber(number);
107 }
108 if (accountPath != null
109 && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)
110 && !canRelinkExistingAccount(accountPath)) {
111 throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
112 }
113 if (accountPath == null) {
114 accountPath = accountsStore.addAccount(number, aci);
115 } else {
116 accountsStore.updateAccount(accountPath, number, aci);
117 }
118
119 var encryptedDeviceName = deviceName == null
120 ? null
121 : DeviceNameUtil.encryptDeviceName(deviceName, ret.getAciIdentity().getPrivateKey());
122
123 logger.debug("Finishing new device registration");
124 var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
125 false,
126 true,
127 registrationId,
128 encryptedDeviceName);
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 aci,
139 pni,
140 password,
141 encryptedDeviceName,
142 deviceId,
143 ret.getAciIdentity(),
144 ret.getPniIdentity(),
145 registrationId,
146 profileKey,
147 TrustNewIdentity.ON_FIRST_USE);
148
149 ManagerImpl m = null;
150 try {
151 final var accountPathFinal = accountPath;
152 m = new ManagerImpl(account,
153 pathConfig,
154 (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci),
155 serviceEnvironmentConfig,
156 userAgent);
157 account = null;
158
159 logger.debug("Refreshing pre keys");
160 try {
161 m.refreshPreKeys();
162 } catch (Exception e) {
163 logger.error("Failed to refresh pre keys.");
164 }
165
166 logger.debug("Requesting sync data");
167 try {
168 m.requestAllSyncData();
169 } catch (Exception e) {
170 logger.error(
171 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
172 }
173
174 if (newManagerListener != null) {
175 newManagerListener.accept(m);
176 m = null;
177 }
178 return number;
179 } finally {
180 if (m != null) {
181 m.close();
182 }
183 }
184 } finally {
185 if (account != null) {
186 account.close();
187 }
188 }
189 }
190
191 private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
192 final SignalAccount signalAccount;
193 try {
194 signalAccount = SignalAccount.load(pathConfig.dataPath(),
195 accountPath,
196 false,
197 TrustNewIdentity.ON_FIRST_USE);
198 } catch (IOException e) {
199 logger.debug("Account in use or failed to load.", e);
200 return false;
201 } catch (OverlappingFileLockException e) {
202 logger.debug("Account in use.", e);
203 return false;
204 }
205
206 try (signalAccount) {
207 if (signalAccount.isMasterDevice()) {
208 logger.debug("Account is a master device.");
209 return false;
210 }
211
212 final var m = new ManagerImpl(signalAccount,
213 pathConfig,
214 (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
215 serviceEnvironmentConfig,
216 userAgent);
217 try (m) {
218 m.checkAccountState();
219 } catch (AuthorizationFailedException ignored) {
220 return true;
221 }
222
223 logger.debug("Account is still successfully linked.");
224 return false;
225 }
226 }
227 }