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