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