]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java
6445e5111fc8f68e6fd3150971123cf22d0deeb3
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / ProvisioningManager.java
1 /*
2 Copyright (C) 2015-2021 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.config.ServiceConfig;
20 import org.asamk.signal.manager.config.ServiceEnvironment;
21 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
22 import org.asamk.signal.manager.storage.SignalAccount;
23 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
24 import org.asamk.signal.manager.util.KeyUtils;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.whispersystems.libsignal.IdentityKeyPair;
28 import org.whispersystems.libsignal.util.KeyHelper;
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.util.DynamicCredentialsProvider;
36
37 import java.io.File;
38 import java.io.IOException;
39 import java.net.URI;
40 import java.util.concurrent.TimeoutException;
41 import java.util.function.Consumer;
42
43 public class ProvisioningManager {
44
45 private final static Logger logger = LoggerFactory.getLogger(ProvisioningManager.class);
46
47 private final PathConfig pathConfig;
48 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
49 private final String userAgent;
50 private final Consumer<Manager> newManagerListener;
51
52 private final SignalServiceAccountManager accountManager;
53 private final IdentityKeyPair tempIdentityKey;
54 private final int registrationId;
55 private final String password;
56
57 ProvisioningManager(
58 PathConfig pathConfig,
59 ServiceEnvironmentConfig serviceEnvironmentConfig,
60 String userAgent,
61 final Consumer<Manager> newManagerListener
62 ) {
63 this.pathConfig = pathConfig;
64 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
65 this.userAgent = userAgent;
66 this.newManagerListener = newManagerListener;
67
68 tempIdentityKey = KeyUtils.generateIdentityKeyPair();
69 registrationId = KeyHelper.generateRegistrationId(false);
70 password = KeyUtils.createPassword();
71 GroupsV2Operations groupsV2Operations;
72 try {
73 groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
74 } catch (Throwable ignored) {
75 groupsV2Operations = null;
76 }
77 accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
78 new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
79 userAgent,
80 groupsV2Operations,
81 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
82 }
83
84 public static ProvisioningManager init(
85 File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
86 ) {
87 return init(settingsPath, serviceEnvironment, userAgent, null);
88 }
89
90 public static ProvisioningManager init(
91 File settingsPath,
92 ServiceEnvironment serviceEnvironment,
93 String userAgent,
94 Consumer<Manager> newManagerListener
95 ) {
96 var pathConfig = PathConfig.createDefault(settingsPath);
97
98 final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
99
100 return new ProvisioningManager(pathConfig, serviceConfiguration, userAgent, newManagerListener);
101 }
102
103 public URI getDeviceLinkUri() throws TimeoutException, IOException {
104 var deviceUuid = accountManager.getNewDeviceUuid();
105
106 return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
107 }
108
109 public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
110 var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
111 var number = ret.getNumber();
112
113 logger.info("Received link information from {}, linking in progress ...", number);
114
115 if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
116 throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
117 }
118
119 var encryptedDeviceName = deviceName == null
120 ? null
121 : DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().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 number,
137 ret.getAci(),
138 password,
139 encryptedDeviceName,
140 deviceId,
141 ret.getIdentity(),
142 registrationId,
143 profileKey,
144 TrustNewIdentity.ON_FIRST_USE);
145
146 ManagerImpl m = null;
147 try {
148 m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
149
150 logger.debug("Refreshing pre keys");
151 try {
152 m.refreshPreKeys();
153 } catch (Exception e) {
154 logger.error("Failed to refresh pre keys.");
155 }
156
157 logger.debug("Requesting sync data");
158 try {
159 m.requestAllSyncData();
160 } catch (Exception e) {
161 logger.error(
162 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
163 }
164
165 if (newManagerListener != null) {
166 newManagerListener.accept(m);
167 m = null;
168 }
169 return number;
170 } finally {
171 if (m != null) {
172 m.close();
173 }
174 }
175 } finally {
176 if (account != null) {
177 account.close();
178 }
179 }
180 }
181
182 private boolean canRelinkExistingAccount(final String number) throws IOException {
183 final SignalAccount signalAccount;
184 try {
185 signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
186 } catch (IOException e) {
187 logger.debug("Account in use or failed to load.", e);
188 return false;
189 }
190
191 try (signalAccount) {
192 if (signalAccount.isMasterDevice()) {
193 logger.debug("Account is a master device.");
194 return false;
195 }
196
197 final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent);
198 try (m) {
199 m.checkAccountState();
200 } catch (AuthorizationFailedException ignored) {
201 return true;
202 }
203
204 logger.debug("Account is still successfully linked.");
205 return false;
206 }
207 }
208 }