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